5

mvvm パターンのチェック リスト ボックス項目テンプレートを使用して、フラグ属性を持つ列挙型をリスト ボックスにバインドしますか? これどうやってするの?

[Flags]
public enum SportTypes
{
   None = 0,
   Baseball = 1,
   Basketball = 2,
   Football = 4,
   Handball = 8,
   Soccer = 16,
   Volleyball = 32
}


<ListBox Name="checkboxList2"
                 ItemsSource="{Binding Sports}"

                 Margin="0,5" 
                 SelectionMode="Multiple">
            <ListBox.ItemTemplate>
                <DataTemplate>
                    <CheckBox IsChecked="{Binding Path=SportTypeEnum, Converter={StaticResource enumBooleanConverter}}" 
                  Content="{Binding Item}"/>
                </DataTemplate>

            </ListBox.ItemTemplate>
4

3 に答える 3

5

コンバーターは単一のフラグからフラグの組み合わせを構築できないため、値を直接バインドするのは簡単ではありません。したがって、フラグのコレクションを管理し、このコレクションに基づいて組み合わせを構築する必要があります。難点は、ListBox.SelectedItemsプロパティが読み取り専用であることです。ただし、このブログ投稿では、添付プロパティを使用して適切な回避策を提供しています。

このソリューションを使用した完全な例を次に示します。

分離コード

[Flags]
public enum MyEnum
{
    Foo = 1,
    Bar = 2,
    Baz = 4
}

public partial class TestEnum : Window, INotifyPropertyChanged
{
    public TestEnum()
    {
        InitializeComponent();
        _flags = (MyEnum[])Enum.GetValues(typeof(MyEnum));
        _selectedFlags = new ObservableCollection<MyEnum>();
        _selectedFlags.CollectionChanged += new System.Collections.Specialized.NotifyCollectionChangedEventHandler(_selectedFlags_CollectionChanged);
        this.DataContext = this;
    }

    void _selectedFlags_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
    {
        if (_selectedFlags.Count == 0)
            Value = default(MyEnum);
        else
            Value = _selectedFlags.Aggregate((v, acc) => acc | v);
    }

    private MyEnum[] _flags;
    public MyEnum[] Flags
    {
        get { return _flags; }
        set
        {
            _flags = value;
            OnPropertyChanged("Flags");
        }
    }

    private ObservableCollection<MyEnum> _selectedFlags;
    public ObservableCollection<MyEnum> SelectedFlags
    {
        get { return _selectedFlags; }
        set
        {
            _selectedFlags = value;
            OnPropertyChanged("SelectedFlags");
        }
    }

    private MyEnum _value;
    public MyEnum Value
    {
        get { return _value; }
        set
        {
            _value = value;
            OnPropertyChanged("Value");
            var currentFlags = _flags.Where(f => _value.HasFlag(f));
            var addedFlags = currentFlags.Except(_selectedFlags).ToArray();
            var removedFlags = _selectedFlags.Except(currentFlags).ToArray();
            foreach (var f in addedFlags)
            {
                _selectedFlags.Add(f);
            }
            foreach (var f in removedFlags)
            {
                _selectedFlags.Remove(f);
            }
        }
    }

    private DelegateCommand<MyEnum> _setValueCommand;
    public ICommand SetValueCommand
    {
        get
        {
            if (_setValueCommand == null)
            {
                _setValueCommand = new DelegateCommand<MyEnum>(v => Value = v);
            }
            return _setValueCommand;
        }
    }

    public event PropertyChangedEventHandler PropertyChanged;

    protected virtual void OnPropertyChanged(string propertyName)
    {
        var handler = PropertyChanged;
        if (handler != null)
            handler(this, new PropertyChangedEventArgs(propertyName));
    }
}

XAML

<Window x:Class="TestPad.TestEnum"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:my="clr-namespace:TestPad"
        Title="TestEnum" Height="300" Width="300">
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto" />
            <RowDefinition Height="*" />
            <RowDefinition Height="*" />
        </Grid.RowDefinitions>
        <TextBlock Text="{Binding Value}" />
        <ListBox Grid.Row="1"
                 ItemsSource="{Binding Flags}"
                 SelectionMode="Extended"
                 my:MultiSelectorBehavior.SynchronizedSelectedItems="{Binding SelectedFlags}">
            <ListBox.ItemTemplate>
                <DataTemplate>
                    <CheckBox Content="{Binding}" IsChecked="{Binding IsSelected, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=ListBoxItem}}" />
                </DataTemplate>
            </ListBox.ItemTemplate>
        </ListBox>
        <ItemsControl Grid.Row="2"
                      ItemsSource="{Binding Flags}">
            <ItemsControl.ItemTemplate>
                <DataTemplate>
                    <Button Command="{Binding DataContext.SetValueCommand, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=Window}}"
                            CommandParameter="{Binding}"
                            Content="{Binding}" />
                </DataTemplate>
            </ItemsControl.ItemTemplate>
        </ItemsControl>
    </Grid>
</Window>

注: 簡単にするために、ViewModel は Window クラスです。実際の MVVM アプリケーションでは、もちろん別の ViewModel クラスを使用します...

于 2010-08-13T12:48:30.887 に答える
4

完全に動的なソリューションと静的なソリ​​ューションの 2 つがあります。動的ソリューションは多くの作業であり、IMO は簡単ではありません。静的なものは簡単なはずです:

DataTemplate 内に Panel を作成します。そこに、各フラグ値にチェックボックスを配置します。次に、ConverterParameter を使用して、コンバーターが使用するフラグを指定します。これは次のようになります。

<StackPanel>    
   <CheckBox IsChecked="{Binding Path=SportTypeEnum, Converter={StaticResource enumBooleanConverter},ConverterParameter={x:Static local:SportTypes.Baseball}}" Content="Baseball"/>
   <CheckBox IsChecked="{Binding Path=SportTypeEnum, Converter={StaticResource enumBooleanConverter},ConverterParameter={x:Static local:SportTypes.Basketball}}" Content="Basketball"/>
   <CheckBox IsChecked="{Binding Path=SportTypeEnum, Converter={StaticResource enumBooleanConverter},ConverterParameter={x:Static local:SportTypes.Football}}" Content="Football"/>
   <CheckBox IsChecked="{Binding ..../>
</StackPanel/>

コンバーターでは、いくつかの論理 AND 比較を行うだけで、探しているものが得られます。動的ソリューションに興味がある場合は、コメントしてください。どこから始めればよいか、いくつかのアイデアを提供できます。しかし、IMO これは本当に簡単なことではありません。

追加情報

StackPanel の代わりにリストが必要な場合は、Border または ListBox で ScrollViewer を使用します。

于 2010-08-13T10:27:08.007 に答える
1

Chris の投稿を拡張するために、これを行う方法のより完全な説明を次に示します。

列挙型を保持するプロパティは通常より少し複雑にする必要があるため、これは最も理想的なシナリオではありませんが、機能します。

コンバーターコード:

public class EnumFlagConverter : IValueConverter
{
    public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
    {
        var theEnum = value as Enum;
        return theEnum.HasFlag(parameter as Enum);
    }

    public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
    {
        var theEnum = parameter as Enum;
        return theEnum;
    }
}

コンバーターの XAML:

<StackPanel>
    <StackPanel.Resources>
        <local:EnumFlagConverter x:Key="MyConverter" />
    </StackPanel.Resources>
    <CheckBox IsChecked="{Binding Path=SportTypeEnum, Converter={StaticResource MyConverter},ConverterParameter={x:Static local:SportTypes.Baseball}}" Content="Baseball"/>
    <CheckBox IsChecked="{Binding Path=SportTypeEnum, Converter={StaticResource MyConverter},ConverterParameter={x:Static local:SportTypes.Basketball}}" Content="Basketball"/>
    <CheckBox IsChecked="{Binding Path=SportTypeEnum, Converter={StaticResource MyConverter},ConverterParameter={x:Static local:SportTypes.Football}}" Content="Football"/>
    <CheckBox IsChecked="{Binding ..../>
</StackPanel>

次に、これにバインドするために、コンバーターを適切に動作させるためにちょっとしたトリックを行いました。

private SportTypeEnum _TheSportType;
public SportTypeEnum _TheSportType
{
    get { return _TheSportType; }
    set
    {
        if (_TheSportType.HasFlag(value))
            _TheSportType &= ~value;
        else
            _TheSportType |= value;
        NotifyPropertyChanged();
    }
}

この特別なセッター ロジックにより、コードから値を完全に設定できるようにするために、おそらく次のようなメソッドを含める必要があります。

private void ResetTheSportType()
{
    _TheSportType = _TheSportType.None;
    NotifyPropertyChanged(() => TheSportType);
}
于 2014-06-11T20:29:08.000 に答える