Судоку в WPF - Какой базовый элемент я должен использовать для таблицы? - PullRequest
0 голосов
/ 05 января 2019

Я создал очень базовое приложение для судоку на C #, XAML и MVVM-Light. MainWindow имеет сетку 9x9 и текстовое поле 81 для каждой ячейки, а MainViewModel имеет 81 свойство int. Не красиво Таблица Судоку не похожа на классическую таблицу, здесь нет «более толстой границы», чтобы дифференцировать каждый квадрант.

Теперь я хочу перейти на следующий уровень и сделать его красивым и практичным.

Содержание каждой ячейки должно быть «отзывчивым» т.е. изменить размер (размер шрифта) в зависимости от ширины / высоты окна. Я уже начал работу над CellUserControl, который обрабатывает цвет шрифта, цвет фона и т. Д.

Какой должна быть база для главного окна? Сетка, Таблица, ListBox, Пользовательский?

  • Я пробовал Grid and Table, но обычно заканчивал тем, что зашел в тупик. XAML становится очень многословным и повторяющимся.
  • Я помню MS, демонстрирующий силу WPF с ListBox, отображаемым в виде карусели, просто применяя стили. Это правильный путь?
  • Пользовательский контроль: это избыточно или существует именно для этой ситуации?

Кстати, я буду много использовать MVVM-Light и Data Binding.

Ответы [ 3 ]

0 голосов
/ 07 января 2019

Поскольку все ячейки должны иметь одинаковый размер, вы также можете использовать UniformGrid . Как предположил Лео Барткус, вы можете использовать выделенный код для генерации ячеек и текстовых полей. Для этого начните с создания заполнителя для таблицы Судоку в XAML:

<!-- Placeholder for Sudoku table (filled in code-behind) -->
<Border x:Name="SudokuTable" />

Предполагая, что вы используете Window, вот код:

public partial class MainWindow : Window
{
    private const int InnerWidth = 3;
    private const int OuterWidth = InnerWidth * InnerWidth;

    private const int Thin = 1;
    private const int Thick = 3;

    public MainWindow()
    {
        InitializeComponent();
        InitializeViewModel();
        InitializeSudokuTable();
    }

    public SudokuViewModel ViewModel => (SudokuViewModel)DataContext;

    private void InitializeViewModel()
    {
        DataContext = new SudokuViewModel(OuterWidth);
    }

    private void InitializeSudokuTable()
    {
        var grid = new UniformGrid
        {
            Rows = OuterWidth,
            Columns = OuterWidth
        };

        for (var i = 0; i < OuterWidth; i++)
        {
            for (var j = 0; j < OuterWidth; j++)
            {
                var border = CreateBorder(i, j);
                border.Child = CreateTextBox(i, j);
                grid.Children.Add(border);
            }
        }

        SudokuTable.Child = grid;
    }

    private static Border CreateBorder(int i, int j)
    {
        var left = j % InnerWidth == 0 ? Thick : Thin;
        var top = i % InnerWidth == 0 ? Thick : Thin;
        var right = j == OuterWidth - 1 ? Thick : 0;
        var bottom = i == OuterWidth - 1 ? Thick : 0;

        return new Border
        {
            BorderThickness = new Thickness(left, top, right, bottom),
            BorderBrush = Brushes.Black
        };
    }

    private TextBox CreateTextBox(int i, int j)
    {
        var textBox = new TextBox
        {
            VerticalAlignment = VerticalAlignment.Center,
            HorizontalAlignment = HorizontalAlignment.Center
        };

        var binding = new Binding
        {
            Source = ViewModel,
            Path = new PropertyPath($"[{i},{j}]"),
            Mode = BindingMode.TwoWay
        };

        textBox.SetBinding(TextBox.TextProperty, binding);

        return textBox;
    }
}

Вложенный цикл создает каждый Border и TextBox для 81 ячейки. Толщины границ определяются на основе текущего положения ячейки. Это даст вам типичный вид стола Судоку.

Текстовые поля привязаны к данным для свойства двумерного индексатора модели представления. Вот вид модели:

public class SudokuViewModel : ViewModelBase
{
    private readonly string[,] _values;

    public SudokuViewModel(int width)
    {
        _values = new string[width, width];
    }

    public string this[int i, int j]
    {
        get => _values[i, j];
        set => Set(ref _values[i, j], value);
    }
}

Этот индексатор возвращает строку, но вы можете изменить ее на целое число и выполнить соответствующие преобразования и проверки ошибок. В любом случае он использует MVVM Light для вызова события PropertyChanged при обновлении свойства индексатора.

Я создал хранилище с моим решением здесь: https://github.com/redcurry/Sudoku.

0 голосов
/ 08 января 2019

Отличные ответы всем, ваши идеи заставили меня сосредоточиться на цели :) 1001 *

Вот где я сейчас,

XAML: я использую многословный подход XAML и сетку.

Сетка: 11x11 - (9x9 для клеток Судоку + 2x2 для границ).

<Grid Grid.Row="1" >
        <Grid.ColumnDefinitions>
            <ColumnDefinition/>
            <ColumnDefinition/>
            <ColumnDefinition/>

            <ColumnDefinition Width="2px"/>

            <ColumnDefinition/>
            <ColumnDefinition/>
            <ColumnDefinition/>

            <ColumnDefinition Width="2px"/>

            <ColumnDefinition/>
            <ColumnDefinition/>
            <ColumnDefinition/>
        </Grid.ColumnDefinitions>

        <Grid.RowDefinitions>
            <RowDefinition/>
            <RowDefinition/>
            <RowDefinition/>

            <RowDefinition Height="2px"/>

            <RowDefinition/>
            <RowDefinition/>
            <RowDefinition/>

            <RowDefinition Height="2px"/>

            <RowDefinition/>
            <RowDefinition/>
            <RowDefinition/>
        </Grid.RowDefinitions>

        <Rectangle Grid.Row="0" Grid.Column="3" Grid.RowSpan="11" Fill="Black"></Rectangle>
        <Rectangle Grid.Row="0" Grid.Column="7" Grid.RowSpan="11" Fill="Black"></Rectangle>
        <Rectangle Grid.Row="3" Grid.Column="0" Grid.ColumnSpan="11" Fill="Black"></Rectangle>
        <Rectangle Grid.Row="7" Grid.Column="0" Grid.ColumnSpan="11" Fill="Black"></Rectangle>

        <local:CellUserControl Grid.Row="0" Grid.Column="0"  DataContext="{Binding Path=Cells[0], Source={StaticResource Locator}}"/>
        <local:CellUserControl Grid.Row="0" Grid.Column="1"  DataContext="{Binding Path=Cells[1], Source={StaticResource Locator}}"/>
...

Я ленив, поэтому в итоге я использовал электронную таблицу Excel, чтобы перечислить 81 ячейку, и использовал комбинацию Floor.Math, MOD и Concatenate:)

Следующим вызовом стал рефакторинг 81 MVVM Properties в нечто более тривиальное. В XAML: синтаксис: {Binding Path = Cells [0]} и выберите (на данный момент) свойство в ViewModelLocator.

    public IList<CellViewModel> Cells
    {
        get
        {
            return new List<CellViewModel>(ServiceLocator.Current.GetAllInstances<CellViewModel>());
        }
    }

XAML и код чистые. Мне нравится это до сих пор. Я все еще жонглирую правильное местоположение для IList - он должен оставаться в ViewModelLocator или он должен быть на самом деле в MainViewModel? Полагаю, чтобы ответить на этот вопрос, мне нужно провести несколько юнит-тестов.

Большое спасибо.

0 голосов
/ 06 января 2019

Вот пример того, как я буду делать судоку с сеткой ...

<Grid>


 <Grid.RowDefinitions>
        <RowDefinition Height="*"/>
        <RowDefinition Height="*"/>
        <RowDefinition Height="*"/>
        <RowDefinition Height="5"/>
        <RowDefinition Height="*"/>
        <RowDefinition Height="*"/>
        <RowDefinition Height="*"/>
        <RowDefinition Height="5"/>
        <RowDefinition Height="*"/>
        <RowDefinition Height="*"/>
        <RowDefinition Height="*"/>
    </Grid.RowDefinitions>
    <Grid.ColumnDefinitions>
        <ColumnDefinition Width="*"/>
        <ColumnDefinition Width="*"/>
        <ColumnDefinition Width="*"/>
        <ColumnDefinition Width="5"/>
        <ColumnDefinition Width="*"/>
        <ColumnDefinition Width="*"/>
        <ColumnDefinition Width="*"/>
        <ColumnDefinition Width="5"/>
        <ColumnDefinition Width="*"/>
        <ColumnDefinition Width="*"/>
        <ColumnDefinition Width="*"/>
    </Grid.ColumnDefinitions>
    <TextBox x:Name="textBox00" TextWrapping="Wrap" HorizontalContentAlignment="Center" VerticalContentAlignment="Center" Text="1"/>
    <TextBox x:Name="textBox00_Copy" TextWrapping="Wrap" HorizontalContentAlignment="Center" VerticalContentAlignment="Center" Text="1" Grid.Column="1"/>
    <TextBox x:Name="textBox00_Copy1" TextWrapping="Wrap" HorizontalContentAlignment="Center" VerticalContentAlignment="Center" Text="1"  Grid.Column="2"/>
    <TextBox x:Name="textBox00_Copy2" TextWrapping="Wrap" HorizontalContentAlignment="Center" VerticalContentAlignment="Center" Text="1"  Grid.Column="4"/>
    <TextBox x:Name="textBox00_Copy3" TextWrapping="Wrap" HorizontalContentAlignment="Center" VerticalContentAlignment="Center" Text="1"  Grid.Column="5"/>
    <TextBox x:Name="textBox00_Copy4" TextWrapping="Wrap" HorizontalContentAlignment="Center" VerticalContentAlignment="Center" Text="1" Grid.Column="6"/>
    <TextBox x:Name="textBox00_Copy5" TextWrapping="Wrap" HorizontalContentAlignment="Center" VerticalContentAlignment="Center" Text="1"  Grid.Column="2" Grid.Row="1"/>
    <TextBox x:Name="textBox00_Copy6" TextWrapping="Wrap" HorizontalContentAlignment="Center" VerticalContentAlignment="Center" Text="1" Grid.Column="4" Grid.Row="1"/>
    <TextBox x:Name="textBox00_Copy7" TextWrapping="Wrap" HorizontalContentAlignment="Center" VerticalContentAlignment="Center" Text="1" Grid.Column="5" Grid.Row="1"/>
    <TextBox x:Name="textBox00_Copy8" TextWrapping="Wrap" HorizontalContentAlignment="Center" VerticalContentAlignment="Center" Text="1"  Grid.Column="1" Grid.Row="1"/>
    <TextBox x:Name="textBox00_Copy9" TextWrapping="Wrap" HorizontalContentAlignment="Center" VerticalContentAlignment="Center" Text="1"  Grid.Column="1" Grid.Row="2"/>
    <TextBox x:Name="textBox00_Copy10" TextWrapping="Wrap" HorizontalContentAlignment="Center" VerticalContentAlignment="Center" Text="1"  Grid.Column="2" Grid.Row="2"/>
    <TextBox x:Name="textBox00_Copy11" TextWrapping="Wrap" HorizontalContentAlignment="Center" VerticalContentAlignment="Center" Text="1"  Grid.Column="4" Grid.Row="2"/>
    <Rectangle Grid.Column="3" Fill="#FF663003" Grid.RowSpan="12"/>
    <Rectangle Grid.Column="7" Fill="#FF663003" Grid.RowSpan="12"/>
    <Rectangle Grid.Row="3" Fill="#FF663003" Grid.ColumnSpan="12"/>
    <Rectangle Grid.Row="7" Fill="#FF663003" Grid.ColumnSpan="12"/>

Это просто стиль текстового поля по умолчанию. Вы можете изменить его с помощью пользовательского контроля или создать собственный стиль для текстового поля, загрузив xaml в Blend для Visual studio, щелкнув правой кнопкой мыши текстовое поле и выбрав «Редактировать шаблон». -> Редактировать копию

...