【问题标题】:How to bind Multiple Buttons in Combobox如何在组合框中绑定多个按钮
【发布时间】:2020-04-18 21:34:17
【问题描述】:

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

下面是我的代码:

<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,然后在其中绑定多个按钮?

【问题讨论】:

  • 为什么是ComboBox?使用Expander或自定义TreeView
  • @aepot ...你能分享我的例子吗,我可以绑定一个Exander控件列表,每个exander控件都有多个按钮?
  • Expander 不是项目控制,而是包装。它只为您提供展开/折叠功能。
  • 正在使用 MVVM
  • 没有 mvvm ... @aepot

标签: c# wpf xaml combobox


【解决方案1】:

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

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

1) 创建类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 类。它将用于Binding 目标属性。

MainViewModel.cs

public class MainViewModel : NotifyPropertyChanged
{
    public MainViewModel()
    {

    }
}

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

MainWindow.xaml

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

4) 从NotifyPropertyChanged 派生数据类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。为了方便使用命令,我建议转发它的逻辑类(获取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) 现在一切准备就绪,可以填充标记了。我在这里发布完整的标记以使一切清楚。

注意:我在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 完成工作。享受。 :)

附:啊,是的,我忘了给你看代码隐藏类。在这里!

MainWindow.xaml.cs

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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2018-04-28
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-11-12
    • 1970-01-01
    • 2013-10-17
    相关资源
    最近更新 更多