2

Требуется нарисовать поле, с которым в последствии можно будет работать через логику, описанную в коде. В логике программы доска и расстановка на ней фигур описана в виде двумерного массива. Есть идея с Canvas и Rectangle, но я не понимаю, как автоматом наклепать этих Rectangle и закрасить их в соответствии с доской, после чего привязать фигуры, основываясь на опять же, двумерном массиве.

Ссылка на реп: https://github.com/anyast103/MegaChess_Game

aepot
  • 49,560
Murrchalkina
  • 57
  • 1
  • 6

1 Answers1

7

Этот ответ не зависит от ссылки в вопросе, полноценный пример использования 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&lt;object&gt; execute, Func&lt;object, bool&gt; canExecute = null)
{
    _execute = execute;
    _canExecute = canExecute;
}

public bool CanExecute(object parameter) =&gt; _canExecute == null || _canExecute(parameter);
public void Execute(object parameter) =&gt; _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 =&gt; _state;
    set
    {
        _state = value;
        OnPropertyChanged(); // сообщить интерфейсу, что значение поменялось, чтобы интефейс перерисовался в этом месте
    }
}
public bool Active // это будет показывать, что ячейка выделена пользователем
{
    get =&gt; _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 =&gt; _area[row, column].State;
    set =&gt; _area[row, column].State = value;
}

public Board()
{
    _area = new Cell[8, 8];
    for (int i = 0; i &lt; _area.GetLength(0); i++)
        for (int j = 0; j &lt; _area.GetLength(1); j++)
            _area[i, j] = new Cell();
}

public IEnumerator&lt;Cell&gt; GetEnumerator() 
    =&gt; _area.Cast&lt;Cell&gt;().GetEnumerator();

IEnumerator IEnumerable.GetEnumerator() 
    =&gt; _area.GetEnumerator();

}

Дальше я голопом по европам, потому что кода много, а ответ не резиновый.

3. ViewModel

Здесь вся основаня логика взаимодействия с интерфейсом, собственно это основная и самая интересная часть приложения, которую вам и придется дорабатывать.

public class MainViewModel : NotifyPropertyChanged
{
    private Board _board = new Board();
    private ICommand _newGameCommand;
    private ICommand _clearCommand;
    private ICommand _cellCommand;
public IEnumerable&lt;char&gt; Numbers =&gt; &quot;87654321&quot;;
public IEnumerable&lt;char&gt; Letters =&gt; &quot;ABCDEFGH&quot;;

public Board Board 
{
    get =&gt; _board;
    set
    {
        _board = value;
        OnPropertyChanged();
    }
}

public ICommand NewGameCommand =&gt; _newGameCommand ??= new RelayCommand(parameter =&gt; 
{
    SetupBoard();
});

public ICommand ClearCommand =&gt; _clearCommand ??= new RelayCommand(parameter =&gt;
{
    Board = new Board();
});

public ICommand CellCommand =&gt; _cellCommand ??= new RelayCommand(parameter =&gt;
{
    Cell cell = (Cell)parameter;
    Cell activeCell = Board.FirstOrDefault(x =&gt; x.Active);
    if (cell.State != State.Empty)
    {
        if (!cell.Active &amp;&amp; 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 =&gt; parameter is Cell cell &amp;&amp; (Board.Any(x =&gt; 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 &lt; 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)
    =&gt; 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 =&gt; (State)GetValue(PieceProperty);
    set =&gt; 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

aepot
  • 49,560
  • В ответе места больше нет, напишу сюда. Сохранить доску в файл - добавьте метод в класс Board: public void Save(string filename) => File.WriteAllText(filename, JsonSerializer.Serialize(_area.Cast<Cell>()));. Загрузить доску из файла - добавьте конструктор public Board(string filename) { _area = new Cell[8, 8]; Cell[] data = JsonSerializer.Deserialize<Cell[]>(File.ReadAllText(filename)); for (int i = 0; i < _area.GetLength(0); i++) for (int j = 0; j < _area.GetLength(1); j++) _area[i, j] = data[i * 8 + j]; }. Использовать Board.Save("board.json") и Board = new Board("board.json") – aepot Mar 13 '21 at 22:10
  • Связанные ответы: Крестики-нолики, Змейка. – aepot Mar 13 '21 at 22:19
  • Проблема в том, что задание нам дали такое: использовать Canvas, Rectangle и не использовать MVVM..) Я прекрасно понимаю, что с MVVM реализация была бы куда проще, но задание - есть задание.. – Murrchalkina Mar 14 '21 at 07:46
  • @Нюта что значит для вас "не использовать MVVM" - свалить все методы в один класс и устроить мешанину? И я не могу представить себе преподавателя, который сказал бы "использовать шаблоны проектирования нельзя". А если он так сказал, то сожгите на костре этого еретика. :) Ничему хорошему он вас не научит. В общем, меняете UniformGrid на Canvas, Border на Rectangle, добавляете себе гемора с заданием координат на канвасе, и может быть оно не сломается. – aepot Mar 14 '21 at 07:58
  • @Нюта если вам нужно рисование на канвасе без привязок данных и MVVM, я могу принять этот вызов и сделать еще один пример. Вижу ваш новый вопрос, там отпишусь.. – aepot Mar 14 '21 at 08:19