Хочу реализовать:
Button расположены квадратом (как 2-мерный массив NxN).
При клике на кнопку поворачиваются все кнопки в одной строке и в одном столбце.
Число N настраиваемое.
Начал сначала всё делать в MainWindow.xaml.cs, посоветовали всё сделать нормально и использовать MVVM. Код MainWindow.xaml.cs
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private Button[,] CreateButtons(int quantity)
{
Form.Rows = quantity;
Form.Columns = quantity;
Button[,] buttons = new Button[quantity, quantity];
for (int i = 0; i < quantity; i++)
{
for (int j = 0; j < quantity; j++)
{
buttons[i, j] = new Button();
buttons[i, j].Width = 100;
buttons[i, j].Height = 20;
buttons[i, j].Margin = new Thickness(5,80,0,0);
buttons[i, j].Click += new RoutedEventHandler(new_button_click);
}
}
return buttons;
}
void new_button_click(object sender, RoutedEventArgs e)
{
Button btn = sender as Button;
if (btn != null)
{
var rotateTransform = btn.RenderTransform as RotateTransform;
var transform = new RotateTransform(90 + (rotateTransform == null ? 0 : rotateTransform.Angle));
transform.CenterX = 50;
transform.CenterY = 10;
btn.RenderTransform = transform;
}
}
private void AddToWrapPanel(int quantity, Button[,] buttons)
{
for (int i = 0; i < quantity; i++)
for (int j = 0; j < quantity; j++)
{
Form.Children.Add(buttons[i, j]);
}
}
private int GetQuantityButtons()
{
ComboBoxItem item = (ComboBoxItem)comboBox1.SelectedItem;
int count = int.Parse((string)item.Content);
return count;
}
private void СreateButton_Click(object sender, RoutedEventArgs e)
{
if (Form.Children.Count > 0)
Form.Children.Clear();
int count = GetQuantityButtons();
Button[,] buttons = CreateButtons(count);
AddToWrapPanel(count, buttons);
}
}
Теперь начинаю всё переносить.
XAML
Код VM
public class MainWindowModel
{
public int Three { get; set; }
public int Four { get; set; }
public int Five { get; set; }
public int Six { get; set; }
public string Lvl { get; set; }
public MainWindowModel()
{
Three = 3;
Four = 4;
Five = 5;
Six = 6;
Lvl = "Сложность (3 - 6):";
}
private ICommand _seter;
public ICommand Seter
{
get
{
return _seter ?? (_seter = new RelayCommand(() =>
{
// действие при вызове команды
}));
}
}
}
Пока только так.. Помогите, пожалуйста, перенести и доделать задуманные мной моменты. Например: как в VM, обслуживающем кнопку, завести свойство RotationAngle? Как при клике по "создать" с генерировать массив из кнопок и потом работать с ними? Как обращаться к UniformGrid и связать кол-во строк и столбцов с выбранным int в combobox?
Ответ
Давайте пойдём сначала. Построим VM. Нам пригодится базовый класс для VM, в котором будет имплементация INotifyPropertyChanged
class VM : INotifyPropertyChanged
{
protected bool Set
field = value;
RaisePropertyChanged(propertyName);
return true;
}
protected void RaisePropertyChanged([CallerMemberName] string propertyName = null) =>
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
public event PropertyChangedEventHandler PropertyChanged;
}
(Если вы пользуетесь каким-нибудь MVVM-фреймворком, то аналогичный базовый класс у вас уже может быть определён.)
Теперь VM для одной клетки. Что нам нужно знать? Угол поворота — сделаем из него свойство с INPC. Строку и столбец — эти свойства неизменяемые. И команда, которая будет вызываться при активации клетки. (Она тоже неизменяемая.)
Действие, которое будет выполняться при нажатии на клетку, сама клетка выполнить не может, так как поворот происходит у многих клеток. Поэтому реакцию на действие передадим «сверху» в качестве параметра. Получаем такой вот код:
class CellVM : VM
{
public CellVM(int row, int column, Action
double rotationAngle = 0;
public double RotationAngle
{
get { return rotationAngle; }
set { Set(ref rotationAngle, value); }
}
public int Row { get; }
public int Column { get; }
public ICommand Activate { get; }
}
Следующая VM — вся доска. Ей придётся и принимать решение о вращении клеток. Какие нам тут нужны данные? Ширина и высота нужны, и при изменении нужно пересоздать массив клеток. Нужны сами клетки, и поскольку клетки у нас будут подменяться только как целое, берём не ObservableCollection
class BoardVM : VM
{
int width;
public int Width
{
get { return width; }
set { if (Set(ref width, value)) { GenerateCells(); } }
}
int height;
public int Height
{
get { return height; }
set { if (Set(ref height, value)) { GenerateCells(); } }
}
CellVM[,] cells;
public IEnumerable
Далее, при изменении количества строк или столбцов нам нужно перегенерировать клетки.
void GenerateCells()
{
var cells = new CellVM[width, height];
for (int row = 0; row < height; row++)
for (int column = 0; column < width; column++)
cells[column, row] = new CellVM(row, column, OnCellActivate);
ShuffleAngles(cells);
// отбрасываем существующие клетки
this.cells = cells;
RaisePropertyChanged(nameof(AllCells));
}
... и установить им случайный начальный угол:
static Random random = new Random();
void ShuffleAngles(CellVM[,] cells)
{
for (int y = 0; y < height; y++)
for (int x = 0; x < width; x++)
cells[x, y].RotationAngle = random.Next(4) * 90;
}
Теперь функция, которая вызывается при активации клетки. Нам нужно повернуть все клетки в том же столбце и той же строке. Во втором цикле пропускем уже один раз повёрнутую клетку.
void OnCellActivate(int row0, int column0)
{
for (int row = 0; row < height; row++)
Rotate(cells[column0, row]);
for (int column = 0; column < width; column++)
if (column != column0)
Rotate(cells[column, row0]);
}
void Rotate(CellVM cellVM)
{
cellVM.RotationAngle = (cellVM.RotationAngle + 90) % 360;
}
}
Окей, с VM более-менее ясно. Переходим к View.
Нам понадобится ItemsControl, т. к. мы хотим показать последовательность элементов. У нас последовательность элементов содержится в свойстве AllCells
Далее, нам нужно, чтобы клетки укладывались в UniformGrid. Выберем в качестве носителя UniformGrid, заодно привяжем количество строк и столбцов:
Дальше, как показывать отдельную клетку? Вы хотите Button, пускай. Пишем DataTemplate
Запустив программу, видим, что кнопка слишком прилегает к границам клетки, поэтому даём ей Margin="10". Теперь, нам нужно как-то обозначить, где верх и где низ. Для этого нарисуем стрелочку вверх (но вам придётся сделать что-то покрасивее). Стрелочку будем поворачивать на угол из привязки:
Вроде бы всё.
Теперь нужно ещё задать размеры поля.
Для этого, по-хорошему, нужно завести другое окно (и показывать его только в самом начале игры), потому что изменять размер поля во время игры как-то неправильно. Но в нашем быстром прототипе мы закроем на это глаза. (А вам потом придётся таки переделать.)
Итак, нам нужна информация о том, сколько у нас возможно строк и столбцов. Возвращаемся в VM и заводим класс:
static class GameInfo
{
static public IEnumerable
Для красоты, нам нужно инициализировать значения в BoardVM валидным числом. Находим и меняем строки:
int width = GameInfo.PossibleColumnNumber.Min();
и
int height = GameInfo.PossibleRowNumber.Min();
Теперь View. Дописываем два комбобокса и метки к ним:
Весь MainWindow.xaml
Теперь нам нужно прикрепить VM к View. Лучше всего делать это не в XAML, а в App.xaml.cs (смотрите тут). Пишем:
public partial class App : Application
{
BoardVM boardVM = new BoardVM();
protected override void OnStartup(StartupEventArgs e)
{
base.OnStartup(e);
new MainWindow() { DataContext = boardVM }.Show();
}
}
и убираем из App.xaml StartupUri
Компилируем, запускаем. Сразу видим пустое поле. Недоработка, мы ж не сгенерировали поле в конструкторе BoardVM! Исправляем:
public BoardVM()
{
GenerateCells();
}
Вот что у меня получилось:
Комментариев нет:
Отправить комментарий