ListBox 및 ComboBox에서 ItemSorce를 자동으로 생성하는 첨부 속성

개요



UWP나 WPF에서 열거형에서 하나의 값을 선택하기 위해 ComboBox나 ListBox를 사용하는 경우가 많습니다.
이 경우 ItemSource에 열거형 값 목록을 포함해야 합니다.

이전 기사에서는 열거형을 지정하는 것으로, 그 형태의 전치를 제공하는 MarkUpExtension를 XAML측에서 ItemSource에 사용하는 방법을 소개했습니다.

그러나 XAML 측에서 형식을 지정하기 때문에 그 내용을 동적으로 변경할 수 없습니다.
또, 많은 용도에 있어서, SelectedItem의 형태와 같기 때문에, 가능하면 SelectedItem에 자동으로 맞추어 주는 것이 좋다.
그래서 이번에는 첨부 속성을 사용하여 SelectedItem에 맞게 ItemSource를 자동 생성합니다.

실행 결과




두 개의 ListBox가 늘어서 있으며 한쪽을 선택하면 다른 한쪽에 전달됩니다.


또한 아래의 CheckBox를 클릭하면 ListBox의 후보 값이 전환됩니다.


보기



View는
(왼쪽) VM의 ItemSourceSelectedItem에 같은 이름의 속성을 바인딩한 일반 ListBox
(오른쪽) 이번에 소개하는 첨부 프로퍼티를 VM의 SelectedItem 에만 바인드 한 ListBox
두 가지가 있습니다.
또한 그 아래의 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 와 원래의 두 배열( 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

좋은 웹페이지 즐겨찾기