如何在组合框中绑定多个按钮

How to bind Multiple Buttons in Combobox

我想在组合框中绑定一个按钮列表。每个 Combobox 将包含一个类别的按钮,依此类推。如附图所示。

下面是我的代码:

<ItemsControl x:Name="iNumbersList">
    <ItemsControl.ItemsPanel>
        <ItemsPanelTemplate>
            <WrapPanel HorizontalAlignment="Center" VerticalAlignment="Center" Orientation="Horizontal" MaxWidth="930"/>
        </ItemsPanelTemplate>
    </ItemsControl.ItemsPanel>
    <ItemsControl.ItemTemplate>
        <DataTemplate>
            <Button  Click="ItemButtonClick"
                 Tag="{Binding ItemTag}"
                 HorizontalContentAlignment="Center"
                 VerticalContentAlignment="Center"
                 Height="100" Width="300">
                <TextBlock TextAlignment="Center" Foreground="Red"
                       HorizontalAlignment="Center" FontWeight="SemiBold"
                       FontSize="25" TextWrapping="Wrap"
                       Text="{Binding ItemDisplayMember}"/>
            </Button>
        </DataTemplate>
    </ItemsControl.ItemTemplate>
public class NumberModel
{
    public string ItemDisplayMember { get; set; }
    public object ItemTag { get; set; }
    public string ItemCategory { get; set; }
}

如何按 ItemCategory 属性 分组并将其绑定到 GUI 上,每个 ItemCategory 一个 ComboBox,然后其中有多个按钮?

可能您不需要 ComboBoxExpander 因为使用它可以到达 objective。当您必须过滤其中的内容或使用 Window 内容上方显示的下拉菜单时,需要 ComboBox

我使用 MVVM 编程模式写了一个简单的例子。将会有许多新的 classes,但其中大部分只需要添加到项目中一次。 让我们从头开始!

1) 创建class NotifyPropertyChanged实现INotifyPropertyChanged接口。它需要能够 Binding 在运行时动态更新布局。

NotifyPropertyChanged.cs

public class NotifyPropertyChanged : INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged;
    protected virtual void OnPropertyChanged([CallerMemberName]string propertyName = null)
        => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}

2) 从 NotifyPropertyChanged 派生 MainViewModel class。它将用于 Binding 个目标属性。

MainViewModel.cs

public class MainViewModel : NotifyPropertyChanged
{
    public MainViewModel()
    {

    }
}

3) 将 MainViewModel 附加到 MainWindowDataContext。其中一种方法 - 在 xaml.

中进行

MainWindow.xaml

<Window.DataContext>
    <local:MainViewModel/>
</Window.DataContext>

4) 从 NotifyPropertyChanged 导出数据 class NumberModel 并为每个 属性 添加 OnPropertyChanged 调用。这将产生奇妙的效果:当您在运行时更改任何 属性 时,您将立即看到 UI 中的更改。 MVVM 魔术调用 Binding :)

NumberModel.cs

public class NumberModel : NotifyPropertyChanged
{
    private string _itemDisplayMember;
    private object _itemTag;
    private string _itemCategory;

    public string ItemDisplayMember
    { 
        get => _itemDisplayMember;
        set 
        {
            _itemDisplayMember = value;
            OnPropertyChanged();
        }
    }
    public object ItemTag
    {
        get => _itemTag;
        set
        {
            _itemTag = value;
            OnPropertyChanged();
        }
    }
    public string ItemCategory
    {
        get => _itemCategory;
        set
        {
            _itemCategory = value;
            OnPropertyChanged();
        }
    }
}

5) 单击按钮时,我不会处理 Click 事件,而是调用 Command。为了便于使用命令,我建议中继其逻辑 class(抓取 here)。

RelayCommand.cs

public class RelayCommand : ICommand
{
    private readonly Action<object> _execute;
    private readonly Func<object, bool> _canExecute;

    public event EventHandler CanExecuteChanged
    {
        add { CommandManager.RequerySuggested += value; }
        remove { CommandManager.RequerySuggested -= value; }
    }

    public RelayCommand(Action<object> execute, Func<object, bool> canExecute = null)
    {
        _execute = execute;
        _canExecute = canExecute;
    }

    public bool CanExecute(object parameter) => _canExecute == null || _canExecute(parameter);
    public void Execute(object parameter) => _execute(parameter);
}

6) 准备好用代码填充 MainViewModel。我在那里添加了一个命令和一些要收集的项目以供测试。

MainViewModel.cs

public class MainViewModel : NotifyPropertyChanged
{
    private ObservableCollection<NumberModel> _itemsList;
    private ICommand _myCommand;

    public ObservableCollection<NumberModel> ItemsList
    {
        get => _itemsList;
        set
        {
            _itemsList = value;
            OnPropertyChanged();
        }
    }

    public ICommand MyCommand => _myCommand ?? (_myCommand = new RelayCommand(parameter =>
    {
        if (parameter is NumberModel number) 
            MessageBox.Show("ItemDisplayMember: " + number.ItemDisplayMember + "\r\nItemTag: " + number.ItemTag.ToString() + "\r\nItemCategory: " + number.ItemCategory);
    }));

    public MainViewModel()
    {
        ItemsList = new ObservableCollection<NumberModel>
        {
            new NumberModel { ItemDisplayMember = "Button1", ItemTag="Tag1", ItemCategory = "Category1" },
            new NumberModel { ItemDisplayMember = "Button2", ItemTag="Tag2", ItemCategory = "Category1" },
            new NumberModel { ItemDisplayMember = "Button3", ItemTag="Tag3", ItemCategory = "Category1" },
            new NumberModel { ItemDisplayMember = "Button4", ItemTag="Tag4", ItemCategory = "Category2" },
            new NumberModel { ItemDisplayMember = "Button5", ItemTag="Tag5", ItemCategory = "Category2" },
            new NumberModel { ItemDisplayMember = "Button6", ItemTag="Tag6", ItemCategory = "Category2" },
            new NumberModel { ItemDisplayMember = "Button7", ItemTag="Tag7", ItemCategory = "Category3" },
            new NumberModel { ItemDisplayMember = "Button8", ItemTag="Tag8", ItemCategory = "Category4" },
            new NumberModel { ItemDisplayMember = "Button9", ItemTag="Tag9", ItemCategory = "Category4" }
        };
    }
}

7) 您的主要问题的主要答案是: 使用 IValueConverter 以符合要求的条件过滤列表。我写了 2 个转换器。首先是类别,其次是按钮。

Converters.cs

public class CategoryConverter : IValueConverter
{
    public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
    {
        if (value is ObservableCollection<NumberModel> collection)
        {
            List<NumberModel> result = new List<NumberModel>();
            foreach (NumberModel item in collection)
            {
                if (!result.Any(x => x.ItemCategory == item.ItemCategory))
                    result.Add(item);
            }
            return result;
        }
        return null;
    }

    public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
    {
        throw new NotImplementedException();
    }
}

public class ItemGroupConverter : IMultiValueConverter
{
    public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
    {
        if (values[0] is ObservableCollection<NumberModel> collection && values[1] is string categoryName)
        {
            List<NumberModel> result = new List<NumberModel>();
            foreach (NumberModel item in collection)
            {
                if (item.ItemCategory == categoryName)
                    result.Add(item);
            }
            return result;
        }
        return null;
    }

    public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
    {
        throw new NotImplementedException();
    }
}

8) 现在一切都准备好填充标记了。我在这里 post 完整标记以使一切清楚。

注意: 我在 ItemsSource 中设置 MultiBinding 时遇到 Visual Studio 2019 16.5.4 crash 并应用了解决方法。

MainWindow.xaml

<Window x:Class="WpfApp1.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:local="clr-namespace:WpfApp1"
        Title="MainWindow" Height="600" Width="1000" WindowStartupLocation="CenterScreen">
    <Window.DataContext>
        <local:MainViewModel/>
    </Window.DataContext>
    <Window.Resources>
        <local:CategoryConverter x:Key="CategoryConverter"/>
        <local:ItemGroupConverter x:Key="ItemGroupConverter"/>
    </Window.Resources>
    <Grid>
        <ItemsControl ItemsSource="{Binding ItemsList, Converter={StaticResource CategoryConverter}}">
            <ItemsControl.ItemsPanel>
                <ItemsPanelTemplate>
                    <WrapPanel Orientation="Vertical"/>
                </ItemsPanelTemplate>
            </ItemsControl.ItemsPanel>
            <ItemsControl.ItemTemplate>
                <DataTemplate DataType="{x:Type local:NumberModel}">
                    <Expander Header="{Binding ItemCategory}">
                        <ItemsControl DataContext="{Binding DataContext,RelativeSource={RelativeSource AncestorType=Window}}">
                            <ItemsControl.Style>
                                <Style TargetType="ItemsControl">
                                    <Setter Property="ItemsSource">
                                        <Setter.Value>
                                            <MultiBinding Converter="{StaticResource ItemGroupConverter}">
                                                <Binding Path="ItemsList"/>
                                                <Binding Path="Header" RelativeSource="{RelativeSource AncestorType=Expander}"/>
                                            </MultiBinding>
                                        </Setter.Value>
                                    </Setter>
                                </Style>
                            </ItemsControl.Style>
                            <ItemsControl.ItemsPanel>
                                <ItemsPanelTemplate>
                                    <WrapPanel HorizontalAlignment="Left" VerticalAlignment="Center" Orientation="Horizontal" MaxWidth="930"/>
                                </ItemsPanelTemplate>
                            </ItemsControl.ItemsPanel>
                            <ItemsControl.ItemTemplate>
                                <DataTemplate DataType="{x:Type local:NumberModel}">
                                    <Button Tag="{Binding ItemTag}"
                                            HorizontalContentAlignment="Center"
                                            VerticalContentAlignment="Center"
                                            Height="100" Width="300"
                                            Command="{Binding DataContext.MyCommand,RelativeSource={RelativeSource AncestorType=Window}}"
                                            CommandParameter="{Binding}">
                                        <TextBlock TextAlignment="Center" Foreground="Red"
                                                   HorizontalAlignment="Center" FontWeight="SemiBold"
                                                   FontSize="25" TextWrapping="Wrap"
                                                   Text="{Binding ItemDisplayMember}"/>
                                    </Button>
                                </DataTemplate>
                            </ItemsControl.ItemTemplate>
                        </ItemsControl>
                    </Expander>
                </DataTemplate>
            </ItemsControl.ItemTemplate>
        </ItemsControl>
    </Grid>
</Window>

使用 MVVM 完成作业。享受。 :)

P.S。啊,是的,我忘了给你看隐藏代码class。在这里!

MainWindow.xaml.cs

public partial class MainWindow : Window
{
    public MainWindow()
    {
        InitializeComponent();
    }
}