如何在组合框中绑定多个按钮
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
,然后其中有多个按钮?
可能您不需要 ComboBox
但 Expander
因为使用它可以到达 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
附加到 MainWindow
的 DataContext
。其中一种方法 - 在 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();
}
}
我想在组合框中绑定一个按钮列表。每个 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
,然后其中有多个按钮?
可能您不需要 ComboBox
但 Expander
因为使用它可以到达 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
附加到 MainWindow
的 DataContext
。其中一种方法 - 在 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();
}
}