しっぽを追いかけて

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

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

アイテムの選択状態に応じてアプリバーの表示状態を変化させる

f:id:matatabi_ux:20140125223531p:plain

ストアアプリのアプリバーは通常非表示なので、表示ジェスチャを行うまでどんな機能が用意されているかわかりにくい面があります

この欠点を補うため、GridView などでアイテムが選択されているときはアプリバーを自動で表示し、アイテムの選択が解除されたら非表示に遷移するようにしたい!

しかし、GridView の SelectedItems プロパティは DependencyProperty ではないのでバインディングができません;

SelectionChanged イベントハンドラでアプリバーの表示を制御してもいいんですが、できるだけこういう処理はコントロール内部で閉じたいので GridView を拡張します・・・

/// <summary>
/// 色々なサイズのタイルを表示する GridView
/// </summary>
public class VariableSizedGridView : GridView
{
    #region SelectedItems 依存関係プロパティ
    /// <summary>
    /// 選択中アイテム 依存関係プロパティ
    /// </summary>
    public static readonly DependencyProperty SelectedItemViewModelsProperty
        = DependencyProperty.Register("SelectedItemViewModels", typeof(IList<ViewModelBase>), typeof(VariableSizedGridView), new PropertyMetadata(new ObservableCollection<ViewModelBase>()));

    /// <summary>
    /// 選択中アイテム
    /// </summary>
    public IList<ViewModelBase> SelectedItemViewModels
    {
        get { return (IList<ViewModelBase>)this.GetValue(SelectedItemViewModelsProperty); }
        set { this.SetValue(SelectedItemViewModelsProperty, value); }
    }
    #endregion //SelectedItems 依存関係プロパティ

    /// <summary>
    /// コンストラクタ
    /// </summary>
    public VariableSizedGridView() : base()
    {
        this.SelectionChanged += this.OnSelectionChanged;
    }

    /// <summary>
    /// 子要素の破棄
    /// </summary>
    protected override void OnDisconnectVisualChildren()
    {
        this.SelectionChanged -= this.OnSelectionChanged;
        base.OnDisconnectVisualChildren();
    }

    /// <summary>
    /// 選択中アイテム変更イベントハンドラ
    /// </summary>
    /// <param name="sender">イベント発行者</param>
    /// <param name="e">イベント引数</param>
    private void OnSelectionChanged(object sender, SelectionChangedEventArgs e)
    {
        if (e.AddedItems != null)
        {
            foreach (var item in e.AddedItems)
            {
                this.SelectedItemViewModels.Add((ViewModelBase)item);
            }
        }
        if (e.RemovedItems != null)
        {
            foreach (var item in e.RemovedItems)
            {
                this.SelectedItemViewModels.Remove((ViewModelBase)item);
            }
        }

        var expression = this.GetBindingExpression(VariableSizedGridView.SelectedItemViewModelsProperty);
        if(expression != null && expression.DataItem != null)
        {
            expression.UpdateSource();
        }
    }
}

SelectedItems とは別に SelectedItemViewModels という DependencyProperty を公開し、SelectionChanged イベントが発生したらバインディング元の ViewModel のプロパティを明示的に更新しています

/// <summary>
/// 選択中アイテム
/// </summary>
private IList<ViewModelBase> selectedItems = new ObservableCollection<ViewModelBase>();

/// <summary>
/// 選択中アイテム
/// </summary>
public IList<ViewModelBase> SelectedItems 
{
    get { return this.selectedItems; }
    set 
    {
        this.SetProperty<IList<ViewModelBase>>(ref this.selectedItems, value);
        this.OnPropertyChanged("IsAppBarOpen");
    }
}

/// <summary>
/// アプリバー表示フラグ
/// </summary>
public bool IsAppBarOpen 
{ 
    get { return this.selectedItems.Count > 0; }
}

ViewModel 側はこんな風に SelectedItems の件数でアプリバーの表示フラグを制御します

<Page.BottomAppBar>
    <CommandBar IsOpen="{Binding IsAppBarOpen, Mode=OneWay}" IsSticky="True">
        <CommandBar.PrimaryCommands>
            <AppBarButton Icon="Delete" Label="削除" />
            <AppBarButton Icon="Add" Label="新規" />
        </CommandBar.PrimaryCommands>
    </CommandBar>
</Page.BottomAppBar>

あとは XAML 側のアプリバーの IsOpen プロパティにバインディングするだけ!

まぁ作った後にたくさん画面を作る場合でないと GridView 内部で選択アイテムを更新する意味は薄いかもと思いましたが;