如何在 MVVM 中将标志枚举绑定到列表框

发布于 2024-09-14 08:11:10 字数 730 浏览 6 评论 0原文

我想将具有 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>

I want to bind an enum which has flags attribute to a listbox with a check list box item template in mvvm pattern? How can I do this?

[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>

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

止于盛夏 2024-09-21 08:11:10

您无法轻松地直接绑定该值,因为转换器无法从单个标志构建标志组合。因此,您需要管理标志的集合,并基于该集合构建组合。困难在于 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 类......

You can't easily bind the value directly, because the converter can't build the flag combination from a single flag. So you need to manage a collection of flags, and build the combination based on this collection. The difficulty is that the ListBox.SelectedItems property is readonly. However, this blog post gives a nice workaround, using an attached property.

Here's a complete example using this solution :

Code-behind

[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>

NOTE: for the sake of simplicity, the ViewModel is the Window class. In a real MVVM application, you would of course use a separate ViewModel class...

苦笑流年记忆 2024-09-21 08:11:10

我看到两种解决方案:一种是完全动态的,另一种是静态的。动态解决方案需要大量工作,而且在我看来并非微不足道。静态面板应该很简单:

在数据模板中创建一个面板。在其中,为每个标志值放置一个复选框。然后使用 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,请在 Border 甚至 ListBox 中使用 ScrollViewer。

I see two solutions: One that is fully dynamic, and one that is static. The dynamic solution is a lot of work and IMO not trivial. The static one should be easy:

Create an Panel within your DataTemplate. There in, place for each Flag-Value a CheckBox. Then use the ConverterParameter to specifiy the flag, the converter should use. This would look something like this:

<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/>

In your converter you only have to do some logical AND-comparisons and you will have what you're looking for. If you're interested in the dynamic solution, make a comment, I can give you some ideas where to start. But IMO this will really not be trivial.

Additonal info

If you want to have a list instead of the StackPanel, use a ScrollViewer in a Border or even a ListBox.

浅浅 2024-09-21 08:11:10

为了扩展克里斯的帖子,这里更全面地解释了如何做到这一点。

这不是最理想的情况,因为保存枚举的属性必须比平常更复杂一些,但它是有效的。

转换器代码:

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

To expand on Chris's post, here's a more thorough explanation of how you can do this.

This isn't the most ideal scenario, as the property holding the enum has to be a bit more complex than usual, but it works.

Converter code:

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 for converter:

<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>

Then, to bind to this, I did a bit of trickery to get the converter to work properly:

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

Because of this special setter logic, you probably want to include a method like this to allow you to fully set the value from code:

private void ResetTheSportType()
{
    _TheSportType = _TheSportType.None;
    NotifyPropertyChanged(() => TheSportType);
}
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文