带有复选框的 WPF ComboBox 显示有关选中项目的信息?
WPF ComboBox with CheckBoxes display info about checked items?
我正在尝试制作一个组合框,该组合框将复选框作为项目,并且当组合框为 "closed" 时,根据选中的内容显示不同的内容。
图像中可以看到我尝试实现的外观。
理想情况下,我不希望用户能够 select 顶部(图片中)的文本。
有没有简单的解决方法?我见过这样的解决方案,当通过使用 DataTriggers 隐藏不同的嵌套控件来显示所有项目时,可以显示更多信息,但这并不是我真正想要的。
有什么想法吗?
/艾瑞克
这是一种使用 ComboBox
实现您想要的大部分内容的方法,但仍然可以选择文本(使用自定义文本仅在 IsEditable
为真时有效)。由于 IsReadOnly="true"
.
,它不可编辑
查看
<ComboBox
IsEditable="True"
IsReadOnly="True"
ItemsSource="{Binding Items}"
Text="{Binding Text}">
<ComboBox.ItemTemplate>
<DataTemplate
DataType="{x:Type local:Item}">
<CheckBox
Content="{Binding Name}"
IsChecked="{Binding IsChecked}" />
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
视图模型
// ObservableObject is a custom base class that implements INotifyPropertyChanged
internal class MainWindowVM : ObservableObject
{
private ObservableCollection<Item> mItems;
private HashSet<Item> mCheckedItems;
public IEnumerable<Item> Items { get { return mItems; } }
public string Text
{
get { return _text; }
set { Set(ref _text, value); }
}
private string _text;
public MainWindowVM()
{
mItems = new ObservableCollection<Item>();
mCheckedItems = new HashSet<Item>();
mItems.CollectionChanged += Items_CollectionChanged;
// Adding test data
for (int i = 0; i < 10; ++i)
{
mItems.Add(new Item(string.Format("Item {0}", i.ToString("00"))));
}
}
private void Items_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
if (e.OldItems != null)
{
foreach (Item item in e.OldItems)
{
item.PropertyChanged -= Item_PropertyChanged;
mCheckedItems.Remove(item);
}
}
if (e.NewItems != null)
{
foreach (Item item in e.NewItems)
{
item.PropertyChanged += Item_PropertyChanged;
if (item.IsChecked) mCheckedItems.Add(item);
}
}
UpdateText();
}
private void Item_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
if (e.PropertyName == "IsChecked")
{
Item item = (Item)sender;
if (item.IsChecked)
{
mCheckedItems.Add(item);
}
else
{
mCheckedItems.Remove(item);
}
UpdateText();
}
}
private void UpdateText()
{
switch (mCheckedItems.Count)
{
case 0:
Text = "<none>";
break;
case 1:
Text = mCheckedItems.First().Name;
break;
default:
Text = "<multiple>";
break;
}
}
}
// Test item class
// Test item class
internal class Item : ObservableObject
{
public string Name { get; private set; }
public bool IsChecked
{
get { return _isChecked; }
set { Set(ref _isChecked, value); }
}
private bool _isChecked;
public Item(string name)
{
Name = name;
}
public override string ToString()
{
return Name;
}
}
如果可选文本有问题,您可能需要创建自定义 ComboBox 控件模板(默认示例 here)。或者,您可以使用其他东西代替 ComboBox
,并使其看起来像 ComboBox
.
示例截图:
@Xaviers 的回答成功了 99%。然而,用户可以不小心 select 一个复选框,然后复选框的 ToString() 显示为 selected 文本。这实际上可能发生很多。我还没有弄清楚为什么会发生这种情况,但我找到了一种方法来防止这种情况发生。
创建一个绑定到组合框的 DropDownOpen 属性 的 bool 属性,当 DropDownOpen 变为 false 时,这意味着 DropDown 刚刚关闭,您可能会遇到上述情况问题。
所以在这里您只需为视图模型引发 属性changed 事件并将 属性 绑定到组合框的文本 属性。
结合使用@Erik83 和@Xavier 解决方案,我仍然遇到这样的问题,即在 CheckBox 文本的右侧位置选择 ComboBoxItem 会关闭 ComboBox-DropDown 并显示 ComboBoxItem 的 ToString() 值因为 CheckBox 没有拉伸到 DropDown-Width。我通过添加
解决了它
HorizontalContentAlignment="Stretch"
到 CheckBox 并添加 ItemContainerStyle:
<ComboBox x:Name="combobox"
Background="White"
Padding="2"
Text="{Binding ElementName=DockPanelTemplateComboCheck, Path=ComboTextFilter}"
IsEditable="True"
IsReadOnly="True"
HorizontalAlignment="Stretch"
ItemsSource="{Binding ...}"
IsDropDownOpen="{Binding Path=DropOpen, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type UserControl}}, UpdateSourceTrigger=PropertyChanged}">
<ComboBox.ItemTemplate>
<DataTemplate>
<CheckBox IsChecked="{Binding IsChecked}" Content="{Binding Eintrag}" HorizontalContentAlignment="Stretch" Checked="CheckBox_Checked_Unchecked" Unchecked="CheckBox_Checked_Unchecked"/>
</DataTemplate>
</ComboBox.ItemTemplate>
<ComboBox.ItemContainerStyle>
<Style TargetType="{x:Type ComboBoxItem}">
<Setter Property="HorizontalContentAlignment" Value="Stretch"/>
</Style>
</ComboBox.ItemContainerStyle>
</ComboBox>
代码隐藏:
private string combotextfilter = "<No Selection>";
public string ComboTextFilter
{
get { return combotextfilter; }
set
{
if (value != null && value.IndexOf("ComboModel") != -1) return;
combotextfilter = value;
NotifyPropertyChanged(nameof(ComboTextFilter));
}
}
private void CheckBox_Checked_Unchecked(object sender, RoutedEventArgs e)
{
switch (((ObservableCollection<ComboModel>)combobox.ItemsSource).Count(x => x.IsChecked))
{
case 0:
ComboTextFilter = "<No Selection>";
break;
case 1:
ComboTextFilter = ((ObservableCollection<ComboModel>)combobox.ItemsSource).Where(x => x.IsChecked).First().Eintrag;
break;
default:
ComboTextFilter = ((ObservableCollection<ComboModel>)combobox.ItemsSource).Where(x => x.IsChecked).Select(x => x.Eintrag).Aggregate((i, j) => i + " | " + j);
//ComboTextFilter = "<Multiple Selected>";
break;
}
NotifyPropertyChanged(nameof(C_Foreground));
}
public bool DropOpen
{
get { return dropopen; }
set { dropopen = value; NotifyPropertyChanged(nameof(ComboTextFilter)); }
}
private bool dropopen = false;
我正在尝试制作一个组合框,该组合框将复选框作为项目,并且当组合框为 "closed" 时,根据选中的内容显示不同的内容。
图像中可以看到我尝试实现的外观。
理想情况下,我不希望用户能够 select 顶部(图片中)的文本。
有没有简单的解决方法?我见过这样的解决方案,当通过使用 DataTriggers 隐藏不同的嵌套控件来显示所有项目时,可以显示更多信息,但这并不是我真正想要的。
有什么想法吗?
/艾瑞克
这是一种使用 ComboBox
实现您想要的大部分内容的方法,但仍然可以选择文本(使用自定义文本仅在 IsEditable
为真时有效)。由于 IsReadOnly="true"
.
查看
<ComboBox
IsEditable="True"
IsReadOnly="True"
ItemsSource="{Binding Items}"
Text="{Binding Text}">
<ComboBox.ItemTemplate>
<DataTemplate
DataType="{x:Type local:Item}">
<CheckBox
Content="{Binding Name}"
IsChecked="{Binding IsChecked}" />
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
视图模型
// ObservableObject is a custom base class that implements INotifyPropertyChanged
internal class MainWindowVM : ObservableObject
{
private ObservableCollection<Item> mItems;
private HashSet<Item> mCheckedItems;
public IEnumerable<Item> Items { get { return mItems; } }
public string Text
{
get { return _text; }
set { Set(ref _text, value); }
}
private string _text;
public MainWindowVM()
{
mItems = new ObservableCollection<Item>();
mCheckedItems = new HashSet<Item>();
mItems.CollectionChanged += Items_CollectionChanged;
// Adding test data
for (int i = 0; i < 10; ++i)
{
mItems.Add(new Item(string.Format("Item {0}", i.ToString("00"))));
}
}
private void Items_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
if (e.OldItems != null)
{
foreach (Item item in e.OldItems)
{
item.PropertyChanged -= Item_PropertyChanged;
mCheckedItems.Remove(item);
}
}
if (e.NewItems != null)
{
foreach (Item item in e.NewItems)
{
item.PropertyChanged += Item_PropertyChanged;
if (item.IsChecked) mCheckedItems.Add(item);
}
}
UpdateText();
}
private void Item_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
if (e.PropertyName == "IsChecked")
{
Item item = (Item)sender;
if (item.IsChecked)
{
mCheckedItems.Add(item);
}
else
{
mCheckedItems.Remove(item);
}
UpdateText();
}
}
private void UpdateText()
{
switch (mCheckedItems.Count)
{
case 0:
Text = "<none>";
break;
case 1:
Text = mCheckedItems.First().Name;
break;
default:
Text = "<multiple>";
break;
}
}
}
// Test item class
// Test item class
internal class Item : ObservableObject
{
public string Name { get; private set; }
public bool IsChecked
{
get { return _isChecked; }
set { Set(ref _isChecked, value); }
}
private bool _isChecked;
public Item(string name)
{
Name = name;
}
public override string ToString()
{
return Name;
}
}
如果可选文本有问题,您可能需要创建自定义 ComboBox 控件模板(默认示例 here)。或者,您可以使用其他东西代替 ComboBox
,并使其看起来像 ComboBox
.
示例截图:
@Xaviers 的回答成功了 99%。然而,用户可以不小心 select 一个复选框,然后复选框的 ToString() 显示为 selected 文本。这实际上可能发生很多。我还没有弄清楚为什么会发生这种情况,但我找到了一种方法来防止这种情况发生。
创建一个绑定到组合框的 DropDownOpen 属性 的 bool 属性,当 DropDownOpen 变为 false 时,这意味着 DropDown 刚刚关闭,您可能会遇到上述情况问题。 所以在这里您只需为视图模型引发 属性changed 事件并将 属性 绑定到组合框的文本 属性。
结合使用@Erik83 和@Xavier 解决方案,我仍然遇到这样的问题,即在 CheckBox 文本的右侧位置选择 ComboBoxItem 会关闭 ComboBox-DropDown 并显示 ComboBoxItem 的 ToString() 值因为 CheckBox 没有拉伸到 DropDown-Width。我通过添加
解决了它HorizontalContentAlignment="Stretch"
到 CheckBox 并添加 ItemContainerStyle:
<ComboBox x:Name="combobox"
Background="White"
Padding="2"
Text="{Binding ElementName=DockPanelTemplateComboCheck, Path=ComboTextFilter}"
IsEditable="True"
IsReadOnly="True"
HorizontalAlignment="Stretch"
ItemsSource="{Binding ...}"
IsDropDownOpen="{Binding Path=DropOpen, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type UserControl}}, UpdateSourceTrigger=PropertyChanged}">
<ComboBox.ItemTemplate>
<DataTemplate>
<CheckBox IsChecked="{Binding IsChecked}" Content="{Binding Eintrag}" HorizontalContentAlignment="Stretch" Checked="CheckBox_Checked_Unchecked" Unchecked="CheckBox_Checked_Unchecked"/>
</DataTemplate>
</ComboBox.ItemTemplate>
<ComboBox.ItemContainerStyle>
<Style TargetType="{x:Type ComboBoxItem}">
<Setter Property="HorizontalContentAlignment" Value="Stretch"/>
</Style>
</ComboBox.ItemContainerStyle>
</ComboBox>
代码隐藏:
private string combotextfilter = "<No Selection>";
public string ComboTextFilter
{
get { return combotextfilter; }
set
{
if (value != null && value.IndexOf("ComboModel") != -1) return;
combotextfilter = value;
NotifyPropertyChanged(nameof(ComboTextFilter));
}
}
private void CheckBox_Checked_Unchecked(object sender, RoutedEventArgs e)
{
switch (((ObservableCollection<ComboModel>)combobox.ItemsSource).Count(x => x.IsChecked))
{
case 0:
ComboTextFilter = "<No Selection>";
break;
case 1:
ComboTextFilter = ((ObservableCollection<ComboModel>)combobox.ItemsSource).Where(x => x.IsChecked).First().Eintrag;
break;
default:
ComboTextFilter = ((ObservableCollection<ComboModel>)combobox.ItemsSource).Where(x => x.IsChecked).Select(x => x.Eintrag).Aggregate((i, j) => i + " | " + j);
//ComboTextFilter = "<Multiple Selected>";
break;
}
NotifyPropertyChanged(nameof(C_Foreground));
}
public bool DropOpen
{
get { return dropopen; }
set { dropopen = value; NotifyPropertyChanged(nameof(ComboTextFilter)); }
}
private bool dropopen = false;