1

I have a viewmodel which contains an ObservableCollection of objects name MyLabel. These objects have 3 properties (content, rowNr, columnNr) which should be bound to the Content, Grid.Rowand Grid.Column attributes respectively.

The reason I defined ItemsControl is because it didnt work inside my grid, that is I couldnt bind rowNr and columnNr since the grids Grid.Column /Grid.Row properties kept overwriting my data.

How can I make this work so that my labels are inside of the grid?

<StackPanel>
    <ItemsControl ItemsSource="{Binding Path=MyLabelList}">
        <ItemsControl.ItemTemplate>
            <DataTemplate>
                <Label Content="{Binding content}" Grid.Column="{Binding columnNr}" Grid.Row="{Binding rowNr}" Style="{StaticResource MyLabel}" />
            </DataTemplate>
        </ItemsControl.ItemTemplate>
    </ItemsControl>

    <Grid>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="*" />
            <ColumnDefinition Width="*" />
            <ColumnDefinition Width="*" />
        </Grid.ColumnDefinitions>
    </Grid>
</StackPanel>
Asperger
  • 3,064
  • 8
  • 52
  • 100

4 Answers4

3

Try to use a Grid as the ItemsPanel for the ItemsControl:

<ItemsControl ItemsSource="{Binding Path=MyLabelList}">
    <ItemsControl.ItemsPanel>
        <ItemsPanelTemplate>
            <Grid>
                <Grid.ColumnDefinitions>
                    <ColumnDefinition Width="*" />
                    <ColumnDefinition Width="*" />
                    <ColumnDefinition Width="*" />
                </Grid.ColumnDefinitions>
                <Grid.RowDefinitions>
                    <RowDefinition Height="*" />
                    <RowDefinition Height="*" />
                    <RowDefinition Height="*" />
                </Grid.RowDefinitions>
            </Grid>
        </ItemsPanelTemplate>
    </ItemsControl.ItemsPanel>
    <ItemsControl.ItemContainerStyle>
        <Style TargetType="ContentPresenter">
            <Setter Property="Grid.Column" Value="{Binding columnNr}" />
            <Setter Property="Grid.Row" Value="{Binding rowNr}" />
        </Style>
    </ItemsControl.ItemContainerStyle>
    <ItemsControl.ItemTemplate>
        <DataTemplate>
            <Label Content="{Binding content}" />
        </DataTemplate>
    </ItemsControl.ItemTemplate>
</ItemsControl>
mm8
  • 163,881
  • 10
  • 57
  • 88
  • This will not work because your label is not directly inside the `ItemsPanel`. – Peter Jun 30 '17 at 10:12
  • Will check it out. Looks interesting. – Asperger Jun 30 '17 at 10:43
  • @Peter: It works with an ItemContainerStyle. You will of course have to define the RowDefinitions though. – mm8 Jun 30 '17 at 10:43
  • Im a bit hesitant to implement things I dont understand but this is still ok for a mortal wpf user like me. – Asperger Jun 30 '17 at 10:44
  • I think ill accept your answer. Testing a bit more : ) – Asperger Jun 30 '17 at 10:51
  • One last thing and ill accept your answer. Is binding column definitions too complex? If so should I just give the grid an x name and apply the definitions in code behind even if it violate mvvm principles? – Asperger Jun 30 '17 at 10:54
  • Applying the column definitions in the code-behind does *not* violate the MVVM pattern. The pattern is about separation of concerns, not about eliminating *view* related code from the views. – mm8 Jun 30 '17 at 10:55
  • I thought mv shouldnt know about view elements because id have to access my grid directly to give it some row / column definitions. – Asperger Jun 30 '17 at 11:03
1

You have to set the bindings to the ItemContainer programmaticly on GetContainerForItemOverride()

public class GridItemsControl : ItemsControl
{
    protected override DependencyObject GetContainerForItemOverride()
    {
        FrameworkElement fwE = base.GetContainerForItemOverride() as FrameworkElement;
        fwE.SetBinding(Grid.ColumnProperty, "columnNr");
        fwE.SetBinding(Grid.RowProperty, "rowNr");

        return fwE;
    }
}

And in the View like this:

<custom:GridItemsControl>
    <custom:GridItemsControl.ItemsPanel>
        <ItemsPanelTemplate>
            <Grid>
                <Grid.ColumnDefinitions>
                    <!-- Add here -->
                </Grid.ColumnDefinitions>
                <Grid.RowDefinitions>
                    <!-- Add here -->
                </Grid.RowDefinitions>
            </Grid>
        </ItemsPanelTemplate>
    </custom:GridItemsControl.ItemsPanel>
</custom:GridItemsControl>

You could also implement an own grid that generates the wanted amount of rows and columns.

This trick is pretty legit.

Additional Information Your ItemTemplate will not be directly added to your ItemsPanel (in your case a Grid) there is allways a ContentPresenter or something like this around, because you are using DataTemplate. So you have to go like this.

To make it more contrihensible you could imagine it like this:

                <Grid> <!-- This is the ItemsPanel -->
                    <ContentPresenter>
                        <!-- This needs the Binding on Grid.Row, but doesn't know shit without my solution -->
                        <!-- This is your ItemContainer that has the DataContext of your ViewModelItem -->
                        <ContentPresenter.ContentTemplate>
                            <DataTemplate>
                                <!-- Here is your ItemTemplate -->
                            </DataTemplate>
                        </ContentPresenter.ContentTemplate>
                    </ContentPresenter>
                </Grid>
Peter
  • 1,655
  • 22
  • 44
  • Peter is it just me or is this specific topic one of the more complicated aspects of xaml? – Asperger Jun 30 '17 at 10:17
  • Your `ItemTemplate` will not be directly added to your `ItemsPanel` (in your case a `Grid`) there is allways a `ContentPresenter` or something like this around, because you are using `DataTemplate`. So you have to go like this. – Peter Jun 30 '17 at 10:18
  • Thanks, ill try this out and study it. Ill accept in a moment – Asperger Jun 30 '17 at 10:20
  • Does this violate MVVM in any way? – Asperger Jun 30 '17 at 10:21
  • No it does not ! – Peter Jun 30 '17 at 10:21
  • You could make it more generic if you want, but your `ViewModel` don't have to know about the structure of the `View`. – Peter Jun 30 '17 at 10:22
  • Could you explain the first section of code a bit more? and why you did custom: ? I could google it but since you already usedit in your answer it would be cool if you could explain how this works. Its not a must, just if you feel like it. – Asperger Jun 30 '17 at 10:23
  • Added more info ;) – Peter Jun 30 '17 at 10:28
  • I googled custom: but could find anything. What exactly is custom? Is it something like a user control? Thanks for the additional infos! – Asperger Jun 30 '17 at 10:28
  • You could always use `ItemsControl.ItemContainerStyle` to set the bindings on the `ContentPresenter` without the need to subclass `ItemsControl`. – Grx70 Jun 30 '17 at 10:29
  • 1
    There is an example of how to define an ItemContainerStyle for an ItemsControl in @Clemens answer here: https://stackoverflow.com/questions/22324359/add-n-rectangles-to-canvas-with-mvvm-in-wpf/22325266#22325266. – mm8 Jun 30 '17 at 10:35
1

A Grid and ItemsControl don't play that well together but you could refer to the following blog post:

Using a Grid as the Panel for an ItemsControl: http://blog.scottlogic.com/2010/11/15/using-a-grid-as-the-panel-for-an-itemscontrol.html

Another option may be to "manually" add the <RowDefinition> and <ColumnDefinition> elements to the Grid dynamically in the view.

mm8
  • 163,881
  • 10
  • 57
  • 88
  • *"don't play that well together"* - I am often using `Grid` with `ItemsControl` to contain multiple items which doesn't have layout relations (e.g. can override each other). In such case there are no problems with columns/row as you don't use them. Order of items is like z-order of controls in winforms. – Sinatr Jun 30 '17 at 10:24
  • Well, I guess it should be a Grid *with fixed columns and rows* doesn't play very well together with an ItemsControl then :) – mm8 Jun 30 '17 at 10:26
  • Seems really complicated for such a simple task right? Refering to the article – Asperger Jun 30 '17 at 10:33
  • That's what I mean by "don't play that well together". As mentioned you might be better of creating the and elements yourself in the code-behind of the view. – mm8 Jun 30 '17 at 10:34
  • Or, if you know the actual number of rows up-front, you could refer to my first answer. – mm8 Jun 30 '17 at 10:45
1

If you don't want to subclass ItemsControl like @Peter suggested in his answer, you could simply use ItemsControl.ItemContainerStyle to bind Grid.Column and Grid.Row properties:

<ItemsControl ItemsSource="{Binding Path=MyLabelList}">
    <ItemsPanelTemplate>
        <Grid>
            <Grid.ColumnDefinitions>
                <!-- Add here -->
            </Grid.ColumnDefinitions>
            <Grid.RowDefinitions>
                <!-- Add here -->
            </Grid.RowDefinitions>
        </Grid>
    </ItemsPanelTemplate>
    <ItemsControl.ItemTemplate>
        <DataTemplate>
            <Label Content="{Binding content}" Style="{StaticResource MyLabel}" />
        </DataTemplate>
    </ItemsControl.ItemTemplate>
    <ItemsControl.ItemContainerStyle>
        <Style TargetType="ContentPresenter">
            <Setter Property="Grid.Row" Value="{Binding rowNr}" />
            <Setter Property="Grid.Column" Value="{Binding columnNr}" />
        </Style>
    </ItemsControl.ItemContainerStyle>
</ItemsControl>

But, as others already mentioned, column and row definitions will not be added automatically and you'll either need to know it in design-time and add them statically, or you'll need to find some way to add them dynamically in run-time.

Grx70
  • 10,041
  • 1
  • 40
  • 55