9

I have a collection that I wish to bind to a WPF grid.

The problem I'm facing is that the number of columns is dynamic and is dependent on a collection. Here is a simple mock up:

public interface IRows
{
    string Message{get;}
    IColumns[] Columns{get;}
}

public interface IColumns
{
    string Header {get;}
    AcknowledgementState AcknowledgementState{get;}
}

public interface IViewModel
{
    ObservableCollection<IRows> Rows {get;}
}

I want my view to bind to the the Rows collection, which contains a collection of Columns.

My Columns collection contains an enum which should be represented by an image (1 of 3 possibilities). It also contains a Message property which should only be displayed in one column (static and is just some text information). It also contains a Header string which should be displayed as a header for that column.

The link to what I want to show

Note that the number of columns is variable (at the moment the headers are set to Acknowledge but this will change to represent dynamic data).

Update: This is after implementing suggestions from Rachel

    <ItemsControl
 ItemsSource="{Binding Items, Converter={StaticResource PresentationConverter}}">
  <ItemsControl.ItemsPanel>
    <ItemsPanelTemplate>
      <Grid ShowGridLines="true"
         local:GridHelpers.RowCount="{Binding RowCount}"
         local:GridHelpers.ColumnCount="{Binding ColumnCount}" />
    </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>
      <ContentControl Content="{Binding}">
        <ContentControl.Resources>
          <DataTemplate DataType="{x:Type UI:MessageEntity}">
            <TextBox Text="{Binding Message}"></TextBox>
          </DataTemplate>
          <DataTemplate DataType="{x:Type UI:StateEntity}">
            <TextBox Text="{Binding State}"></TextBox>
          </DataTemplate>
        </ContentControl.Resources>
      </ContentControl>
    </DataTemplate>
  </ItemsControl.ItemTemplate>
</ItemsControl>

This almost gives me what I want now. I'm only stuck with what I should do for the headers. Any suggestions are welcome.

zman
  • 333
  • 1
  • 2
  • 9
  • To receive help, give the maximum information. Includes link... – Harry Dec 11 '12 at 06:38
  • Simplified and added a link. – zman Dec 11 '12 at 07:32
  • Link on how to add rows and coloumns dynamically - http://stackoverflow.com/questions/13344788/how-to-create-listview-to-a-grid-programmatically/ – Sai Dec 11 '12 at 10:44
  • I would like it to work with the view model directly using bindings (i.e. without having any code behind) if possible – zman Dec 11 '12 at 10:56
  • Three AcknowledgementState but only one Message? In the controls from MSFT there is no feature to bind to a collection of columns in XAML. I do it in code behind and use ListView GridView. – paparazzo Dec 11 '12 at 14:53
  • Yes one message per row, with either Acknowledged, TimedOut, or Unknown for each column value. The header of the column is also variable (unlike in the image above). Using the ListView/GridView is the last resort. Do you modify the GridView in the code behind then? – zman Dec 12 '12 at 05:38
  • Then your data model is wrong. The model has an equal number of AcknowledgementState and Message. 3 ack = 3 msg (not 1 msg). – paparazzo Dec 12 '12 at 20:17
  • Yes you are right, I must've copied it incorrectly. Actually the Message property sits one level up in the IRows interface. – zman Dec 13 '12 at 05:41

4 Answers4

7

You can use nested ItemsControls for this

Here's a basic example:

<!-- Bind Rows using the default StackPanel for the ItemsPanel -->
<ItemsControl ItemsSource="{Binding Rows}">
    <!-- Set the Template for each row to a TextBlock and another ItemsControl -->
    <ItemsControl.ItemTemplate>
        <DataTemplate>
            <StackPanel Orientation="Horizontal">
                <!-- Need to set Width of name TextBlock so items line up correctly -->
                <TextBlock Width="200" Text="{Binding Name}" />

                <ItemsControl ItemsSource="{Binding Columns}">
                    <!-- Use a horizontal StackPanel to display columns -->
                    <ItemsControl.ItemsPanel>
                        <ItemsPanelTemplate>
                            <StackPanel Orientation="Horizontal" />
                        </ItemsPanelTemplate>
                    </ItemsControl.ItemsPanel>
                </ItemsControl>
            </StackPanel>
        </DataTemplate>
    </ItemsControl.ItemTemplate>
</ItemsControl>
Rachel
  • 130,264
  • 66
  • 304
  • 490
  • This is almost what I want. I have tried implementing a solution using this idea but I found issues when I added a header row which I have made independent of the ItemsControl (just a stack panel above it). It is quite tricky to arrange the widths of the columns so that they synchronize. – zman Dec 12 '12 at 08:05
  • 1
    @ManuelZanin You could use a Grid with a [SharedSizeScope](http://msdn.microsoft.com/en-us/library/system.windows.controls.grid.issharedsizescope.aspx) attached property, however that requires you to define the column definitions in advance. If the number of columns can change, I have some attached properties for the Grid [on my blog](http://rachel53461.wordpress.com/2011/09/17/wpf-grids-rowcolumn-count-properties/) which lets you bind the column count instead of manually specifying the ColumnDefinitions. – Rachel Dec 12 '12 at 12:58
  • Ok I have almost got it working thanks to your suggestions. I have updated my question. The only remaining issue is how to deal with the headers nicely. If I can't get it working using the ItemsControl I might have to use a ListView (although I'm not very familiar with this control). – zman Dec 13 '12 at 08:37
2

Using a grid approach might make things more complicated than they should be. Have you tried changing the template of a listview, or to use the DataGrid instead for this purpose?

For an example, take a look at this project: http://www.codeproject.com/Articles/25058/ListView-Layout-Manager

Or this one: http://www.codeproject.com/Articles/16009/A-Much-Easier-to-Use-ListView

If you go with the Grid, I believe you'll have to add a lot of code behind to manage the amount of columns and rows, their size, the cell content... Whereas a ListView/DataGrid will let you do this dynamically through Templates.

Joe
  • 2,496
  • 1
  • 22
  • 30
  • Those links don't deal with a column collection that I saw. – paparazzo Dec 11 '12 at 14:49
  • @Blam And they don't deal with a Grid either! Which is exactly my point: a DataTemplate inside a ListView might be a more fit solution for the OP's scenario. When I started WPF I have once started to code a class that would generate a Grid with an unknown amount of columns + headers, and eventually learned about ListView and how it simplifies this sort of problem - especially since the OP wants this to be a pure XAML solution. – Joe Dec 11 '12 at 17:01
  • You make a good point, maybe I'm making life too difficult for myself. I have thought about using a ListView/GridView but I haven't found any examples of using them to achieve what I want (I'm quite rusty on WPF which doesn't help). The examples you posted are useful but I think it is overkill for my purpose. – zman Dec 12 '12 at 05:41
  • The examples provided are overkill, although the principle to make them work is simple. The ListView already has the row/column logic so you don't have to code it. Then, use a DataTemplate that decides how to render your object. You can have multiple DataTemplates target multiple object types too, so that WPF will "query" what kind of object it is and apply the template you chose. Rachel has a good answer below that is similar to what I would do. I think your issue about the headers might be easier to deal with in a ListView since it's already a component of ListView. – Joe Dec 12 '12 at 12:06
1

Create a Grid using code as shown at http://msdn.microsoft.com/en-us/library/system.windows.controls.grid(v=vs.90).aspx#feedback

create a property of type ColumnDefinition,( and use property changed ) to create columns.

user2330678
  • 2,221
  • 14
  • 43
  • 67
0

There is also the option of using a dynamic object to create your columns. This is a bit laborious but the results are very effective and the solution in general is quite flexible.

This will show you the basics to the dynamic object Binding DynamicObject to a DataGrid with automatic column generation?

I had some trouble using it with nested objects, columns that have objects and then trying to bind cell content to the object.

Here's a question I raised with an example of how to do this

Problems binding to a the content of a WPF DataGridCell in XAML

Community
  • 1
  • 1
Kezza
  • 736
  • 9
  • 25