Reputation: 549
Im trying to make a ComboBox that have checkboxes as items and based on what is checked display different things when the combobox is "closed".
The look Im trying achieve can be seen in the image.
Ideally I dont want the user to be able to select the text in the top ( in the image).
Is there a simple solution to this? I've seen solutions where one can display more information when all the items are shown by using DataTriggers to hide different nested controls, but that is not really what Im looking for.
Any ideas?
/Erik
Upvotes: 7
Views: 19846
Reputation: 1
Adding to all the other answers if you want to get rid of the accidental selection of value when ticking CheckBoxes you can override the Template of ComboBoxItem like below. This removes the original ComboBoxItem container and leaves only CheckBox container eliminating that risk.
<ComboBox IsEditable="True"
IsReadOnly="True"
ItemsSource="{Binding ItemsSource}"
Text="{Binding SelectedValues, Mode=OneWay}">
<ComboBox.ItemContainerStyle>
<Style TargetType="ComboBoxItem">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate>
<CheckBox Content="{Binding Item.Value}"
IsChecked="{Binding
Item.IsChecked}">
</CheckBox>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ComboBox.ItemContainerStyle>
</ComboBox>
Upvotes: 0
Reputation: 845
Using a combination of the @Erik83 and @Xavier solution I still had the problem that selecting a ComboBoxItem in a location right from the CheckBox text closes the ComboBox-DropDown and shows the ToString() value of the ComboBoxItem as the CheckBox is not stretched to DropDown-Width. I solved it by adding
HorizontalContentAlignment="Stretch"
to the CheckBox and adding the 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>
CodeBehind:
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;
Upvotes: 3
Reputation: 549
@Xaviers answer works 99% of the way. However the user is able to accidently select a checkbox and then the ToString() of the checkbox is shown as the selected text. This can happen quite alot actually. I havent yet worked out why this happens, but I've found a way to prevent this.
Create a bool property that binds to the DropDownOpen property of the combobox and when the DropDownOpen has changed to false it means that the DropDown has just been closed and you might be facing the above problem. So here you just raise the propertychanged event for the viewmodel and pass the property bound to the Text property of the combobox.
Upvotes: 2
Reputation: 3404
Here is a way to achieve most of what you want using a ComboBox
, except that the text can still be selected (using custom text only works when IsEditable
is true). It is not editable though because of IsReadOnly="true"
.
View
<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>
Viewmodel
// 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;
}
}
If the selectable text is an issue, you may want to create a custom ComboBox control template (default example here). Alternatively, you could use something else instead of a ComboBox
, and make it look like a ComboBox
.
Screenshot of example:
Upvotes: 12