13

I have a list of AvailableItems that I want to display as a list of checkboxes, so that users can pick which items to generate, which are then stored in another list called ItemsToGenerate (my lists are actually just lists of strings).

Showing all available items with corresponding checkboxes is easy:

<ItemsControl ItemsSource="{Binding Path=AvailableItems}">
    <ItemsControl.ItemTemplate>
        <DataTemplate>
            <CheckBox Content="{Binding}" />
        </DataTemplate>
    </ItemsControl.ItemTemplate>    
</ItemsControl>

But now I need to bind each Checkbox.IsChecked property, to the fact that the item is in the ItemsToGenerate list. I thought of making a ListContainmentToBoolConverter like this:

IsChecked="{Binding Path=ItemsToGenerate, 
            Converter={StaticResource ListContainmentToBoolConverter}}"

But that doesn't work because I'm missing a ConverterParameter to pass the value of each item, but I can't do that, because ConverterParameter does not support binding.

Any ideas?

Anthony Brien
  • 6,106
  • 7
  • 43
  • 56

2 Answers2

13

I've found a solution to my problem.

I've changed my ItemsControl to a ListBox, and added a binding between the SelectedItems with my ItemsToGenerate collection using the technique described here. It basically allows me to synchronize any custom collection to ListBox.SelectedItems using a simple attached property.

<ListBox ItemsSource="{Binding AvailableItems}"
         Behaviors:MultiSelectorBehaviours.SynchronizedSelectedItems=
             "{Binding ItemsToGenerate}"
         SelectionMode="Multiple"
         Background="{x:Null}">  
    <ItemsControl.ItemsPanel>
        <ItemsPanelTemplate>
            <WrapPanel />                    
        </ItemsPanelTemplate>
    </ItemsControl.ItemsPanel>
    <ItemsControl.ItemTemplate>
        <DataTemplate>
            <CheckBox Content="{Binding}"
                      Margin="3"
                      IsChecked="{Binding RelativeSource=
                           {RelativeSource Mode=FindAncestor,
                            AncestorType={x:Type ListBoxItem}},
                           Path=IsSelected}" />
        </DataTemplate>
    </ItemsControl.ItemTemplate>
</ListBox>

I'm still able to display this as I initially wanted (a list of checkboxes), by adding a data template to change each ListBoxItem to a checkbox and binding each Checkbox.IsChecked to ListBoxItem.IsSelected.

I had this pattern in so many places in my application that this is the ideal solution for me, because now I just need to specify one attached property, and the rest is all handled by the data bindings, and I don't need any additional code.

Anthony Brien
  • 6,106
  • 7
  • 43
  • 56
  • 1
    I would like to see the implementation of your attached property. Unfortunately this only works if you reuse the selection from the ListBox but what if your using an ItemsControl with a checkbox within the template? – jpierson Aug 15 '12 at 05:53
  • I tried this and the end result is a little funky looking (http://i.imgur.com/ZRyrjfe.png). I'm going to stick with a plain ListBox, personally. – Chris Aug 08 '16 at 23:23
11

I honestly would create a list of objects containing both the string and a boolean indicating if it is checked.

With a little Linq you can generate your list of objects and bind it to itemSource instead of binding the list of strings.

It will be simpler in the end, especially if you actually need to update something if the user is allowed to check/uncheck the checkboxes.

== update ==

in answer to the comment, my take on this because I'm not sure I understand what the actual problem would be: provided we have the full list (AvailableItems) and the list of selected items (ItemsToGenerate):

public class ItemEntry
{
  public string Name { get; set; }
  public bool IsSelected {get; set; }
}

...

_Items = from item in AvailableItems
            select new ItemEntry() { 
                    Name = item, 
                    IsSelected = ItemsToGenerate.contains(item)
                  }).ToList();

You can then bind your list like so, by exposing _Items as a property named Items:

<ItemsControl ItemsSource="{Binding Path=Items}">
    <ItemsControl.ItemTemplate>
        <DataTemplate>
            <CheckBox Content="{Binding Name}" IsChecked="{Binding IsSelected}" />
        </DataTemplate>
    </ItemsControl.ItemTemplate>    
</ItemsControl>

You can at a later time select from _Items where IsSelected is true to get the selected items if you need to.

Also, if ItemsToGenerate can get big, you should create a HashSet of the values and use it in the query, that should make it faster if need be.

Denis Troller
  • 7,411
  • 1
  • 23
  • 36
  • The reason I don't want to do that is because my data is saved to xml and edited by hand sometimes. So if there's 300 items available and they just want to generate 2 items, I want them just to write Item1Item2. – Anthony Brien Apr 23 '09 at 19:35
  • I've thought of another way to go about this: using a ListBox where the item template binds the checkbox.IsChecked to the ListItem.IsSelected. Then it would just be a question of binding ItemsSource to the AvailableItems and binding the SelectedItems to ItemsToGenerate. – Anthony Brien Apr 23 '09 at 21:58
  • Your method using linq however seems it would do what I want, so I'll give it a try. But I'll have to manually update the ItemsToGenerate using Linq if I understand (its not a binding). The reason I'd prefer this to be as simple as possible is that I have a lot of places where I will have this pattern: Choosing which targets to compile, which languages to use, which worlds to generate, which missions to run, etc. I want to minimize the data format, but show all available options in the UI. – Anthony Brien Apr 23 '09 at 22:02
  • 1
    you could possiblyvuse a multibinding with a multivalueconverter if you want to pull off your original idea. It would take no parameter and bind to both the item and the ItemsToGenerate collection. – Denis Troller Apr 24 '09 at 01:31
  • I tried making a multivalueconverter, binding the first value to the item, and the second value to the list ItemsToGenerate, and I return true of false correctly if the item is in the list of not. But how can I convert back? The convert back methods just receives a bool. I'm missing the list & item values so I can add/remove the item to the list. – Anthony Brien Apr 24 '09 at 17:28
  • hmm, yes, I forgot about that part. I guess you're back to one of the other option. – Denis Troller Apr 25 '09 at 00:08
  • This worked nearly perfectly. Instead of a POD object though, I linked to the thing I was actually turning on/off. Something like: IFoo{bool Enabled {get;set;}; string Name{get;set;} then ItemEntry has a reference to IFoo and the two properties are linked directly to it like this: public bool IsSelected { get => _foo.Enabled; set => _foo.Enabled = value; } – Reginald Blue Feb 01 '23 at 18:41