You'll still have to use code-behind to change the RowDefinitions
and ColumnDefinitions
properties of the Grid
in this example, since they're not dependency properties. But all the rest of the logic can be handled in the view model class.
The XAML:
<Window x:Class="GameBoard.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:GameBoard="clr-namespace:GameBoard"
Title="Window1"
SizeToContent="WidthAndHeight">
<Grid Margin="50">
<Grid.Resources>
<!-- This template presents the Piece object. Note that you can't set
the Grid.Row and Grid.Column properties on this Rectangle - well,
you *can*, but the Grid won't see them. See the Style below. -->
<DataTemplate DataType="{x:Type GameBoard:Piece}">
<Rectangle Fill="{Binding Fill}"
Width="50"
Height="50" />
</DataTemplate>
<!-- When the ItemsControl creates its items, it wraps each item in a
ContentPresenter. You have to set Grid.Row and Grid.Column
on this ContentPresenter in order for the Grid to see them. -->
<Style TargetType="{x:Type ContentPresenter}">
<Setter Property="Grid.Row"
Value="{Binding Row}" />
<Setter Property="Grid.Column"
Value="{Binding Column}" />
</Style>
</Grid.Resources>
<Border BorderBrush="Black"
BorderThickness="1">
<ItemsControl x:Name="Board"
ItemsSource="{Binding}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="50" />
<RowDefinition Height="50" />
<RowDefinition Height="50" />
<RowDefinition Height="50" />
<RowDefinition Height="50" />
<RowDefinition Height="50" />
<RowDefinition Height="50" />
<RowDefinition Height="50" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="50" />
<ColumnDefinition Width="50" />
<ColumnDefinition Width="50" />
<ColumnDefinition Width="50" />
<ColumnDefinition Width="50" />
<ColumnDefinition Width="50" />
<ColumnDefinition Width="50" />
<ColumnDefinition Width="50" />
</Grid.ColumnDefinitions>
</Grid>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
</Border>
</Grid>
</Window>
The Piece
class - obviously you'll need to implement INotifyPropertyChanged
on the Row
and Column
properties to handle moving pieces about.
public class Piece
{
public int Column { get; set; }
public Brush Fill { get; set; }
public int Row { get; set; }
}
Populating the board:
public Window1()
{
InitializeComponent();
ObservableCollection<Piece> pieces = new ObservableCollection<Piece>();
pieces.Add(
new Piece {Row = 0, Column = 0, Fill = new SolidColorBrush(Colors.BlanchedAlmond)});
pieces.Add(
new Piece {Row = 7, Column = 7, Fill = new SolidColorBrush(Colors.RosyBrown)});
pieces.Add(
new Piece { Row = 3, Column = 4, Fill = new SolidColorBrush(Colors.BlueViolet) });
pieces.Add(
new Piece { Row = 5, Column = 4, Fill = new SolidColorBrush(Colors.Orange) });
Board.DataContext = pieces;
}
I've used an ItemsControl
to contain the pieces in this example. You could use a ListBox
instead - that's kind of nice because it gives you item selection for free. Note that if you do this you'll have to change the Style
's TargetType
to ListBoxItem
, since that's what the ListBox
wraps its item elements in instead of ContentPresenter
.
Edit:
I wrote this answer quite a while ago, and it's got a problem.
Assigning the Grid.Row
and Grid.Column
properties using a style that's applied to the item container generated by the grid is right. Figuring out that the item container is a ContentPresenter
and creating a default style for that type is not. (It'll work reliably in this case, but there are lots of cases where it won't.)
You should still create a style, but it should be assigned to the ItemsControl
's ItemContainerStyle
. This style's automatically applied to whatever container element the control generates for its items - so if the ItemsControl
you're using is a ListBox
, it will apply it to the ListBoxItem
, and if it's a TabControl
, it'll get applied to the TabItem
, and so on.