How do I display a collection of items with a template that uses a Grid in WPF?
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. A开发者_开发问答ny 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.
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 UserControl
s 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.
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.
精彩评论