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

개요



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

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

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

실행 결과




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


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


보기



View는
(왼쪽) VM의 ItemSource 및 SelectedItem에 같은 이름의 속성을 바인딩한 일반 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 가 변경되었을 경우, ItemSource 와 SelectedItem 의 내용이 dayOfWeeks ⇔ dateTimeKinds 전치 배열간에 변경됩니다.

이 첨부 속성을 사용하면 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

좋은 웹페이지 즐겨찾기