Как связать перечисление флагов с ListBox в MVVM - PullRequest
5 голосов
/ 13 августа 2010

Я хочу связать перечисление, которое имеет атрибут flags, со списком с шаблоном элемента флажка списка в шаблоне 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>

Ответы [ 3 ]

4 голосов
/ 13 августа 2010

Вы не можете легко связать значение напрямую, потому что конвертер не может построить комбинацию флагов из одного флага. Таким образом, вам нужно управлять коллекцией флагов и создавать комбинации на основе этой коллекции. Сложность в том, что свойство 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 ...

4 голосов
/ 13 августа 2010

Я вижу два решения: одно полностью динамическое, другое статическое. Динамическое решение - это много работы и ИМО не тривиально. Статический должен быть простым:

Создайте панель в своем шаблоне данных. Там, для каждого значения флага установите флажок. Затем используйте 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/>

В вашем конвертере вам нужно только выполнить несколько логических сравнений И, и вы получите то, что ищете. Если вы заинтересованы в динамическом решении, оставьте комментарий, я могу дать вам несколько идей, с чего начать. Но ИМО это действительно не будет тривиальным.

Дополнительная информация

Если вы хотите иметь список вместо StackPanel, используйте ScrollViewer в Border или даже ListBox.

1 голос
/ 12 июня 2014

Чтобы подробнее остановиться на посте Криса, вот более подробное объяснение того, как вы можете это сделать.

Это не самый идеальный сценарий, поскольку свойство, содержащее перечисление, должно быть немного сложнее, чемобычно, но это работает.

Код конвертера:

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);
}
...