Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Grid of Checkboxes in WPF

I have a WPF UserControl's datacontext tied to a class like this:

public class CheckBoxGridViewModel
{
  public List<List<bool>> Checkboxes {get; set;}
}

I want it to display a grid of checkboxes. I'm assuming I can use an Itemscontrol, but don't know exactly how to do it with a dynamic set of columns for each row.

This question seems to answer mine, except the answer didn't give the example and I can't figure how to write it out.

So the question is, how would I write the xaml to display the checkboxes of the Checkboxes property so that they are lined up in a nice grid?

The outer list would be each row and the inner list would be each column of the row.

like image 387
Jose Avatar asked Dec 20 '10 15:12

Jose


3 Answers

It's not clear if you're expecting each inner List to be the same size but if they are you can use a simple setup. Using nested ItemsControls with single row/column UniformGrids will give you an even distribution and automatically handle collections of any size without needing to setup Row and Column Definitions like with Grid:

<ItemsControl ItemsSource="{Binding Checkboxes}">
  <ItemsControl.ItemTemplate>
    <DataTemplate>
      <ItemsControl ItemsSource="{Binding}">
        <ItemsControl.ItemTemplate>
          <DataTemplate>
            <CheckBox IsChecked="{Binding}"/>
          </DataTemplate>
        </ItemsControl.ItemTemplate>
        <ItemsPanelTemplate>
          <UniformGrid Rows="1"/>
        </ItemsPanelTemplate>
      </ItemsControl>
    </DataTemplate>
  </ItemsControl.ItemTemplate>
  <ItemsControl.ItemsPanel>
    <ItemsPanelTemplate>
      <UniformGrid Columns="1"/>
    </ItemsPanelTemplate>
  </ItemsControl.ItemsPanel>
</ItemsControl>
like image 94
John Bowen Avatar answered Nov 19 '22 09:11

John Bowen


See this question. The answer by Jobi Joy will let you present the 2D list but Binding won't work so you can't edit your values.

To be able to bind the values you can use a helper class like this

public static class BindableListHelper
{
    public static List<List<Ref<T>>> GetBindable2DList<T>(List<List<T>> list)
    {
        List<List<Ref<T>>> refInts = new List<List<Ref<T>>>();

        for (int i = 0; i < list.Count; i++)
        {
            refInts.Add(new List<Ref<T>>());
            for (int j = 0; j < list[i].Count; j++)
            {
                int a = i;
                int b = j;
                refInts[i].Add(new Ref<T>(() => list[a][b], z => { list[a][b] = z; }));
            }
        }
        return refInts;
    }
}

This method uses this Ref class

public class Ref<T> 
{
    private readonly Func<T> getter; 
    private readonly Action<T> setter;
    public Ref(Func<T> getter, Action<T> setter) 
    { 
        this.getter = getter; 
        this.setter = setter; 
    }
    public T Value { get { return getter(); } set { setter(value); } } 
}

Then you can set ItemsSource for the ItemsControl with

itemsControl.ItemsSource = BindableListHelper.GetBindable2DList<bool>(Checkboxes);

and the editing should work

Using the same code as Jobi Joy from the question I linked, you can change the Button in DataTemplate_Level2 to a CheckBox and bind IsChecked for it to Value instead (since it will point to the Ref class otherwise)

<Window.Resources>
    <DataTemplate x:Key="DataTemplate_Level2">
        <CheckBox IsChecked="{Binding Path=Value}" Height="15" Width="15" Margin="2"/>
    </DataTemplate>
    <DataTemplate x:Key="DataTemplate_Level1">
        <ItemsControl ItemsSource="{Binding}" ItemTemplate="{DynamicResource DataTemplate_Level2}">
            <ItemsControl.ItemsPanel>
                <ItemsPanelTemplate>
                    <StackPanel Orientation="Horizontal"/>
                </ItemsPanelTemplate>
            </ItemsControl.ItemsPanel>
        </ItemsControl>
    </DataTemplate>
</Window.Resources>
<StackPanel>
    <Border HorizontalAlignment="Left" BorderBrush="Black" BorderThickness="2">
        <ItemsControl x:Name="itemsControl" ItemTemplate="{DynamicResource DataTemplate_Level1}"/>
    </Border>
</StackPanel>

Without setting the Content property for the CheckBox it'll look something like this

alt text

like image 42
Fredrik Hedblad Avatar answered Nov 19 '22 09:11

Fredrik Hedblad


You might consider creating a class that exposes Row, Column, and Value properties, and binding to a collection of these. This lets you assign row and column positions using the method of your choice, and layout in a grid is very straightforward (once you understand how ItemsControl uses its ItemsPanel and ItemContainerStyle properties, of course):

      <ItemsControl ItemsSource="{Binding Checkboxes}">
        <ItemsControl.ItemTemplate>
          <DataTemplate>
            <CheckBox IsChecked="{Binding Value, Mode=TwoWay}"/>
          </DataTemplate>
        </ItemsControl.ItemTemplate>
        <ItemsControl.ItemsPanel>
          <ItemsPanelTemplate>
          <Grid DockPanel.Dock="Top">
            <Grid.ColumnDefinitions>
              <ColumnDefinition Width="20"/>
              <ColumnDefinition Width="20"/>
              <ColumnDefinition Width="20"/>
              <ColumnDefinition Width="20"/>
              <ColumnDefinition Width="20"/>
            </Grid.ColumnDefinitions>
            <Grid.RowDefinitions>
              <RowDefinition Height="20"/>
              <RowDefinition Height="20"/>
              <RowDefinition Height="20"/>
              <RowDefinition Height="20"/>
              <RowDefinition Height="20"/>
              <RowDefinition Height="20"/>
            </Grid.RowDefinitions> 
            </Grid>
          </ItemsPanelTemplate>
        </ItemsControl.ItemsPanel>
        <ItemsControl.ItemContainerStyle>
          <Style TargetType="ContentPresenter">
            <Setter Property="Grid.Row" Value="{Binding Row}"/>
            <Setter Property="Grid.Column" Value="{Binding Column}"/>
          </Style>
        </ItemsControl.ItemContainerStyle>
      </ItemsControl>
like image 2
Robert Rossney Avatar answered Nov 19 '22 08:11

Robert Rossney