Пользовательский интерфейс Windows Forms для C#
Упражнение 2. Простой калькулятор
Разработаем калькулятор, выполняющий простые действия над целыми и вещественными числами в десятичной форме.
- Поместите на форму элементы из вкладки Common Controls панели Toolbox в соответствии с таблицей, чтобы пользовательский интерфейс формы был таким
Здесь мы использовали некоторые префиксы при назначении имен элементам управления. Для лучшей узнаваемости в коде программы элементов управления и их типа рекомендуется использовать общеупотребительные префиксы, которые приведены в таблице
static void Main() { Application.Run(new Calculate()); }Листинг 25.10. Добавление к форме Calculate точки входа
- Выполните команду меню WinFormsApp Properties и для вкладки Application установите в свойстве Startup Object форму Calculate стартовой
- Защитите интерфейс формы Calculate от случайных изменений, выполнив команду Format/Lock Controls меню оболочки
Теперь нужно реализовать функциональность интерфейса. Прежде всего нужно защитить поля ввода от всех символов, кроме цифр, одного знака минус и одной точки. Стрелки и клавиша Del в текстовых полях функционируют, но работу клавиши Backspace нужно учесть явно.
- Объявите в конце класса Calculate логическое поле isNumeric для отфильтровывания нецифровых символов, которые пользователь может попытаться ввести в текстовые поля
private bool isNumber = false;Листинг 25.11. Добавим логическую переменную isNumeric
- Выделите текстовое поле txtFirst и через панель Properties в режиме Events создайте обработчик для события KeyDown. Заполните обработчик так
private bool isNumber = false; private void txtFirst_KeyDown(object sender, System.Windows.Forms.KeyEventArgs e) { isNumber = e.KeyCode >= Keys.D0 && e.KeyCode <= Keys.D9 // keyboard - основная клавиатура || e.KeyCode >= Keys.NumPad0 && e.KeyCode <= Keys.NumPad9 // keypad - дополнительная клавиатура || e.KeyCode == Keys.Back; }Листинг 25.12. Обработчик события KeyDown для текстовых полей txtFirst и txtSecond
- Выделите текстовое поле txtFirst и через панель Properties в режиме Events создайте обработчик для события KeyPress. Заполните обработчик так
private void txtFirst_KeyPress(object sender, System.Windows.Forms.KeyPressEventArgs e) { TextBox box = (TextBox) sender;// Явное преобразование типов switch(e.KeyChar) // Переключатель { case '-': // Разрешаем минус, если он первый if(box.Text.Length == 0) isNumber = true; break; case '.': // Точка не должна быть первой if(box.Text.Length == 0) break; // Точка не должна следовать сразу за минусом if(box.Text[0] == '-' && box.Text.Length == 1) break; // Точка должна быть одна if(box.Text.IndexOf('.') == -1) isNumber = true; // Еще не было точек break; } // Запрещаем в текстовом поле лишние символы if(!isNumber) e.Handled = true; }Листинг 25.13. Обработчик события KeyPress для полей txtFirst и txtSecond
- Выделите текстовое поле txtSecond и через панель Properties в режиме Events подключите к событиям KeyDown и KeyPress соответствующие обработчики, только что созданные для поля txtFirst. Обработчики подключите через раскрывающиеся списки полей значений
Здесь мы одни и те же обработчики используем для разных полей, поскольку необходимо выполнять одинаковые действия. Задача одна - не допускать для ввода в поля никакие другие символы, кроме цифр, одного минуса и одной точки. Возможность ввода цифр реализована для основной ( keyboard ) и дополнительной ( keypad ) частей клавиатуры. Параметр sender передает в обработчик ссылку на активное текстовое поле, вызвавшее событие редактирования.
Конечно, программа будет более понятна, если для каждого элемента создавать свой обработчик. Но очень часто, как в нашем случае, преобладающая часть кода может быть совершенно одинакова в каждом обработчике. Тогда имеет смысл объединять несколько индивидуальных обработчиков в один. Какой подход выбрать - зависит от конкретной задачи и предпочтений программиста.
- Добавьте в конец класса Calculate объявления числовых переменных, с которыми мы будем производить простые арифметические операции
// Объявляем числовые переменные как поля-члены класса // Их можно объявить как локальные и внутри обработчика btn_Click() // Но оставляем область видимости класс, вдруг где-нибудь еще пригодятся! private double numFirst, numSecond, numResult;Листинг 25.14. Добавление числовых переменных-членов класса Calculate
- Установите в панели Properties режим Events, найдите событие Click и введите вручную в поле значений этого события имя обработчика btn_Click
- В созданный оболочкой общий обработчик для всех выделенных кнопок введите следующий код
// Один обработчик для всех кнопок-операций private void btn_Click(object sender, System.EventArgs e) { // Копируем текстовые поля в локальные переменные string strFirst = string.Copy(txtFirst.Text); string strSecond = string.Copy(txtSecond.Text); // Замена в строке точки символом запятой // для корректного преобразования в число int pos = strFirst.IndexOf('.'); if(pos != -1) { strFirst = strFirst.Substring(0, pos) + ',' + strFirst.Substring(pos + 1); } pos = strSecond.IndexOf('.'); if(pos != -1) { strSecond = strSecond.Substring(0, pos) + ',' + strSecond.Substring(pos + 1); } // Преобразуем текст в число для выполнения операций if(txtFirst.Text.Length > 0) numFirst = Convert.ToDouble(strFirst); else numFirst = 0.0D; if(txtSecond.Text.Length > 0) numSecond = Convert.ToDouble(strSecond); else numSecond = 0.0D; // Выполняем нужную операцию string btnText = "";// Создали строковую переменную bool divideFlag = false;// Флаг деления на ноль Button btn = (Button) sender;// Явное приведение типов для распознавания кнопок switch(btn.Name)// Переключатель { case "btnIncrement":// Операция сложения btnText = "\"+\"";// Экраны кавычек numResult = numFirst + numSecond; break; case "btnDecrement":// Операция вычитания btnText = "\"-\"";// Экраны кавычек numResult = numFirst - numSecond; break; case "btnIncrease":// Операция умножения btnText = "\"*\"";// Экраны кавычек numResult = numFirst * numSecond; break; case "btnDivide":// Операция деления btnText = "\":\"";// Экраны кавычек // Проверяем корректность деления if (Math.Abs(numSecond) < 1.0E-30) { MessageBox.Show( "Делить на ноль нельзя!", // Сообщение "Ошибка", // Заголовок окна MessageBoxButtons.OK, // Кнопка OK MessageBoxIcon.Stop);// Критическая иконка divideFlag = true; } else numResult = numFirst / numSecond; break; } // Для отображения в панели Output режима Debug System.Diagnostics.Debug.WriteLine("Нажата кнопка " + btnText);// Конкатенация // Отображение результата if(!divideFlag) { txtResult.Text = Convert.ToString(numResult); this.Validate(); // Обновить экран (можно убрать-излишне) } }Листинг 25.15. Обработчик нажатия кнопок-операций btn_Click()
Здесь мы прежде всего скопировали значения полей ввода в промежуточные текстовые переменные, используя статический метод System.String. Copy() класса String (псевдоним string ). Затем мы проверяем наличие символа "точки" в этих переменных и при обнаружении производим замену на символ "запятая" с полным формированием новой строки. Мы не можем заменить символ "на месте" в существующей строке, обратившись к нему как элементу массива, поскольку индексная форма адресации в строках существует только для чтения.
Но мы можем, сформировав новую строку справа от знака присваивания, присвоить результат вновь той же самой ссылке, которая фактически будет адресоваться к новой области памяти, а старая строка, как брошенный фрагмент памяти, будет оставлена для сборщика мусора. Операция замены точки на запятую выполняется для того, чтобы корректно преобразовать текстовое значение поля ввода в числовое, иначе генерируется исключение.
Далее мы явно приводим переданную в обработчик ссылку типа object на нажатую кнопку к типу Button. В заголовке переключателя выделяем имя нажатой кнопки и выполняем соответствующую арифметическую операцию. Перед выполнением последней операции мы проверяем возможность возникновения случая " деления на ноль " и при его обнаружении предупреждаем пользователя выводом простого диалогового окна сообщений, отменяя саму операцию деления.
После переключателя мы располагаем оператор вывода строки System.Diagnostics.Debug.WriteLine() с выполненной операцией в панель оболочки Output. Этот класс предназначен для работы с программой в отладочном режиме Debug и автоматически изымается из кода при компиляции окончательного варианта приложения в режиме Release.
На последнем этапе, в коде обработчика, мы выводим результат в соответствующее текстовое поле с предварительным преобразованием типов. Затем мы даем команду системе обновить окно приложения, хотя в данном случае эта мера является излишней - система и так обновляет результат в текстовом поле.
Теперь создадим обработчик нажатия на кнопку закрытия формы.
- В режиме Design двойным щелчком на кнопке btnExit создайте обработчик, который заполните так
private void btnExit_Click(object sender, System.EventArgs e) { this.Close(); }Листинг 25.16. Код закрытия формы калькулятора
- Запустите проект и проверьте функциональность простого калькулятора
Теперь мы должны подключить разработанную форму калькулятора к основному окну приложения.
- Установите в качестве стартовой форму Start. Для этого выполните команду меню оболочки Project/WinFormsApp Properties и в появившемся диалоговом окне установите для вкладки Application свойство Startup Object в значение WinFormsApp.Program
- Откройте файл Start.cs в режиме View Code. Через раскрывающийся список Members в верхней части окна редактора кода найдите обработчик события generalList_SelectedIndexChanged(), который мы создали ранее, и дополните его вызовом окна калькулятора
private void generalList_SelectedIndexChanged(object sender, System.EventArgs e) { switch(generalList.SelectedIndex + 1) { case 1: Smiles frm1 = new Smiles(); frm1.ShowDialog(); break; case 2: Calculate frm2 = new Calculate(); frm2.ShowDialog(); break; } }Листинг 25.17. Код вызова окна калькулятора в модальном режиме
- В панели Solution Explorer оболочки щелкните дважды на пиктограмме файла Start.cs, чтобы открыть его в режиме View Designer, выделите элемент списка в клиентской области формы и через панель Properties добавьте в свойство Items строку " 2) Простой калькулятор "
- Запустите приложение и проверьте его работоспособность