views:

29

answers:

2

I have a collection of objects that I would like to represent as a Grid with each object represented as a Rectangle (or a Button - haven't settled on this yet). Each object has an X and a Y property which represent its location in the Grid.

For example, I might have four objects:

var one = new MyClass(){X=0, Y=0}
var two = new MyClass(){X=1, Y=0}
var three = new MyClass(){X=0, Y=1}
var four = new MyClass(){X=1, Y=1}

The Grid should have two rows and two columns. Object one would be represented as a Rectangle in the top left part of the Grid. Object two would be in the slot to the right of object one, three would be below one, and four would be in the bottom right slot.

I am trying to figure out how to create both templates in an idiomatic WPF manner (e.g. using DataTemplate and similar constructs), but need some help. Any advice?

I'd also like to be able to store the template(s) in a separate file from my main window, but am not sure how to do that either.

+1  A: 

One approach would be to use a ListView. You can set it's ItemsPanel to be a Canvas and then in the datatemplate bind the Canvas.Top and Canvas.Left to the X and Y co-ordinates that you want the item to have:

<ListView>
    <ListView.ItemsPanel>
        <ItemsPanelTemplate>
            <Canvas/>
        </ItemsPanelTemplate>
    </ListView.ItemsPanel>
    <ListView.ItemTemplate>
        <DataTemplate>
             <Button Canvas.Top="{Binding YPosition}", Canvas.Left="{Binding XPosition}"/>
        </DataTemplate>
    </ListView.ItemTemplate>
</ListView>

If you need to you can use a converter to multiply the YPosition and XPosition by the cell size so that the properties can refer to the cell number rather than the pixel size.


On the other (simpler) hand: If you know in advance how many rows or columns the grid will have and there is one, and only one, element per cell then you may be able to use a UniformGrid instead of a ListView.

Martin Harris
The UniformGrid looks promising, but it doesn't have an ItemsSource property or support the ItemsPanel/ItemTemplate syntax. How is it used when data-binding?
Pat
Answer to my own question: http://stackoverflow.com/questions/1966258/chessboard-in-wpf/1966620#1966620
Pat
+2  A: 

As to your first question, this is probably the pattern you're looking for:

<ItemsControl>
  <ItemsControl.ItemsPanel>
    <ItemsPanelTemplate>
      <Grid>
        <Grid.ColumnDefinitions>
          <ColumnDefinition Width="100"/>
          <ColumnDefinition Width="100"/>
          <ColumnDefinition Width="100"/>
        </Grid.ColumnDefinitions>
        <Grid.RowDefinitions>
          <RowDefinition Height="50"/>          
          <RowDefinition Height="50"/>          
          <RowDefinition Height="50"/>          
        </Grid.RowDefinitions>
      </Grid>
    </ItemsPanelTemplate>
  </ItemsControl.ItemsPanel>
  <Button Grid.Row="1" Grid.Column="2">R1C2</Button>
  <Button Grid.Row="2" Grid.Column="1">R2C1</Button>
  <Button Grid.Row="0" Grid.Column="0">R0C0</Button>
</ItemsControl>

In a real application, you'd set the ItemsControl.ItemsSource to a Binding whose source is a collection of objects, and then create a DataTemplate like:

<DataTemplate DataType="{x:Type MyObject}">
   <Rectangle Grid.Row="{Binding Row}" Grid.Column="{Binding Column}">
     <!-- other visuals go here -->
   </Rectangle>
</DataTemplate>

As far as organizing the code into separate files goes: you should consider creating a UserControl for displaying the object, instead of a DataTemplate. It's no more difficult to create one than the other, and UserControls are classes that live in their own XAML files and can be instantiated in XAML, like any other object, by their name.

Depending on your design, you might separate out the grid positioning from the actual visual presentation of the object, so that you can reuse the presentation elsewhere. That's probably how I'd approach it. After creating a UserControl for my object, I'd create a DataTemplate in Grid.Resources (since it's describing how that specific Grid should display the objects) like this:

<DataTemplate DataType="{x:Type MyObject}">
   <DockPanel Grid.Row="{Binding Row}" Grid.Column="{Binding Column}">
      <local:MyObjectUserControl DataContext="{Binding}"/>
   </DockPanel>
</DataTemplate>

It's also possible to organize XAML using an include-like approach: create a standalone XAML file that contains a resource dictionary, and then merge the dictionaries into your window's (or application's, or anything else really) resource dictionary:

<Window.Resources>
  <ResourceDictionary>
    <ResourceDictionary.MergedDictionaries>
      <ResourceDictionary Source="myresourcedictionary.xaml"/>
      <ResourceDictionary Source="myresourcedictionary2.xaml"/>
    </ResourceDictionary.MergedDictionaries>
  </ResourceDictionary>
</Window.Resources>

This can be a good approach for organizing a lot of styles and templates, though a problem with it is that if ResourceDictionary.MergedDictionaries is set, you can't put any items directly in the dictionary, so you have to create a separate XAML file to contain the resources that belong solely to the window, which is kind of a pain.

Robert Rossney
Great, thorough answer Robert. Thanks very much! I also found this answer - http://stackoverflow.com/questions/1966258/chessboard-in-wpf/1966620#1966620 - to be of help. Unfortunately, the Row and Column Binding isn't working for me, but I will post another answer when I finally figure it all out. Thanks again!
Pat
Well, I still haven't found a way to bind to the `Grid.Row` and `Grid.Column`. I assume that the code in your answer didn't actually display objects in the correct row/column - or does it work for you?
Pat
My mistake. Here's why it doesn't work: setting the `Grid.Row` property on the `Button` only works if the `Button` is in the `Items` collection. But the `ItemsControl sticks each templated item in a container. That container doesn't have `Grid.Row` or `Grid.Column` set, and so the `Grid` doesn't see them. That's what `ItemContainerStyle` is used for. But I haven't gotten it to work yet myself. I'll post something when I do.
Robert Rossney