1

I have a comboBox which get populated by an XML. It lists every item as checkbox item I would like the ComboBox to display the checked selections, like the image below

enter image description here

This is my XML

<?xml version="1.0" encoding="utf-8" ?>
<ComboBox>
  <Customer name="John">
    <Data>
      <System>Linux</System>
    </Data>
  </Customer>
  <Customer name="Fernando">
    <Data>
      <System>Microsoft</System>
      <System>Mac</System>
    </Data>
  </Customer>
</ComboBox>

This is my XAML

<ComboBox x:Name="customer_comboBox" HorizontalAlignment="Left" Margin="83,259,0,0" VerticalAlignment="Top" Width="172" SelectionChanged="customer_comboBox_SelectionChanged" >
     <ComboBox.ItemTemplate>
         <DataTemplate>
                <CheckBox Content="{Binding}"/>
        </DataTemplate>
     </ComboBox.ItemTemplate>
</ComboBox>

And this is the code that is used to populate the customer_comboBox

 XmlDocument doc = new XmlDocument();
 doc.Load(Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments) +@"\comboBox.xml");
 XmlNodeList customerList = doc.SelectNodes("ComboBox/Customer");
 List<string> customers = new List<string>();

 foreach (XmlNode node in customerList)
 {
     customers.Add(node.InnerText);
 }
 customer_comboBox.ItemsSource = customers;
Goigle
  • 138
  • 13
Fernando
  • 29
  • 6
  • Does this answer your question? [Looking for a WPF ComboBox with checkboxes](https://stackoverflow.com/questions/859227/looking-for-a-wpf-combobox-with-checkboxes) – Goigle Oct 30 '20 at 00:47
  • I have edited your question's tags a little bit, I hope you don't mind. It's not necessary to tag your question with minor things like user control types. Major things like language (`c#`), technology/framework (`winforms`, `data-binding`) is generally the way to go. If you aren't sure, hover over a tag and if it has a low post count (say < 1000) and is quite old, it's probably a good reason not to use it. Good luck. –  Oct 30 '20 at 00:55

1 Answers1

0

My working MVVM approach for multiselection WPF ComboBox.

In xaml I use Combobox and IsHitTestVisible="False" TextBlock with joined values which are checked in ComboBox. I used Grid container because we need to have TextBlock under ComboBox.

<Grid>
        <ComboBox ItemsSource="{Binding ComboItemsCollection}"
                  IsReadOnly="True"
                  IsEditable="True">
            <b:Interaction.Behaviors>
                <local:PreventSelectionBehavior/>
            </b:Interaction.Behaviors>
            <ComboBox.ItemTemplate>
                <DataTemplate>
                    <StackPanel Orientation="Horizontal">
                        <CheckBox IsChecked="{Binding IsSelected}" Width="Auto" Content="{Binding Name}"/>
                    </StackPanel>
                </DataTemplate>
            </ComboBox.ItemTemplate>
        </ComboBox>
        <TextBlock Text="{Binding ComboItemsCollectionDisplayString}"
                   IsHitTestVisible="False"/>
    </Grid>

My ViewModels look like

 public class ComboItemVm : INotifyPropertyChanged
{
    private bool _isSelected;
    public bool IsSelected
    {
        get => _isSelected;
        set
        {
            if (value == _isSelected)
                return;

            _isSelected = value;
            OnPropertyChanged(nameof(IsSelected));
        }
    }

    private string _name;
    public string Name
    {
        get => _name;
        set
        {
            if (value == _name)
                return;

            _name = value;
            OnPropertyChanged(nameof(Name));
        }
    }

    public event PropertyChangedEventHandler PropertyChanged;

    protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
        => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}

public class WpfTestVm : INotifyPropertyChanged
{
    public string ComboItemsCollectionDisplayString
        => string.Join(", ", ComboItemsCollection
            .Where(item => item.IsSelected)
            .Select(item => item.Name));


    public ObservableCollectionEx<ComboItemVm> ComboItemsCollection { get; set; } = new ObservableCollectionEx<ComboItemVm>
    {
        new ComboItemVm{Name = "Item1"},
        new ComboItemVm{Name = "Item2"},
        new ComboItemVm{Name = "Item3"},
    };

    public WpfTestVm()
    {
        ComboItemsCollection.ItemChanged += (sender, args) =>
        {
            OnPropertyChanged(nameof(ComboItemsCollectionDisplayString));
        };
    }

    public event PropertyChangedEventHandler PropertyChanged;

    protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
        => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}

Extended observable collection implementation for tracking changes of IsSelected in Collection's items.

    public class ObservableCollectionEx<T> : ObservableCollection<T> where T : INotifyPropertyChanged
{
    public ObservableCollectionEx(IEnumerable<T> initialData) : base(initialData)
    {
        Init();
    }

    public ObservableCollectionEx()
    {
        Init();
    }

    private void Init()
    {
        foreach (T item in Items)
            item.PropertyChanged += ItemOnPropertyChanged;

        CollectionChanged += OnObservableCollectionCollectionChanged;
    }

    private void OnObservableCollectionCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
    {
        if (e.NewItems != null)
        {
            foreach (T item in e.NewItems)
            {
                if (item != null)
                    item.PropertyChanged += ItemOnPropertyChanged;
            }
        }

        if (e.OldItems != null)
        {
            foreach (T item in e.OldItems)
            {
                if (item != null)
                    item.PropertyChanged -= ItemOnPropertyChanged;
            }
        }
    }

    private void ItemOnPropertyChanged(object sender, PropertyChangedEventArgs e)
        => ItemChanged?.Invoke(sender, e);

    public event PropertyChangedEventHandler ItemChanged;
}

And behavior which prevent Selection in ComboBox, because we don't want it.

public class PreventSelectionBehavior : Behavior<ComboBox>
{
    protected override void OnAttached()
    {
        base.OnAttached();
        AssociatedObject.SelectionChanged += AssociatedObjectOnSelectionChanged;
    }

    protected override void OnDetaching()
    {
        base.OnDetaching();
        AssociatedObject.SelectionChanged -= AssociatedObjectOnSelectionChanged;
    }

    private void AssociatedObjectOnSelectionChanged(object sender, SelectionChangedEventArgs e)
    {
        ComboBox element = sender as ComboBox;
        if (element == null)
            return;

        element.SelectedItem = null;
    }
}

I tryied different approached, I tryied to change ComboBox template, but this approach is the best for me.

Dharman
  • 30,962
  • 25
  • 85
  • 135
Lana
  • 1,024
  • 1
  • 7
  • 14