Этот ответ не зависит от ссылки в вопросе, полноценный пример использования UI в WPF "с нуля".
Я вас огорчу, но в ниже следующем примере не используется ни Canvas, не Rectangle.
Все что я узнал из репозитория, это то что вы очень слабо знакомы с WPF, совсем незнакомы с шаблоном проектирвоания MVVM. И вам надо качать навыки C# в принципе. Но это все нормально, когда вы учитесь. Поэтому я вам сразу покажу, как реализовать правильно в WPF шахматную доску. При этом будет показана только UI часть игры без привязки к какой-либо игровой логике. Игровую логику вам придется прикрутить самостоятельно.
Итак, ТЗ:
- Нарисовать в интерфейсе шахматную доску с координатными метками
- Разместить на ней шахматные фигуры
- Реализовать перемещение шахматных фигур в свободные клетки пользователем с помощью мыши
Выглядит просто, поехали:
Шахматная доска с шахматами в WPF

1. MVVM, INotifyPropertyChanged, ICommand
Если кратко, то MVVM (Model View ViewModel) - это шаблон проектирования приложений, позволяющий удобно их разрабатывать за счет полного разделения логики данных (Model), интерфейса (View) и логики взаимодействия данных с интерфейсом (ViewModel). Все эти 3 слоя разрабатываются отдельно. В моем конкретном случае у меня не будет Model как таковой, так что будет немного проще, чем полноценный MVVM.
INotifyPropertyChanged - нужен для того чтобы можно было сообщать интерфейсу о том, что данные поменялись посредством вызова события PropertyChanged. По-началу будет выглядеть как магия, но вся магическая часть здесь в том, что интерфейс сам подписывается на это событие автоматически. Оно зашито в WPF, надо только пользоваться. За реализацию этого интерфейса будет отвечать простой класс, его нужно просто добавить в проект.
public class NotifyPropertyChanged : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
=> PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
ICommand - прощай обработчики событий Click, команда имеет больше возможностей, позже вы увидите их. Для удобного использования команд, был создан вот такой вспомогательный класс. Его тоже просто надо добавить в проект.
public class RelayCommand : ICommand
{
private readonly Action<object> _execute;
private readonly Func<object, bool> _canExecute;
public event EventHandler CanExecuteChanged
{
add { CommandManager.RequerySuggested += value; }
remove { CommandManager.RequerySuggested -= value; }
}
public RelayCommand(Action<object> execute, Func<object, bool> canExecute = null)
{
_execute = execute;
_canExecute = canExecute;
}
public bool CanExecute(object parameter) => _canExecute == null || _canExecute(parameter);
public void Execute(object parameter) => _execute(parameter);
}
2. Структура данных
Ячейка шахматного поля может быть всего в 13 состояниях: пусто или на ней 1 из 12 типов шахматных фигур (6 белых и 6 черных). Чтобы перечислить все состояния, я создам (внезапно) перечисление.
public enum State
{
Empty, // пусто
WhiteKing, // король
WhiteQueen, // ферзь
WhiteRook, // ладья
WhiteKnight, // конь
WhiteBishop, // слон
WhitePawn, // пешка
BlackKing,
BlackQueen,
BlackRook,
BlackKnight,
BlackBishop,
BlackPawn
}
Ячейка, которая содержит это состояние, выглядит так. Кстати, вот он сразу NotifyPropertyChanged и несложный способ его использовать.
public class Cell : NotifyPropertyChanged
{
private State _state;
private bool _active;
public State State
{
get => _state;
set
{
_state = value;
OnPropertyChanged(); // сообщить интерфейсу, что значение поменялось, чтобы интефейс перерисовался в этом месте
}
}
public bool Active // это будет показывать, что ячейка выделена пользователем
{
get => _active;
set
{
_active = value;
OnPropertyChanged();
}
}
}
Шахматное поле будет "хитрым" классом, с одной стороны это будет двумерный массив состояний State, с другой - поле с ячейками Cell. Первое - для вас, чтобы вы легко могли в коде писать типа Board[0, 0] = State.WhiteQueen. Второе - для интерфейса, чтобы он мог все эти состояния отслеживать.
Здесь все просто, массив, индексатор State и IEnumerable<Cell>, чтобы интерфейс (ItemsControl) мог это прожевать.
public class Board : IEnumerable<Cell>
{
private readonly Cell[,] _area;
public State this[int row, int column]
{
get => _area[row, column].State;
set => _area[row, column].State = value;
}
public Board()
{
_area = new Cell[8, 8];
for (int i = 0; i < _area.GetLength(0); i++)
for (int j = 0; j < _area.GetLength(1); j++)
_area[i, j] = new Cell();
}
public IEnumerator<Cell> GetEnumerator()
=> _area.Cast<Cell>().GetEnumerator();
IEnumerator IEnumerable.GetEnumerator()
=> _area.GetEnumerator();
}
Дальше я голопом по европам, потому что кода много, а ответ не резиновый.
3. ViewModel
Здесь вся основаня логика взаимодействия с интерфейсом, собственно это основная и самая интересная часть приложения, которую вам и придется дорабатывать.
public class MainViewModel : NotifyPropertyChanged
{
private Board _board = new Board();
private ICommand _newGameCommand;
private ICommand _clearCommand;
private ICommand _cellCommand;
public IEnumerable<char> Numbers => "87654321";
public IEnumerable<char> Letters => "ABCDEFGH";
public Board Board
{
get => _board;
set
{
_board = value;
OnPropertyChanged();
}
}
public ICommand NewGameCommand => _newGameCommand ??= new RelayCommand(parameter =>
{
SetupBoard();
});
public ICommand ClearCommand => _clearCommand ??= new RelayCommand(parameter =>
{
Board = new Board();
});
public ICommand CellCommand => _cellCommand ??= new RelayCommand(parameter =>
{
Cell cell = (Cell)parameter;
Cell activeCell = Board.FirstOrDefault(x => x.Active);
if (cell.State != State.Empty)
{
if (!cell.Active && activeCell != null)
activeCell.Active = false;
cell.Active = !cell.Active;
}
else if (activeCell != null)
{
activeCell.Active = false;
cell.State = activeCell.State;
activeCell.State = State.Empty;
}
}, parameter => parameter is Cell cell && (Board.Any(x => x.Active) || cell.State != State.Empty));
private void SetupBoard()
{
Board board = new Board();
board[0, 0] = State.BlackRook;
board[0, 1] = State.BlackKnight;
board[0, 2] = State.BlackBishop;
board[0, 3] = State.BlackQueen;
board[0, 4] = State.BlackKing;
board[0, 5] = State.BlackBishop;
board[0, 6] = State.BlackKnight;
board[0, 7] = State.BlackRook;
for (int i = 0; i < 8; i++)
{
board[1, i] = State.BlackPawn;
board[6, i] = State.WhitePawn;
}
board[7, 0] = State.WhiteRook;
board[7, 1] = State.WhiteKnight;
board[7, 2] = State.WhiteBishop;
board[7, 3] = State.WhiteQueen;
board[7, 4] = State.WhiteKing;
board[7, 5] = State.WhiteBishop;
board[7, 6] = State.WhiteKnight;
board[7, 7] = State.WhiteRook;
Board = board;
}
public MainViewModel()
{
}
}
Чтобы прикрутить ViewModel к приложению, нужно, чтобы класс MainWindow выглядел следующим образом:
public partial class MainWindow
{
public MainWindow()
{
InitializeComponent();
DataContext = new MainViewModel();
}
}
Да, это весь код класса MainWindow, больше в нём ничего нет :)
4. Интерфейс
Основная часть интерфейса содержит 2 кнопки и рисует шахматную доску на 64 клетки. Для того, чтобы у интерфейса получилось сделать доску из темных и светлых клеток, я написал маленький конвертер. В него передается порядковый номер ячейки 0..63, а он возвращает true или false, где true - темная клетка. Каждая клетка - тоже кнопка.
public class CellColorConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
=> value is int v && (v % 2 == 0 ^ v / 8 % 2 == 0);
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
=> null;
}
XAML основного окна получился не сложный.
<Window x:Class="ChessBoard.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:ChessBoard"
xmlns:controls="clr-namespace:ChessBoard.Controls"
mc:Ignorable="d"
Title="WPF Window" SizeToContent="WidthAndHeight" ResizeMode="CanMinimize"
d:DataContext="{d:DesignInstance local:MainViewModel, IsDesignTimeCreatable=True}"
SnapsToDevicePixels="True">
<Window.Resources>
<local:CellColorConverter x:Key="CellColorConverter"/>
</Window.Resources>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<StackPanel Orientation="Horizontal" Grid.Column="1" Margin="-5,0">
<Button Content="New Game" Margin="5" Padding="10,5" Command="{Binding NewGameCommand}"/>
<Button Content="Clear" Margin="5" Padding="10,5" Command="{Binding ClearCommand}"/>
</StackPanel>
<ItemsControl Grid.Row="2" ItemsSource="{Binding Numbers}" Width="21">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Grid Height="60">
<TextBlock Padding="5,0" Text="{Binding}" VerticalAlignment="Center" TextAlignment="Center" FontSize="16"/>
</Grid>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
<ItemsControl Grid.Row="1" Grid.Column="1" ItemsSource="{Binding Letters}" Height="21">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Grid Width="60">
<TextBlock Text="{Binding}" VerticalAlignment="Center" TextAlignment="Center" FontSize="16"/>
</Grid>
</DataTemplate>
</ItemsControl.ItemTemplate>
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
<ItemsControl Grid.Row="2" Grid.Column="1" ItemsSource="{Binding Board}" AlternationCount="64">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Button Width="60" Height="60" Command="{Binding DataContext.CellCommand, RelativeSource={RelativeSource AncestorType=Window}}" CommandParameter="{Binding}" >
<Button.Style>
<Style TargetType="{x:Type Button}">
<Setter Property="OverridesDefaultStyle" Value="True"/>
<Setter Property="Background" Value="Bisque"/>
<Setter Property="BorderBrush" Value="{x:Null}"/>
<Setter Property="BorderThickness" Value="2"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate>
<Border Background="{TemplateBinding Background}" >
<Border BorderThickness="{TemplateBinding BorderThickness}" BorderBrush="{TemplateBinding BorderBrush}">
<controls:ChessPiece Piece="{Binding State}" />
</Border>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
<Style.Triggers>
<DataTrigger Binding="{Binding (ItemsControl.AlternationIndex), RelativeSource={RelativeSource AncestorType=ContentPresenter}, Converter={StaticResource CellColorConverter}}" Value="True">
<Setter Property="Background" Value="SandyBrown"/>
</DataTrigger>
<DataTrigger Binding="{Binding Active}" Value="True">
<Setter Property="BorderBrush" Value="Red"/>
</DataTrigger>
</Style.Triggers>
</Style>
</Button.Style>
</Button>
</DataTemplate>
</ItemsControl.ItemTemplate>
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<UniformGrid Columns="8" Rows="8"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
<ItemsControl Grid.Row="3" Grid.Column="1" ItemsSource="{Binding Letters}" Height="21">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Grid Width="60">
<TextBlock Text="{Binding}" VerticalAlignment="Center" TextAlignment="Center" FontSize="16"/>
</Grid>
</DataTemplate>
</ItemsControl.ItemTemplate>
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
<ItemsControl Grid.Row="2" Grid.Column="2" ItemsSource="{Binding Numbers}" Width="21">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Grid Height="60">
<TextBlock Padding="5,0" Text="{Binding}" VerticalAlignment="Center" TextAlignment="Center" FontSize="16"/>
</Grid>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Grid>
</Window>
И я бы закончил на этом, но так как вам будет очень сложно продвинуться дальше с шахматными фигурами не вникнув полностью в суть привязки данных, поэтому я сразу написал UserControl, которому вы передаете State, а он вам выдает картинку. Картинки векторные - Path картинок выдернут из SVG файлов, скачанных с сайта iconfinder. Вбейте там в поиск Chess и вы быстро их найдете.
Код юзерконтрола
public partial class ChessPiece : UserControl
{
public static readonly DependencyProperty PieceProperty = DependencyProperty.Register("Piece", typeof(State), typeof(ChessPiece));
public State Piece
{
get => (State)GetValue(PieceProperty);
set => SetValue(PieceProperty, value);
}
public ChessPiece()
{
InitializeComponent();
}
}
И разметка юзерконтрола. Здесь я применил все приемы кунгфу при работе со стилями в WPF, которые знаю. :)
<UserControl x:Class="ChessBoard.Controls.ChessPiece"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:controls="clr-namespace:ChessBoard.Controls"
mc:Ignorable="d"
SnapsToDevicePixels="True">
<UserControl.Resources>
<DropShadowEffect x:Key="BlackShadow" BlurRadius="5" ShadowDepth="0" Color="Black"/>
<DropShadowEffect x:Key="WhiteShadow" BlurRadius="5" ShadowDepth="0" Color="White"/>
<Style TargetType="Path" x:Key="InvisiblePath">
<Setter Property="Visibility" Value="Collapsed"/>
</Style>
<Style TargetType="Path" x:Key="Queen" BasedOn="{StaticResource InvisiblePath}">
<Setter Property="Data" Value="M63,36c0,1.1-0.9,2-2,2H39c-1.1,0-2-0.9-2-2s0.9-2,2-2h22C62.1,34,63,34.9,63,36z M34,84h32c1.1,0,2-0.9,2-2s-0.9-2-2-2H34 c-1.1,0-2,0.9-2,2S32.9,84,34,84z M69,85H31c-2.2,0-4,1.8-4,4s1.8,4,4,4h38c2.2,0,4-1.8,4-4S71.2,85,69,85z M40.973,39 c-0.277,29.941-2.637,33.513-3.583,40H62.61c-0.946-6.487-3.306-10.059-3.583-40H40.973z M34.965,23l3.89,10h22.291l3.89-10H34.965 z M65.424,22l2.44-6.275l-3.729-1.45l-1.361,3.501c-1.851-0.886-5.641-1.543-10.218-1.724C53.432,15.318,54,14.231,54,13 c0-2.208-1.791-4-4-4s-4,1.792-4,4c0,1.231,0.568,2.318,1.443,3.052c-4.577,0.181-8.367,0.838-10.218,1.724l-1.361-3.501 l-3.729,1.45L34.576,22H65.424z"/>
</Style>
<Style TargetType="Path" x:Key="King" BasedOn="{StaticResource InvisiblePath}">
<Setter Property="Data" Value="M37,36c0-1.1,0.9-2,2-2h22c1.1,0,2,0.9,2,2s-0.9,2-2,2H39C37.9,38,37,37.1,37,36z M34,84h32c1.1,0,2-0.9,2-2s-0.9-2-2-2H34 c-1.1,0-2,0.9-2,2S32.9,84,34,84z M69,85H31c-2.2,0-4,1.8-4,4s1.8,4,4,4h38c2.2,0,4-1.8,4-4S71.2,85,69,85z M37,20l0.615,2h24.77 L63,20l-11-4.231V11h2V7h-2V5h-4v2h-2v4h2v4.769L37,20z M59,33l3.077-10H37.923L41,33H59z M40.973,39 c-0.277,29.941-2.637,33.513-3.583,40H62.61c-0.946-6.487-3.306-10.059-3.583-40H40.973z"/>
</Style>
<Style TargetType="Path" x:Key="Rook" BasedOn="{StaticResource InvisiblePath}">
<Setter Property="Data" Value="M31,25V10h7v6h6v-6h12v6h6v-6h7v15c0,2.2-1.8,4-4,4H35C32.8,29,31,27.2,31,25z M65,34c1.1,0,2-0.9,2-2s-0.9-2-2-2H35 c-1.1,0-2,0.9-2,2s0.9,2,2,2H65z M30,84h40c1.1,0,2-0.9,2-2s-0.9-2-2-2H30c-1.1,0-2,0.9-2,2S28.9,84,30,84z M73,85H27 c-2.2,0-4,1.8-4,4s1.8,4,4,4h46c2.2,0,4-1.8,4-4S75.2,85,73,85z M68.262,79C66.464,72.751,62,70.139,62,35H38 c0,35.139-4.464,37.751-6.262,44H68.262z"/>
</Style>
<Style TargetType="Path" x:Key="Knight" BasedOn="{StaticResource InvisiblePath}">
<Setter Property="Data" Value="M31.375,40.219l1.249,1.562l-5.475,4.379C27.676,48.357,29.645,50,32,50c2.527,0,4.622-1.884,4.954-4.321l5.849-2.507 c2.944,2.45,7.337,2.296,10.097-0.465c2.924-2.924,2.924-7.682,0-10.606l0.707-0.707c1.605,1.605,2.49,3.74,2.49,6.01 c0,1.329-0.311,2.608-0.884,3.765l0,0c-0.196,0.396-0.425,0.775-0.681,1.139c-0.024,0.034-0.05,0.066-0.074,0.1 c-0.256,0.353-0.536,0.692-0.851,1.007c-0.276,0.276-0.57,0.523-0.873,0.752c-0.07,0.053-0.143,0.101-0.213,0.151 c-0.252,0.178-0.51,0.343-0.775,0.492c-1.508,0.843-3.216,1.203-4.894,1.057C45.944,52.159,40.545,57,34,57l2,22h28 c0-9.957,2.698-18.563,5.535-25.822C64.908,57.412,58.751,60,52,60v-1c13.785,0,25-11.215,25-25S65.785,9,52,9h-1v10h-1v-4h-7 c-3.866,0-7,3.134-7,7c0,1.831-16,7.76-16,16c0,3.38,2.395,6.199,5.58,6.855L31.375,40.219z M45.485,20.143l1.029,1.715l-5,3 l-1.029-1.715L45.485,20.143z M23.445,38.168l3-2l1.109,1.664l-3,2L23.445,38.168z M69,80c1.1,0,2,0.9,2,2s-0.9,2-2,2H31 c-1.1,0-2-0.9-2-2s0.9-2,2-2H69z M76,89c0,2.2-1.8,4-4,4H28c-2.2,0-4-1.8-4-4s1.8-4,4-4h44C74.2,85,76,86.8,76,89z"/>
</Style>
<Style TargetType="Path" x:Key="Bishop" BasedOn="{StaticResource InvisiblePath}">
<Setter Property="Data" Value="M37,40c0-1.1,0.9-2,2-2h22c1.1,0,2,0.9,2,2s-0.9,2-2,2H39C37.9,42,37,41.1,37,40z M34,84h32c1.1,0,2-0.9,2-2s-0.9-2-2-2H34 c-1.1,0-2,0.9-2,2S32.9,84,34,84z M69,85H31c-2.2,0-4,1.8-4,4s1.8,4,4,4h38c2.2,0,4-1.8,4-4S71.2,85,69,85z M40.95,43 c-0.358,27.587-2.586,30.262-3.528,36h25.156c-0.942-5.738-3.17-8.413-3.528-36H40.95z M59,37c0,0,4-6,4-11 c0-4.411-10.112-13.489-12.496-19h-1.008c-0.871,2.015-2.776,4.506-4.842,7.072l4.24,8.48l-1.789,0.895l-3.834-7.668 C40.1,19.685,37,23.558,37,26c0,5,4,11,4,11H59z"/>
</Style>
<Style TargetType="Path" x:Key="Pawn" BasedOn="{StaticResource InvisiblePath}">
<Setter Property="Data" Value="M37,38c0-1.1,0.9-2,2-2h22c1.1,0,2,0.9,2,2s-0.9,2-2,2H39C37.9,40,37,39.1,37,38z M34,84h32c1.1,0,2-0.9,2-2s-0.9-2-2-2H34 c-1.1,0-2,0.9-2,2S32.9,84,34,84z M69,85H31c-2.2,0-4,1.8-4,4s1.8,4,4,4h38c2.2,0,4-1.8,4-4S71.2,85,69,85z M50,35 c7.18,0,13-5.82,13-13S57.18,9,50,9s-13,5.82-13,13S42.82,35,50,35z M58,41H42c0,33.478-4.052,33.959-5.99,38H63.99 C62.052,74.959,58,74.478,58,41z"/>
</Style>
<Style TargetType="Path" x:Key="WhiteQueen" BasedOn="{StaticResource Queen}">
<Setter Property="Fill" Value="White"/>
<Setter Property="Effect" Value="{StaticResource BlackShadow}"/>
<Style.Triggers>
<DataTrigger Binding="{Binding Piece, RelativeSource={RelativeSource AncestorType=UserControl}}" Value="WhiteQueen">
<Setter Property="Visibility" Value="Visible"/>
</DataTrigger>
</Style.Triggers>
</Style>
<Style TargetType="Path" x:Key="BlackQueen" BasedOn="{StaticResource Queen}">
<Setter Property="Fill" Value="Black"/>
<Setter Property="Effect" Value="{StaticResource WhiteShadow}"/>
<Style.Triggers>
<DataTrigger Binding="{Binding Piece, RelativeSource={RelativeSource AncestorType=UserControl}}" Value="BlackQueen">
<Setter Property="Visibility" Value="Visible"/>
</DataTrigger>
</Style.Triggers>
</Style>
<Style TargetType="Path" x:Key="WhiteKing" BasedOn="{StaticResource King}">
<Setter Property="Fill" Value="White"/>
<Setter Property="Effect" Value="{StaticResource BlackShadow}"/>
<Style.Triggers>
<DataTrigger Binding="{Binding Piece, RelativeSource={RelativeSource AncestorType=UserControl}}" Value="WhiteKing">
<Setter Property="Visibility" Value="Visible"/>
</DataTrigger>
</Style.Triggers>
</Style>
<Style TargetType="Path" x:Key="BlackKing" BasedOn="{StaticResource King}">
<Setter Property="Fill" Value="Black"/>
<Setter Property="Effect" Value="{StaticResource WhiteShadow}"/>
<Style.Triggers>
<DataTrigger Binding="{Binding Piece, RelativeSource={RelativeSource AncestorType=UserControl}}" Value="BlackKing">
<Setter Property="Visibility" Value="Visible"/>
</DataTrigger>
</Style.Triggers>
</Style>
<Style TargetType="Path" x:Key="WhiteRook" BasedOn="{StaticResource Rook}">
<Setter Property="Fill" Value="White"/>
<Setter Property="Effect" Value="{StaticResource BlackShadow}"/>
<Style.Triggers>
<DataTrigger Binding="{Binding Piece, RelativeSource={RelativeSource AncestorType=UserControl}}" Value="WhiteRook">
<Setter Property="Visibility" Value="Visible"/>
</DataTrigger>
</Style.Triggers>
</Style>
<Style TargetType="Path" x:Key="BlackRook" BasedOn="{StaticResource Rook}">
<Setter Property="Fill" Value="Black"/>
<Setter Property="Effect" Value="{StaticResource WhiteShadow}"/>
<Style.Triggers>
<DataTrigger Binding="{Binding Piece, RelativeSource={RelativeSource AncestorType=UserControl}}" Value="BlackRook">
<Setter Property="Visibility" Value="Visible"/>
</DataTrigger>
</Style.Triggers>
</Style>
<Style TargetType="Path" x:Key="WhiteKnight" BasedOn="{StaticResource Knight}">
<Setter Property="Fill" Value="White"/>
<Setter Property="Effect" Value="{StaticResource BlackShadow}"/>
<Style.Triggers>
<DataTrigger Binding="{Binding Piece, RelativeSource={RelativeSource AncestorType=UserControl}}" Value="WhiteKnight">
<Setter Property="Visibility" Value="Visible"/>
</DataTrigger>
</Style.Triggers>
</Style>
<Style TargetType="Path" x:Key="BlackKnight" BasedOn="{StaticResource Knight}">
<Setter Property="Fill" Value="Black"/>
<Setter Property="Effect" Value="{StaticResource WhiteShadow}"/>
<Style.Triggers>
<DataTrigger Binding="{Binding Piece, RelativeSource={RelativeSource AncestorType=UserControl}}" Value="BlackKnight">
<Setter Property="Visibility" Value="Visible"/>
</DataTrigger>
</Style.Triggers>
</Style>
<Style TargetType="Path" x:Key="WhiteBishop" BasedOn="{StaticResource Bishop}">
<Setter Property="Fill" Value="White"/>
<Setter Property="Effect" Value="{StaticResource BlackShadow}"/>
<Style.Triggers>
<DataTrigger Binding="{Binding Piece, RelativeSource={RelativeSource AncestorType=UserControl}}" Value="WhiteBishop">
<Setter Property="Visibility" Value="Visible"/>
</DataTrigger>
</Style.Triggers>
</Style>
<Style TargetType="Path" x:Key="BlackBishop" BasedOn="{StaticResource Bishop}">
<Setter Property="Fill" Value="Black"/>
<Setter Property="Effect" Value="{StaticResource WhiteShadow}"/>
<Style.Triggers>
<DataTrigger Binding="{Binding Piece, RelativeSource={RelativeSource AncestorType=UserControl}}" Value="BlackBishop">
<Setter Property="Visibility" Value="Visible"/>
</DataTrigger>
</Style.Triggers>
</Style>
<Style TargetType="Path" x:Key="WhitePawn" BasedOn="{StaticResource Pawn}">
<Setter Property="Fill" Value="White"/>
<Setter Property="Effect" Value="{StaticResource BlackShadow}"/>
<Style.Triggers>
<DataTrigger Binding="{Binding Piece, RelativeSource={RelativeSource AncestorType=UserControl}}" Value="WhitePawn">
<Setter Property="Visibility" Value="Visible"/>
</DataTrigger>
</Style.Triggers>
</Style>
<Style TargetType="Path" x:Key="BlackPawn" BasedOn="{StaticResource Pawn}">
<Setter Property="Fill" Value="Black"/>
<Setter Property="Effect" Value="{StaticResource WhiteShadow}"/>
<Style.Triggers>
<DataTrigger Binding="{Binding Piece, RelativeSource={RelativeSource AncestorType=UserControl}}" Value="BlackPawn">
<Setter Property="Visibility" Value="Visible"/>
</DataTrigger>
</Style.Triggers>
</Style>
<Style TargetType="controls:ChessPiece">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate>
<Viewbox>
<Grid Width="100" Height="100">
<Path Style="{StaticResource WhiteQueen}"/>
<Path Style="{StaticResource BlackQueen}"/>
<Path Style="{StaticResource WhiteKing}"/>
<Path Style="{StaticResource BlackKing}"/>
<Path Style="{StaticResource WhiteRook}"/>
<Path Style="{StaticResource BlackRook}"/>
<Path Style="{StaticResource WhiteKnight}"/>
<Path Style="{StaticResource BlackKnight}"/>
<Path Style="{StaticResource WhiteBishop}"/>
<Path Style="{StaticResource BlackBishop}"/>
<Path Style="{StaticResource WhitePawn}"/>
<Path Style="{StaticResource BlackPawn}"/>
</Grid>
</Viewbox>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</UserControl.Resources>
<Grid/>
</UserControl>
Вот и всё приложение, ничего больше нет.

Архив с решением на Яндекс.Диске - https://disk.yandex.ru/d/k9BxlysXJ_JyCA