7. Графічні інтерфейси на C# та VS.NET
7.1. Основи графічних інтерфейсів
7.1.1. Перший проєкт
Створимо перший проєкт типу «Windows-додаток»:
![]() |
- [1]: створення нового проєкту
- [2]: типу «Windows-додаток»
- [3]: назва проєкту наразі не має значення
- [4]: проект створено
![]() |
- [5]: збереження поточного рішення
- [6]: назва проєкту
- [7]: папка рішення
- [8]: назва рішення
- [9]: для рішення [Chap5] буде створено папку. Проєкти цього рішення будуть розміщені в підпапках.
![]() |
- [10]: проект [01] у рішенні [Chap5]:
- [Program.cs] — головний клас проекту
- [Form1.cs] — це вихідний файл, який керуватиме поведінкою вікна [11]
- [Form1.Designer.cs] — це вихідний файл, який буде інкапсулювати інформацію про компоненти вікна [11]
- [11]: файл [Form1.cs] у режимі «проектування» (design)
- [12]: згенеровану програму можна запустити за допомогою комбінації клавіш (Ctrl-F5). З’являється вікно [Form1]. Його можна переміщати, змінювати розміри та закривати. Отже, ми маємо основні елементи графічного вікна.
Головний клас [Program.cs] має такий вигляд:
using System;
using System.Windows.Forms;
namespace Chap5 {
static class Program {
/// <summary>
/// Основна точка входу для програми.
/// </summary>
[STAThread]
static void Main() {
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
Application.Run(new Form1());
}
}
}
- рядок 2: додатки з формами використовують простір імен System.Windows.Forms.
- рядок 4: початковий простір імен було перейменовано на Chap5.
- рядок 10: під час виконання проекту (Ctrl-F5) виконується метод [Main].
- рядки 11–13: клас Application належить до простору імен System.Windows.Forms. Він містить статичні методи для запуску та зупинки графічних додатків Windows.
- рядок 11: необов’язковий — дозволяє надавати різні візуальні стилі елементам управління, розміщеним у формі
- рядок 12: необов’язковий — визначає механізм візуалізації тексту елементів управління: GDI+ (true), GDI (false)
- рядок 13: єдиний обов’язковий рядок методу [Main]: створює екземпляр класу [Form1], який є класом форми, та запускає його.
Вихідний файл [Form1.cs] має такий вигляд:
using System;
using System.Windows.Forms;
namespace Chap5 {
public partial class Form1 : Form {
public Form1() {
InitializeComponent();
}
}
}
- рядок 5: клас Form1 походить від класу [System.Windows.Forms.Form], який є базовим класом для всіх вікон. Ключове слово partial вказує, що клас є частковим і може бути доповнений іншими вихідними файлами. Саме так і відбувається в даному випадку, де клас Form1 розподілений між двома файлами:
- [Form1.cs]: у ньому міститься поведінка форми, зокрема її обробники подій
- [Form1.Designer.cs]: у якому містяться компоненти форми та їхні властивості. Особливістю цього файлу є те, що він перегенерується щоразу, коли користувач змінює вікно в режимі [conception].
- рядки 6–8: конструктор класу Form1
- рядок 7: викликає метод InitializeComponent. Бачимо, що цей метод відсутній у [Form1.cs]. Його можна знайти в [Form1.Designer.cs].
Вихідний файл [Form1.Designer.cs] має такий вигляд:
namespace Chap5 {
partial class Form1 {
/// <summary>
/// Необхідна змінна дизайнера.
/// </summary>
private System.ComponentModel.IContainer components = null;
/// <summary>
/// Очистіть усі ресурси, що використовуються.
/// </summary>
/// <param name="disposing">true, якщо керовані ресурси слід звільнити; в іншому разі — false.</param>
protected override void Dispose(bool disposing) {
if (disposing && (components != null)) {
components.Dispose();
}
base.Dispose(disposing);
}
#— код, згенерований Windows Form Designer
/// <summary>
/// Необхідний метод для підтримки Designer — не змінювати
/// вміст цього методу за допомогою редактора коду.
/// </summary>
private void InitializeComponent() {
this.SuspendLayout();
//
// Form1
//
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.ClientSize = new System.Drawing.Size(196, 98);
this.Name = "Form1";
this.Text = "Form1";
this.ResumeLayout(false);
}
#endregion
}
}
- рядок 2: це все ще клас Form1. Зазначимо, що більше не потрібно повторювати, що він походить від класу Form.
- рядки 25–37: метод InitializeComponent, що викликається конструктором класу [Form1]. Цей метод створює та ініціалізує всі компоненти форми. Він перегенерується при кожній зміні форми в режимі [conception]. Для її обмеження створюється розділ із назвою région (рядки 19–39). Розробник не повинен додавати код у цю область: він буде перезаписаний під час наступного перегенерування.
Спочатку простіше не звертати уваги на код [Form1.Designer.cs]. Він генерується автоматично і є перекладом на мову C# тих налаштувань, які розробник вибирає в режимі [conception]. Розглянемо перший приклад:
![]() |
- [1]: виберіть режим [conception], двічі клацнувши на файлі [Form1.cs]
- [2]: клацніть правою кнопкою миші на формі та виберіть [Properties]
- [3]: вікно властивостей [Form1]
- [4]: властивість [Text] відповідає заголовку вікна
- [5]: зміна властивості [Text] враховується в режимі [conception], а також у вихідному коді [Form1.Designer.cs]:
private void InitializeComponent() {
this.SuspendLayout();
...
this.Text = "Mon 1er formulaire";
...
}
7.1.2. Другий проєкт
7.1.2.1. Форма
Ми розпочинаємо новий проєкт під назвою 02. Для цього дотримуємося описаної раніше процедури створення проєкту. Створюється таке вікно:
![]() |
Форма складається з таких компонентів:
№ | назва | тип | роль |
1 | labelSaisie | Етикетка | назва |
2 | textBoxSaisie | TextBox | поле для введення даних |
3 | buttonAfficher | Кнопка | для відображення вмісту поля введення у діалоговому вікні textBoxSaisie |
Для створення цього вікна можна вчинити наступним чином:
![]() |
- [1]: клацнути правою кнопкою миші на формі поза межами будь-якого компонента та вибрати опцію [Properties]
- [2]: у правому нижньому куті Visual Studio з’явиться вікно властивостей
Серед властивостей форми варто звернути увагу на:
для встановлення кольору фону вікна | |
для встановлення кольору малюнків або тексту у вікні | |
для прив’язки меню до вікна | |
щоб надати вікну заголовок | |
для визначення типу вікна | |
щоб встановити шрифт тексту у вікні | |
щоб задати назву вікна |
Тут ми встановлюємо властивості Text та Name:
Поля введення та кнопки — 1 | |
frmSaisiesBoutons |
![]() |
- [1]: виберіть набір інструментів [Common Controls] серед наборів інструментів, запропонованих Visual Studio
- [2, 3, 4]: послідовно двічі клацнути на компонентах [Label], [Button] та [TextBox]
- [5]: три компоненти розміщені на формі
Щоб правильно вирівняти та змінити розміри компонентів, можна скористатися елементами панелі інструментів:
![]() | |||||||||
Принцип форматування такий:
- виберіть різні елементи, які потрібно відформатувати разом (утримуйте клавішу Ctrl під час кліків для виділення елементів)
- виберіть бажаний тип форматування:
- (продовження)
- опції Align дозволяють вирівнювати компоненти по верхньому, нижньому, лівому або правому краю, а також по центру
- параметри «Make Same Size» дозволяють задати однакову висоту або ширину для компонентів
- опція «Horizontal Spacing» дозволяє вирівнювати компоненти по горизонталі з рівними проміжками між ними. Те саме стосується опції «Vertical Spacing» для вирівнювання по вертикалі.
- опція Center дозволяє центрувати компонент по горизонталі (Horizontally) або по вертикалі (Vertically) у вікні
Після розміщення компонентів ми встановлюємо їхні властивості. Для цього клацніть правою кнопкою миші на компоненті та виберіть опцію Properties:
![]() |
- [1]: виберіть компонент, щоб відкрити вікно його властивостей. У ньому змініть такі властивості: name: labelSaisie, text: Saisie
- [2]: виконайте ті самі дії: name: textBoxSaisie, text: залиште порожнім
- [3]: name: buttonAfficher, text: Afficher
- [4]: саме вікно: name: frmSaisiesBoutons, text: Поля введення та кнопки — 1
- [5]: запустіть (Ctrl-F5) проект, щоб отримати перше уявлення про роботу вікна.
Те, що було зроблено в режимі [conception], було перенесено в код [Form1.Designer.cs]:
namespace Chap5 {
partial class frmSaisiesBoutons {
...
private System.ComponentModel.IContainer components = null;
...
private void InitializeComponent() {
this.labelSaisie = new System.Windows.Forms.Label();
this.buttonAfficher = new System.Windows.Forms.Button();
this.textBoxSaisie = new System.Windows.Forms.TextBox();
this.SuspendLayout();
//
// labelSaisie
//
this.labelSaisie.AutoSize = true;
this.labelSaisie.Location = new System.Drawing.Point(12, 19);
this.labelSaisie.Name = "labelSaisie";
this.labelSaisie.Size = new System.Drawing.Size(35, 13);
this.labelSaisie.TabIndex = 0;
this.labelSaisie.Text = "Saisie";
//
// buttonAfficher
//
this.buttonAfficher.Location = new System.Drawing.Point(80, 49);
this.buttonAfficher.Name = "buttonAfficher";
this.buttonAfficher.Size = new System.Drawing.Size(75, 23);
this.buttonAfficher.TabIndex = 1;
this.buttonAfficher.Text = "Afficher";
this.buttonAfficher.UseVisualStyleBackColor = true;
this.buttonAfficher.Click += new System.EventHandler(this.buttonAfficher_Click);
//
// textBoxSaisie
//
this.textBoxSaisie.Location = new System.Drawing.Point(80, 19);
this.textBoxSaisie.Name = "textBoxSaisie";
this.textBoxSaisie.Size = new System.Drawing.Size(100, 20);
this.textBoxSaisie.TabIndex = 2;
//
// frmSaisiesBoutons
//
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.ClientSize = new System.Drawing.Size(292, 118);
this.Controls.Add(this.textBoxSaisie);
this.Controls.Add(this.buttonAfficher);
this.Controls.Add(this.labelSaisie);
this.Name = "frmSaisiesBoutons";
this.Text = "Saisies et boutons - 1";
this.ResumeLayout(false);
this.PerformLayout();
}
private System.Windows.Forms.Label labelSaisie;
private System.Windows.Forms.Button buttonAfficher;
private System.Windows.Forms.TextBox textBoxSaisie;
}
}
- рядки 53–55: три компоненти дали початок трьом приватним полям класу [Form1]. Зазначимо, що імена цих полів відповідають іменам, наданим компонентам у режимі [conception]. Це також стосується форми у рядку 2, яка є самим класом.
- рядки 7–9: створюються три об’єкти типів [Label], [TextBox] та [Button]. Саме через них здійснюється управління візуальними компонентами.
- рядки 14–19: налаштування мітки labelSaisie
- рядки 23–29: налаштування кнопки buttonAfficher
- рядки 33–36: налаштування поля введення textBoxSaisie
- рядки 40–47: налаштування форми frmSaisiesBoutons. Зверніть увагу на рядки 43–45, де показано, як додавати компоненти до форми.
Цей код є зрозумілим. Таким чином можна створювати форми за допомогою коду без використання режиму [conception]. Численні приклади цього наведено в документації MSDN до Visual Studio. Володіння цим кодом дозволяє створювати форми під час виконання: наприклад, миттєво створити форму, що дозволяє оновлювати таблицю бази даних, структура якої стає відомою лише під час виконання.
Залишилося написати процедуру обробки кліка на кнопці Afficher. Виберіть кнопку, щоб відкрити вікно її властивостей. Воно має кілька вкладок:
![]() |
- [1]: список властивостей у алфавітному порядку
- [2]: події, пов’язані з елементом управління
До властивостей та подій елемента управління можна отримати доступ за категоріями або в алфавітному порядку:
- [3]: Властивості або події за категоріями
- [4]: Властивості або події в алфавітному порядку
Вкладка Events у режимі Catégories для кнопки buttonAfficher виглядає наступним чином:
![]() |
- [1]: у лівій колонці вікна перелічено можливі події для кнопки. Клацання на кнопці відповідає події Click.
- [2]: у правій колонці вказано назву процедури, яка викликається при виникненні відповідної події.
- [3]: якщо двічі клацнути на комірці події Click, то автоматично відкриється вікно коду для написання обробника події Click для кнопки buttonAfficher:
using System;
using System.Windows.Forms;
namespace Chap5 {
public partial class frmSaisiesBoutons : Form {
public frmSaisiesBoutons() {
InitializeComponent();
}
private void buttonAfficher_Click(object sender, EventArgs e) {
}
}
}
- рядки 10–12: каркас обробника події Click для кнопки з назвою buttonAfficher. Зверніть увагу на наступні моменти:
- метод названо за схемою nomDuComposant_NomEvénement
- метод є приватним. Він приймає два параметри:
- sender — це об’єкт, який викликав подію. Якщо процедура виконується внаслідок натискання кнопки buttonAfficher, то sender дорівнюватиме buttonAfficher. Можна уявити, що процедура buttonAfficher_Click виконується з іншої процедури. У такому разі ця процедура матиме повну свободу встановити як перший параметр об’єкт sender на свій вибір.
- EventArgs — об’єкт, що містить інформацію про подію. Для події Click він не містить нічого. Для події, пов’язаної з переміщенням миші, у ньому містяться координати (X, Y) миші.
- Тут ми не будемо використовувати жоден із цих параметрів.
Написання обробника події полягає у заповненні попереднього шаблону коду. Тут ми хочемо відобразити діалогове вікно, у якому буде вміст поля textBoxSaisie, якщо воно не порожнє ([1]), або повідомлення про помилку в іншому випадку ([2]):
![]() |
Код, що реалізує це, може виглядати так:
private void buttonAfficher_Click(object sender, EventArgs e) {
// відображається текст, який було введено в TextBox textboxSaisie
string texte = textBoxSaisie.Text.Trim();
if (texte.Length != 0) {
MessageBox.Show("Texte saisi= " + texte, "Vérification de la saisie", MessageBoxButtons.OK, MessageBoxIcon.Information);
} else {
MessageBox.Show("Saissez un texte...", "Vérification de la saisie", MessageBoxButtons.OK, MessageBoxIcon.Error);
}
Клас MessageBox призначений для виведення повідомлень у вікні. Тут ми використали такий метод Show:
public static DialogResult Show(string text, string caption, MessageBoxButtons buttons, MessageBoxIcon icon);
разом із
повідомленням, яке потрібно відобразити | |
заголовок вікна | |
кнопки у вікні | |
піктограма у вікні |
Параметр buttons може приймати значення з-поміж таких констант (з префіксом MessageBoxButtons, як показано у рядку 7) вище:
кнопки | |
![]() | |
![]() | |
![]() | |
![]() | |
![]() | |
![]() |
Параметр icon може приймати значення з-поміж таких констант (з префіксом MessageBoxIcon, як показано у рядку 10) вище:
![]() | те саме, зупинка | ||
те саме Попередження | ![]() | ||
так само, як Asterisk | ![]() | ||
![]() | те саме Hand | ||
![]() |
Метод Show — це статичний метод, який повертає результат типу [System.Windows.Forms.DialogResult], що є переліком:

Щоб дізнатися, яку кнопку натиснув користувач для закриття вікна типу MessageBox, слід написати:
7.1.2.2. Код, пов'язаний з обробкою подій
Окрім функції buttonAfficher_Click, яку ми написали, Visual Studio згенерував у методі InitializeComponents класу [Form1.Designer.cs], який створює та ініціалізує компоненти форми, такий рядок:
this.buttonAfficher.Click += new System.EventHandler(this.buttonAfficher_Click);
Click — це подія класу Button [1, 2, 3]:
![]() |
- [5]: оголошення події [Control.Click] [4]. Отже, бачимо, що подія Click не є власною для класу [Button]. Вона належить класу [Control], батьківському класу класу [Button].
- EventHandler — це прототип (модель) методу, який називається делегатом. Ми ще повернемося до цього.
- event — це ключове слово, яке обмежує функціональні можливості delegate та EventHandler: об’єкт delegate має ширші можливості, ніж об’єкт event.
delegate EventHandler визначається наступним чином:
![]() |
delegate EventHandler позначає шаблон методу:
- перший параметр якої має тип Object
- другий параметр якого має тип EventArgs
- який не повертає жодного результату
Це стосується методу обробки кліка на кнопці buttonAfficher, який було згенеровано Visual Studio:
private void buttonAfficher_Click(object sender, EventArgs e);
Отже, метод buttonAfficher_Click відповідає прототипу, визначеному типом EventHandler. Щоб створити об’єкт типу EventHandler, слід виконати такі дії:
EventHandler evtHandler=new EventHandler(méthode correspondant au prototype défini par le type EventHandler);
Оскільки метод buttonAfficher_Click відповідає прототипу, визначеному типом EventHandler, можна записати:
Змінна типу delegate насправді є списком посилань на методи типу delegate. Щоб додати новий метод M до наведеної вище змінної evtHandler, слід використати такий синтаксис:
Запис += можна використовувати навіть тоді, коли evtHandler є порожнім списком.
Повернемося до рядка [InitializeComponent], який додає обробник події до події Click об’єкта buttonAfficher:
this.buttonAfficher.Click += new System.EventHandler(this.buttonAfficher_Click);
Ця інструкція додає метод типу EventHandler до списку методів поля buttonAfficher.Click. Ці методи викликатимуться щоразу, коли буде виявлено подію Click на компоненті buttonAfficher. Зазвичай їх буває лише один. Його називають «обробником події».
Повернемося до сигнатури EventHandler:
private delegate void EventHandler(object sender, EventArgs e);
Другий параметр delegate — це об’єкт типу EventArgs або похідного класу. Тип EventArgs є дуже загальним і фактично не надає жодної інформації про подію, що відбулася. Для кліка на кнопку цього достатньо. Для переміщення миші по формі ми б отримали подію MouseMove класу [Form], визначену таким чином:
delegate MouseEventHandler визначено як:
![]() |
Це делегована функція (delegate) з сигнатурою void f (object, MouseEventArgs). Клас MouseEventArgs визначено як:
![]() |
Клас MouseEventArgs має більше можливостей, ніж клас EventArgs. Наприклад, можна дізнатися координати миші X та Y у момент виникнення події.
7.1.2.3. Conclusion
На основі двох розглянутих проектів можна зробити висновок, що після створення графічного інтерфейсу за допомогою Visual Studio робота розробника полягає головним чином у написанні обробників подій, які він хоче обробляти для цього графічного інтерфейсу. Код генерується автоматично Visual Studio. Цей код, який може бути складним, на першому етапі можна проігнорувати. Згодом його вивчення може сприяти кращому розумінню створення та управління формами.
7.2. Основні компоненти
Тепер ми розглянемо різні програми, в яких використовуються найпоширеніші компоненти, щоб ознайомитися з їхніми основними методами та властивостями. Для кожної програми ми покажемо графічний інтерфейс та відповідний код, насамперед код обробників подій.
7.2.1. Форма Form
Почнемо з огляду найнеобхіднішого компонента — форми, на яку розміщуються інші компоненти. Ми вже розглядали деякі з її основних властивостей. Тут ми зупинимося на кількох важливих подіях форми.
форма завантажується | |
форма закривається | |
форма закрита |
Подія Load відбувається ще до того, як форма з'явиться на екрані. Подія Closing відбувається під час закриття форми. Це закриття ще можна зупинити програмним шляхом.
Ми створюємо форму з назвою Form1 без компонентів:
![]() |
- [1]: форма
- [2]: три оброблені події
Код [Form1.cs] такий:
using System;
using System.Windows.Forms;
namespace Chap5 {
public partial class Form1 : Form {
public Form1() {
InitializeComponent();
}
private void Form1_Load(object sender, EventArgs e) {
// початкове завантаження форми
MessageBox.Show("Evt Load", "Load");
}
private void Form1_FormClosing(object sender, FormClosingEventArgs e) {
// форма закривається
MessageBox.Show("Evt FormClosing", "FormClosing");
// запитується підтвердження
DialogResult réponse = MessageBox.Show("Voulez-vous vraiment quitter l'application", "Closing", MessageBoxButtons.YesNo, MessageBoxIcon.Question);
if (réponse == DialogResult.No)
e.Cancel = true;
}
private void Form1_FormClosed(object sender, FormClosedEventArgs e) {
// форма буде закрита
MessageBox.Show("Evt FormClosed", "FormClosed");
}
}
}
Ми використовуємо функцію MessageBox для отримання сповіщень про різні події.
рядок 10: Подія Load відбудеться під час запуску запуску програми, ще до того, як з’явиться форма: | ![]() |
рядок 15: Подія FormClosing відбудеться, коли користувач закриє вікно. | ![]() |
рядок 19: Тоді ми запитуємо його, чи він дійсно хоче вийти програму: | ![]() |
рядок 20: Якщо він відповідає «Ні», ми встановлюємо властивість Cancel події CancelEventArgs, яку метод отримав у параметр. Якщо ми встановимо для цього властивості значення False, закриття вікна скасовується, інакше воно продовжується. Тоді відбудеться подія FormClosed: | ![]() |
7.2.2. Етикетки та поля введення TextBox
Ми вже зустрічали ці два компоненти. Label — це текстовий компонент, а TextBox — компонент поля введення. Їхньою основною властивістю є Text, яка позначає або вміст поля введення, або текст мітки. Ця властивість доступна для читання та запису.
Подія, яка зазвичай використовується для TextBox, — це TextChanged, яка сигналізує, що користувач змінив поле введення. Ось приклад, у якому використовується подія TextChanged для відстеження змін у полі введення:
![]() |
№ | тип | назва | роль |
1 | TextBox | textBoxSaisie | поле введення |
2 | Етикетка | labelControle | відображає текст з 1 у режимі реального часу AutoSize=False, Text=(порожньо) |
3 | Кнопка | buttonEffacer | для очищення полів 1 і 2 |
4 | Кнопка | buttonQuitter | для виходу з програми |
Код цього додатка такий:
using System.Windows.Forms;
namespace Chap5 {
public partial class Form1 : Form {
public Form1() {
InitializeComponent();
}
private void textBoxSaisie_TextChanged(object sender, System.EventArgs e) {
// вміст TextBox змінився — його копіюють у мітку labelControle
labelControle.Text = textBoxSaisie.Text;
}
private void buttonEffacer_Click(object sender, System.EventArgs e) {
// вміст поля введення очищено
textBoxSaisie.Text = "";
}
private void buttonQuitter_Click(object sender, System.EventArgs e) {
// натискання кнопки «Вийти» — виходимо з програми
Application.Exit();
}
private void Form1_Shown(object sender, System.EventArgs e) {
// переносимо фокус на поле введення
textBoxSaisie.Focus();
}
}
}
- рядок 24: подія [Form].Shown відбувається, коли форму відображають
- рядок 26: після цього фокус (для введення даних) встановлюється на компонент textBoxSaisie.
- рядок 9: подія [TextBox].TextChanged відбувається щоразу, коли змінюється вміст компонента TextBox
- рядок 11: вміст компонента [TextBox] копіюється в компонент [Label]
- рядок 14: обробляє натискання кнопки [Effacer]
- рядок 16: в компонент [TextBox] вставляється порожній рядок
- рядок 19: обробляє натискання кнопки [Quitter]
- рядок 21: для зупинки програми, що виконується. Нагадаємо, що об’єкт Application використовується для запуску програми в методі [Main] класу [Form1.cs]:
static void Main() {
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
Application.Run(new Form1());
}
У наступному прикладі використовується багаторядковий TextBox:
![]() |
Список елементів управління такий:
№ | тип | назва | роль |
1 | TextBox | textBoxLignes | багаторядкове поле введення Multiline=true, ScrollBars=Both, AcceptReturn=True, AcceptTab=True |
2 | TextBox | textBoxLigne | однорядкове поле введення |
3 | Кнопка | buttonAjouter | Додає вміст з 2 до 1 |
Щоб TextBox став багаторядковим, слід встановити такі властивості елемента управління:
для підтримки декількох рядків тексту | |
щоб задати наявність (Horizontal, Vertical, Both) або відсутність (None) смуг прокрутки в елементі управління | |
якщо значення true, клавіша Enter перейде на новий рядок | |
якщо значення true, клавіша Tab створюватиме табуляцію в тексті |
Додаток дозволяє вводити рядки безпосередньо в [1] або додавати їх за допомогою [2] та [3].
Код програми такий:
using System.Windows.Forms;
using System;
namespace Chap5 {
public partial class Form1 : Form {
public Form1() {
InitializeComponent();
}
private void buttonAjouter_Click(object sender, System.EventArgs e) {
// додавання вмісту textBoxLigne до вмісту textBoxLignes
textBoxLignes.Text += textBoxLigne.Text+Environment.NewLine;
textBoxLigne.Text = "";
}
private void Form1_Shown(object sender, EventArgs e) {
// фокус встановлюється на полі введення
textBoxLigne.Focus();
}
}
}
- рядок 18: коли форму відображено (можливо, Shown), фокус встановлюється на поле введення textBoxLigne
- рядок 10: обробляє натискання кнопки [Ajouter]
- рядок 12: текст поля введення textBoxLigne додається до тексту поля введення textBoxLignes, після чого ставиться перенос рядка.
- рядок 13: поле введення textBoxLigne очищується
7.2.3. Списки, що розгортаються ComboBox
Ми створюємо таку форму:
![]() |
№ | тип | назва | роль |
1 | ComboBox | comboNombres | містить символьні рядки DropDownStyle=DropDownList |
Компонент ComboBox — це випадаючий список, доповнений полем введення: користувач може або вибрати елемент у (2), або ввести текст у (1). Існує три типи ComboBox, що визначаються властивістю DropDownStyle:
нерозгортальний список із полем редагування | |
розгортальний список із полем редагування | |
розгортальний список без поля редагування |
За замовчуванням тип ComboBox — це DropDown.
Клас ComboBox має єдиний конструктор:
створює порожній комбінований список |
Елементи ComboBox доступні у властивості Items:
Це індексоване властивість, де Items[i] позначає елемент i комбінованого списку. Воно доступне лише для читання.
Нехай C — це комбо-поле, а C.Items — його список елементів. Ми маємо такі властивості:
кількість елементів комбо-поля | |
елемент i комбінованого списку | |
додає об’єкт o як останній елемент комбінації | |
додає масив об’єктів у кінець комбінації | |
додає об’єкт o на позицію i комбо | |
видаляє елемент i зі списку | |
видаляє об’єкт o зі списку | |
видаляє всі елементи з комбінації | |
повертає позицію i об’єкта o у комбінованому списку | |
індекс вибраного елемента | |
вибраний елемент | |
текст, що відображається для вибраного елемента | |
текст, що відображається для вибраного елемента |
Може здатися дивним, що комбо-поле може містити об'єкти, хоча візуально воно відображає символьні рядки. Якщо ComboBox містить об'єкт obj, воно відображає рядок obj.ToString(). Нагадаємо, що кожен об’єкт має метод ToString, успадкований від класу object, який повертає символьний рядок, що «представляє» цей об’єкт.
Елемент Item, вибраний у комбінованому списку C, — це C.SelectedItem або C.Items[C.SelectedIndex], де C.SelectedIndex — це номер вибраного елемента, причому цей номер починається з нуля для першого елемента. Вибраний текст можна отримати різними способами: C.SelectedItem.Text, C.Text
Під час вибору елемента у випадаючому списку відбувається подія SelectedIndexChanged, яку можна використовувати для отримання повідомлення про зміну вибору у комбінованому списку. У наступному додатку ми використовуємо цю подію для відображення елемента, який було вибрано зі списку.
![]() |
Код програми такий:
using System.Windows.Forms;
namespace Chap5 {
public partial class Form1 : Form {
private int previousSelectedIndex=0;
public Form1() {
InitializeComponent();
// заповнення комбінованого списку
comboBoxNombres.Items.AddRange(new string[] { "zéro", "un", "deux", "trois", "quatre" });
// вибір елемента № 0
comboBoxNombres.SelectedIndex = 0;
}
private void comboBoxNombres_SelectedIndexChanged(object sender, System.EventArgs e) {
int newSelectedIndex = comboBoxNombres.SelectedIndex;
if (newSelectedIndex != previousSelectedIndex) {
// вибраний елемент змінився — відображаємо його
MessageBox.Show(string.Format("Elément sélectionné : ({0},{1})", comboBoxNombres.Text, newSelectedIndex), "Combo", MessageBoxButtons.OK, MessageBoxIcon.Information);
// записуємо новий індекс
previousSelectedIndex = newSelectedIndex;
}
}
}
}
- рядок 5: previousSelectedIndex запам'ятовує останній індекс, вибраний у комбінованому списку
- рядок 10: заповнення комбінованого списку масивом рядків
- рядок 12: вибирається перший елемент
- рядок 15: метод, що виконується щоразу, коли користувач вибирає елемент зі списку. На відміну від того, що може наводити на думку назва події, вона відбувається навіть тоді, коли вибраний елемент збігається з попереднім.
- рядок 16: записується індекс вибраного елемента
- рядок 17: якщо він відрізняється від попереднього
- рядок 19: виводимо номер та текст вибраного елемента
- рядок 21: записується новий індекс
7.2.4. Компонент ListBox
Пропонується створити такий інтерфейс:
![]() |
Компоненти цього вікна такі:
№ | тип | назва | роль/властивості |
0 | Форма | Form1 | форма FormBorderStyle=FixedSingle (рамка, розмір якої не можна змінювати) |
1 | TextBox | textBoxSaisie | поле введення |
2 | Кнопка | buttonAjouter | кнопка, що дозволяє додати вміст поля введення [1] до списку [3] |
3 | ListBox | listBox1 | список 1 SelectionMode=MultiExtended: |
4 | ListBox | listBox2 | список 2 SelectionMode=MultiSimple: |
5 | Кнопка | button1vers2 | переносить вибрані елементи зі списку 1 до списку 2 |
6 | Кнопка | button2vers1 | виконує зворотну дію |
7 | Button | buttonEffacer1 | очищає список 1 |
8 | Кнопка | buttonEffacer2 | очистити список 2 |
Компоненти ListBox мають режим вибору своїх елементів, який визначається їхньою властивістю SelectionMode:
можна вибрати лише один елемент | |
можливий множинний вибір: утримуючи натиснутою клавішу SHIFT і клацнувши на елементі, вибір розширюється з попередньо вибраного елемента на поточний. | |
можливий множинний вибір: елемент вибирається/скасовується одним клацанням миші або натисканням пробілу. |
- Користувач вводить текст у поле 1. Він додає його до списку 1 за допомогою кнопки Ajouter (2). Після цього поле введення (1) очищується, і користувач може додати новий елемент.
- Він може переносити елементи з одного списку в інший, вибравши елемент для перенесення в одному зі списків та натиснувши відповідну кнопку перенесення 5 або 6. Перенесений елемент додається в кінець списку призначення та видаляється зі списку-джерела.
- Користувач може двічі клацнути на елементі зі списку 1. Цей елемент переноситься у поле введення для редагування та видаляється зі списку 1.
Кнопки вмикаються або вимикаються за такими правилами:
- кнопка Ajouter підсвічується лише тоді, коли в полі введення є текст, що не є порожнім
- кнопка [5] для перенесення зі списку 1 до списку 2 підсвічується лише тоді, коли у списку 1 вибрано елемент
- кнопка [6] для перенесення зі списку 2 до списку 1 світиться лише тоді, коли у списку 2 вибрано елемент
- кнопки [7] та [8] для очищення списків 1 і 2 підсвічуються лише тоді, коли список, який потрібно очистити, містить елементи.
За зазначених умов усі кнопки мають бути вимкнені під час запуску програми. Тому властивість Enabled цих кнопок слід встановити на значення false. Це можна зробити на етапі проектування, що призведе до генерації відповідного коду в методі InitializeComponent, або зробити самостійно у конструкторі, як показано нижче:
public Form1() {
InitializeComponent();
// --- додаткові ініціалізації ---
// деякі кнопки вимкнено
buttonAjouter.Enabled = false;
button1vers2.Enabled = false;
button2vers1.Enabled = false;
buttonEffacer1.Enabled = false;
buttonEffacer2.Enabled = false;
}
Стан кнопки Ajouter контролюється вмістом поля введення. Подія TextChanged дозволяє відстежувати зміни цього вмісту:
private void textBoxSaisie_TextChanged(object sender, System.EventArgs e) {
// зміст textBoxSaisie змінився
// кнопка «Додати» підсвічується лише тоді, коли поле введення не порожнє
buttonAjouter.Enabled = textBoxSaisie.Text.Trim() != "";
}
Стан кнопок перенесення залежить від того, чи було вибрано елемент у списку, який вони контролюють:
private void listBox1_SelectedIndexChanged(object sender, System.EventArgs e) {
// елемент було вибрано
// вмикається кнопка перенесення з 1 до 2
button1vers2.Enabled = true;
}
private void listBox2_SelectedIndexChanged(object sender, System.EventArgs e) {
// елемент було вибрано
// вмикається кнопка перенесення з 2 до 1
button2vers1.Enabled = true;
}
Код, пов’язаний із натисканням кнопки Ajouter, є таким:
private void buttonAjouter_Click(object sender, System.EventArgs e) {
// додано новий елемент до списку 1
listBox1.Items.Add(textBoxSaisie.Text.Trim());
// очищення введених даних
textBoxSaisie.Text = "";
// Список 1 не порожній
buttonEffacer1.Enabled = true;
// фокус повертається до поля введення
textBoxSaisie.Focus();
}
Зверніть увагу на метод Focus, який дозволяє встановити «фокус» на елементі управління форми. Код, пов'язаний з натисканням на кнопки Effacer:
private void buttonEffacer1_Click(object sender, System.EventArgs e) {
// список 1 очищено
listBox1.Items.Clear();
// кнопка «Очистити»
buttonEffacer1.Enabled = false;
}
private void buttonEffacer2_Click(object sender, System.EventArgs e) {
// список 2 очищено
listBox2.Items.Clear();
// кнопка «Очистити»
buttonEffacer2.Enabled = false;
}
Код для перенесення вибраних елементів з одного списку в інший:
private void button1vers2_Click(object sender, System.EventArgs e) {
// перенесення вибраного елемента зі списку 1 до списку 2
transfert(listBox1, button1vers2, buttonEffacer1, listBox2, button2vers1, buttonEffacer2);
}
private void button2vers1_Click(object sender, System.EventArgs e) {
// перенесення вибраного елемента зі списку 2 до списку 1
transfert(listBox2, button2vers1, buttonEffacer2, listBox1, button1vers2, buttonEffacer1);
}
Обидва наведені вище методи делегують перенесення вибраних елементів з одного списку в інший одному й тому ж приватному методу під назвою «transfer»:
// перенесення
private void transfert(ListBox l1, Button button1vers2, Button buttonEffacer1, ListBox l2, Button button2vers1, Button buttonEffacer2) {
// перенесення у список l2 вибраних елементів зі списку l1
for (int i = l1.SelectedIndices.Count - 1; i >= 0; i--) {
// індекс вибраного елемента
int index = l1.SelectedIndices[i];
// додавання до l2
l2.Items.Add(l1.Items[index]);
// видалення зі списку l1
l1.Items.RemoveAt(index);
}
// кнопки «Очистити»
buttonEffacer2.Enabled = l2.Items.Count != 0;
buttonEffacer1.Enabled = l1.Items.Count != 0;
// кнопки перенесення
button1vers2.Enabled = false;
}
- рядок b: метод «transfert» приймає шість параметрів:
- посилання на список, що містить вибрані елементи, який тут називається l1. Під час виконання програми l1 може бути або listBox1, або listBox2. Приклади викликів наведено у рядках 3 та 8 процедури «перенесення» buttonXversY_Click.
- посилання на кнопку перенесення, пов’язану зі списком l1. Наприклад, якщо l1 дорівнює listBox2, то це буде button2vers1 (див. виклик у рядку 8)
- посилання на кнопку очищення списку l1. Наприклад, якщо l1 дорівнює listBox1, то це буде buttonEffacer1 (див. виклик у рядку 3)
- три інші посилання аналогічні, але стосуються списку l2.
- рядок d: колекція [ListBox].SelectedIndices містить індекси елементів, вибраних у компоненті [ListBox]. Це колекція:
- [ListBox].SelectedIndices.Count — кількість елементів у цій колекції
- [ListBox].SelectedIndices[i] — це елемент № i цієї колекції
Ми переглядаємо колекцію у зворотному напрямку: починаємо з кінця колекції, а закінчуємо на початку. Ми пояснимо, чому.
- рядок f: індекс вибраного елемента зі списку l1
- рядок h: цей елемент додається до списку l2
- рядок j: і видаляється зі списку l1. Оскільки він видалений, він більше не є вибраним. Колекція l1.SelectedIndices з рядка d буде перерахована. Вона втратить елемент, який щойно було видалено. У всіх елементів, що йдуть після нього, номер зміниться з n на n-1.
- якщо цикл у рядку (d) є зростаючим і щойно обробив елемент № 0, то далі він обробить елемент № 1. Однак елемент, який мав номер 1 до видалення елемента № 0, тепер матиме номер 0. Тож цикл його пропустить.
- якщо цикл у рядку (d) є спадаючим і щойно обробив елемент № n, то наступним він обробить елемент № n-1. Після видалення елемента № n номер елемента № n-1 не змінюється. Тому він обробляється в наступному циклі.
- рядки m–n: стан кнопок [Effacer] залежить від наявності або відсутності елементів у відповідних списках
- рядок p: у списку l2 більше немає вибраних елементів: кнопку перенесення вимикають.
7.2.5. Позначки CheckBox, перемикачі ButtonRadio
Ми пропонуємо написати таку програму:
![]() |
Компоненти вікна такі:
№ | тип | назва | роль |
1 | GroupBox див. [6] | groupBox1 | контейнер компонентів. У нього можна додавати інші компоненти. Text=Радіо-кнопки |
2 | RadioButton | radioButton1 radioButton2 radioButton3 | 3 перемикачі — radioButton1 має властивість Checked=True та властивість Text=1 - radioButton2 має властивість Text=2 - radioButton3 має властивість Text=3 Радіо-кнопки, що знаходяться в одному контейнері (у даному випадку GroupBox), є взаємовиключними: одночасно може бути активована лише одна з них. |
3 | GroupBox | groupBox2 | |
4 | CheckBox | checkBox1 checkBox2 checkBox3 | 3 прапорці. chechBox1 має властивість Checked=True та властивість Text=A - chechBox2 має властивість Text=B - chechBox3 має властивість Text=C |
5 | ListBox | listBoxValeurs | список, який відображає значення перемикачів та прапорців одразу після будь-якої зміни. |
6 | показує, де знайти контейнер GroupBox |
Подія, яка нас цікавить для цих шести елементів управління, — це подія CheckChanged, яка вказує на зміну стану прапорця або перемикача. Цей стан у обох випадках представлений булевою властивістю Checked, яка, якщо має значення «true», означає, що елемент управління позначено. Тут ми будемо використовувати лише один метод для обробки шести подій CheckChanged — метод affiche. Щоб усі шість подій CheckChanged оброблялися одним і тим самим методом affiche, можна вчинити так:
Виберіть компонент radioButton1 і клацніть на ньому правою кнопкою миші, щоб відкрити його властивості:
![]() |
На вкладці événements [1] прив’язуємо метод affiche [2] до події CheckChanged. Це означає, що ми хочемо, щоб клік на опцію A1 оброблявся методом із назвою affiche. Visual Studio автоматично генерує метод affiche у вікні коду:
private void affiche(object sender, EventArgs e) {
}
Метод affiche є методом типу EventHandler.
З іншими п’ятьма компонентами діють аналогічно. Виберемо, наприклад, опцію CheckBox1 та її події [3]. Навпроти події Click розташований випадаючий список [4], у якому містяться існуючі методи, здатні обробляти цю подію. Тут є лише метод affiche.. Вибираємо його. Повторюємо цей процес для всіх інших компонентів.
У методі InitializeComponent було згенеровано код. Метод affiche було оголошено як обробник шести подій CheckedChanged наступним чином:
this.radioButton1.CheckedChanged += new System.EventHandler(this.affiche);
this.radioButton2.CheckedChanged += new System.EventHandler(this.affiche);
this.radioButton3.CheckedChanged += new System.EventHandler(this.affiche);
this.checkBox1.CheckedChanged += new System.EventHandler(this.affiche);
this.checkBox2.CheckedChanged += new System.EventHandler(this.affiche);
this.checkBox3.CheckedChanged += new System.EventHandler(this.affiche);
Метод affiche доповнено наступним чином:
private void affiche(object sender, System.EventArgs e) {
// відображає стан перемикача або прапорця
// чи це прапорець?
if (sender is CheckBox) {
CheckBox chk = (CheckBox)sender;
listBoxvaleurs.Items.Add(chk.Name + "=" + chk.Checked);
}
// чи це перемикач?
if (sender is RadioButton) {
RadioButton rdb = (RadioButton)sender;
listBoxvaleurs.Items.Add(rdb.Name + "=" + rdb.Checked);
}
}
Синтаксис
if (sender is CheckBox) {
дозволяє перевірити, чи об’єкт sender належить до типу CheckBox. Це дає змогу потім виконати перетворення типу до точного типу sender. Метод affiche записує у список listBoxValeurs назву компонента, що ініціював подію, та значення його властивості Checked. Під час виконання [7] видно, що натискання на перемикач викликає дві події CheckChanged: одну — на старому перемикачі, який переходить у стан «не відмічений», та іншу — на новому перемикачі, який переходить у стан «відмічений».
7.2.6. Регулятори ScrollBar
Існує кілька типів регуляторів: горизонтальний регулятор (HscrollBar), вертикальний регулятор (VscrollBar), інкрементатор (NumericUpDown). |
Створимо таку програму:
![]() |
№ | тип | назва | роль |
1 | hScrollBar | hScrollBar1 | горизонтальний регулятор |
2 | hScrollBar | hScrollBar2 | горизонтальний регулятор, що слідує за змінами регулятора 1 |
3 | Етикетка | labelValeurHS1 | відображає значення горизонтального регулятора |
4 | NumericUpDown | numericUpDown2 | дозволяє зафіксувати значення регулятора 2 |
Регулятор ScrollBar дозволяє користувачеві вибрати значення в діапазоні цілих чисел, що символізується «смугою» регулятора, по якій переміщується курсор. Значення регулятора доступне у його властивості Value.
- Для горизонтального повзунка лівий край відповідає мінімальному значенню діапазону, правий край — максимальному, а курсор — поточно вибраному значенню. Для вертикального повзунка мінімум відповідає верхньому краю, а максимум — нижніму. Ці значення представлені властивостями Minimum та Maximum і за замовчуванням дорівнюють 0 та 100 відповідно.
- Клік на крайніх точках повзунка змінює значення на один крок (позитивний або негативний) відповідно до крайньої точки, на яку натиснуто, що має назву SmallChange і за замовчуванням дорівнює 1.
- Клік по обидва боки від повзунка змінює значення на один крок (позитивний або негативний) залежно від того, на який кінець було натиснуто, що позначається як LargeChange і за замовчуванням дорівнює 10.
- При натисканні на верхній край вертикального повзунка його значення зменшується. Це може здивувати пересічного користувача, який зазвичай очікує, що значення «зросте». Цю проблему можна вирішити, присвоївши від’ємні значення властивостям SmallChange та LargeChange
- Ці п’ять властивостей (Value, Minimum, Maximum, SmallChange, LargeChange) доступні для читання та запису.
- Головною подією приводу є та, що сигналізує про зміну значення: подія Scroll.
Поруч із регулятором розташований компонент NumericUpDown: він також має властивості Minimum, Maximum та Value, значення яких за замовчуванням становлять 0, 100, 0. Однак у цьому випадку властивість Value відображається у полі введення, яке є невід’ємною частиною елемента управління. Користувач може самостійно змінити це значення, якщо властивість ReadOnly елемента управління не встановлено у значення «true». Значення кроку встановлюється властивістю Increment, за замовчуванням 1. Головною подією компонента NumericUpDown є та, що сигналізує про зміну значення: подія ValueChanged
Код програми такий:
using System.Windows.Forms;
namespace Chap5 {
public partial class Form1 : Form {
public Form1() {
InitializeComponent();
// встановлюємо параметри регулятора 1
hScrollBar1.Value = 7;
hScrollBar1.Minimum = 1;
hScrollBar1.Maximum = 130;
hScrollBar1.LargeChange = 11;
hScrollBar1.SmallChange = 1;
// присвоюємо приводу 2 ті самі характеристики, що й приводу 1
hScrollBar2.Value = hScrollBar1.Value;
hScrollBar2.Minimum = hScrollBar1.Minimum;
hScrollBar2.Maximum = hScrollBar1.Maximum;
hScrollBar2.LargeChange = hScrollBar1.LargeChange;
hScrollBar2.SmallChange = hScrollBar1.SmallChange;
// те саме для інкрементатора
numericUpDown2.Value = hScrollBar1.Value;
numericUpDown2.Minimum = hScrollBar1.Minimum;
numericUpDown2.Maximum = hScrollBar1.Maximum;
numericUpDown2.Increment = hScrollBar1.SmallChange;
// присвоюємо мітці значення приводу 1
labelValeurHS1.Text = hScrollBar1.Value.ToString();
}
private void hScrollBar1_Scroll(object sender, ScrollEventArgs e) {
// зміна значення приводу 1
// його значення передається на привід 2 та на мітку
hScrollBar2.Value = hScrollBar1.Value;
labelValeurHS1.Text = hScrollBar1.Value.ToString();
}
private void numericUpDown2_ValueChanged(object sender, System.EventArgs e) {
// інкрементатор змінив значення
// встановлюємо значення регулятора 2
hScrollBar2.Value = (int)numericUpDown2.Value;
}
}
}
7.3. Події миші
Під час малювання в контейнері важливо знати положення миші, щоб, наприклад, відобразити точку під час кліка. Переміщення миші викликають події в контейнері, в якому вона переміщується.
![]() |
- [1]: події, що виникають під час переміщення миші по формі або елементу управління
- [2]: події, що виникають під час перетягування та відпускання (Drag'nDrop)
миша щойно увійшла в область елемента управління | |
миша щойно вийшла з області елемента управління | |
миша рухається в області контролю | |
Натискання лівої кнопки миші | |
Відпускання лівої кнопки миші | |
користувач відпускає об’єкт на елементі управління | |
користувач входить в область елемента управління, перетягуючи об’єкт | |
користувач виходить з області елемента управління, перетягуючи об’єкт | |
користувач наводить курсор на область елемента управління, перетягуючи об’єкт |
Ось додаток, що дозволяє краще зрозуміти, в які моменти відбуваються різні події миші:
![]() |
№ | тип | назва | роль |
1 | Етикетка | lblPositionSouris | для відображення положення миші у формі 1, списку 2 або кнопці 3 |
2 | ListBox | listBoxEvts | для відображення подій миші, відмінних від MouseMove |
3 | Кнопка | buttonEffacer | щоб очистити вміст 2 |
Щоб відстежувати переміщення миші по трьох елементах управління, ми пишемо лише один обробник — обробник affiche:
![]() |
Код процедури affiche такий:
private void affiche(object sender, MouseEventArgs e) {
// рух миші — відображаються її координати (X, Y)
labelPositionSouris.Text = "(" + e.X + "," + e.Y + ")";
}
Щоразу, коли курсор миші потрапляє в область елемента управління, його система координат змінюється. Його початок координат (0,0) — це лівий верхній кут елемента управління, на якому він знаходиться. Таким чином, під час виконання, коли курсор миші переміщується з форми на кнопку, чітко видно зміну координат. Щоб краще побачити ці зміни області миші, можна скористатися властивістю Cursor [1] елементів управління:
![]() |
Цей атрибут дозволяє задати форму курсора миші, коли він потрапляє в область елемента управління. Так, у нашому прикладі ми встановили курсор у вигляді «Default» для самої форми [2], «Hand» для списку 2 [3] та «Cross» для кнопки 3 [4].
Крім того, щоб виявляти вхід і вихід курсора миші зі списку 2, ми обробляємо події MouseEnter та MouseLeave цього самого списку:
private void listBoxEvts_MouseEnter(object sender, System.EventArgs e) {
// подається сигнал про подію
listBoxEvts.Items.Insert(0, string.Format("MouseEnter à {0:hh:mm:ss}",DateTime.Now));
}
private void listBoxEvts_MouseLeave(object sender, EventArgs e) {
// подається сигнал про подію
listBoxEvts.Items.Insert(0, string.Format("MouseLeave à {0:hh:mm:ss}", DateTime.Now));
}
Для обробки кліків у формі ми обробляємо події MouseDown та MouseUp:
private void listBoxEvts_MouseDown(object sender, MouseEventArgs e) {
// подається сигнал про подію
listBoxEvts.Items.Insert(0, string.Format("MouseDown à {0:hh:mm:ss}", DateTime.Now));
}
private void listBoxEvts_MouseUp(object sender, MouseEventArgs e) {
// подається сигнал про подію
listBoxEvts.Items.Insert(0, string.Format("MouseUp à {0:hh:mm:ss}", DateTime.Now));
}
- рядки 3 та 8: повідомлення розміщуються на першому місці у ListBox, щоб найсвіжіші події були першими у списку.
![]() |
Нарешті, код обробника натискання кнопки Effacer:
private void buttonEffacer_Click(object sender, EventArgs e) {
listBoxEvts.Items.Clear();
}
7.4. Створення вікна з меню
Тепер розглянемо, як створити вікно з меню. Ми створимо таке вікно:
![]() |
Щоб створити меню, вибираємо компонент «MenuStrip» у панелі «Menus & Toolbars»:
![]() |
- [1]: вибір компонента [MenuStrip]
- [2]: після цього у формі з’являється меню з порожніми полями під назвою «Type Here». Достатньо вказати в них різні опції меню.
- [3]: введено назву «Options A». Переходимо до назви [4].
- [5]: назви опцій А введені. Переходимо до назви [6]
![]() |
- [6]: перші опції B
- [7]: під B1 ставимо роздільник. Він доступний у комбінованому списку, пов’язаному з текстом «Type Here»
- [8]: щоб створити підменю, скористайтеся стрілкою [8] і введіть назву підменю в поле [9]
Залишається присвоїти імена різним компонентам форми:
![]() |
№ | тип | назва(и) | роль |
1 | Етикетка | labelStatut | для відображення тексту натиснутої опції меню |
2 | toolStripMenuItem | toolStripMenuItemOptionsA toolStripMenuItemA1 toolStripMenuItemA2 toolStripMenuItemA3 | пункти меню в розділі «Параметри А» |
3 | toolStripMenuItem | toolStripMenuItemOptionsB toolStripMenuItemB1 toolStripMenuItemB2 toolStripMenuItemB3 | пункти меню в розділі «Параметри B» |
4 | toolStripMenuItem | toolStripMenuItemB31 toolStripMenuItemB32 | пункти меню під головним пунктом «B3» |
Пункти меню є елементами управління, як і інші візуальні компоненти, і мають властивості та події. Наприклад, властивості пункту меню A1 такі:
![]() |
У нашому прикладі використовуються дві властивості:
ім’я елемента управління меню | |
назва пункту меню |
У структурі меню виберімо пункт A1 і клацнімо правою кнопкою миші, щоб отримати доступ до властивостей елемента управління:
![]() |
На вкладці événements [1] ми пов’язуємо метод affiche [2] з подією Click. Це означає, що ми хочемо, щоб клік на опцію A1 оброблявся методом із назвою affiche. Visual Studio автоматично генерує метод affiche у вікні коду:
private void affiche(object sender, EventArgs e) {
}
У цьому методі ми просто відобразимо в мітці labelStatut властивість Text того пункту меню, на який натиснули:
private void affiche(object sender, EventArgs e) {
// відображає у TextBox назву обраного підменю
labelStatut.Text = ((ToolStripMenuItem)sender).Text;
}
Джерело події sender має тип object. Опції меню мають тип ToolStripMenuItem, тому необхідно виконати перетворення типу з object у ToolStripMenuItem.
Для всіх пунктів меню встановлюємо обробник кліка на метод affiche [3,4].
Запустимо додаток і виберемо пункт меню:
![]() | ![]() |
7.5. Невізуальні компоненти
Тепер розглянемо низку невидимих компонентів: їх використовують під час проектування, але під час виконання їх не видно.
7.5.1. Діалогові вікна , OpenFileDialog та SaveFileDialog
Ми створимо таку програму:
![]() |
Елементи управління такі:
№ | тип | назва | роль |
1 | TextBox | TextBoxLignes | текст, введений користувачем або завантажений із файлу MultiLine=True, ScrollBars=Both, AccepReturn=True, AcceptTab=True |
2 | Кнопка | buttonSauvegarder | дозволяє зберегти текст з [1] у текстовому файлі |
3 | Кнопка | buttonCharger | дозволяє завантажити вміст текстового файлу у [1] |
4 | Кнопка | buttonEffacer | очищає вміст [1] |
5 | SaveFileDialog | saveFileDialog1 | компонент, що дозволяє вибрати ім'я та місце розташування файлу резервної копії [1]. Цей компонент береться з панелі інструментів [7] і просто перетягується на форму. Після цього він зберігається, але не займає місця на формі. Це невидимий компонент. |
6 | OpenFileDialog | openFileDialog1 | компонент, що дозволяє вибрати файл для завантаження в [1]. |
Код, пов’язаний із кнопкою Effacer, є простим:
private void buttonEffacer_Click(object sender, EventArgs e) {
// в TexBox вставляється порожній рядок
textBoxLignes.Text = "";
}
Ми будемо використовувати такі властивості та методи класу SaveFileDialog:
Поле | Тип | Роль |
Propriété | типи файлів, запропоновані у випадаючому списку типів файлів діалогового вікна | |
Propriété | номер типу файлу, запропонованого за замовчуванням у наведеному вище списку. Починається з 0. | |
Propriété | папка, яка спочатку була вказана для збереження файлу | |
Propriété | ім'я файлу резервної копії, вказане користувачем | |
Méthode | метод, що відображає діалогове вікно збереження. Повертає результат типу DialogResult. |
Метод ShowDialog відображає діалогове вікно, подібне до наведеного нижче:
![]() |
випадаючий список, сформований на основі властивості Filter. Тип файлу, запропонований за замовчуванням, визначається методом FilterIndex | |
поточна папка, яка визначається InitialDirectory, якщо це властивість було вказано | |
назва файлу, обрана або введена безпосередньо користувачем. Буде доступна у властивості FileName | |
кнопки «Зберегти»/«Скасувати». Якщо використовується кнопка Enregistrer, функція ShowDialog повертає результат DialogResult.OK |
Процедуру збереження можна записати так:
private void buttonSauvegarder_Click(object sender, System.EventArgs e) {
// зберігає поле введення у текстовому файлі
// настроюється діалогове вікно savefileDialog1
saveFileDialog1.InitialDirectory = Application.ExecutablePath;
saveFileDialog1.Filter = "Fichiers texte (*.txt)|*.txt|Tous les fichiers (*.*)|*.*";
saveFileDialog1.FilterIndex = 0;
// відображаємо діалогове вікно та отримуємо його результат
if (saveFileDialog1.ShowDialog() == DialogResult.OK) {
// отримуємо ім'я файлу
string nomFichier = saveFileDialog1.FileName;
StreamWriter fichier = null;
try {
// відкриваємо файл для запису
fichier = new StreamWriter(nomFichier);
// записуємо в нього текст
fichier.Write(textBoxLignes.Text);
} catch (Exception ex) {
// проблема
MessageBox.Show("Problème à l'écriture du fichier (" +
ex.Message + ")", "Erreur", MessageBoxButtons.OK, MessageBoxIcon.Error);
return;
} finally {
// закриваємо файл
if (fichier != null) {
fichier.Dispose();
}
}
}
}
- рядок 4: встановлюємо початкову папку (InitialDirectory) як папку (Application.ExecutablePath), що містить виконуваний файл програми.
- рядок 5: вказуються типи файлів, які потрібно відобразити. Зверніть увагу на синтаксис фільтрів: filtre1|filtre2|..|filtren, де фільтр i = «Текст»|«Шаблон файлу». Тут користувач матиме можливість вибрати між файлами *.txt та *.*.
- рядок 6: встановлюється тип файлу, який буде показано користувачеві першим. Тут індекс 0 позначає файли *.txt.
- рядок 8: відображається діалогове вікно та отримується його результат. Поки діалогове вікно відкрите, користувач не має доступу до головної форми (так зване модальне діалогове вікно). Користувач вказує ім’я файлу для збереження та залишає діалогове вікно, натиснувши кнопку Enregistrer, кнопку Annuler, або закривши вікно. Результатом методу ShowDialog є DialogResult.OK лише в тому випадку, якщо користувач використав кнопку Enregistrer для виходу з діалогового вікна.
- Після цього ім’я файлу, що створюється, тепер міститься у властивості FileName об’єкта saveFileDialog1. Далі відбувається звичайне створення текстового файлу. У нього записується вміст TextBox: textBoxLignes.Text, при цьому обробляються винятки, що можуть виникнути.
Клас OpenFileDialog дуже схожий на клас SaveFileDialog. Ми будемо використовувати ті самі методи та властивості, що й раніше. Метод ShowDialog відображає діалогове вікно, подібне до такого:
![]() |
випадаючий список, сформований на основі властивості Filter. Тип файлу, що пропонується за замовчуванням, визначається методом FilterIndex | |
поточна папка, визначена InitialDirectory, якщо це властивість було вказано | |
назва файлу, обрана або введена безпосередньо користувачем. Буде доступна у властивості FileName | |
кнопки «Відкрити»/«Скасувати». Якщо використовується кнопка Ouvrir, функція ShowDialog повертає результат DialogResult.OK |
Процедуру завантаження текстового файлу можна записати так:
private void buttonCharger_Click(object sender, EventArgs e) {
// завантажуємо текстовий файл у поле введення
// налаштування діалогового вікна openfileDialog1
openFileDialog1.InitialDirectory = Application.ExecutablePath;
openFileDialog1.Filter = "Fichiers texte (*.txt)|*.txt|Tous les fichiers (*.*)|*.*";
openFileDialog1.FilterIndex = 0;
// відображається діалогове вікно та отримується його результат
if (openFileDialog1.ShowDialog() == DialogResult.OK) {
// отримуємо ім'я файлу
string nomFichier = openFileDialog1.FileName;
StreamReader fichier = null;
try {
// відкриваємо файл для читання
fichier = new StreamReader(nomFichier);
// зчитуємо весь файл і записуємо його в TextBox
textBoxLignes.Text = fichier.ReadToEnd();
} catch (Exception ex) {
// проблема
MessageBox.Show("Problème à la lecture du fichier (" +
ex.Message + ")", "Erreur", MessageBoxButtons.OK, MessageBoxIcon.Error);
return;
} finally {
// файл закрито
if (fichier != null) {
fichier.Dispose();
}
}//нарешті
}//якщо
}
- рядок 4: встановлюємо початкову папку (InitialDirectory) як папку (Application.ExecutablePath), що містить виконуваний файл програми.
- рядок 5: задаються типи файлів, які потрібно відобразити. Зверніть увагу на синтаксис фільтрів: filtre1|filtre2|..|filtren, де фільтр i = «Текст»|«Шаблон файлу». Тут користувач матиме можливість вибрати між файлами *.txt та *.*.
- рядок 6: встановлюється тип файлу, який буде показано користувачеві першим. Тут індекс 0 позначає файли *.txt.
- рядок 8: відображається діалогове вікно та отримується його результат. Поки діалогове вікно відкрите, користувач не має доступу до головної форми (так зване модальне діалогове вікно). Користувач вказує ім’я файлу для збереження та залишає діалогове вікно, натиснувши кнопку Ouvrir, кнопку Annuler, або закривши вікно. Результатом методу ShowDialog є DialogResult.OK лише в тому випадку, якщо користувач використав кнопку Enregistrer для виходу з діалогового вікна.
- Після цього ім’я файлу, що має бути створено, тепер міститься у властивості FileName об’єкта openFileDialog1. Далі відбувається звичайне читання текстового файлу. Зверніть увагу на рядок 16, де наведено метод, що дозволяє прочитати файл повністю.
7.5.2. Діалогові вікна FontColor та ColorDialog
Продовжуємо попередній приклад, додавши до нього дві нові кнопки та два нових невидимих елементи управління:
![]() |
6
7
№ | тип | назва | роль |
1 | Кнопка | buttonCouleur | для визначення кольору символів у TextBox |
2 | Кнопка | buttonPolice | для встановлення шрифту TextBox |
3 | ColorDialog | colorDialog1 | компонент, що дозволяє вибрати колір — взято з набору інструментів [5]. |
4 | FontDialog | colorDialog1 | компонент, що дозволяє вибрати шрифт — взято з набору інструментів [5]. |
Класи FontDialog та ColorDialog мають метод ShowDialog, аналогічний методу ShowDialog класів OpenFileDialog та SaveFileDialog.
![]() |
Метод ShowDialog класу ColorDialog дозволяє вибрати колір [1]. Метод класу FontDialog дозволяє вибрати шрифт [2]:
- [1]: якщо користувач закриває діалогове вікно за допомогою кнопки OK, результатом виклику методу ShowDialog буде DialogResult.OK, а вибраний колір буде зберігатися у властивості Color використовуваного об’єкта ColorDialog.
- [2]: якщо користувач закриває діалогове вікно за допомогою кнопки OK, результатом виклику методу ShowDialog буде DialogResult.OK, а вибраний шрифт буде вказано у властивості Font об’єкта FontDialog, що використовується.
Тепер ми маємо все необхідне для обробки кліків на кнопках Couleur та Police:
private void buttonCouleur_Click(object sender, EventArgs e) {// вибір кольору тексту
if (colorDialog1.ShowDialog() == DialogResult.OK) {
// змінюємо властивість Forecolor у TextBox
textBoxLignes.ForeColor = colorDialog1.Color;
}//if
}
private void buttonPolice_Click(object sender, EventArgs e) {
// вибір шрифту
if (fontDialog1.ShowDialog() == DialogResult.OK) {
// зміна властивості Font об’єкта TextBox
textBoxLignes.Font = fontDialog1.Font;
}
- рядок [4]: властивість [ForeColor] компонента TextBox визначає колір типу [Color] символів TextBox. У цьому випадку цей колір — це той, який користувач вибрав у діалоговому вікні типу [ColorDialog].
- рядок [12]: властивість [Font] компонента TextBox позначає шрифт типу [Font] для символів TextBox. У даному випадку це шрифт, обраний користувачем у діалоговому вікні типу [FontDialog].
7.5.3. Таймер
Тут ми пропонуємо написати таку програму:
![]() |
№ | Тип | Назва | Роль |
1 | Лейбл | labelChrono | відображає таймер |
2 | Кнопка | buttonArretMarche | кнопка «Старт/Стоп» хронометра |
3 | Таймер | timer1 | компонент, що генерує подію кожну секунду |
У [4] ми бачимо, що хронометр працює, а в [5] — що він зупинений.
Щоб щосекунди змінювати вміст мітки LabelChrono, нам потрібен компонент, який генерує подію щосекунди; цю подію можна перехопити, щоб оновити відображення таймера. Цим компонентом є Timer [1], доступний у наборі інструментів Components [2]:
![]() |
Властивості компонента Timer, що використовуються тут, будуть такими:
кількість мілісекунд, після закінчення яких генерується подія Tick. | |
подія, що генерується після закінчення Interval мілісекунд | |
встановлює таймер у активний (true) або неактивний (false) стан |
У нашому прикладі таймер називається timer1, а timer1.Interval встановлено на 1000 мс (1 с). Отже, подія Tick відбуватиметься щосекунди. Натискання кнопки «Увімкнути/Вимкнути» обробляється наступною процедурою buttonArretMarche_Click:
using System;
using System.Windows.Forms;
namespace Chap5 {
public partial class Form1 : Form {
public Form1() {
InitializeComponent();
}
// змінна екземпляра
private DateTime début = DateTime.Now;
...
private void buttonArretMarche_Click(object sender, EventArgs e) {
// зупинка чи запуск?
if (buttonArretMarche.Text == "Marche") {
// фіксуємо час початку
début = DateTime.Now;
// виводимо її на екран
labelChrono.Text = "00:00:00";
// запускаємо таймер
timer1.Enabled = true;
// змінити напис на кнопці
buttonArretMarche.Text = "Arrêt";
// кінець
return;
}//
if (buttonArretMarche.Text == "Arrêt") {
// зупинка таймера
timer1.Enabled = false;
// змінюємо напис на кнопці
buttonArretMarche.Text = "Marche";
// кінець
return;
}
}
}
}
- рядок 13: процедура, яка обробляє натискання кнопки «Стоп/Пуск».
- рядок 15: напис на кнопці «Вимкнути/Увімкнути» — це або «Вимкнути», або «Увімкнути». Тому необхідно перевірити цей напис, щоб визначити, що робити.
- рядок 17: у разі «Увімкнути» час початку записується у змінну début, яка є глобальною змінною (рядок 11) об’єкта-форми
- рядок 19: ініціалізується вміст мітки LabelChrono
- рядок 21: запускається таймер (Enabled=true)
- рядок 23: напис на кнопці змінюється на «Зупинка».
- рядок 27: у разі «Зупинити»
- рядок 29: таймер зупиняється (Enabled=false)
- рядок 31: напис на кнопці змінюється на «Увімкнути».
Залишається обробити подію Tick на об’єкті timer1, яка відбувається щосекунди:
private void timer1_Tick(object sender, EventArgs e) {
// минула одна секунда
DateTime maintenant = DateTime.Now;
TimeSpan durée = maintenant - début;
// оновлення таймера
labelChrono.Text = durée.Hours.ToString("d2") + ":" + durée.Minutes.ToString("d2") + ":" + durée.Seconds.ToString("d2");
}
- рядок 3: фіксуємо поточний час
- рядок 4: обчислюється час, що минув з моменту запуску хронометра. Отримуємо об’єкт типу TimeSpan, який представляє тривалість у часі.
- рядок 6: цей об’єкт має відображатися на хронометрі у вигляді hh:mm:ss. Для цього ми використовуємо властивості Hours, Minutes, Seconds об’єкта TimeSPan, які відповідно позначають години, хвилини та секунди тривалості, яку ми відображаємо у форматі ToString("d2") для відображення у вигляді двох цифр.
7.6. Приклад застосування — , версія 6
Повернемося до прикладу програми IMPOTS. Останню версію було розглянуто в розділі 6.4. Це була така трирівнева програма:
![]() |
- шари [metier] та [dao] були інкапсульовані в DLL
- шар [ui] був шаром [console]
- інстанціювання шарів та їх інтеграція в додаток забезпечувалися Spring.
У цій новій версії рівень [ui] буде реалізовано за допомогою такого графічного інтерфейсу:
![]() |
7.6.1. Рішення для Visual Studio
Проект Visual Studio складається з таких елементів:
![]() |
- [1]: проект складається з таких елементів:
- [Program.cs]: клас, що запускає додаток
- [Form1.cs]: клас першої форми
- [Form2]: клас другої форми
- [lib], детально описаний у [2]: туди поміщено всі DLL, необхідні для проекту:
- [ImpotsV5-dao.dll]: DLL шару [dao], згенерованого в розділі 6.4.3;
- [ImpotsV5-metier.dll]: файл DLL з шару [dao], згенерованого в пункті 6.4.4;
- [Spring.Core.dll], [Common.Logging.dll], [antlr.runtime.dll]: DLL з Spring, які вже використовувалися в попередній версії (див. розділ 6.4.6).
- [references], детально описаний у [3]: посилання на проект. Було додано одне посилання для кожного з файлів DLL у папці [lib]
- [App.config]: файл конфігурації проекту. Він ідентичний файлу попередньої версії, описаному в розділі 6.4.6;
- [DataImpot.txt]: файл податкових шкал, налаштований на автоматичне копіювання до папки виконання проекту [4]
Форма [Form1] — це форма для введення параметрів розрахунку податку [A], яка вже була представлена вище. Форма [Form2] [B] слугує для відображення повідомлення про помилку:
![]() |
7.6.2. Клас [Program.cs]
Клас [Program.cs] запускає додаток. Його код такий:
using System;
using System.Windows.Forms;
using Spring.Context;
using Spring.Context.Support;
using Metier;
using System.Text;
namespace Chap5 {
static class Program {
/// <summary>
/// Основна точка входу в додаток.
/// </summary>
[STAThread]
static void Main() {
// код, згенерований Vs
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
// --------------- Код розробника
// інстанції шарів [metier] та [dao]
IApplicationContext ctx = null;
Exception ex = null;
IImpotMetier metier = null;
try {
// контекст Spring
ctx = ContextRegistry.GetContext();
// запитується посилання на рівень [metier]
metier = (IImpotMetier)ctx.GetObject("metier");
} catch (Exception e1) {
// запис винятку
ex = e1;
}
// форма для відображення
Form form = null;
// Чи стався виняток?
if (ex != null) {
// так — створюється повідомлення про помилку для відображення
StringBuilder msgErreur = new StringBuilder(String.Format("Chaîne des exceptions : {0}{1}", "".PadLeft(40, '-'), Environment.NewLine));
Exception e = ex;
while (e != null) {
msgErreur.Append(String.Format("{0}: {1}{2}", e.GetType().FullName, e.Message, Environment.NewLine));
msgErreur.Append(String.Format("{0}{1}", "".PadLeft(40, '-'), Environment.NewLine));
e = e.InnerException;
}
// створення вікна помилки, до якого передається повідомлення про помилку для відображення
Form2 form2 = new Form2();
form2.MsgErreur = msgErreur.ToString();
// це буде вікно, яке потрібно відобразити
form = form2;
} else {
// все пройшло добре
// створення графічного інтерфейсу [Form1], якому передається посилання на рівень [metier]
Form1 form1 = new Form1();
form1.Metier = metier;
// це буде вікно, яке потрібно відобразити
form = form1;
}
// відображення вікна
Application.Run(form);
}
}
}
Код, згенерований Visual Studio, було доповнено, починаючи з рядка 19. Додаток використовує такий файл [App.config] :
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<configSections>
<sectionGroup name="spring">
<section name="context" type="Spring.Context.Support.ContextHandler, Spring.Core" />
<section name="objects" type="Spring.Context.Support.DefaultSectionHandler, Spring.Core" />
</sectionGroup>
</configSections>
<spring>
<context>
<resource uri="config://spring/objects" />
</context>
<objects xmlns="http://www.springframework.net">
<object name="dao" type="Dao.FileImpot, ImpotsV5-dao">
<constructor-arg index="0" value="DataImpot.txt"/>
</object>
<object name="metier" type="Metier.ImpotMetier, ImpotsV5-metier">
<constructor-arg index="0" ref="dao"/>
</object>
</objects>
</spring>
</configuration>
- рядки 24–32: використання попереднього файлу [App.config] для створення екземплярів шарів [metier] та [dao]
- рядок 26: обробка файлу [App.config]
- рядок 28: отримання посилання на шар [metier]
- рядок 31: збереження можливого винятку
- рядок 34: посилання form вказуватиме на форму, яку потрібно відобразити (form1 або form2)
- рядки 36–50: якщо сталося виключення, готуємося до відображення форми типу [Form2]
- рядки 38–44: формується повідомлення про помилку, яке потрібно відобразити. Воно складається з об’єднання повідомлень про помилки різних винятків, присутніх у ланцюжку винятків.
- рядок 46: створюється форма типу [Form2].
- рядок 47: як ми побачимо пізніше, ця форма має загальнодоступну властивість MsgErreur, яка є повідомленням про помилку, що має відобразитися:
public string MsgErreur { private get; set; }
Заповнюємо це властивість.
- рядок 49: ініціалізується посилання form, яке вказує на вікно, що має відобразитися. Зверніть увагу на поліморфізм, що тут проявляється. form2 належить не до типу [Form], а до типу [Form2] — типу, похідного від [Form].
- рядки 50–57: винятків не сталося. Готуємося до відображення форми типу [Form1].
- рядок 53: створюється форма типу [Form1].
- рядок 54: як ми побачимо пізніше, ця форма має публічну властивість Metier, яка є посиланням на шар [metier]:
public IImpotMetier Metier { private get; set; }
Заповнюємо цю властивість.
- рядок 56: ініціалізується посилання form, яке вказує на вікно, що має відобразитися. Знову слід звернути увагу на прояв поліморфізму. form1 не є типом [Form], а типом [Form1], похідним від [Form].
- рядок 59: вікно, на яке посилається form, відображається.
7.6.3. Форма [Form1]
У режимі [conception] форма [Form1] має такий вигляд:
![]() |
Елементи управління такі
№ | тип | назва | роль |
0 | GroupBox | groupBox1 | Text=Чи перебуваєте ви у шлюбі? |
1 | RadioButton | radioButtonOui | позначте, якщо одружені |
2 | RadioButton | radioButtonNon | позначте, якщо не одружений Checked=True |
3 | NumericUpDown | numericUpDownEnfants | кількість дітей платника податків Мінімальне значення=0, Максимальне значення=20, Крок=1 |
4 | TextBox | textSalaire | річний дохід платника податків у євро |
5 | Маркування | labelImpot | сума податку до сплати BorderStyle=Fixed3D |
6 | Кнопка | buttonCalculer | запускає розрахунок податку |
7 | Кнопка | buttonEffacer | повертає форму до стану, в якому вона перебувала під час завантаження |
8 | Кнопка | buttonQuitter | для виходу з програми |
Правила роботи форми
- кнопка Calculer залишається неактивною, доки в полі «зарплата» немає даних
- якщо під час запуску розрахунку виявляється, що зарплата вказана неправильно, з’являється повідомлення про помилку [9]
Код класу такий:
using System.Windows.Forms;
using Metier;
using System;
namespace Chap5 {
public partial class Form1 : Form {
// шар [métier]
public IImpotMetier Metier { private get; set; }
public Form1() {
InitializeComponent();
}
private void buttonCalculer_Click(object sender, System.EventArgs e) {
// чи правильна заробітна плата
int salaire;
bool ok=int.TryParse(textSalaire.Text.Trim(), out salaire);
if (! ok || salaire < 0) {
// повідомлення про помилку
MessageBox.Show("Salaire incorrect", "Erreur de saisie", MessageBoxButtons.OK, MessageBoxIcon.Error);
// повернення до поля з помилкою
textSalaire.Focus();
// виділення тексту у полі введення
textSalaire.SelectAll();
// повернення до інтерфейсу введення
return;
}
// зарплата правильна — можна розрахувати податок
labelImpot.Text = Metier.CalculerImpot(radioButtonOui.Checked, (int)numericUpDownEnfants.Value, salaire).ToString();
}
private void buttonQuitter_Click(object sender, System.EventArgs e) {
Environment.Exit(0);
}
private void buttonEffacer_Click(object sender, System.EventArgs e) {
// очищення форми
labelImpot.Text = "";
numericUpDownEnfants.Value = 0;
textSalaire.Text = "";
radioButtonNon.Checked = true;
}
private void textSalaire_TextChanged(object sender, EventArgs e) {
// стан кнопки [Calculer]
buttonCalculer.Enabled=textSalaire.Text.Trim()!="";
}
}
}
Ми коментуємо лише важливі частини:
- рядок [8]: публічна властивість Metier, яка дозволяє класу запуску [Program.cs] вставляти в [Form1] посилання на шар [metier].
- рядок [14]: процедура розрахунку податку
- рядки 15–27: перевірка правильності значення заробітної плати (ціле число >=0).
- рядок 29: обчислення податку за допомогою методу [CalculerImpot] шару [metier]. Слід відзначити простоту цієї операції, досягнуту завдяки інкапсуляції шару [metier] у DLL.
7.6.4. Форма [Form2]
У режимі [conception] форма [Form2] має такий вигляд:
![]() |
Елементи управління такі
№ | тип | назва | роль |
1 | TextBox | textBoxErreur | Multiline=True, Scrollbars=Both |
Код класу такий:
using System.Windows.Forms;
namespace Chap5 {
public partial class Form2 : Form {
// повідомлення про помилку
public string MsgErreur { private get; set; }
public Form2() {
InitializeComponent();
}
private void Form2_Load(object sender, System.EventArgs e) {
// відображається повідомлення про помилку
textBoxErreur.Text = MsgErreur;
// скасовується виділення всього тексту
textBoxErreur.Select(0, 0);
}
}
}
- рядок 6: загальнодоступна властивість MsgErreur, яка дозволяє класу запуску [Program.cs] вставляти в [Form2] повідомлення про помилку, яке має бути відображене. Це повідомлення відображається під час обробки події Load, рядки 12–16.
- рядок 14: повідомлення про помилку поміщається в TextBox
- рядок 16: скасовується виділення, яке відбулося під час попередньої операції. [TextBox].Select(початок, довжина) виділяє (підсвічує) longueur символів, починаючи з символу № début. [TextBox].Select(0,0) скасовує виділення всього тексту.
7.6.5. Висновок
Повернемося до використовуваної трирівневої архітектури:
![]() |
Ця архітектура дозволила нам замінити консольну реалізацію існуючого шару [ui] на графічну, не змінюючи нічого в шарах [metier] та [dao]. Ми змогли зосередитися на шарі [ui], не турбуючись про можливий вплив на інші шари. У цьому полягає головна перевага тришарових архітектур. Ще один приклад ми розглянемо далі, коли шар [dao], який наразі обробляє дані з текстового файлу, буде замінено шаром [dao], що обробляє дані з бази даних. Ми побачимо, що це відбудеться без впливу на рівні [ui] та [metier].


































































