7. Interfejsy graficzne w języku C# i VS.NET
7.1. Podstawy interfejsów graficznych
7.1.1. Pierwszy projekt
Stwórzmy pierwszy projekt typu „Aplikacja Windows”:
![]() |
- [1]: utwórz nowy projekt
- [2]: typu „Aplikacja Windows”
- [3]: nazwa projektu nie ma na razie znaczenia
- [4]: projekt utworzony
![]() |
- [5]: zapisujemy bieżące rozwiązanie
- [6]: nazwa projektu
- [7]: folder rozwiązania
- [8]: nazwa rozwiązania
- [9]: zostanie utworzony folder dla rozwiązania [Chap5]. Projekty wchodzące w jego skład będą znajdować się w podfolderach.
![]() |
- [10]: projekt [01] w rozwiązaniu [Chap5]:
- [Program.cs] to główna klasa projektu
- [Form1.cs] to plik źródłowy, który będzie zarządzał zachowaniem okna [11]
- [Form1.Designer.cs] to plik źródłowy, który będzie zawierał informacje o komponentach okna [11]
- [11]: plik [Form1.cs] w trybie projektowania (design)
- [12]: wygenerowaną aplikację można uruchomić za pomocą skrótu (Ctrl-F5). Wyświetla się okno [Form1]. Można je przesuwać, zmieniać jego rozmiar oraz zamykać. Mamy zatem podstawowe elementy okna graficznego.
Główna klasa [Program.cs] wygląda następująco:
using System;
using System.Windows.Forms;
namespace Chap5 {
static class Program {
/// <summary>
/// Główny punkt wejścia do aplikacji.
/// </summary>
[STAThread]
static void Main() {
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
Application.Run(new Form1());
}
}
}
- wiersz 2: aplikacje z formularzami korzystają z przestrzeni nazw System.Windows.Forms.
- wiersz 4: pierwotna przestrzeń nazw została przemianowana na Chap5.
- wiersz 10: podczas uruchamiania projektu (Ctrl-F5) wykonywana jest metoda [Main].
- wiersze 11–13: klasa Application należy do przestrzeni nazw System.Windows.Forms. Zawiera ona metody statyczne służące do uruchamiania i zamykania graficznych aplikacji systemu Windows.
- wiersz 11: opcjonalny – pozwala nadać różne style wizualne kontrolkom umieszczonym w formularzu
- wiersz 12: opcjonalny – określa silnik renderowania tekstu elementów sterujących: GDI+ (true), GDI (false)
- wiersz 13: jedyny niezbędny wiersz metody [Main]: tworzy instancję klasy [Form1], która jest klasą formularza, i nakazuje jej uruchomienie.
Plik źródłowy [Form1.cs] ma następującą treść:
using System;
using System.Windows.Forms;
namespace Chap5 {
public partial class Form1 : Form {
public Form1() {
InitializeComponent();
}
}
}
- wiersz 5: klasa Form1 wywodzi się z klasy [System.Windows.Forms.Form], która jest klasą nadrzędną wszystkich okien. Słowo kluczowe „partial” oznacza, że klasa jest częściowa i może być uzupełniona przez inne pliki źródłowe. Tak jest w tym przypadku, gdzie klasa Form1 jest podzielona na dwa pliki:
- [Form1.cs]: w którym znajdują się zachowania formularza, w szczególności jego procedury obsługi zdarzeń
- [Form1.Designer.cs]: w którym znajdują się komponenty formularza i ich właściwości. Cechą charakterystyczną tego pliku jest to, że jest on regenerowany za każdym razem, gdy użytkownik modyfikuje okno w trybie [conception].
- wiersze 6–8: konstruktor klasy Form1
- wiersz 7: wywołuje metodę InitializeComponent. Widać, że ta metoda nie występuje w klasie [Form1.cs]. Znajduje się ona w klasie [Form1.Designer.cs].
Plik źródłowy [Form1.Designer.cs] wygląda następująco:
namespace Chap5 {
partial class Form1 {
/// <summary>
/// Wymagana zmienna projektanta.
/// </summary>
private System.ComponentModel.IContainer components = null;
/// <summary>
/// Oczyść wszystkie używane zasoby.
/// </summary>
/// <param name="disposing">true, jeśli zasoby zarządzane mają zostać usunięte; w przeciwnym razie false.</param>
protected override void Dispose(bool disposing) {
if (disposing && (components != null)) {
components.Dispose();
}
base.Dispose(disposing);
}
#kod wygenerowany przez projektanta formularzy Windows Forms
/// <summary>
/// Metoda wymagana do obsługi projektanta — nie modyfikować
/// zawartości tej metody za pomocą edytora kodu.
/// </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);
}
#koniec regionu
}
}
- wiersz 2: nadal chodzi o klasę Form1. Należy zauważyć, że nie ma już potrzeby powtarzania, iż wywodzi się ona z klasy Form.
- wiersze 25–37: metoda InitializeComponent wywoływana przez konstruktor klasy [Form1]. Metoda ta utworzy i zainicjuje wszystkie komponenty formularza. Jest ona regenerowana przy każdej zmianie formularza w trybie [conception]. W celu wyznaczenia granic utworzono sekcję o nazwie région (wiersze 19–39). Programista nie powinien dodawać kodu w tym obszarze: zostanie on nadpisany podczas następnej regeneracji.
Na początku łatwiej jest nie zwracać uwagi na kod z sekcji [Form1.Designer.cs]. Jest on generowany automatycznie i stanowi przekład na język C# wyborów dokonanych przez programistę w trybie [conception]. Rozważmy pierwszy przykład:
![]() |
- [1]: wybierz tryb [conception], klikając dwukrotnie plik [Form1.cs]
- [2]: kliknij prawym przyciskiem myszy na formularz i wybierz [Properties]
- [3]: okno właściwości pliku [Form1]
- [4]: właściwość [Text] odpowiada tytułowi okna
- [5]: zmiana właściwości [Text] jest uwzględniana w trybie [conception], a także w kodzie źródłowym [Form1.Designer.cs]:
private void InitializeComponent() {
this.SuspendLayout();
...
this.Text = "Mon 1er formulaire";
...
}
7.1.2. Drugi projekt
7.1.2.1. Formularz
Rozpoczynamy nowy projekt o nazwie 02. W tym celu postępujemy zgodnie z procedurą opisaną wcześniej w celu utworzenia projektu. Okno, które należy utworzyć, wygląda następująco:
![]() |
Elementy składowe formularza są następujące:
nr | nazwa | typ | rola |
1 | labelSaisie | Etykieta | nazwa |
2 | textBoxSaisie | TextBox | pole wprowadzania danych |
3 | buttonAfficher | Przycisk | służący do wyświetlenia zawartości pola wprowadzania danych w oknie dialogowym textBoxSaisie |
Aby utworzyć to okno, można postępować w następujący sposób:
![]() |
- [1]: kliknąć prawym przyciskiem myszy na formularzu poza jakimkolwiek komponentem i wybrać opcję [Properties]
- [2]: w prawym dolnym rogu programu Visual Studio pojawi się okno właściwości
Wśród właściwości formularza warto zwrócić uwagę na:
w celu ustawienia koloru tła okna | |
do ustawienia koloru elementów graficznych lub tekstu w oknie | |
aby przypisać menu do okna | |
aby nadać tytuł oknu | |
aby określić typ okna | |
w celu ustawienia czcionki tekstu w oknie | |
aby ustawić nazwę okna |
W tym przypadku ustalamy właściwości Text i Name:
Pola wprowadzania danych i przyciski – 1 | |
frmSaisiesBoutons |
![]() |
- [1]: wybierz zestaw narzędzi [Common Controls] spośród zestawów narzędzi oferowanych przez Visual Studio
- [2, 3, 4]: kliknij dwukrotnie kolejno na komponenty [Label], [Button] i [TextBox]
- [5]: wszystkie trzy komponenty znajdują się na formularzu
Aby prawidłowo wyrównać i dopasować rozmiar komponentów, można skorzystać z elementów paska narzędzi:
![]() | |||||||||
Zasada formatowania jest następująca:
- należy zaznaczyć różne elementy, które mają zostać sformatowane razem (trzymając wciśnięty klawisz Ctrl podczas klikania poszczególnych elementów)
- wybierz żądany rodzaj formatowania:
- (ciąg dalszy)
- opcje Align umożliwiają wyrównanie elementów do góry, do dołu, do lewej lub prawej strony oraz do środka
- opcje „Make Same Size” pozwalają ustawić dla elementów tę samą wysokość lub szerokość
- opcja „Horizontal Spacing” pozwala wyrównać elementy w poziomie, zachowując między nimi odstępy o tej samej szerokości. To samo dotyczy opcji „Vertical Spacing” służącej do wyrównywania w pionie.
- Opcja Center pozwala wyśrodkować komponent w poziomie (Horizontally) lub w pionie (Vertically) w oknie
Po umieszczeniu komponentów ustalamy ich właściwości. W tym celu należy kliknąć prawym przyciskiem myszy na komponent i wybrać opcję Properties:
![]() |
- [1]: należy zaznaczyć komponent, aby wyświetlić okno jego właściwości. W tym oknie należy zmodyfikować następujące właściwości: nazwa: labelSaisie, tekst: Saisie
- [2]: postępuj w ten sam sposób: nazwa: textBoxSaisie, tekst: nie wpisuj niczego
- [3]: nazwa: buttonAfficher, tekst: Afficher
- [4]: samo okno: nazwa: frmSaisiesBoutons, tekst: Pola wprowadzania danych i przyciski – 1
- [5]: uruchom (Ctrl-F5) projekt, aby uzyskać wstępny podgląd okna w działaniu.
To, co zostało wykonane w trybie [conception], zostało przełożone na kod w pliku [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;
}
}
- wiersze 53–55: trzy komponenty dały początek trzem polom prywatnym klasy [Form1]. Należy zauważyć, że nazwy tych pól są zgodne z nazwami nadanymi komponentom w trybie [conception]. Dotyczy to również formularza w wierszu 2, który jest samą klasą.
- wiersze 7–9: utworzono trzy obiekty typu [Label], [TextBox] i [Button]. To właśnie za ich pośrednictwem zarządzane są elementy wizualne.
- wiersze 14–19: konfiguracja etykiety labelSaisie
- wiersze 23–29: konfiguracja przycisku buttonAfficher
- wiersze 33–36: konfiguracja pola wprowadzania danych textBoxSaisie
- wiersze 40–47: konfiguracja formularza frmSaisiesBoutons. Warto zwrócić uwagę na wiersze 43–45, które pokazują, jak dodawać elementy do formularza.
Kod ten jest zrozumiały. Dzięki temu możliwe jest tworzenie formularzy za pomocą kodu bez korzystania z trybu [conception]. Wiele przykładów tego rozwiązania znajduje się w dokumentacji MSDN programu Visual Studio. Opanowanie tego kodu pozwala na tworzenie formularzy w trakcie wykonywania programu: na przykład na bieżąco utworzyć formularz umożliwiający aktualizację tabeli bazy danych, której struktura jest rozpoznawana dopiero w trakcie wykonywania.
Pozostaje nam jeszcze napisać procedurę obsługi kliknięcia przycisku Afficher. Zaznacz przycisk, aby uzyskać dostęp do jego okna właściwości. Okno to zawiera kilka zakładek:
![]() |
- [1]: lista właściwości w porządku alfabetycznym
- [2]: zdarzenia związane z kontrolką
Dostęp do właściwości i zdarzeń elementu sterującego można uzyskać według kategorii lub w porządku alfabetycznym:
- [3]: Właściwości lub zdarzenia według kategorii
- [4]: Właściwości lub zdarzenia w porządku alfabetycznym
Zakładka Events w trybie Catégories dla przycisku buttonAfficher wygląda następująco:
![]() |
- [1]: lewa kolumna okna zawiera listę możliwych zdarzeń związanych z przyciskiem. Kliknięcie przycisku odpowiada zdarzeniu Click.
- [2]: prawa kolumna zawiera nazwę procedury wywoływanej w momencie wystąpienia odpowiedniego zdarzenia.
- [3]: jeśli dwukrotnie klikniesz komórkę zdarzenia Click, nastąpi automatyczne przejście do okna kodu w celu napisania procedury obsługi zdarzenia Click dla przycisku 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) {
}
}
}
- wiersze 10–12: szkielet procedury obsługi zdarzenia Click dla przycisku o nazwie buttonAfficher. Należy zwrócić uwagę na następujące kwestie:
- metoda jest nazwana zgodnie ze schematem nomDuComposant_NomEvénement
- metoda jest prywatna. Przyjmuje dwa parametry:
- sender: jest to obiekt, który wywołał zdarzenie. Jeśli procedura jest wykonywana w wyniku kliknięcia przycisku o nazwie buttonAfficher, to sender będzie równe buttonAfficher. Można sobie wyobrazić, że procedura buttonAfficher_Click jest uruchamiana z poziomu innej procedury. Ta ostatnia miałaby wówczas pełną swobodę, aby jako pierwszy parametr podać wybrany przez siebie obiekt sender.
- EventArgs: obiekt zawierający informacje o zdarzeniu. W przypadku zdarzenia Click nie zawiera on żadnych danych. W przypadku zdarzenia związanego z ruchami myszy znajdziemy w nim współrzędne (X, Y) myszy.
- W tym przypadku nie będziemy korzystać z żadnego z tych parametrów.
Napisanie procedury obsługi zdarzenia polega na uzupełnieniu powyższego szkieletu kodu. W tym przypadku chcemy wyświetlić okno dialogowe zawierające treść pola textBoxSaisie, jeśli nie jest ono puste ([1]), a w przeciwnym razie komunikat o błędzie ([2]):
![]() |
Kod realizujący to zadanie mógłby wyglądać następująco:
private void buttonAfficher_Click(object sender, EventArgs e) {
// wyświetlany jest tekst, który został wpisany w 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);
}
Klasa MessageBox służy do wyświetlania komunikatów w oknie. Wykorzystaliśmy tutaj następującą metodę Show:
public static DialogResult Show(string text, string caption, MessageBoxButtons buttons, MessageBoxIcon icon);
wraz z
komunikatem do wyświetlenia | |
tytuł okna | |
przyciski w oknie | |
ikona w oknie |
Parametr buttons może przyjmować wartości spośród następujących stałych (z prefiksem MessageBoxButtons, jak pokazano w wierszu 7) powyżej:
przyciski | |
![]() | |
![]() | |
![]() | |
![]() | |
![]() | |
![]() |
Parametr icon może przyjmować wartości spośród następujących stałych (z prefiksem MessageBoxIcon, jak pokazano w wierszu 10) powyżej:
![]() | idem Stop | ||
to samo Ostrzeżenie | ![]() | ||
to samo jak Asterisk | ![]() | ||
![]() | to samo, ręcznie | ||
![]() |
Metoda Show jest metodą statyczną, która zwraca wynik typu [System.Windows.Forms.DialogResult], który jest wyliczeniem:

Aby dowiedzieć się, który przycisk został naciśnięty przez użytkownika w celu zamknięcia okna typu MessageBox, należy napisać:
7.1.2.2. Kod związany z obsługą zdarzeń
Oprócz funkcji buttonAfficher_Click, którą napisaliśmy, Visual Studio wygenerowało w metodzie InitializeComponents klasy [Form1.Designer.cs], która tworzy i inicjuje komponenty formularza, następujący wiersz:
this.buttonAfficher.Click += new System.EventHandler(this.buttonAfficher_Click);
Click jest zdarzeniem klasy Button [1, 2, 3]:
![]() |
- [5]: deklaracja zdarzenia [Control.Click] [4]. Widać zatem, że zdarzenie Click nie jest właściwe dla klasy [Button]. Należy ono do klasy [Control], która jest klasą nadrzędną klasy [Button].
- EventHandler jest prototypem (wzorem) metody zwanym delegatem. Wrócimy do tego później.
- event to słowo kluczowe, które ogranicza funkcjonalność klasy delegate oraz klasy EventHandler: obiekt delegate ma bogatszy zestaw funkcji niż obiekt event.
delegate EventHandler jest zdefiniowany w następujący sposób:
![]() |
delegate EventHandler oznacza wzorzec metody:
- którego pierwszym parametrem jest typ Object
- którego drugim parametrem jest typ EventArgs
- nie zwracający żadnego wyniku
Tak jest w przypadku metody obsługi kliknięcia przycisku buttonAfficher, która została wygenerowana przez Visual Studio:
private void buttonAfficher_Click(object sender, EventArgs e);
Zatem metoda buttonAfficher_Click odpowiada prototypowi zdefiniowanemu przez typ EventHandler. Aby utworzyć obiekt typu EventHandler, należy postępować w następujący sposób:
EventHandler evtHandler=new EventHandler(méthode correspondant au prototype défini par le type EventHandler);
Ponieważ metoda buttonAfficher_Click odpowiada prototypowi zdefiniowanemu przez typ EventHandler, można zapisać:
Zmienna typu delegate jest w rzeczywistości listą odwołań do metod typu delegate. Aby dodać nową metodę M do powyższej zmiennej evtHandler, należy użyć następującej składni:
Notację += można zastosować nawet wtedy, gdy evtHandler jest pustą listą.
Wróćmy do wiersza [InitializeComponent], który dodaje procedurę obsługi zdarzenia do zdarzenia Click obiektu buttonAfficher:
this.buttonAfficher.Click += new System.EventHandler(this.buttonAfficher_Click);
Ta instrukcja dodaje metodę typu EventHandler do listy metod pola buttonAfficher.Click. Metody te będą wywoływane za każdym razem, gdy wykryte zostanie zdarzenie Click w komponencie buttonAfficher. Często występuje tylko jedna. Nazywa się ją „obsługą zdarzenia”.
Wróćmy do sygnatury EventHandler:
private delegate void EventHandler(object sender, EventArgs e);
Drugim parametrem obiektu delegate jest obiekt typu EventArgs lub klasy od niego pochodnej. Typ EventArgs jest bardzo ogólny i w rzeczywistości nie dostarcza żadnych informacji na temat zdarzenia, które miało miejsce. W przypadku kliknięcia przycisku to wystarczy. W przypadku przesunięcia myszy po formularzu mielibyśmy zdarzenie MouseMove z klasy [Form] zdefiniowane przez:
Zdarzenie delegate MouseEventHandler jest zdefiniowane jako:
![]() |
Jest to funkcja delegowana (delegate) o sygnaturze void f (object, MouseEventArgs). Czy klasa MouseEventArgs jest zdefiniowana przez:
![]() |
Klasa MouseEventArgs ma bogatszy zakres funkcji niż klasa EventArgs. Można na przykład uzyskać współrzędne myszy X i Y w momencie wystąpienia zdarzenia.
7.1.2.3. Conclusion
Na podstawie dwóch analizowanych projektów można stwierdzić, że po utworzeniu interfejsu graficznego w programie Visual Studio praca programisty polega głównie na pisaniu procedur obsługi zdarzeń, które mają być obsługiwane przez ten interfejs. Kod jest generowany automatycznie przez Visual Studio. Kod ten, który może być złożony, można na początku pominąć. Późniejsze zapoznanie się z nim może jednak pozwolić na lepsze zrozumienie tworzenia i zarządzania formularzami.
7.2. Podstawowe komponenty
Przedstawiamy teraz różne aplikacje wykorzystujące najczęściej spotykane komponenty, aby zapoznać się z ich głównymi metodami i właściwościami. Dla każdej aplikacji prezentujemy interfejs graficzny oraz istotny kod, głównie ten dotyczący procedur obsługi zdarzeń.
7.2.1. Formularz Form
Zaczynamy od przedstawienia niezbędnego komponentu, jakim jest formularz, na którym umieszcza się inne komponenty. Omówiliśmy już niektóre z jego podstawowych właściwości. W tym miejscu skupimy się na kilku ważnych zdarzeniach związanych z formularzem.
formularz jest w trakcie ładowania | |
formularz jest w trakcie zamykania | |
formularz został zamknięty |
Zdarzenie Load ma miejsce jeszcze przed wyświetleniem formularza. Zdarzenie Closing ma miejsce podczas zamykania formularza. Proces ten można jeszcze zatrzymać programowo.
Tworzymy formularz o nazwie Form1 bez żadnych komponentów:
![]() |
- [1]: formularz
- [2]: trzy obsługiwane zdarzenia
Kod dla [Form1.cs] wygląda następująco:
using System;
using System.Windows.Forms;
namespace Chap5 {
public partial class Form1 : Form {
public Form1() {
InitializeComponent();
}
private void Form1_Load(object sender, EventArgs e) {
// początkowe ładowanie formularza
MessageBox.Show("Evt Load", "Load");
}
private void Form1_FormClosing(object sender, FormClosingEventArgs e) {
// formularz jest właśnie zamykany
MessageBox.Show("Evt FormClosing", "FormClosing");
// wymagane jest potwierdzenie
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) {
// formularz zostanie zamknięty
MessageBox.Show("Evt FormClosed", "FormClosed");
}
}
}
Korzystamy z funkcji MessageBox, aby otrzymywać powiadomienia o różnych zdarzeniach.
wiersz 10: Zdarzenie Load nastąpi przy uruchomieniu aplikacji, jeszcze przed wyświetleniem formularza: | ![]() |
wiersz 15: Zdarzenie FormClosing wystąpi, gdy użytkownik zamknie okno. | ![]() |
wiersz 19: Następnie pytamy go, czy na pewno chce opuścić aplikację: | ![]() |
wiersz 20: Jeśli odpowie „Nie”, ustawiamy właściwość Cancel zdarzenia CancelEventArgs, które metoda otrzymała jako jako parametr. Jeśli ustawimy tę właściwość na False, zamknięcie okna zostanie przerwane, w przeciwnym razie będzie kontynuowane. Nastąpi wówczas zdarzenie FormClosed: | ![]() |
7.2.2. Etykiety i pola wprowadzania danych TextBox
Z tymi dwoma komponentami mieliśmy już do czynienia. Label jest komponentem tekstowym, a TextBox – komponentem pola wprowadzania danych. Ich główną właściwością jest Text, która określa albo zawartość pola wprowadzania danych, albo tekst etykiety. Ta właściwość jest dostępna do odczytu i zapisu.
Zdarzeniem zwykle używanym dla TextBox jest TextChanged, które sygnalizuje, że użytkownik zmienił zawartość pola wprowadzania danych. Oto przykład wykorzystujący zdarzenie TextChanged do śledzenia zmian w polu wprowadzania danych:
![]() |
nr | typ | nazwa | rola |
1 | TextBox | textBoxSaisie | pole wprowadzania danych |
2 | Etykieta | labelControle | wyświetla tekst z pola 1 w czasie rzeczywistym AutoSize=False, Text=(brak) |
3 | Przycisk | buttonEffacer | w celu wyczyszczenia pól 1 i 2 |
4 | Przycisk | buttonQuitter | aby zamknąć aplikację |
Kod tej aplikacji wygląda następująco:
using System.Windows.Forms;
namespace Chap5 {
public partial class Form1 : Form {
public Form1() {
InitializeComponent();
}
private void textBoxSaisie_TextChanged(object sender, System.EventArgs e) {
// zawartość pola TextBox uległa zmianie – jest kopiowana do etykiety labelControle
labelControle.Text = textBoxSaisie.Text;
}
private void buttonEffacer_Click(object sender, System.EventArgs e) {
// usuwamy zawartość pola wprowadzania danych
textBoxSaisie.Text = "";
}
private void buttonQuitter_Click(object sender, System.EventArgs e) {
// kliknięcie przycisku „Wyjdź” – aplikacja zostaje zamknięta
Application.Exit();
}
private void Form1_Shown(object sender, System.EventArgs e) {
// ustawiamy fokus na polu wprowadzania danych
textBoxSaisie.Focus();
}
}
}
- wiersz 24: zdarzenie [Form].Shown ma miejsce, gdy formularz jest wyświetlany
- wiersz 26: następnie ustawia się fokus (w celu wprowadzenia danych) na komponencie textBoxSaisie.
- wiersz 9: zdarzenie [TextBox].TextChanged występuje za każdym razem, gdy zmienia się zawartość komponentu TextBox
- wiersz 11: kopiujemy zawartość komponentu [TextBox] do komponentu [Label]
- wiersz 14: obsługuje kliknięcie przycisku [Effacer]
- wiersz 16: umieszczamy pusty ciąg znaków w komponencie [TextBox]
- wiersz 19: obsługuje kliknięcie przycisku [Quitter]
- wiersz 21: służy do zatrzymania uruchomionej aplikacji. Przypomnijmy, że obiekt Application służy do uruchamiania aplikacji w metodzie [Main] klasy [Form1.cs]:
static void Main() {
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
Application.Run(new Form1());
}
W poniższym przykładzie wykorzystano wielowierszowy obiekt TextBox:
![]() |
Lista elementów kontrolnych jest następująca:
nr | typ | nazwa | rola |
1 | TextBox | textBoxLignes | pole wprowadzania wielowierszowego Multiline=true, ScrollBars=Both, AcceptReturn=True, AcceptTab=True |
2 | TextBox | textBoxLigne | pole wprowadzania danych jednowierszowe |
3 | Przycisk | buttonAjouter | Dodaj zawartość z 2 do 1 |
Aby pole TextBox stało się wielowierszowe, należy ustawić następujące właściwości kontrolki:
aby akceptował wiele wierszy tekstu | |
aby określić, czy kontrolka ma mieć paski przewijania (Horizontal, Vertical, Both), czy nie (None) | |
jeśli wartość wynosi „true”, naciśnięcie klawisza Enter spowoduje przejście do następnej linii | |
jeśli wartość wynosi „true”, naciśnięcie klawisza Tab spowoduje wstawienie tabulatora w tekście |
Aplikacja umożliwia wpisywanie wierszy bezpośrednio w polu [1] lub dodawanie ich za pomocą pól [2] i [3].
Kod aplikacji wygląda następująco:
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) {
// dodaj zawartość pola textBoxLigne do zawartości pola textBoxLignes
textBoxLignes.Text += textBoxLigne.Text+Environment.NewLine;
textBoxLigne.Text = "";
}
private void Form1_Shown(object sender, EventArgs e) {
// ustawianie fokusu na polu wprowadzania danych
textBoxLigne.Focus();
}
}
}
- wiersz 18: po wyświetleniu formularza (ewentualnie Shown) fokus jest umieszczany na polu wprowadzania danych textBoxLigne
- wiersz 10: obsługuje kliknięcie przycisku [Ajouter]
- wiersz 12: tekst z pola wprowadzania danych textBoxLigne jest dodawany do tekstu w polu wprowadzania danych textBoxLignes, po czym następuje znak końca wiersza.
- wiersz 13: pole wprowadzania danych textBoxLigne zostaje wyczyszczone
7.2.3. Listy rozwijane ComboBox
Tworzymy następujący formularz:
![]() |
nr | typ | nazwa | rola |
1 | ComboBox | comboNombres | zawiera ciągi znaków DropDownStyle=DropDownList |
Komponent ComboBox to lista rozwijana połączona z polem wprowadzania danych: użytkownik może albo wybrać element z (2), albo wpisać tekst w (1). Istnieją trzy rodzaje elementów ComboBox, określane przez właściwość DropDownStyle:
lista bez rozwijania z polem edycji | |
lista rozwijana z polem edycji | |
lista rozwijana bez pola edycji |
Domyślnie typ obiektu ComboBox to DropDown.
Klasa ComboBox posiada tylko jeden konstruktor:
tworzy pustą listę rozwijaną |
Elementy typu ComboBox są dostępne we właściwości Items:
Jest to właściwość indeksowana, gdzie Items[i] oznacza element i listy rozwijanej. Jest to właściwość tylko do odczytu.
Niech C będzie listą rozwijaną, a C.Items jej listą elementów. Mamy następujące właściwości:
liczba elementów listy rozwijanej | |
element i listy rozwijanej | |
dodaje obiekt o jako ostatni element listy rozwijanej | |
dodaje tablicę obiektów na końcu listy rozwijanej | |
dodaje obiekt o na pozycji i listy rozwijanej | |
usuwa element i z listy rozwijanej | |
usuwa obiekt o z listy rozwijanej | |
usuwa wszystkie elementy z listy rozwijanej | |
zwraca pozycję i obiektu o w polu rozwijanym | |
indeks wybranego elementu | |
wybrany element | |
wyświetlany tekst wybranego elementu | |
wyświetlany tekst wybranego elementu |
Może budzić zdziwienie fakt, że pole kombi może zawierać obiekty, podczas gdy wizualnie wyświetla ciągi znaków. Jeśli ComboBox zawiera obiekt obj, wyświetla ciąg znaków obj.ToString(). Należy pamiętać, że każdy obiekt posiada metodę ToString odziedziczoną po klasie object, która zwraca ciąg znaków „reprezentujący” ten obiekt.
Elementem Item wybranym z listy rozwijanej C jest C.SelectedItem lub C.Items[C.SelectedIndex], gdzie C.SelectedIndex to numer wybranego elementu, przy czym numer ten zaczyna się od zera dla pierwszego elementu. Wybrany tekst można uzyskać na różne sposoby: C.SelectedItem.Text, C.Text
Podczas wyboru elementu z listy rozwijanej występuje zdarzenie SelectedIndexChanged, które można następnie wykorzystać do otrzymania powiadomienia o zmianie wyboru w polu kombi. W poniższej aplikacji wykorzystujemy to zdarzenie do wyświetlenia elementu, który został wybrany z listy.
![]() |
Kod aplikacji wygląda następująco:
using System.Windows.Forms;
namespace Chap5 {
public partial class Form1 : Form {
private int previousSelectedIndex=0;
public Form1() {
InitializeComponent();
// wypełnienie listy rozwijanej
comboBoxNombres.Items.AddRange(new string[] { "zéro", "un", "deux", "trois", "quatre" });
// wybór elementu nr 0
comboBoxNombres.SelectedIndex = 0;
}
private void comboBoxNombres_SelectedIndexChanged(object sender, System.EventArgs e) {
int newSelectedIndex = comboBoxNombres.SelectedIndex;
if (newSelectedIndex != previousSelectedIndex) {
// wybrany element uległ zmianie – wyświetlamy go
MessageBox.Show(string.Format("Elément sélectionné : ({0},{1})", comboBoxNombres.Text, newSelectedIndex), "Combo", MessageBoxButtons.OK, MessageBoxIcon.Information);
// zapisujemy nowy indeks
previousSelectedIndex = newSelectedIndex;
}
}
}
}
- wiersz 5: previousSelectedIndex zapisuje ostatni wybrany indeks z listy rozwijanej
- wiersz 10: wypełnienie listy rozwijanej tablicą ciągów znaków
- wiersz 12: wybierany jest pierwszy element
- wiersz 15: metoda wykonywana za każdym razem, gdy użytkownik wybiera element z listy rozwijanej. Wbrew temu, co mogłaby sugerować nazwa zdarzenia, ma ono miejsce nawet wtedy, gdy wybrany element jest taki sam jak poprzedni.
- wiersz 16: zapisujemy indeks wybranego elementu
- wiersz 17: jeśli różni się od poprzedniego
- wiersz 19: wyświetlany jest numer i tekst wybranego elementu
- wiersz 21: zapisujemy nowy indeks
7.2.4. Komponent ListBox
Proponujemy stworzyć następujący interfejs:
![]() |
Elementy tego okna są następujące:
nr | typ | nazwa | rola/właściwości |
0 | Form | Form1 | formularz FormBorderStyle=FixedSingle (ramka o stałym rozmiarze) |
1 | TextBox | textBoxSaisie | pole wprowadzania danych |
2 | Przycisk | buttonAjouter | przycisk umożliwiający dodanie zawartości pola wprowadzania danych [1] do listy [3] |
3 | ListBox | listBox1 | lista 1 SelectionMode=MultiExtended: |
4 | ListBox | listBox2 | lista 2 SelectionMode=MultiSimple: |
5 | Przycisk | przycisk1do2 | przenosi zaznaczone elementy z listy 1 do listy 2 |
6 | Przycisk | button2vers1 | wykonuje odwrotną operację |
7 | Przycisk | buttonEffacer1 | czyści listę 1 |
8 | Przycisk | buttonEffacer2 | wyczyść listę 2 |
Komponenty ListBox posiadają tryb wyboru swoich elementów, który jest definiowany przez ich właściwość SelectionMode:
można zaznaczyć tylko jeden element | |
możliwy jest wybór wielu elementów: przytrzymanie klawisza SHIFT i kliknięcie na elemencie rozszerza zaznaczenie z poprzednio zaznaczonego elementu na bieżący element. | |
możliwy jest wielokrotny wybór: element jest zaznaczany/odznaczany poprzez kliknięcie myszą lub naciśnięcie klawisza spacji. |
- Użytkownik wpisuje tekst w polu 1. Dodaje go do listy 1 za pomocą przycisku Ajouter (2). Pole wprowadzania danych (1) zostaje wówczas wyczyszczone, a użytkownik może dodać nowy element.
- Może przenosić elementy z jednej listy do drugiej, zaznaczając element do przeniesienia na jednej z list i wybierając odpowiedni przycisk przenoszenia 5 lub 6. Przeniesiony element jest dodawany na koniec listy docelowej i usuwany z listy źródłowej.
- Może dwukrotnie kliknąć element z listy 1. Element ten zostanie wówczas przeniesiony do pola wprowadzania danych w celu edycji i usunięty z listy 1.
Przyciski są podświetlone lub niepodświetlone zgodnie z następującymi zasadami:
- przycisk Ajouter jest podświetlony tylko wtedy, gdy w polu wprowadzania danych znajduje się tekst inny niż pusty
- przycisk [5] służący do przenoszenia z listy 1 do listy 2 jest podświetlony tylko wtedy, gdy w liście 1 jest zaznaczony jakiś element
- przycisk [6] służący do przenoszenia z listy 2 do listy 1 jest podświetlony tylko wtedy, gdy w liście 2 jest zaznaczony jakiś element
- przyciski [7] i [8] służące do czyszczenia listy 1 i listy 2 są podświetlone tylko wtedy, gdy lista, która ma zostać wyczyszczona, zawiera elementy.
W powyższych warunkach wszystkie przyciski muszą być wyłączone podczas uruchamiania aplikacji. Należy wówczas ustawić właściwość Enabled przycisków na wartość false. Można to zrobić na etapie projektowania, co spowoduje wygenerowanie odpowiedniego kodu w metodzie InitializeComponent, lub wykonać to samodzielnie w konstruktorze, jak pokazano poniżej:
public Form1() {
InitializeComponent();
// --- dodatkowe inicjalizacje ---
// wyłączamy pewną liczbę przycisków
buttonAjouter.Enabled = false;
button1vers2.Enabled = false;
button2vers1.Enabled = false;
buttonEffacer1.Enabled = false;
buttonEffacer2.Enabled = false;
}
Stan przycisku Ajouter jest kontrolowany przez zawartość pola wprowadzania danych. To zdarzenie TextChanged pozwala nam śledzić zmiany tej zawartości:
private void textBoxSaisie_TextChanged(object sender, System.EventArgs e) {
// zmień się zawartość textBoxSaisie
// przycisk „Dodaj” jest podświetlony tylko wtedy, gdy pole nie jest puste
buttonAjouter.Enabled = textBoxSaisie.Text.Trim() != "";
}
Stan przycisków przesyłania zależy od tego, czy w liście, którą kontrolują, został wybrany jakiś element:
private void listBox1_SelectedIndexChanged(object sender, System.EventArgs e) {
// zaznaczono element
// włączamy przycisk przeniesienia z 1 do 2
button1vers2.Enabled = true;
}
private void listBox2_SelectedIndexChanged(object sender, System.EventArgs e) {
// zaznaczono element
// włączany jest przycisk przeniesienia z 2 do 1
button2vers1.Enabled = true;
}
Kod powiązany z kliknięciem przycisku Ajouter jest następujący:
private void buttonAjouter_Click(object sender, System.EventArgs e) {
// dodano nowy element do listy 1
listBox1.Items.Add(textBoxSaisie.Text.Trim());
// wyczyszczenie wpisu
textBoxSaisie.Text = "";
// Lista 1 nie jest pusta
buttonEffacer1.Enabled = true;
// przeniesienie fokusu z powrotem na pole wprowadzania danych
textBoxSaisie.Focus();
}
Warto zwrócić uwagę na metodę Focus, która pozwala ustawić „fokus” na elemencie formularza. Kod powiązany z kliknięciem przycisków Effacer:
private void buttonEffacer1_Click(object sender, System.EventArgs e) {
// listę 1 wyczytano
listBox1.Items.Clear();
// przycisk „Wyczyść”
buttonEffacer1.Enabled = false;
}
private void buttonEffacer2_Click(object sender, System.EventArgs e) {
// listę 2 wyczytano
listBox2.Items.Clear();
// przycisk „Wyczyść”
buttonEffacer2.Enabled = false;
}
Kod służący do przenoszenia zaznaczonych elementów z jednej listy do drugiej:
private void button1vers2_Click(object sender, System.EventArgs e) {
// przeniesienie wybranego elementu z listy 1 do listy 2
transfert(listBox1, button1vers2, buttonEffacer1, listBox2, button2vers1, buttonEffacer2);
}
private void button2vers1_Click(object sender, System.EventArgs e) {
// przeniesienie wybranego elementu z listy 2 do listy 1
transfert(listBox2, button2vers1, buttonEffacer2, listBox1, button1vers2, buttonEffacer1);
}
Obie powyższe metody przekazują przenoszenie zaznaczonych elementów z jednej listy do drugiej tej samej prywatnej metodzie o nazwie „transfer”:
// przeniesienie
private void transfert(ListBox l1, Button button1vers2, Button buttonEffacer1, ListBox l2, Button button2vers1, Button buttonEffacer2) {
// przeniesienie elementów zaznaczonych na liście l1 do listy l2
for (int i = l1.SelectedIndices.Count - 1; i >= 0; i--) {
// indeks wybranego elementu
int index = l1.SelectedIndices[i];
// dodanie do l2
l2.Items.Add(l1.Items[index]);
// usunięcie z l1
l1.Items.RemoveAt(index);
}
// przyciski „Usuń”
buttonEffacer2.Enabled = l2.Items.Count != 0;
buttonEffacer1.Enabled = l1.Items.Count != 0;
// przyciski przenoszenia
button1vers2.Enabled = false;
}
- wiersz b: metoda „transfert” przyjmuje sześć parametrów:
- odwołanie do listy zawierającej zaznaczone elementy, nazywanej tutaj l1. Podczas działania aplikacji l1 to albo listBox1, albo listBox2. Przykłady wywołań można zobaczyć w wierszach 3 i 8 procedur transferu buttonXversY_Click.
- odwołanie do przycisku transferu powiązanego z listą l1. Na przykład, jeśli l1 to listBox2, będzie to button2vers1 (por. wywołanie w wierszu 8)
- odwołanie na przycisk kasowania listy l1. Na przykład, jeśli l1 to listBox1, będzie to buttonEffacer1 (patrz wywołanie w wierszu 3)
- trzy pozostałe odniesienia działają analogicznie, ale odnoszą się do listy l2.
- wiersz d: zbiór [ListBox].SelectedIndices przedstawia indeksy elementów wybranych w komponencie [ListBox]. Jest to zbiór:
- [ListBox].SelectedIndices.Count to liczba elementów tej kolekcji
- [ListBox].SelectedIndices[i] to element nr i tej kolekcji
Przeglądamy kolekcję w odwrotnym kierunku: zaczynamy od końca kolekcji, a kończymy na początku. Wyjaśnimy, dlaczego.
- wiersz f: indeks wybranego elementu z listy l1
- wiersz h: ten element zostaje dodany do listy l2
- wiersz j: i usunięty z listy l1. Ponieważ został usunięty, nie jest już zaznaczony. Kolekcja l1.SelectedIndices z wiersza d zostanie ponownie obliczona. Straci element, który właśnie został usunięty. Wszystkie elementy znajdujące się po nim otrzymają numery zmniejszone z n do n-1.
- jeśli pętla w wierszu (d) przebiega rosnąco i właśnie przetworzyła element nr 0, to następnie przetworzy element nr 1. Jednak element, który przed usunięciem elementu nr 0 miał numer 1, będzie teraz miał numer 0. Zostanie on wówczas pominięty przez pętlę.
- Jeśli pętla w wierszu (d) przebiega w porządku malejącym i właśnie przetworzyła element o numerze n, to następnie przetworzy element o numerze n-1. Po usunięciu elementu o numerze n element o numerze n-1 nie zmienia swojego numeru. Zostanie więc przetworzony w następnej iteracji pętli.
- wiersze m–n: stan przycisków [Effacer] zależy od tego, czy w powiązanych listach znajdują się elementy
- wiersz p: lista l2 nie ma już żadnych zaznaczonych elementów: wyłączamy jej przycisk przeniesienia.
7.2.5. Pola wyboru CheckBox, przyciski opcji ButtonRadio
Proponujemy napisać następującą aplikację:
![]() |
Elementy okna są następujące:
nr | typ | nazwa | rola |
1 | GroupBox patrz [6] | groupBox1 | kontener komponentów. Można w nim umieścić inne komponenty. Text=Przyciski opcji |
2 | RadioButton | radioButton1 radioButton2 radioButton3 | 3 przyciski opcji – radioButton1 ma właściwość Checked=True oraz właściwość Text=1 - radioButton2 ma właściwość Text=2 - radioButton3 ma właściwość Text=3 Przyciski opcji znajdujące się w tym samym kontenerze, w tym przypadku GroupBox, wykluczają się wzajemnie: tylko jeden z nich jest zaznaczony. |
3 | GroupBox | groupBox2 | |
4 | CheckBox | checkBox1 checkBox2 checkBox3 | 3 pola wyboru. chechBox1 ma właściwość Checked=True oraz właściwość Text=A - chechBox2 ma właściwość Text=B - chechBox3 ma właściwość Text=C |
5 | ListBox | listBoxValeurs | lista, która wyświetla wartości przycisków opcji i pól wyboru natychmiast po wprowadzeniu zmiany. |
6 | wskazuje, gdzie znajduje się kontener GroupBox |
W przypadku tych sześciu elementów sterujących interesuje nas zdarzenie CheckChanged, wskazujące, że stan pola wyboru lub przycisku opcji uległ zmianie. Stan ten jest w obu przypadkach reprezentowany przez właściwość logiczną Checked, której wartość „prawda” oznacza, że element sterujący jest zaznaczony. W tym przypadku wykorzystamy tylko jedną metodę do obsługi wszystkich sześciu zdarzeń CheckChanged – metodę affiche. Aby zapewnić, że wszystkie sześć zdarzeń CheckChanged będzie obsługiwanych przez tę samą metodę affiche, można postępować w następujący sposób:
Wybierzmy komponent radioButton1 i kliknijmy na nim prawym przyciskiem myszy, aby uzyskać dostęp do jego właściwości:
![]() |
W zakładce événements [1] przypisujemy metodę affiche [2] do zdarzenia CheckChanged. Oznacza to, że kliknięcie opcji A1 ma być obsługiwane przez metodę o nazwie affiche. Visual Studio automatycznie generuje metodę affiche w oknie kodu:
private void affiche(object sender, EventArgs e) {
}
Metoda affiche jest metodą typu EventHandler.
W przypadku pozostałych pięciu komponentów postępujemy w ten sam sposób. Wybierzmy na przykład opcję CheckBox1 i jej zdarzenia [3]. Obok zdarzenia Click znajduje się lista rozwijana [4], w której znajdują się istniejące metody mogące obsłużyć to zdarzenie. W tym przypadku dostępna jest tylko metoda affiche.. Wybieramy ją. Powtarzamy ten proces dla wszystkich pozostałych komponentów.
W metodzie InitializeComponent wygenerowano kod. Metoda affiche została zadeklarowana jako obsługująca sześć zdarzeń CheckedChanged w następujący sposób:
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);
Metoda affiche została uzupełniona w następujący sposób:
private void affiche(object sender, System.EventArgs e) {
// wyświetla stan przycisku opcji lub pola wyboru
// czy to pole wyboru?
if (sender is CheckBox) {
CheckBox chk = (CheckBox)sender;
listBoxvaleurs.Items.Add(chk.Name + "=" + chk.Checked);
}
// czy to przycisk opcji?
if (sender is RadioButton) {
RadioButton rdb = (RadioButton)sender;
listBoxvaleurs.Items.Add(rdb.Name + "=" + rdb.Checked);
}
}
Składnia
if (sender is CheckBox) {
pozwala sprawdzić, czy obiekt sender jest typu CheckBox. Dzięki temu możemy następnie dokonać konwersji typu na dokładny typ sender. Metoda affiche zapisuje na liście listBoxValeurs nazwę komponentu, który wywołał zdarzenie, oraz wartość jego właściwości Checked. Podczas wykonywania [7] widać, że kliknięcie przycisku opcji powoduje dwa zdarzenia CheckChanged: jedno dotyczące poprzedniego przycisku zaznaczonego, który przechodzi w stan „niezaznaczony”, a drugie dotyczące nowego przycisku, który przechodzi w stan „zaznaczony”.
7.2.6. Regulatory ScrollBar
Istnieje kilka rodzajów regulatorów: regulator poziomy (HscrollBar), regulator pionowy (VscrollBar), inkrementator (NumericUpDown). |
Zrealizujmy następującą aplikację:
![]() |
nr | typ | nazwa | rola |
1 | hScrollBar | hScrollBar1 | przesuwacz poziomy |
2 | hScrollBar | hScrollBar2 | regulacja pozioma, która podąża za zmianami regulacji 1 |
3 | Etykieta | labelValeurHS1 | wyświetla wartość regulatora poziomego |
4 | NumericUpDown | numericUpDown2 | umożliwia ustawienie wartości suwaka 2 |
Suwak ScrollBar pozwala użytkownikowi wybrać wartość z zakresu liczb całkowitych, symbolizowanego przez „pasek” suwaka, po którym porusza się kursor. Wartość suwaka jest dostępna w jego właściwości Value.
- W przypadku suwaka poziomego lewy koniec reprezentuje minimalną wartość zakresu, prawy koniec – wartość maksymalną, a suwak – aktualnie wybraną wartość. W przypadku suwaka pionowego minimum reprezentuje górny koniec, a maksimum – dolny koniec. Wartości te są reprezentowane przez właściwości Minimum i Maximum, a ich wartości domyślne to 0 i 100.
- Kliknięcie na skrajne punkty suwaka powoduje zmianę wartości o jeden przyrost (dodatni lub ujemny) w zależności od klikniętego skrajnego punktu o nazwie SmallChange, którego domyślna wartość wynosi 1.
- Kliknięcie po obu stronach suwaka powoduje zmianę wartości o jeden przyrost (dodatni lub ujemny) w zależności od klikniętego końca o nazwie LargeChange, którego domyślna wartość wynosi 10.
- Po kliknięciu górnego końca suwaka pionowego jego wartość maleje. Może to zaskoczyć przeciętnego użytkownika, który zazwyczaj oczekuje, że wartość będzie „rosła”. Problem ten rozwiązuje się, nadając wartości ujemne właściwościom SmallChange i LargeChange
- Te pięć właściwości (Value, Minimum, Maximum, SmallChange, LargeChange) są dostępne do odczytu i zapisu.
- Głównym zdarzeniem falownika jest to, które sygnalizuje zmianę wartości: zdarzenie Scroll.
W pobliżu falownika znajduje się komponent NumericUpDown: posiada on również właściwości Minimum, Maximum i Value, których wartości domyślne to 0, 100, 0. Jednak w tym przypadku właściwość Value jest wyświetlana w polu wprowadzania danych, które stanowi integralną część kontrolki. Użytkownik może samodzielnie zmienić tę wartość, chyba że właściwość ReadOnly kontrolki została ustawiona na wartość „true”. Wartość przyrostu jest ustalana przez właściwość Increment, domyślnie 1. Głównym zdarzeniem komponentu NumericUpDown jest to, które sygnalizuje zmianę wartości: zdarzenie ValueChanged
Kod aplikacji wygląda następująco:
using System.Windows.Forms;
namespace Chap5 {
public partial class Form1 : Form {
public Form1() {
InitializeComponent();
// ustawiamy parametry regulatora 1
hScrollBar1.Value = 7;
hScrollBar1.Minimum = 1;
hScrollBar1.Maximum = 130;
hScrollBar1.LargeChange = 11;
hScrollBar1.SmallChange = 1;
// nadajemy regulatorowi 2 takie same parametry jak regulatorowi 1
hScrollBar2.Value = hScrollBar1.Value;
hScrollBar2.Minimum = hScrollBar1.Minimum;
hScrollBar2.Maximum = hScrollBar1.Maximum;
hScrollBar2.LargeChange = hScrollBar1.LargeChange;
hScrollBar2.SmallChange = hScrollBar1.SmallChange;
// to samo dotyczy licznika
numericUpDown2.Value = hScrollBar1.Value;
numericUpDown2.Minimum = hScrollBar1.Minimum;
numericUpDown2.Maximum = hScrollBar1.Maximum;
numericUpDown2.Increment = hScrollBar1.SmallChange;
// przypisuje się etykiecie wartość napędu 1
labelValeurHS1.Text = hScrollBar1.Value.ToString();
}
private void hScrollBar1_Scroll(object sender, ScrollEventArgs e) {
// zmiana wartości napędu 1
// przenosimy tę wartość na falownik 2 i na etykietę
hScrollBar2.Value = hScrollBar1.Value;
labelValeurHS1.Text = hScrollBar1.Value.ToString();
}
private void numericUpDown2_ValueChanged(object sender, System.EventArgs e) {
// inkrementator zmienił wartość
// ustala się wartość regulatora 2
hScrollBar2.Value = (int)numericUpDown2.Value;
}
}
}
7.3. Zdarzenia myszy
Podczas rysowania w kontenerze ważne jest poznanie pozycji myszy, aby na przykład wyświetlić punkt po kliknięciu. Przesunięcia myszy powodują zdarzenia w kontenerze, w którym się porusza.
![]() |
- [1]: zdarzenia występujące podczas przesuwania myszy po formularzu lub elemencie sterującym
- [2]: zdarzenia występujące podczas przeciągania i upuszczania (Drag'nDrop)
mysz właśnie weszła w obszar kontrolki | |
mysz właśnie opuściła obszar kontrolki | |
mysz porusza się w obszarze kontrolnym | |
Naciśnięcie lewego przycisku myszy | |
Zwolnienie lewego przycisku myszy | |
użytkownik upuszcza obiekt na element sterujący | |
użytkownik wchodzi w obszar elementu sterującego, przeciągając obiekt | |
użytkownik opuszcza obszar kontrolki, przeciągając obiekt | |
użytkownik najeżdża kursorem na obszar kontrolki, przeciągając obiekt |
Oto aplikacja pozwalająca lepiej zrozumieć, w jakich momentach występują różne zdarzenia myszy:
![]() |
nr | typ | nazwa | rola |
1 | Etykieta | lblPositionSouris | aby wyświetlić położenie myszy w formularzu 1, liście 2 lub przycisku 3 |
2 | ListBox | listBoxEvts | aby wyświetlić zdarzenia myszy inne niż MouseMove |
3 | Przycisk | buttonEffacer | aby wyczyścić zawartość elementu 2 |
Aby śledzić ruchy myszy na trzech elementach sterujących, piszemy tylko jedną procedurę obsługi, procedurę affiche:
![]() |
Kod procedury affiche jest następujący:
private void affiche(object sender, MouseEventArgs e) {
// ruch myszy – wyświetlane są jej współrzędne (X, Y)
labelPositionSouris.Text = "(" + e.X + "," + e.Y + ")";
}
Za każdym razem, gdy kursor myszy wchodzi w obszar elementu sterującego, zmienia się jego układ współrzędnych. Jego punkt początkowy (0,0) to lewy górny róg elementu sterującego, na którym się znajduje. W ten sposób podczas wykonywania programu, gdy przesuwamy kursor myszy z formularza na przycisk, wyraźnie widać zmianę współrzędnych. Aby lepiej dostrzec te zmiany obszaru działania myszy, można skorzystać z właściwości Cursor [1] elementów sterujących:
![]() |
Właściwość ta pozwala ustawić kształt kursora myszy, gdy mysz wchodzi w obszar kontrolki. W naszym przykładzie ustawiliśmy więc kursor na „Default” dla samego formularza [2], na „Hand” dla listy 2 [3] oraz na „Cross” dla przycisku 3 [4].
Ponadto, aby wykrywać wejście i wyjście myszy z listy 2, obsługujemy zdarzenia MouseEnter i MouseLeave tej samej listy:
private void listBoxEvts_MouseEnter(object sender, System.EventArgs e) {
// zgłaszane jest zdarzenie
listBoxEvts.Items.Insert(0, string.Format("MouseEnter à {0:hh:mm:ss}",DateTime.Now));
}
private void listBoxEvts_MouseLeave(object sender, EventArgs e) {
// zgłaszane jest zdarzenie
listBoxEvts.Items.Insert(0, string.Format("MouseLeave à {0:hh:mm:ss}", DateTime.Now));
}
Aby obsłużyć kliknięcia w formularzu, przetwarzamy zdarzenia MouseDown i MouseUp:
private void listBoxEvts_MouseDown(object sender, MouseEventArgs e) {
// zgłaszamy zdarzenie
listBoxEvts.Items.Insert(0, string.Format("MouseDown à {0:hh:mm:ss}", DateTime.Now));
}
private void listBoxEvts_MouseUp(object sender, MouseEventArgs e) {
// zgłaszane jest zdarzenie
listBoxEvts.Items.Insert(0, string.Format("MouseUp à {0:hh:mm:ss}", DateTime.Now));
}
- wiersze 3 i 8: komunikaty są umieszczane na pierwszej pozycji w ListBox, aby najnowsze zdarzenia znajdowały się na początku listy.
![]() |
Na koniec kod obsługi kliknięcia przycisku Effacer:
private void buttonEffacer_Click(object sender, EventArgs e) {
listBoxEvts.Items.Clear();
}
7.4. Tworzenie okna z menu
Zobaczmy teraz, jak utworzyć okno z menu. Utworzymy następujące okno:
![]() |
Aby utworzyć menu, wybieramy komponent „MenuStrip” z paska „Menus & Toolbars”:
![]() |
- [1]: wybór komponentu [MenuStrip]
- [2]: w ten sposób otrzymujemy menu, które pojawia się w formularzu z pustymi polami o nazwie „Type Here”. Wystarczy wpisać w nich różne opcje menu.
- [3]: wpisano nazwę „Opcje A”. Przechodzimy do nazwy [4].
- [5]: wprowadzono nazwy opcji A. Przechodzimy do nazwy [6]
![]() |
- [6]: pierwsze opcje B
- [7]: pod B1 umieszczamy separator. Jest on dostępny w polu rozwijanym powiązanym z tekstem „Type Here”
- [8]: aby utworzyć podmenu, należy użyć strzałki [8] i wpisać nazwę podmenu w polu [9]
Pozostaje jeszcze nazwać poszczególne elementy formularza:
![]() |
nr | typ | nazwa(y) | rola |
1 | Etykieta | labelStatut | aby wyświetlić tekst klikniętej opcji menu |
2 | toolStripMenuItem | toolStripMenuItemOptionsA toolStripMenuItemA1 toolStripMenuItemA2 toolStripMenuItemA3 | opcje menu w ramach głównej opcji „Opcje A” |
3 | toolStripMenuItem | toolStripMenuItemOptionsB toolStripMenuItemB1 toolStripMenuItemB2 toolStripMenuItemB3 | opcje menu w ramach głównej opcji „Opcje B” |
4 | toolStripMenuItem | toolStripMenuItemB31 toolStripMenuItemB32 | opcje menu pod opcją główną „B3” |
Opcje menu są elementami sterującymi, podobnie jak inne komponenty wizualne, i posiadają właściwości oraz zdarzenia. Na przykład właściwości opcji menu A1 są następujące:
![]() |
W naszym przykładzie wykorzystano dwie właściwości:
nazwa elementu sterującego menu | |
nazwa opcji menu |
W strukturze menu wybierzmy opcję A1 i kliknijmy prawym przyciskiem myszy, aby uzyskać dostęp do właściwości kontrolki:
![]() |
W zakładce événements [1] przypisujemy metodę affiche [2] do zdarzenia Click. Oznacza to, że kliknięcie opcji A1 ma być obsługiwane przez metodę o nazwie affiche. Visual Studio automatycznie generuje metodę affiche w oknie kodu:
private void affiche(object sender, EventArgs e) {
}
W tej metodzie ograniczymy się do wyświetlenia w etykiecie labelStatut właściwości Text opcji menu, która została kliknięta:
private void affiche(object sender, EventArgs e) {
// wyświetla w TextBox nazwę wybranego podmenu
labelStatut.Text = ((ToolStripMenuItem)sender).Text;
}
Źródło zdarzenia sender jest typu object. Opcje menu są typu ToolStripMenuItem, dlatego konieczne jest przekształcenie typu z object na ToolStripMenuItem.
Dla wszystkich opcji menu ustalamy obsługę kliknięcia na metodę affiche [3,4].
Uruchommy aplikację i wybierzmy pozycję menu:
![]() | ![]() |
7.5. Komponenty niewidoczne
Zajmiemy się teraz kilkoma komponentami niewizualnymi: wykorzystuje się je podczas projektowania, ale nie są one widoczne podczas działania aplikacji.
7.5.1. Okna dialogowe , OpenFileDialog oraz SaveFileDialog
Stworzymy następującą aplikację:
![]() |
Elementy sterujące są następujące:
Nr | typ | nazwa | rola |
1 | TextBox | TextBoxLignes | tekst wpisany przez użytkownika lub załadowany z pliku MultiLine=True, ScrollBars=Both, AccepReturn=True, AcceptTab=True |
2 | Przycisk | buttonSauvegarder | umożliwia zapisanie tekstu z [1] w pliku tekstowym |
3 | Przycisk | buttonCharger | umożliwia wczytanie zawartości pliku tekstowego do [1] |
4 | Przycisk | buttonEffacer | usuwa zawartość pliku [1] |
5 | SaveFileDialog | saveFileDialog1 | komponent umożliwiający wybór nazwy i lokalizacji pliku kopii zapasowej dla [1]. Komponent ten jest pobierany z paska narzędzi [7] i po prostu umieszczany na formularzu. Zostaje on wówczas zapisany, ale nie zajmuje miejsca na formularzu. Jest to komponent niewidoczny. |
6 | OpenFileDialog | openFileDialog1 | Komponent umożliwiający wybór pliku do załadowania do [1]. |
Kod powiązany z przyciskiem Effacer jest prosty:
private void buttonEffacer_Click(object sender, EventArgs e) {
// wstawia pusty ciąg znaków do TexBox
textBoxLignes.Text = "";
}
Będziemy korzystać z następujących właściwości i metod klasy SaveFileDialog:
Pole | Typ | Rola |
Propriété | typy plików dostępne na liście rozwijanej typów plików w oknie dialogowym | |
Propriété | numer typu pliku proponowanego domyślnie na powyższej liście. Zaczyna się od 0. | |
Propriété | folder pierwotnie wskazany do zapisania pliku | |
Propriété | nazwa pliku kopii zapasowej podana przez użytkownika | |
Méthode | metoda wyświetlająca okno dialogowe zapisu. Zwraca wynik typu DialogResult. |
Metoda ShowDialog wyświetla okno dialogowe podobne do poniższego:
![]() |
lista rozwijana utworzona na podstawie właściwości Filter. Domyślny typ pliku jest ustalany przez FilterIndex | |
bieżący katalog, określony przez InitialDirectory, jeśli ta właściwość została wypełniona | |
nazwa pliku wybrana lub wpisana bezpośrednio przez użytkownika. Będzie dostępna we właściwości FileName | |
przyciski Zapisz/Anuluj. Jeśli używany jest przycisk Enregistrer, funkcja ShowDialog zwraca wynik DialogResult.OK |
Procedura zapisywania może wyglądać następująco:
private void buttonSauvegarder_Click(object sender, System.EventArgs e) {
// zapisuje pole wprowadzania danych w pliku tekstowym
// konfiguruje się okno dialogowe savefileDialog1
saveFileDialog1.InitialDirectory = Application.ExecutablePath;
saveFileDialog1.Filter = "Fichiers texte (*.txt)|*.txt|Tous les fichiers (*.*)|*.*";
saveFileDialog1.FilterIndex = 0;
// wyświetla się okno dialogowe i pobierany jest jego wynik
if (saveFileDialog1.ShowDialog() == DialogResult.OK) {
// pobieramy nazwę pliku
string nomFichier = saveFileDialog1.FileName;
StreamWriter fichier = null;
try {
// otwieramy plik do zapisu
fichier = new StreamWriter(nomFichier);
// zapisujemy w nim tekst
fichier.Write(textBoxLignes.Text);
} catch (Exception ex) {
// problem
MessageBox.Show("Problème à l'écriture du fichier (" +
ex.Message + ")", "Erreur", MessageBoxButtons.OK, MessageBoxIcon.Error);
return;
} finally {
// zamykamy plik
if (fichier != null) {
fichier.Dispose();
}
}
}
}
- wiersz 4: ustalamy katalog początkowy (InitialDirectory) na katalog (Application.ExecutablePath), który zawiera plik wykonywalny aplikacji.
- wiersz 5: określa się typy plików, które mają zostać wyświetlone. Należy zwrócić uwagę na składnię filtrów: filtre1|filtre2|..|filtren, gdzie filtr = tekst|szablon pliku. W tym przypadku użytkownik będzie miał do wyboru pliki *.txt i *.*.
- wiersz 6: określa się typ pliku, który ma być wyświetlony użytkownikowi jako pierwszy. W tym przypadku indeks 0 oznacza pliki *.txt.
- wiersz 8: wyświetlane jest okno dialogowe, a jego wynik jest pobierany. Podczas wyświetlania okna dialogowego użytkownik nie ma już dostępu do głównego formularza (okno dialogowe tzw. modalne). Użytkownik określa nazwę pliku do zapisania i opuszcza okno, naciskając przycisk Enregistrer, przycisk Annuler, lub zamykając okno. Wynikiem metody ShowDialog jest DialogResult.OK tylko wtedy, gdy użytkownik użył przycisku Enregistrer do zamknięcia okna dialogowego.
- Po wykonaniu tej czynności nazwa pliku, który ma zostać utworzony, znajduje się teraz we właściwości FileName obiektu saveFileDialog1. W tym momencie powracamy do klasycznego tworzenia pliku tekstowego. Zapisujemy w nim zawartość obiektu TextBox: textBoxLignes.Text, jednocześnie obsługując wyjątki, które mogą wystąpić.
Klasa OpenFileDialog jest bardzo zbliżona do klasy SaveFileDialog. Wykorzystamy te same metody i właściwości, co poprzednio. Metoda ShowDialog wyświetla okno dialogowe podobne do poniższego:
![]() |
lista rozwijana utworzona na podstawie właściwości Filter. Domyślny typ pliku jest ustalany przez metodę FilterIndex | |
bieżący katalog, określony przez InitialDirectory, jeśli ta właściwość została wypełniona | |
nazwa pliku wybrana lub wpisana bezpośrednio przez użytkownika. Będzie dostępna we właściwości FileName | |
przyciski „Otwórz”/„Anuluj”. Jeśli używany jest przycisk Ouvrir, funkcja ShowDialog zwraca wynik DialogResult.OK |
Procedura ładowania pliku tekstowego może wyglądać następująco:
private void buttonCharger_Click(object sender, EventArgs e) {
// wczytujemy plik tekstowy do pola wprowadzania danych
// konfigurujemy okno dialogowe openfileDialog1
openFileDialog1.InitialDirectory = Application.ExecutablePath;
openFileDialog1.Filter = "Fichiers texte (*.txt)|*.txt|Tous les fichiers (*.*)|*.*";
openFileDialog1.FilterIndex = 0;
// wyświetla się okno dialogowe i pobierany jest jego wynik
if (openFileDialog1.ShowDialog() == DialogResult.OK) {
// pobieramy nazwę pliku
string nomFichier = openFileDialog1.FileName;
StreamReader fichier = null;
try {
// otwieramy plik w trybie odczytu
fichier = new StreamReader(nomFichier);
// odczytujemy cały plik i umieszczamy go w TextBox
textBoxLignes.Text = fichier.ReadToEnd();
} catch (Exception ex) {
// problem
MessageBox.Show("Problème à la lecture du fichier (" +
ex.Message + ")", "Erreur", MessageBoxButtons.OK, MessageBoxIcon.Error);
return;
} finally {
// zamykamy plik
if (fichier != null) {
fichier.Dispose();
}
}//w końcu
}//jeśli
}
- wiersz 4: ustalamy katalog początkowy (InitialDirectory) na katalog (Application.ExecutablePath), który zawiera plik wykonywalny aplikacji.
- wiersz 5: określa się typy plików, które mają zostać wyświetlone. Należy zwrócić uwagę na składnię filtrów: filtre1|filtre2|..|filtren, gdzie filtr = tekst|szablon pliku. W tym przypadku użytkownik będzie miał do wyboru pliki *.txt i *.*.
- wiersz 6: określa się typ pliku, który ma być wyświetlony użytkownikowi jako pierwszy. W tym przypadku indeks 0 oznacza pliki *.txt.
- wiersz 8: wyświetlane jest okno dialogowe, a jego wynik jest pobierany. Podczas wyświetlania okna dialogowego użytkownik nie ma już dostępu do głównego formularza (okno dialogowe tzw. modalne). Użytkownik określa nazwę pliku do zapisania i opuszcza okno, naciskając przycisk Ouvrir, przycisk Annuler, lub zamykając okno. Wynikiem metody ShowDialog jest DialogResult.OK tylko wtedy, gdy użytkownik użył przycisku Enregistrer do zamknięcia okna dialogowego.
- Po wykonaniu tej czynności nazwa pliku, który ma zostać utworzony, znajduje się teraz we właściwości FileName obiektu openFileDialog1. Następnie powraca się do klasycznego odczytu pliku tekstowego. W wierszu 16 należy zwrócić uwagę na metodę, która umożliwia odczytanie całego pliku.
7.5.2. Okna dialogowe FontColor i ColorDialog
Kontynuujemy poprzedni przykład, dodając do niego dwa nowe przyciski i dwa nowe elementy sterujące niewizualne:
![]() |
6
7
Nr | typ | nazwa | rola |
1 | Przycisk | buttonCouleur | do ustawienia koloru znaków w TextBox |
2 | Przycisk | buttonPolice | w celu ustawienia czcionki dla TextBox |
3 | ColorDialog | colorDialog1 | komponent umożliwiający wybór koloru – pobrany z zestawu narzędzi [5]. |
4 | FontDialog | colorDialog1 | komponent umożliwiający wybór czcionki – pochodzący z zestawu narzędzi [5]. |
Klasy FontDialog i ColorDialog posiadają metodę ShowDialog analogiczną do metody ShowDialog klas OpenFileDialog i SaveFileDialog.
![]() |
Metoda ShowDialog klasy ColorDialog umożliwia wybór koloru [1]. Metoda klasy FontDialog umożliwia wybór czcionki [2]:
- [1]: jeśli użytkownik zamknie okno dialogowe za pomocą przycisku OK, wynikiem działania metody ShowDialog jest DialogResult.OK, a wybrany kolor znajduje się we właściwości Color używanego obiektu ColorDialog.
- [2]: jeśli użytkownik zamknie okno dialogowe za pomocą przycisku OK, wynikiem działania metody ShowDialog jest DialogResult.OK, a wybrana czcionka znajduje się we właściwości Font używanego obiektu FontDialog.
Mamy teraz elementy potrzebne do obsługi kliknięć na przyciskach Couleur i Police:
private void buttonCouleur_Click(object sender, EventArgs e) {// wybór koloru tekstu
if (colorDialog1.ShowDialog() == DialogResult.OK) {
// zmiana właściwości Forecolor w TextBox
textBoxLignes.ForeColor = colorDialog1.Color;
}//if
}
private void buttonPolice_Click(object sender, EventArgs e) {
// wybór czcionki
if (fontDialog1.ShowDialog() == DialogResult.OK) {
// zmiana właściwości Font obiektu TextBox
textBoxLignes.Font = fontDialog1.Font;
}
- wiersz [4]: właściwość [ForeColor] komponentu TextBox określa kolor typu [Color] znaków w TextBox. W tym przypadku kolor ten jest kolorem wybranym przez użytkownika w oknie dialogowym typu [ColorDialog].
- wiersz [12]: właściwość [Font] komponentu TextBox określa czcionkę typu [Font] dla znaków z zestawu TextBox. W tym przypadku jest to czcionka wybrana przez użytkownika w oknie dialogowym typu [FontDialog].
7.5.3. Timer
W tym miejscu proponujemy napisać następującą aplikację:
![]() |
nr | Typ | Nazwa | Rola |
1 | Etykieta | labelChrono | wyświetla stoper |
2 | Przycisk | buttonArretMarche | przycisk Start/Stop stopera |
3 | Timer | timer1 | komponent generujący tutaj zdarzenie co sekundę |
W [4] widzimy działający stoper, a w [5] stoper zatrzymany.
Aby co sekundę zmieniać zawartość etykiety LabelChrono, potrzebujemy komponentu generującego zdarzenie co sekundę, które będziemy mogli przechwycić w celu aktualizacji wyświetlacza stopera. Komponentem tym jest Timer [1] dostępny w zestawie narzędzi Components [2]:
![]() |
Właściwości komponentu Timer użytego w tym przypadku będą następujące:
liczba milisekund, po upływie których generowane jest zdarzenie Tick. | |
zdarzenie generowane po upływie Interval milisekund | |
ustawia timer jako aktywny (true) lub nieaktywny (false) |
W naszym przykładzie timer nosi nazwę timer1, a timer1.Interval jest ustawiony na 1000 ms (1 s). Zdarzenie Tick będzie więc występować co sekundę. Kliknięcie przycisku „Stop/Start” jest obsługiwane przez następującą procedurę buttonArretMarche_Click:
using System;
using System.Windows.Forms;
namespace Chap5 {
public partial class Form1 : Form {
public Form1() {
InitializeComponent();
}
// zmienna instancji
private DateTime début = DateTime.Now;
...
private void buttonArretMarche_Click(object sender, EventArgs e) {
// wyłączone czy włączone?
if (buttonArretMarche.Text == "Marche") {
// odnotowujemy godzinę rozpoczęcia
début = DateTime.Now;
// wyświetla się
labelChrono.Text = "00:00:00";
// uruchamia się licznik czasu
timer1.Enabled = true;
// zmieniamy tekst przycisku
buttonArretMarche.Text = "Arrêt";
// koniec
return;
}//
if (buttonArretMarche.Text == "Arrêt") {
// zatrzymanie licznika czasu
timer1.Enabled = false;
// zmiana nazwy przycisku
buttonArretMarche.Text = "Marche";
// koniec
return;
}
}
}
}
- wiersz 13: procedura obsługująca kliknięcie przycisku „Wyłącz/Włącz”.
- wiersz 15: napis na przycisku „Wyłącz/Włącz” to albo „Wyłącz”, albo „Włącz”. Musimy więc sprawdzić ten napis, aby wiedzieć, co należy zrobić.
- wiersz 17: w przypadku „Uruchom” zapisujemy godzinę rozpoczęcia w zmiennej début, która jest zmienną globalną (wiersz 11) obiektu formularza
- wiersz 19: inicjuje zawartość etykiety LabelChrono
- wiersz 21: uruchamia się licznik czasu (Enabled=true)
- wiersz 23: nazwa przycisku zmienia się na „Zatrzymaj”.
- wiersz 27: w przypadku „Zatrzymaj”
- wiersz 29: zatrzymuje się licznik czasu (Enabled=false)
- wiersz 31: nazwa przycisku zmienia się na „Włącz”.
Pozostaje nam jeszcze obsłużyć zdarzenie Tick dotyczące obiektu timer1, które występuje co sekundę:
private void timer1_Tick(object sender, EventArgs e) {
// minęła sekunda
DateTime maintenant = DateTime.Now;
TimeSpan durée = maintenant - début;
// aktualizacja licznika czasu
labelChrono.Text = durée.Hours.ToString("d2") + ":" + durée.Minutes.ToString("d2") + ":" + durée.Seconds.ToString("d2");
}
- wiersz 3: zapisujemy aktualny czas
- wiersz 4: obliczamy czas, jaki upłynął od momentu uruchomienia stopera. Otrzymujemy obiekt typu TimeSpan, który reprezentuje upływ czasu.
- wiersz 6: wartość ta musi zostać wyświetlona na stoperze w postaci hh:mm:ss. W tym celu wykorzystujemy właściwości Hours, Minutes, Seconds obiektu TimeSPan, które reprezentują odpowiednio godziny, minuty i sekundy wyświetlanego czasu w formacie ToString („d2”), aby uzyskać wyświetlanie z dwoma cyframi.
7.6. Przykładowa aplikacja – wersja 6
Wracamy do przykładowej aplikacji IMPOTS. Najnowsza wersja została omówiona w paragrafie 6.4. Była to następująca aplikacja trójwarstwowa:
![]() |
- warstwy [metier] i [dao] były zawarte w warstwie DLL
- warstwa [ui] była warstwą [console]
- instancjonowanie warstw i ich integracja z aplikacją były realizowane przez Spring.
W tej nowej wersji warstwa [ui] będzie obsługiwana przez następujący interfejs graficzny:
![]() |
7.6.1. Rozwiązanie w Visual Studio
Rozwiązanie Visual Studio składa się z następujących elementów:
![]() |
- [1]: projekt składa się z następujących elementów:
- [Program.cs]: klasa uruchamiająca aplikację
- [Form1.cs]: klasa pierwszego formularza
- [Form2]: klasa drugiego formularza
- [lib], opisana szczegółowo w [2]: umieszczono w niej wszystkie klasy DLL niezbędne dla projektu:
- [ImpotsV5-dao.dll]: plik DLL warstwy [dao] wygenerowany w punkcie 6.4.3;
- [ImpotsV5-metier.dll]: plik DLL z warstwy [dao] wygenerowanej w punkcie 6.4.4;
- [Spring.Core.dll], [Common.Logging.dll], [antlr.runtime.dll]: pliki DLL z pakietu Spring, które zostały już wykorzystane w poprzedniej wersji (patrz punkt 6.4.6).
- [references], opisany szczegółowo w pliku [3]: odniesienia do projektu. Dodano po jednym odniesieniu dla każdego z plików DLL znajdujących się w folderze [lib]
- [App.config]: plik konfiguracyjny projektu. Jest on identyczny z plikiem z poprzedniej wersji opisanym w punkcie 6.4.6;
- [DataImpot.txt]: plik przedziałów podatkowych skonfigurowany tak, aby był automatycznie kopiowany do folderu wykonawczego projektu [4]
Formularz [Form1] służy do wprowadzania parametrów obliczeń podatku [A], przedstawionych już powyżej. Formularz [Form2] [B] służy do wyświetlania komunikatu o błędzie:
![]() |
7.6.2. Klasa [Program.cs]
Klasa [Program.cs] uruchamia aplikację. Jej kod wygląda następująco:
using System;
using System.Windows.Forms;
using Spring.Context;
using Spring.Context.Support;
using Metier;
using System.Text;
namespace Chap5 {
static class Program {
/// <summary>
/// Główny punkt wejścia do aplikacji.
/// </summary>
[STAThread]
static void Main() {
// kod wygenerowany przez Vs
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
// --------------- Kod programisty
// instancje warstw [metier] i [dao]
IApplicationContext ctx = null;
Exception ex = null;
IImpotMetier metier = null;
try {
// kontekst Spring
ctx = ContextRegistry.GetContext();
// żądane jest odwołanie do warstwy [metier]
metier = (IImpotMetier)ctx.GetObject("metier");
} catch (Exception e1) {
// zapisanie wyjątku
ex = e1;
}
// formularz do wyświetlenia
Form form = null;
// czy wystąpił wyjątek?
if (ex != null) {
// tak – tworzy się komunikat o błędzie do wyświetlenia
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;
}
// utworzenie okna błędu, do którego przekazywany jest komunikat o błędzie do wyświetlenia
Form2 form2 = new Form2();
form2.MsgErreur = msgErreur.ToString();
// będzie to okno do wyświetlenia
form = form2;
} else {
// wszystko poszło dobrze
// utworzenie interfejsu graficznego [Form1], do którego przekazywane jest odwołanie na warstwie [metier]
Form1 form1 = new Form1();
form1.Metier = metier;
// będzie to okno, które ma zostać wyświetlone
form = form1;
}
// wyświetlenie okna
Application.Run(form);
}
}
}
Kod wygenerowany przez Visual Studio został uzupełniony od linii 19. Aplikacja korzysta z następującego pliku [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>
- wiersze 24–32: wykorzystanie poprzedniego pliku [App.config] w celu utworzenia instancji warstw [metier] i [dao]
- wiersz 26: wykorzystanie pliku [App.config]
- wiersz 28: pobranie odniesienia do warstwy [metier]
- wiersz 31: zapis ewentualnego wyjątku
- wiersz 34: odwołanie form wskaże formularz do wyświetlenia (form1 lub form2)
- wiersze 36–50: jeśli wystąpił wyjątek, przygotowujemy się do wyświetlenia formularza typu [Form2]
- wiersze 38–44: tworzony jest komunikat o błędzie do wyświetlenia. Składa się on z połączenia komunikatów o błędach dotyczących poszczególnych wyjątków występujących w łańcuchu wyjątków.
- wiersz 46: tworzony jest formularz typu [Form2].
- wiersz 47: jak zobaczymy później, formularz ten posiada właściwość publiczną MsgErreur, która jest komunikatem o błędzie do wyświetlenia:
public string MsgErreur { private get; set; }
Wprowadzamy wartość tej właściwości.
- wiersz 49: inicjowane jest odwołanie form, które określa okno do wyświetlenia. Warto zwrócić uwagę na występujący tu polimorfizm. form2 nie jest typu [Form], lecz typu [Form2], który jest typem pochodnym od [Form].
- wiersze 50–57: nie wystąpił żaden wyjątek. Przygotowujemy się do wyświetlenia formularza typu [Form1].
- wiersz 53: tworzony jest formularz typu [Form1].
- wiersz 54: jak zobaczymy później, ten formularz ma właściwość publiczną Metier, która stanowi odniesienie do warstwy [metier]:
public IImpotMetier Metier { private get; set; }
Wprowadzamy wartość tej właściwości.
- wiersz 56: inicjowane jest odwołanie form, które określa okno do wyświetlenia. Ponownie warto zwrócić uwagę na działający polimorfizm. form1 nie jest typu [Form], lecz typu [Form1], który jest typem pochodnym od [Form].
- wiersz 59: wyświetlane jest okno, do którego odwołuje się form.
7.6.3. Formularz [Form1]
W trybie [conception] formularz [Form1] wygląda następująco:
![]() |
Elementy formularza są następujące
nr | typ | nazwa | rola |
0 | GroupBox | groupBox1 | Text=Czy jesteś w związku małżeńskim? |
1 | RadioButton | radioButtonOui | zaznaczyć, jeśli jesteś w związku małżeńskim |
2 | RadioButton | radioButtonNon | zaznaczyć, jeśli nie jest żonaty/zamężna Checked=True |
3 | NumericUpDown | numericUpDownEnfants | liczba dzieci podatnika Minimum=0, Maksimum=20, Przyrost=1 |
4 | TextBox | textSalaire | roczne wynagrodzenie podatnika w euro |
5 | Etykieta | labelImpot | kwota podatku do zapłaty BorderStyle=Fixed3D |
6 | Przycisk | buttonCalculer | uruchamia obliczenie podatku |
7 | Przycisk | buttonEffacer | przywraca formularz do stanu, w jakim znajdował się w momencie załadowania |
8 | Przycisk | buttonQuitter | służy do zamknięcia aplikacji |
Zasady działania formularza
- przycisk Calculer pozostaje nieaktywny, dopóki w polu wynagrodzenia nie ma żadnych danych
- jeśli po uruchomieniu obliczeń okaże się, że wynagrodzenie jest nieprawidłowe, zgłaszany jest błąd [9]
Kod klasy jest następujący:
using System.Windows.Forms;
using Metier;
using System;
namespace Chap5 {
public partial class Form1 : Form {
// warstwa [métier]
public IImpotMetier Metier { private get; set; }
public Form1() {
InitializeComponent();
}
private void buttonCalculer_Click(object sender, System.EventArgs e) {
// czy wynagrodzenie jest prawidłowe
int salaire;
bool ok=int.TryParse(textSalaire.Text.Trim(), out salaire);
if (! ok || salaire < 0) {
// komunikat o błędzie
MessageBox.Show("Salaire incorrect", "Erreur de saisie", MessageBoxButtons.OK, MessageBoxIcon.Error);
// powrót do błędnego pola
textSalaire.Focus();
// zaznaczenie tekstu w polu wprowadzania danych
textSalaire.SelectAll();
// powrót do interfejsu wprowadzania danych
return;
}
// wynagrodzenie jest prawidłowe – można obliczyć podatek
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) {
// wyczyść formularz
labelImpot.Text = "";
numericUpDownEnfants.Value = 0;
textSalaire.Text = "";
radioButtonNon.Checked = true;
}
private void textSalaire_TextChanged(object sender, EventArgs e) {
// stan przycisku [Calculer]
buttonCalculer.Enabled=textSalaire.Text.Trim()!="";
}
}
}
Komentujemy tylko istotne fragmenty:
- wiersz [8]: właściwość publiczna Metier, która pozwala klasie uruchamiającej [Program.cs] na wstawienie do klasy [Form1] odwołania do warstwy [metier].
- wiersz [14]: procedura obliczania podatku
- wiersze 15–27: weryfikacja poprawności wynagrodzenia (liczba całkowita >=0).
- wiersz 29: obliczenie podatku przy użyciu metody [CalculerImpot] z warstwy [metier]. Warto zwrócić uwagę na prostotę tej operacji, którą uzyskano dzięki enkapsulacji warstwy [metier] w warstwie DLL.
7.6.4. Formularz [Form2]
W trybie [conception] formularz [Form2] wygląda następująco:
![]() |
Kontrole są następujące
nr | typ | nazwa | rola |
1 | TextBox | textBoxErreur | Multiline=True, Scrollbars=Both |
Kod klasy wygląda następująco:
using System.Windows.Forms;
namespace Chap5 {
public partial class Form2 : Form {
// komunikat o błędzie
public string MsgErreur { private get; set; }
public Form2() {
InitializeComponent();
}
private void Form2_Load(object sender, System.EventArgs e) {
// wyświetla się komunikat o błędzie
textBoxErreur.Text = MsgErreur;
// odznaczany jest cały tekst
textBoxErreur.Select(0, 0);
}
}
}
- wiersz 6: właściwość publiczna MsgErreur, która umożliwia klasie uruchamiającej [Program.cs] wstawienie do klasy [Form2] komunikatu o błędzie, który ma zostać wyświetlony. Komunikat ten jest wyświetlany podczas przetwarzania zdarzenia Load, wiersze 12–16.
- wiersz 14: komunikat o błędzie jest umieszczany w TextBox
- wiersz 16: usuwa się zaznaczenie dokonane podczas poprzedniej operacji. [TextBox].Select(początek, długość) zaznacza (podświetla) longueur znaków, począwszy od znaku nr début. [TextBox].Select(0,0) powoduje odznaczenie całego tekstu.
7.6.5. Wniosek
Wróćmy do zastosowanej architektury trójwarstwowej:
![]() |
Architektura ta pozwoliła nam zastąpić konsolową implementację istniejącej warstwy [ui] implementacją graficzną, nie wprowadzając żadnych zmian w warstwach [metier] i [dao]. Mogliśmy skupić się na warstwie [ui], nie martwiąc się o ewentualny wpływ na pozostałe warstwy. Na tym polega główna zaleta architektur trójwarstwowych. Kolejny przykład omówimy nieco później, gdy warstwa [dao], która obecnie przetwarza dane z pliku tekstowego, zostanie zastąpiona warstwą [dao] przetwarzającą dane z bazy danych. Zobaczymy, że nastąpi to bez wpływu na warstwy [ui] i [metier].


































































