しっぽを追いかけて

ぐるぐるしながら考えています

Unity と猫の話題が中心   掲載内容は個人の私見であり、所属組織の見解ではありません

Hub コントロールのスクロール位置によって画面タイトルの色を変える

Windows ストアアプリの Hub 画面で、先頭に背景画像を表示するようにするととてもインパクトがあり印象的になります

f:id:matatabi_ux:20140308163202p:plain

この場合ヘッダ部分の色を画像の背景と区別しやすい色にすると思いますが

f:id:matatabi_ux:20140308163232p:plain

スクロールさせると背景とかぶって困ったことに;

というわけでスクロール位置によってヘッダの色が変化するようにしてみます

/// <summary>
/// Actual Width / Height にバインド可能にするビヘイビア
/// </summary>
public class ActualSizeBindableBehavior : DependencyObject, IBehavior
{
    #region ActualWidth 依存関係プロパティ
    /// <summary>
    /// ActualWidth 依存関係プロパティ
    /// </summary>
    public static readonly DependencyProperty ActualWidthProperty
        = DependencyProperty.Register("ActualWidth", typeof(double), typeof(ActualSizeBindableBehavior), new PropertyMetadata(0d));

    /// <summary>
    /// ActualWidth
    /// </summary>
    public double ActualWidth
    {
        get { return (double)this.GetValue(ActualWidthProperty); }
        set { this.SetValue(ActualWidthProperty, value); }
    }
    #endregion //ActualWidth 依存関係プロパティ

    #region ActualHeight 依存関係プロパティ
    /// <summary>
    /// ActualHeight 依存関係プロパティ
    /// </summary>
    public static readonly DependencyProperty ActualHeightProperty
        = DependencyProperty.Register("ActualHeight", typeof(double), typeof(ActualSizeBindableBehavior), new PropertyMetadata(0d));

    /// <summary>
    /// ActualHeight
    /// </summary>
    public double ActualHeight
    {
        get { return (double)this.GetValue(ActualHeightProperty); }
        set { this.SetValue(ActualHeightProperty, value); }
    }
    #endregion //ActualHeight 依存関係プロパティ

    #region Padding 依存関係プロパティ
    /// <summary>
    /// Padding 依存関係プロパティ
    /// </summary>
    public static readonly DependencyProperty PaddingProperty
        = DependencyProperty.Register("Padding", typeof(Thickness), typeof(ActualSizeBindableBehavior), new PropertyMetadata(new Thickness(0)));

    /// <summary>
    /// Padding
    /// </summary>
    public Thickness Padding
    {
        get { return (Thickness)this.GetValue(PaddingProperty); }
        set { this.SetValue(PaddingProperty, value); }
    }
    #endregion //Padding 依存関係プロパティ

    /// <summary>
    /// コンストラクタ
    /// </summary>
    public ActualSizeBindableBehavior()
    {
    }

    /// <summary>
    /// アタッチ対象のオブジェクト
    /// </summary>
    public DependencyObject AssociatedObject { get; set; }

    /// <summary>
    /// アタッチする
    /// </summary>
    /// <param name="associatedObject">アタッチ対象オブジェクト</param>
    public void Attach(DependencyObject associatedObject)
    {
        this.AssociatedObject = associatedObject;
        if (associatedObject is FrameworkElement)
        {
            ((FrameworkElement)associatedObject).SizeChanged += this.OnSizeChanged;
        }
    }

    /// <summary>
    /// サイズ変更イベントハンドラ
    /// </summary>
    /// <param name="sender">イベント発行者</param>
    /// <param name="e">イベント引数</param>
    private void OnSizeChanged(object sender, SizeChangedEventArgs e)
    {
        if (this.AssociatedObject is FrameworkElement)
        {
            this.ActualWidth = ((FrameworkElement)this.AssociatedObject).ActualWidth;
            this.ActualHeight = ((FrameworkElement)this.AssociatedObject).ActualHeight;
        }

        if (this.AssociatedObject is Control)
        {
            this.Padding = ((Control)this.AssociatedObject).Padding;
        }
    }

    /// <summary>
    /// デタッチする
    /// </summary>
    public void Detach()
    {
        if (this.AssociatedObject is FrameworkElement)
        {
            ((FrameworkElement)this.AssociatedObject).SizeChanged -= this.OnSizeChanged;
        }
        this.AssociatedObject = null;
    }
}

ヘッダやセクション部分の横幅の判定に利用する ActualWidth は通常バインドできないのでバインドして ViewModel で扱えるように Behavior を作りました

横幅を把握するために Padding もバインド可能にしています

<Hub>
    <i:Interaction.Behaviors>
        <b:ScrollBarBindableBehavior HorizontalOffset="{Binding HorizontalOffset, Mode=TwoWay}" />
        <b:ActualSizeBindableBehavior Padding="{Binding HubPadding, Mode=TwoWay}" />
    </i:Interaction.Behaviors>

    <Hub.Header>
        <!--  [戻る] ボタンおよびページ タイトル  -->
        <Grid>
            <Grid.ColumnDefinitions>
                <ColumnDefinition Width="80" />
                <ColumnDefinition Width="*" />
            </Grid.ColumnDefinitions>
            <Button x:Name="backButton"
                    Margin="-1,-1,39,0"
                    VerticalAlignment="Top"
                    AutomationProperties.AutomationId="BackButton"
                    AutomationProperties.ItemType="Navigation Button"
                    AutomationProperties.Name="Back"
                    Command="{Binding NavigationHelper.GoBackCommand,
                                      ElementName=pageRoot}"
                    Style="{StaticResource NavigationBackButtonNormalStyle}" />
            <TextBlock x:Name="pageTitle"
                       Grid.Column="1"
                       VerticalAlignment="Top"
                       IsHitTestVisible="false"
                       Style="{StaticResource HeaderTextBlockStyle}"
                       Text="{StaticResource AppName}"
                       TextWrapping="NoWrap">
                <TextBlock.Foreground>
                    <SolidColorBrush Color="{Binding HeaderColor}" />
                </TextBlock.Foreground>
                <i:Interaction.Behaviors>
                    <b:ActualSizeBindableBehavior ActualWidth="{Binding HeaderWidth, Mode=TwoWay}" />
                </i:Interaction.Behaviors>
            </TextBlock>
        </Grid>
    </Hub.Header>

    <HubSection Width="900" Margin="0,0,80,0">
        <i:Interaction.Behaviors>
            <b:ActualSizeBindableBehavior ActualWidth="{Binding HeaderSectionWidth, Mode=TwoWay}" Padding="{Binding HeaderSectionPadding, Mode=TwoWay}" />
        </i:Interaction.Behaviors>
        <HubSection.Background>
            <ImageBrush ImageSource="http://farm4.staticflickr.com/3311/4627001066_127ff87178_b_d.jpg" Stretch="UniformToFill" />
        </HubSection.Background>
    </HubSection>

以前紹介した ScrollViewer の Offset をバインド可能にする Behavior と一緒に各所で ActualWidth や Padding をバインドして

/// <summary>
/// ヘッダ色
/// </summary>
[IgnoreDataMember]
public Color HeaderColor
{
    get
    {
        var color = Colors.White;
        if (this.horizontalOffset > this.headerSectionWidth - this.hubPadding.Left
            - this.headerSectionPadding.Left - this.headerSectionPadding.Right - this.headerWidth)
        {
            color = Color.FromArgb(255, 52, 52, 52);
        }
        return color;
    }
}

/// <summary>
/// 水平スクロール位置 の変更後の処理
/// </summary>
partial void OnHorizontalOffsetChanged()
{
    this.OnPropertyChanged("HeaderColor");
}

/// <summary>
/// Hub 余白 の変更後の処理
/// </summary>
partial void OnHubPaddingChanged()
{
    this.OnPropertyChanged("HeaderColor");
}

/// <summary>
/// ヘッダ横幅 の変更後の処理
/// </summary>
partial void OnHeaderWidthChanged()
{
    this.OnPropertyChanged("HeaderColor");
}

/// <summary>
/// ヘッダセクション横幅 の変更後の処理
/// </summary>
partial void OnHeaderSectionWidthChanged()
{
    this.OnPropertyChanged("HeaderColor");
}

/// <summary>
/// ヘッダセクション余白 の変更後の処理
/// </summary>
partial void OnHeaderSectionPaddingChanged()
{
    this.OnPropertyChanged("HeaderColor");
}

あとは スクロール位置が切り替え位置を超えた場合に、色を切り替える処理を ViewModel に組み込むだけ!

f:id:matatabi_ux:20140308163254p:plain

できました!