LoginSignup
0
3

More than 5 years have passed since last update.

ListBoxやComboBoxでItemSorceを自動生成する添付プロパティ

Last updated at Posted at 2017-11-14

概要

UWPやWPFで列挙型から1つの値を選ぶ目的でComboBoxやListBoxを使用することはよくあります。
その場合、ItemSourceに列挙型の値のリストを入れる必要があります。

以前の記事では列挙型を指定することで、その型の全値を提供するMarkUpExtensionをXAML側でItemSourceに使用する方法を紹介しました。

しかし、XAML側で型を指定するため、その内容を動的に変更することは出来ません。
また、多くの用途において、SelectedItemの型と同じなので、できればSelectedItemに自動で合わせてくれたほうがよいです。
そこで今回は添付プロパティを用いて、SelectedItemに合わせてItemSourceを自動生成します。

実行結果

スクリーンショット 2017-11-14 22.42.42.png
2つListBoxが並んでおり、片方を選択するともう片方に伝わります。
スクリーンショット 2017-11-14 22.42.47.png

さらに下のCheckBoxをクリックするとListBoxの候補値が切り替わります。
スクリーンショット 2017-11-14 22.43.22.png

View

Viewは
(左)VMのItemSourceSelectedItemに同名のプロパティをバインドした通常のListBox
(右)今回紹介する添付プロパティをVMのSelectedItemのみにバインドしたListBox
の2つがあります。
さらにその下のCheckBoxはIsCheckedがVMのIsDayOfWeekにバインドしてあります。

MainWindow.xaml
<Window
    x:Class="SelectorAttachedEnumProperty.MainWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="clr-namespace:SelectorAttachedEnumProperty"
    Width="325" Height="250">
    <Window.DataContext>
        <local:MainWindowViewModel />
    </Window.DataContext>
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition />
            <RowDefinition Height="Auto" />
        </Grid.RowDefinitions>
        <UniformGrid Grid.Row="0" Rows="1">
            <!--  普通に書いたの  -->
            <ListBox ItemsSource="{Binding ItemSource}" SelectedItem="{Binding SelectedItem}" />

            <!--  添付プロパティで指定  -->
            <ListBox local:AutoEnumSource.SelectedEnumItem="{Binding SelectedItem}" />
        </UniformGrid>
        <!--  Checkを変更するとItemSourceとSelectedItemが切り替わる  -->
        <CheckBox
            Grid.Row="1"
            Content="IsDayOfWeek"
            IsChecked="{Binding IsDayOfWeek}" />
    </Grid>
</Window>

ViewModel

ViewModelでは
ItemSource 通常のListBoxで使用するための列挙値の配列プロパティ
SelectedItem 選択された列挙値プロパティ
IsDayOfWeek 列挙型の切り替えプロパティ
が変更通知プロパティとして実装されています。
またIsDayOfWeekが変更された場合、ItemSourceSelectedItemの中身がdayOfWeeksdateTimeKinds全値配列間で変更されます。

今回の添付プロパティを使用した場合、ItemSourceとその元の2つの配列(dayOfWeeks, dateTimeKinds)は不要になります。
その場合の切り替え方法はSelectedItemへのDayOfWeekとDateTimeKindの任意の値の入力です。

MainWindowViewModel
public class MainWindowViewModel : INotifyPropertyChanged
{
    //INotifyPropertyChanged実装
    public event PropertyChangedEventHandler PropertyChanged;
    private void NotifyPropertyChanged([CallerMemberName]string propertyName = null)
        => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));

    private List<object> dayOfWeeks = typeof(DayOfWeek).GetEnumValues().Cast<object>().ToList();
    private List<object> dateTimeKinds = typeof(DateTimeKind).GetEnumValues().Cast<object>().ToList();

    //添付プロパティでItemSourceは不要になる
    private List<object> _ItemSource;
    public List<object> ItemSource
    {
        get => _ItemSource;
        set
        {
            if (_ItemSource == value)
                return;
            _ItemSource = value;
            NotifyPropertyChanged();
        }
    }

    private object _SelectedItem;
    public object SelectedItem
    {
        get => _SelectedItem;
        set
        {
            if (_SelectedItem == value)
                return;
            _SelectedItem = value;
            NotifyPropertyChanged();
        }
    }

    private bool _IsDayOfWeek = false;
    public bool IsDayOfWeek
    {
        get => _IsDayOfWeek;
        set
        {
            if (_IsDayOfWeek == value)
                return;
            _IsDayOfWeek = value;

            ItemSource = value ? dayOfWeeks : dateTimeKinds;
            SelectedItem = value ? (object)DayOfWeek.Monday : DateTimeKind.Unspecified;

            NotifyPropertyChanged();
        }
    }

    public MainWindowViewModel()
    {
        IsDayOfWeek = true;
    }
}

添付プロパティ実装

添付プロパティの基本的な説明などは
WPF4.5入門 その45 「添付プロパティ」 - かずきのBlog@hatena
などを参考にして下さい。

この添付プロパティの仕事は値が入力された際に、
ItemSourceにその値の列挙型の全値リストを設定する
SelectedItemとこの添付プロパティをバインドする
の2点です。

つまり、UIからの変更は
ViewのSelectorのSelectedItem→添付プロパティのSelectedEnumItem→VMのSelectedItem
の順で伝わります。
逆にVMからの変更は
VMのSelectedItem→添付プロパティ→ViewのSelectorのSelectedItem
の順です。

さらに変更された値の実行型が違うなら、添付プロパティの変更時にViewのItemSourceが変更されます。

AutoEnumSource.cs
public class AutoEnumSource
{
    public static object GetSelectedEnumItem(Selector control) => (object)control.GetValue(SelectedEnumItemProperty);
    public static void SetSelectedEnumItem(Selector control, object value) => control.SetValue(SelectedEnumItemProperty, value);

    public static readonly DependencyProperty SelectedEnumItemProperty = DependencyProperty.RegisterAttached(
        "SelectedEnumItem",
        typeof(object), typeof(AutoEnumSource),
        //デフォルトBindingモードをTwoWayにするために、FrameworkPropertyMetadataを使用
        new FrameworkPropertyMetadata()
        {
            PropertyChangedCallback = OnSelectedEnumItemChanged,
            BindsTwoWayByDefault = true
        });

    private static void OnSelectedEnumItemChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        //添付プロパティの変更値が有効でなかったら無効
        Type newType = e.NewValue?.GetType();
        if (newType == null || !newType.IsEnum)
        {
            return;
        }
        //添付されたコントロールがSelector以外では無効
        var selector = d as Selector;
        //変更値の型が変更前と同じなら何もしない
        if (selector?.SelectedItem?.GetType() == newType)
        {
            return;
        }
        //添付されたSelectorのItemSourceに列挙型の全値を入力
        selector.ItemsSource = Enum.GetValues(newType);
        //SelectedItemに直接値を入力
        selector.SelectedItem = e.NewValue;

        //SelectorのSelctedItemにこの添付プロパティを双方向Bindingする
        var binding = new Binding()
        {
            Path = new PropertyPath(AutoEnumSource.SelectedEnumItemProperty),
            RelativeSource = RelativeSource.Self,
            Mode = BindingMode.TwoWay
        };
        selector.SetBinding(Selector.SelectedItemProperty, binding);
    }
}

環境

VisualStudio2017
.NET Framework 4.7
C#7.1

0
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
3