Программирование / Заочники / 2 семестр / Методичка_ч3_Урок3
.pdfУрок 3: Динамическое создание элементов управления.
Основная цель урока.
1.Научиться создавать элементы управления в режиме выполнения программы.
2.Научиться использовать свойства (Properties).
3.Познакомиться с элементами управления GroupBox и StatusStrip.
Краткая справка.
Динамическое создание элементов управления.
В некоторых случаях может потребоваться создание новых элементов управления в процессе выполнения программы. Создание новых элементов управления выглядит так же, как создание новых переменных (с некоторыми тонкостями). Например, для создания нового элемента управления TextBox,
достаточно написать.
TextBox txt = new TextBox();
В этом случае будет создан новый элемент TextBox, но он не будет отображаться на форме (или где то ещё). Для того чтобы он отображался его нужно добавить на форму (или другой контейнер см. раздел « Контейнеры» на панели инструментов).
TextBox txt = new TextBox(); //добавляем компонент txt на форму Controls.Add(txt);
После выполнения вышенаписанного кода на форме отобразится пустое текстовое поле в точке (0,0),т.е. в левом верхнем углу формы. Меняя свойства Left и Top можно задать определённое положение компонента на форме.
Обратите внимание, что свойство Name у созданного компонента txt, вовсе не равно txt. Свойство Name по умолчанию для всех созданных компонентов равно пустой строке. Для динамически создаваемых элементов управления свойство
Name можно задавать, если вы планируете искать по имени этот элемент в коллекции Controls.
TextBox txt = new TextBox(); txt.Name = "MyTextBox"; Controls.Add(txt);
//Метод Find возвращает массив элементов типа Control
//поскольку Control базовый класс для TextBox, но всё же не TextBox //необходимо явное приведение типов
TextBox foundTxt = (TextBox)Controls.Find("MyTextBox",true)[0]; //foundTxt равен txt т.е.это ссылки на один и тот же элемент управления foundTxt.Left = 20;
Учебное задание 3.1.
Создать компьютерную игру крестики-нолики для игрового поля произвольного
размера (размер задаётся в коде).
Технология выполнения учебного задания 3.1.
Шаг 1. Создадим новый проект. Из основного меню выбираем Файл -> Создать ->
Проект. В появившимся диалоговом окне выбираем Приложение Windows Forms.
Шаг 2. В окне « Обозреватель решений» переименуйте форму Form1 в fMain. При появлении запроса «… переименовать все ссылки на эту часть кода…?» выбрать
« Да».
Шаг 3. Изменение свойств главной формы. Кликните на форму fMain и в окне свойств измените свойство Text формы на «Крестики-нолики».
Шаг 4. Добавим на форму несколько элементов управления: кнопку « Новая игра»,
контейнер GroupBox, который будет содержать игровое поле и компонент
StatusStrip, который представляет статусную строку и будет отображать подсказку о текущем ходе. На рисунке 1 показана форма и размещённые на ней элементы управления. Красным цветом показан тип элемента управления.
Рисунок 1. Размещение компонентов на форме
Для кнопки « Новая игра» необходимо задать следующие свойства:
Свойство Name = “btnNewGame”
Свойство Text = “Новая игра”
Элемент GroupBox находится на панели инструментов в разделе « Контейнеры».
Для размещённого элемента GroupBox необходимо задать следующие свойства:
Свойство Name = “grGameBox”
Свойство Text = “Игровое поле”
Элемент StatusStrip находится на панели инструментов в разделе « Меню и панели инструментов». После того как StatusStrip размещён на форме, нужно разместить на нём элемент StatusLabel. Для этого выделите элемент StatusStrip на форме и
кликните на иконке
, затем из выпадающего списка выберите пункт StatusLabel.
Появится новый элемент toolStripStatusLabel1 (имя по умолчанию). Выделите этот элемент и измените у него следующие свойства:
Свойство Name = “lblStatus”
Свойство Text = “”
Шаг 6. Создадим новое перечисление (enum), которое сможет принимать всего 3
различных значения (None, X и O). Для этого добавьте на уровне формы код из листинга 1.
public partial class fMain : Form
{
public enum PlayerType
{
None,
X,
O
}
public fMain()
{
InitializeComponent();
}
Листинг 1. Форма fMain. Добавление перечисления PlayerType
Шаг 7. Игровое после будет представлять собой двумерный массив кнопок. Каждая кнопка при нажатии будет менять значение свойства Text на “X” или “O”. Таким образом, на уровне класса формы должен быть объявлен двумерный массив кнопок.
Кроме того, важно знать какой игрок ходит в данный момент, для этого объявим ещё одну переменную, которая будет хранить значение типа PlayerType.
Так же вынесем |
на |
уровень формы |
переменные, |
которые будет хранить |
« параметры» игры |
– |
это размер поля |
и количество |
одинаковых элементов |
(крестиков или ноликов) в ряд, необходимых для победы (листинг 2).
public partial class fMain : Form
{
public enum PlayerType
{
None,
X,
O
}
//Игровое поле
private Button[,] btnField; //Активный игрок
private PlayerType ActivePlayer = PlayerType.X; //Размер поля CellCount x CellCount
private const int CellCount = 3;
//Кол-во крестиков или ноликов идущих подряд чтобы выиграть private const int CountToWin = 3;
public fMain()
{
InitializeComponent();
}
Листинг 2. Форма fMain. Подавление переменных на уровне класса формы.
Шаг 8. Реализуем создание игрового поля. В событии Click кнопки btnNewGame
добавьте код, представленный в листинге 3.
ВАЖНО: При написании строчки btn.Click += new EventHandler(btn_Click);
Не переписывайте всю строку с листинга, после написания btn.Click += нажмите клавишу Tab. При этом не только будет дописан необходимый код, но и автоматически будет создана процедура-обработчик события btn_Click (рисунок 2).
Рисунок 2. Автоматическое создание процедуры-обработчика события.
private void btnNewGame_Click(object sender, EventArgs e)
{
//Если массив неинициализирован...
if (btnField == null)
{
//выделяем память под массив,
//размер массива задаётся константой CellCount (см. выше) btnField = new Button[CellCount,CellCount];
//Проходим по всем элементам...
for (int i = 0; i < CellCount; i++)
{
for (int j = 0; j < CellCount; j++)
{
//Создаём новую кнопку
Button btn = new Button(); //задаём её координаты btn.Left = 10 + i * 32; btn.Top = 20 + j * 32; btn.Width = 32; btn.Height = 32;
//меняем шрифт кнопки на полужирный Arial размером 12 btn.Font = new Font("Arial", 12, FontStyle.Bold); //добавляем обработчик события Click
btn.Click += new EventHandler(btn_Click); //добавляем кнопку в контейнер grGameBox grGameBox.Controls.Add(btn);
//сохраняем кнопку в массиве btnField btnField[i, j] = btn;
}
}
}
}
Листинг 3.Событие Click кнопки btnNewGame
Шаг 9. Теперь добавим код в событие btn_Click, которое привязано ко всем кнопкам
на нашем игровом поле (листинг 4).
void btn_Click(object sender, EventArgs e)
{
//btn - кнопка на которую кликнули
Button btn = (Button) sender; //если на кнопке пусто...
if (btn.Text == "")
{
//выставляем на кнопке тест, равный ActivePlayer //При вызове метода ToString() элемент перечисления //будет преобразован в своё тестовое представление, //т.е. PlayerType.X будет равен "X"
btn.Text = ActivePlayer.ToString();
//Игрок сделал ход... меняем активного игрока if (ActivePlayer == PlayerType.X)
ActivePlayer = PlayerType.O; else
ActivePlayer = PlayerType.X;
}
}
Листинг 4.Событие Click для всех созданных в коде кнопок(btn_Click).
Шаг 10. Запустите программу, нажав клавишу F5, проверьте её работоспособность.
Шаг 11. Сейчас, при запуске программы не понятно, кто ходит в данный момент. В
коде у нас создана переменная ActivePlayer, однако на интерфейсе эта информация нигде не отображается. Т.е. каждый раз, когда меняется переменная ActivePlayer на интерфейсе формы должно отображаться, какой игрок ходит в данный момент.
Для этого удобно воспользоваться свойствами. Свойства (Properties) представляют собой переменные, обёрнутые специальными методами get и set, вызываемые при получении значения свойства и установки значения свойства.
В листинге 5 показаны изменения, которые нужно внести, чтобы переменная
ActivePlayer стала полноценным свойством.
//Игровое поле
private Button[,] btnField;
//Активный игрок //Внутренняя переменная, хранящая информацию об активном игроке
private PlayerType innerActivePlayer = PlayerType.X; //Свойство ActivePlayer
public PlayerType ActivePlayer
{
//метод, вызываемый при получении свойства ActivePlayer get
{
return innerActivePlayer;
}
//метод, вызываемый при установке свойства ActivePlayer set
{
//value - новое значение свойства (на которое меняем) innerActivePlayer = value;
if (innerActivePlayer == PlayerType.X) lblStatus.Text = "Ходят Крестики...";
else
lblStatus.Text = "Ходят Нолики...";
}
}
//Размер поля CellCount x CellCount private const int CellCount = 3;
Листинг 5. Создание свойства ActivePlayer
Теперь каждый раз, при изменении свойства ActivePlayer, в статусной строке будет отображаться игрок, который ходит в данный момент. Например, если в коде написано ActivePlayer = PlayerType.O;
То при выполнении этой строчки, будет вызван метод set, а value будет равно
PlayerType.O
Шаг 12. При нажатии на кнопку « Новая игра» генерируется пустое поле. После того как поле заполнено – повторное нажатие на кнопку не очищает его. Исправим это,
добавив метод Cleanup, который будет очищать игровое поле не удаляя компоненты
(кнопки). Код метода Cleanup приведён в листинге 6.
void Cleanup()
{
for (int i = 0; i < CellCount; i++)
{
for (int j = 0; j < CellCount; j++)
{
btnField[i, j].Text = "";
}
}
ActivePlayer = PlayerType.X;
}
Листинг 6. Метод Cleanup.
Добавьте вызов метода самостоятельно в процедуре-обработчике события
Click кнопки “Новая игра”.
Шаг 13. Пока что в игре отсутствует самый важный момент – процедура определения победителя. Несложно написать код, который бы проверял победителя для размера игрового поля 3x3. Однако, если размер поля увеличивается (может быть и 50x50) задача существенно усложняется. Необходимо создать такой
алгоритм, который бы работал с полем любого размера.
Предположим, что мы находимся на поле произвольной длины в ячейке с координатами i, j. Необходимо проверить идёт ли от этой ячейки выигрышная
последовательность, например, крестиков.
Т.к. у нас все кнопки занесены в двумерный массив, мы можем обратиться к
соседним элементам по индексу (рисунок 3).
i - 1, j - 1 |
i - 1, j + 0 |
i - 1, j + 1 |
|
|
|
i + 0, j - 1 |
i, j |
i + 0, j + 1 |
|
|
|
i + 1, j - 1 |
i + 1, j + 0 |
i + 1, j + 1 |
|
|
|
Рисунок 3. Игровое поле 3x3. Текущий элемент [i,j] (или [1,1]).
Таким образом, мы можем пойти в одном из 8-ми направлений, и если в одном из них наберётся 3 (или больше) крестика – можно утверждать, что крестики выиграли. Осталось перебрать все элементы двумерного массива (2 цикла), всех игроков (1 цикл), все возможные направления движения (1 или 2 цикла) и выполнить процедуру поиска в заданном направлении. В листинге 6 представлены процедуры поиска победителя. Поиск непрерывной последовательности заданной фигуры
(крестика или нолика) в определённом направлении осуществляет процедура
RecCheckWinner. Она реализована в виде рекурсивной процедуры (можно так же было сделать с помощью цикла).
Функция CheckWinner ищет победителя на игровом поле и возвращает значение типа PlayerType – либо найденного победителя (PlayerType.X или PlayerType.O),
либо PlayerType.None, если победитель не найден.
void RecCheckWinner(int |
i, int j, int goI, |
int goJ, PlayerType type, |
|
ref |
int count) |
|
|
{ |
|
|
|
PlayerType curType; |
|
|
|
//если i или j выходит за границы поля |
- |
выходим из |
|
//процедуры без проверок |
|
|
|
if (i < 0 || i > btnField.GetLength(0) |
- |
1) return; |
|
if (j < 0 || j > btnField.GetLength(1) |
- |
1) return; |
|
//преобразуем текст |
в кнопке в элемент |
перечисления... |
|
//curType - элемент |
в ячейке [i,j] |
|
|
Enum.TryParse(btnField[i, j].Text, true, |
out curType); |
||
//если элемент в ячейке [i,j] совпадает с type if (type == curType)
{
//прибавляем кол-во совпадений count++;
//вызываем рекурсивно эту же функцию, изменяя //текущие координаты на goI и goJ
RecCheckWinner(i + goI, j + goJ, goI, goJ, type, ref count);
}
}
PlayerType CheckWinner()
{
//перебираем все элементы массива btnField (все игровые ячейки) for (int i = 0; i < CellCount; i++)
{
for (int j = 0; j < CellCount; j++)
{
//перебираем всех игроков, начиная с крестиков
for (PlayerType type = PlayerType.X;type <= PlayerType.O; type++)
{
//перебираем все направления движения по оси X (-1, 0, 1) for (int goI = -1; goI <= 1; goI++)
{
//перебираем все направления движения по оси Y (-1, 0, 1) for (int goJ = -1; goJ <= 1; goJ++)
{
//пропускаем вариант, когда goI и goJ равны 0
//т.к. в этом случае мы никуда не двигаемся, а всегда //находимся на текущей ячейке
if (goI == 0 && goJ == 0) continue;
//количество повторяющихся крестиков(или ноликов) //зависит от type (см. выше)
int count = 0;
//вызываем процедуру проверки непрерывной //последовательности фигуры игрока type //от ячейки [i,j] в направлении [goI, goJ]
//после выполнения процедура должна заполнить //переменную count количеством найденных элементов
RecCheckWinner(i, j, goI, goJ, type, ref count); //если кол-во элементов >= кол-ва необходимого //для победы - возвращаем победителя
if (count >= CountToWin) return type;
}
}
}
}
}
//никто пока не победил... возвращаем None return PlayerType.None;
}
Листинг 6. Методы проверки победителя.
Шаг 14. Теперь необходимо вызывать метод CheckWinner, каждый раз после
совершения очередного хода (листинг 7).
btn.Text = ActivePlayer.ToString();
//Проверка на победителя...
PlayerType winner = CheckWinner(); if (winner != PlayerType.None)
{
if (winner == PlayerType.X) MessageBox.Show("Крестики выиграли!","Победа");
else
MessageBox.Show("Нолики выиграли!","Победа"); Cleanup();
return;
}
//Игрок сделал ход... меняем активного игрока if (ActivePlayer == PlayerType.X)
Листинг 7. Изменения в процедуре-обработчике события btn_Click.
Шаг 15. Запустите программу, нажав клавишу F5.
Измените значения констант на CellCount на 15, а CountToWin на 5.
Контрольные вопросы.
1.Как можно создать элемент управления в режиме выполнения программы?
2.Что такое свойства? Приведите примеры.
3.Чем свойства отличаются от переменных.
4.Для чего используют контейнеры (элементы управления)?
Самостоятельное задание 3.1.
Добавьте процедуру, которая будет проверять « ничью».
Добавьте счётчик побед крестиков и ноликов (отображение на интерфейсе с помощью Label-а или другого элемента управления).
Дополнительное задание (+2 балла к общему рейтингу).
Добавьте отображение крестиков\ноликов на кнопках в виде изображения, а не просто символов “X” и “O”. (см. свойство Image у элемента кнопки).
При победе одного из игроков непрерывная линия из знаков “X” или “O”
перечёркивается или меняется фоновый цвет кнопок на зелёный (или любой другой).
