Skip to content

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:

BackColor
w celu ustawienia koloru tła okna
ForeColor
do ustawienia koloru elementów graficznych lub tekstu w oknie
Menu
aby przypisać menu do okna
Text
aby nadać tytuł oknu
FormBorderStyle
aby określić typ okna
Font
w celu ustawienia czcionki tekstu w oknie
Name
aby ustawić nazwę okna

W tym przypadku ustalamy właściwości Text i Name:

Text
Pola wprowadzania danych i przyciski – 1
Name
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:

  1. 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)
  2. 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

text
komunikatem do wyświetlenia
caption
tytuł okna
buttons
przyciski w oknie
icon
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:

constante
przyciski
   AbortRetryIgnore 
  OK 
    OKCancel 
    RetryCancel 
    YesNo 
    YesNoCancel 

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:

Asterisk
Error
idem Stop
Exclamation
to samo Ostrzeżenie
Hand
Information
to samo jak Asterisk
None
Question
Stop
to samo, ręcznie
Warning

 

Metoda Show jest metodą statyczną, która zwraca wynik typu [System.Windows.Forms.DialogResult], który jest wyliczeniem:

Image

Aby dowiedzieć się, który przycisk został naciśnięty przez użytkownika w celu zamknięcia okna typu MessageBox, należy napisać:

DialogResult res=MessageBox.Show(..);
if (res==DialogResult.Yes){ // nacisnął przycisk „Tak”...}

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ć:

EventHandler evtHandler=new EventHandler(buttonAfficher_Click);

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:

evtHandler+=new EvtHandler(M);

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:

public event MouseEventHandler MouseMove;

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.

Load
formularz jest w trakcie ładowania
Closing
formularz jest w trakcie zamykania
Closed
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:

Multiline=true
aby akceptował wiele wierszy tekstu
ScrollBars=( None, Horizontal, Vertical, Both)
aby określić, czy kontrolka ma mieć paski przewijania (Horizontal, Vertical, Both), czy nie (None)
AcceptReturn=(True, False)
jeśli wartość wynosi „true”, naciśnięcie klawisza Enter spowoduje przejście do następnej linii
AcceptTab=(True, False)
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:

Simple
lista bez rozwijania z polem edycji
DropDown
lista rozwijana z polem edycji
DropDownList
lista rozwijana bez pola edycji

Domyślnie typ obiektu ComboBox to DropDown.

Klasa ComboBox posiada tylko jeden konstruktor:

new ComboBox()
tworzy pustą listę rozwijaną

Elementy typu ComboBox są dostępne we właściwości Items:

public ComboBox.ObjectCollection Items {get;}

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:

C.Items.Count
liczba elementów listy rozwijanej
C.Items[i]
element i listy rozwijanej
C.Add(object o)
dodaje obiekt o jako ostatni element listy rozwijanej
C.AddRange(object[] objets)
dodaje tablicę obiektów na końcu listy rozwijanej
C.Insert(int i, object o)
dodaje obiekt o na pozycji i listy rozwijanej
C.RemoveAt(int i)
usuwa element i z listy rozwijanej
C.Remove(object o)
usuwa obiekt o z listy rozwijanej
C.Clear()
usuwa wszystkie elementy z listy rozwijanej
C.IndexOf(object o)
zwraca pozycję i obiektu o w polu rozwijanym
C.SelectedIndex
indeks wybranego elementu
C.SelectedItem
wybrany element
C.SelectedItem.Text
wyświetlany tekst wybranego elementu
C.Text
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:

One
można zaznaczyć tylko jeden element
MultiExtended
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.
MultiSimple
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)
MouseEnter
mysz właśnie weszła w obszar kontrolki
MouseLeave
mysz właśnie opuściła obszar kontrolki
MouseMove
mysz porusza się w obszarze kontrolnym
MouseDown
Naciśnięcie lewego przycisku myszy
MouseUp
Zwolnienie lewego przycisku myszy
DragDrop
użytkownik upuszcza obiekt na element sterujący
DragEnter
użytkownik wchodzi w obszar elementu sterującego, przeciągając obiekt
DragLeave
użytkownik opuszcza obszar kontrolki, przeciągając obiekt
DragOver
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:

Name
nazwa elementu sterującego menu
Text
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
string Filter
Propriété
typy plików dostępne na liście rozwijanej typów plików w oknie dialogowym
int FilterIndex
Propriété
numer typu pliku proponowanego domyślnie na powyższej liście. Zaczyna się od 0.
string InitialDirectory
Propriété
folder pierwotnie wskazany do zapisania pliku
string FileName
Propriété
nazwa pliku kopii zapasowej podana przez użytkownika
DialogResult.ShowDialog()
Méthode
metoda wyświetlająca okno dialogowe zapisu. Zwraca wynik typu DialogResult.

Metoda ShowDialog wyświetla okno dialogowe podobne do poniższego:

1
lista rozwijana utworzona na podstawie właściwości Filter. Domyślny typ pliku jest ustalany przez FilterIndex
2
bieżący katalog, określony przez InitialDirectory, jeśli ta właściwość została wypełniona
3
nazwa pliku wybrana lub wpisana bezpośrednio przez użytkownika. Będzie dostępna we właściwości FileName
4
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:

1
lista rozwijana utworzona na podstawie właściwości Filter. Domyślny typ pliku jest ustalany przez metodę FilterIndex
2
bieżący katalog, określony przez InitialDirectory, jeśli ta właściwość została wypełniona
3
nazwa pliku wybrana lub wpisana bezpośrednio przez użytkownika. Będzie dostępna we właściwości FileName
4
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:

Interval
liczba milisekund, po upływie których generowane jest zdarzenie Tick.
Tick
zdarzenie generowane po upływie Interval milisekund
Enabled
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].