22

I am using a Grid as ItemsPanel for a list dynamically bound to an ItemsControl. The code below is working - with a remaining problem: I can’t find a way to dynamically initialize the ColumnDefinitions and RowDefinitions of the grid. As consequence all values are placed on top of each other.

<ItemsControl ItemsSource="{Binding Cells}">
    <ItemsControl.ItemsPanel>
        <ItemsPanelTemplate>
            <Grid/>
        </ItemsPanelTemplate>
    </ItemsControl.ItemsPanel>
    <ItemsControl.ItemContainerStyle>
        <Style>
            <Setter Property="Grid.Row" Value="{Binding RowIndex}"/>
            <Setter Property="Grid.Column" Value="{Binding ColumnIndex}"/>
        </Style>
    </ItemsControl.ItemContainerStyle>
    <ItemsControl.ItemTemplate>
        <DataTemplate>
            <TextBlock Text="{Binding Value}" />
        </DataTemplate>
    </ItemsControl.ItemTemplate>
</ItemsControl>

Please be aware, that I am searching an answer according the MVVM pattern. Therefore sub classing and code behind are just workarounds, but no solutions.

Dirk Brockhaus
  • 4,922
  • 3
  • 38
  • 47

3 Answers3

30

You'll need some way to tell the Grid how many Rows/Columns it has. Perhaps as each Item loads you could check the value of RowIndex and ColumnIndex and add Rows/Columns to the Grid if needed.

As another alternative, perhaps you can expose RowCount and ColumnCount properties in your ViewModel that return the max RowIndex and ColumnIndex, and in the Grid's Loaded event add however many Columns/Rows you need.

I find it perfectly acceptable to use code-behind in MVVM IF the code is related to the UI only.

Another idea would be to arrange your items in your code-behind into a 2D grid before returning it to the View, and then bind that Grid to a DataGrid with AutoGenerateColumns=True and the headers removed

Update

My current solution to this problem is to use a set of AttachedProperties for a Grid that allow you to bind RowCount and ColumnCount properties to a property on the ViewModel

You can find the code for my version of the attached properties on my blog here, and they can be used like this:

<ItemsPanelTemplate>
    <Grid local:GridHelpers.RowCount="{Binding RowCount}"
          local:GridHelpers.ColumnCount="{Binding ColumnCount}" />
</ItemsPanelTemplate>
Rachel
  • 130,264
  • 66
  • 304
  • 490
  • Many thanks for your answer. Solved this according to your second suggestion (Grid's Loaded event). I think you are right with your MVVM code-behind opinion. Initially I searched something like "data binding of the grid's definitions", but WPF seems to be not that MVVM friendly at now. – Dirk Brockhaus Aug 10 '11 at 07:47
  • I'm hoping to do the same thing, except I'd like a TextBlock in the Row 0 and a TextBlock in the Row 1, any idea how to make that work? – Didier A. Nov 07 '12 at 21:01
  • 1
    @didibus This is not easily done with an `ItemsControl` as you are trying to add two separate items to the `ItemsPanel` (in this case, a `Grid`) with each record, and each record is wrapped in a single `ContentPresenter`. You should either make your `ItemTemplate` be a `Grid` of its own with 2 cells (can use `SharedSizeScope` to make them all the same size if needed), or you should use a different control than an `ItemsControl` to display your items, such as a `DataGrid`. If you're stuck, post a question on SO with the relevant bits of code and I'd be happy to take a look at it :) – Rachel Nov 07 '12 at 21:05
15

Your grid has zero rows and columns so everything will be displayed on top of each other. Do something like below and it will work.

<ItemsControl ItemsSource="{Binding Cells}">
    <ItemsControl.ItemsPanel>
        <ItemsPanelTemplate>
            <Grid>
                <Grid.RowDefinitions>
                    <RowDefinition Height="Auto" />
                    <RowDefinition Height="Auto" />
                </Grid.RowDefinitions>
                <Grid.ColumnDefinitions>
                    <ColumnDefinition Width="Auto" />
                    <ColumnDefinition Width="Auto" />
                </Grid.ColumnDefinitions>
            </Grid>
        </ItemsPanelTemplate>
    </ItemsControl.ItemsPanel>
    <ItemsControl.ItemContainerStyle>
        <Style>
            <Setter Property="Grid.Row" Value="{Binding RowIndex}" />
            <Setter Property="Grid.Column" Value="{Binding ColumnIndex}" />
        </Style>
    </ItemsControl.ItemContainerStyle>
    <ItemsControl.ItemTemplate>
        <DataTemplate>
            <TextBlock Text="{Binding Value}" />
        </DataTemplate>
    </ItemsControl.ItemTemplate>
</ItemsControl>
Wallstreet Programmer
  • 9,567
  • 3
  • 37
  • 52
3

i found this from here The ItemsControl

            <ItemsControl  ItemsSource="{Binding VehicleMakes}" >
                <ItemsControl.ItemsPanel>
                    <ItemsPanelTemplate>
                        <UniformGrid Columns="3" />
                    </ItemsPanelTemplate>
                </ItemsControl.ItemsPanel>
                <ItemsControl.ItemTemplate>
                    <DataTemplate>
                        <Button Width="300" Height="100"  Click="ButtonOption_Click" Tag="{Binding Name}">
                            <StackPanel Orientation="Vertical">
                                <Image
                                    Initialized="Image_Initialized"
                                    Tag="{Binding ResourseImageName}"
                               Width="116"
                               Height="30"
                               Margin="0,0,0,10" >
                                </Image>
                                <TextBlock Text="{Binding Name}" VerticalAlignment="Bottom"  HorizontalAlignment="Center"/>
                            </StackPanel>
                        </Button>
                    </DataTemplate>
                </ItemsControl.ItemTemplate>
            </ItemsControl>
PowerMan2015
  • 1,307
  • 5
  • 19
  • 40