Universal Windows の GridView で特定のアイテムを選択不可にする方法

WP8 の LongListMultiSelector で特定のアイテムを選択不可にする方法 の続き(?)です。

Universal Windows アプリへの移植


Universal Windows アプリケーションでは、Phone アプリも Windows Runtime API (いわゆるMetro、Modern)アプリ用の API を使って実装します。そのため、今まで Phone アプリを開発していて Universal に移植する場合には多くのコントロールを置き換える必要があり、LongListSelectorToolkit の LongListMultiSelector を使用していた場合もこれに当てはまります。

前回と同じくやりたいことは、
「ViewModel の特定にプロパティの変化に応じて、既に表示済みであっても動的に GridViewItem の選択可不可が変わること」
です。

試したこと


まず、試して効果がなかった方法を挙げておきます。
  1. ViewModel に IsEnabled プロパティを追加する
  2. ItemContainerStyle で、GridViewItem の IsEnabled プロパティに IsSelectable をバインドする
  3. ItemContainerStyleSelector で、IsSelectable に応じて別のStyleを適用する
最初に気づきましたが、アイテムが選択できるかどうかは ItemContainer (GridViewItem) の IsEnabled でも制御できるようです。なので、上記の方法は IsEnabled にどのように ViewModel のプロパティをバインドするか、という観点で行っています。
※ WP8 のときに LongListMultiSelector の中にまで手を加えたのはやり過ぎだったのかもしれません。今更ですが。

1番目の方法ですが、GridViewItem の IsEnabled は ViewModel の IsEnabled をバインドしていないので効果がありません。

2番目の方法ですが、XAMLで下記のような実装をしました。
<GridView.ItemContainerStyle>
    <Style TargetType="GridViewItem">
        <Setter Property="IsEnabled"
            Value="{Binding Path=DataContext.IsSelectable,
                        RelativeSource={RelativeSource Mode=Self}}" />
    </Style>
</GridView.ItemContainerStyle>
Setter で IsEnabled の Value に対して ViewModel のプロパティをバインドしているのですが、効果が無いようです。上記の例だと Path が間違っているとかあるかもしれませんが、思いつく限りのパターンを試してみても期待する動作はしてくれませんでした。
ここで、Value に対して False を静的に指定すると常時選択不可状態になることがわかりました。

3番目の方法ですが、ViewModel のプロパティに応じて Style を出し分ける StyleSelector を用意し、それを GridView にセットするというものです。
public class CustomStyleSelector : StyleSelector
{
    public Style EnabledStyle { get; set;}
    public Style DisabledStyle { get; set;}

    protected override Style SelectStyleCore(object item, DependencyObject container)
    {
        var vm = item as MyViewModel;
        
        if(vm.IsSelectable) {
            return EnabledStyle;
        } else {
            return DisabledStyle;
        }
    }
}
出し分け自体は機能していましたが、SelectStyleCore は表示されるときに一回しか呼ばれないので、期待の動作にはなりませんでした。

解決策


効果がなかった方法の3番目に近いです。StyleSelector を使用します。

先ほど書いたとおり、StyleSelector の SelectStyleCore は GridViewItem が表示される前に一度だけ呼ばれます。適用される ViewModel も引数として渡ってくるので、この時点でコードビハインドで IsEnabled にプロパティをバインドしてしまいます。

public class GridViewItemSelectivityBinder : StyleSelector
{
    protected override Style SelectStyleCore(object item, DependencyObject container)
    {
        var selectorItem = container as GridViewItem;

        selectorItem.SetBinding(GridViewItem.IsEnabledProperty, new Binding
        {
            Source = item as MyViewModel,
            Path = new PropertyPath("IsSelectable"),
            Mode = BindingMode.OneWay
        });

        return base.SelectStyleCore(item, container);
    }
}
あとはこの GridViewItemSelectivityBinder を GridView の ItemContainerStyleSelector としてセットしておけばOKです。


左が、ListViewSelectionMode.None で、全ての IsEnabled が true のとき。
真ん中が、ListViewSelectionMode.Multiple で、鍵マーク付きの IsEnabled が false のとき。
右が、ListViewSelectionMode.Multiple で、動画マーク付きの IsEnabled が false のとき。


見た目はまだこれから改善するとして、期待通りの動作になってくれました。

おわり。

コメント

このブログの人気の投稿

Lumia 920買った - LTE有効化と開発端末登録

Android で Unicode絵文字入力

Nexus 5 買った