[Visual Studio 2015] Dynamiczne tworzenie kontrolek – tworzenie, obsługa i objaśnienia. Językiem początkującego

Witam.

Tym razem chciałbym się z wami podzielić wiedzą dot. dynamicznego tworzenia kontrolek w C#, oraz ich obsługi. Opis kolejnych czynności (kodów w programie) opisywał będę „językiem początkującego”, czyli tak jak Ja to rozumiem.
Od razu chciałbym przeprosić bardziej zaawansowanych czytelników za formę i niepoprawne nazewnictwo niektórych czynności – piszę tak aby początkująca osoba potrafiła zrozumieć co się dzieje w danej linijce kody. Jednak jeżeli popełniłem jakieś rażące błędy, proszę o zwrócenie mi uwagi.


Zaczynajmy!

Wygląd działającego programu
Wygląd działającego programu

Program, który powstanie będzie powodował dynamiczne utworzenie kontrolek – ilość tych kontrolek będzie zależna od tego ile użytkownik wpisze do odpowiedniego pola. Kontrolki, które zostaną utworzone w sposób dynamiczny (tzn. ilość nie jest zdefiniowana w momencie kompilacji programu – będą one tworzone w czasie działania programu) to:

  1. TextBox
  2. Button
  3. Label

Dodatkowo, program będzie umożliwiał wykonanie przepisania zawartości z TextBox do Label po wciśnięciu odpowiadającego im przycisku.

W programie zostaną użyte jeszcze 4 kontrolki – Label, TextBox, Button x2szt.

Wygląd okna formularza przedstawia się następująco:

Wygląd formularza
Wygląd formularza

Pod przyciskiem Zamknij znajduje się polecenie umożliwiające zamknięcie programu:

Close();

Natomiast cały program który będzie tworzył nowe kontrolki będzie znajdował się pod przyciskiem Utwórz. Do TextBox’a będzie wpisywana ilość kompletów elementów jaka ma zostać utworzona.

Jednak przed przystąpieniem do pisania kodu zerknijmy jak Visual Studio tworzy komponenty (po co samemu się głowić jak mamy dostępne gotowe rozwiązania)- w tym celu przechodzimy do pliku Form1.Designer.cs (wybieramy go z eksploratora projektu który znajduje się po prawej stronie). Na samym końcu naszej głównej klasy mamy deklarację użytych komponentów:

        private System.Windows.Forms.Button button1;
        private System.Windows.Forms.Button button2;
        private System.Windows.Forms.TextBox textBox1;
        private System.Windows.Forms.Label label1;

Dalej w pliku tym znajdujemy metodę (czyli funkcję z klasy naszego programu) InitializeComponent() – znajduje się ona przed deklaracją komponentów. Wewnątrz tej metody (może ona być zwinięta – jeżeli tak jest należy po lewej stronie nacisnąć + aby ją rozwinąć) znajduje się cały kod odpowiedzialny za tworzenie naszego formularza i tego co się w nim znajduje. Na początku widzimy definicję nowych kontrolek – obiektów (instancji klasy) ale o tym za chwilę. Fragment tego kodu prezentuje się następująco:

            this.button1 = new System.Windows.Forms.Button();
            this.button2 = new System.Windows.Forms.Button();
            this.textBox1 = new System.Windows.Forms.TextBox();
            this.label1 = new System.Windows.Forms.Label();

Widzimy tutaj wyrażenie – this (z ang. to, ten). Oznacza ono że odnosimy się do obecnej klasy, w której został wywołany (stosuje się go głównie po to, aby uniknąć np. zbieżności nazw zmiennych występujących w klasie i np. w funkcji/metodzie). Do nowej zmiennej, np. button1, przypisywany jest nowy obiekt Button(), a dokładnie tworzony (uruchamiany jest konstruktor tego obiektu, czyli naszej kontrolki) obiekt – rezerwowane jest miejsce w pamięci na wszystkie funkcje (metody) które są przypisane dla tej klasy (która opisuje daną kontrolkę) – wszystko dzięki użyciu new.

Dalej w kodzie widzimy konfigurację przycisku:

            //
            // button1
            //
            this.button1.Location = new System.Drawing.Point(197, 12);
            this.button1.Name = "button1";
            this.button1.Size = new System.Drawing.Size(75, 23);
            this.button1.TabIndex = 0;
            this.button1.Text = "Zamknij";
            this.button1.UseVisualStyleBackColor = true;
            this.button1.Click += new System.EventHandler(this.button1_Click);

W pierwszej kolejności ustawiamy punkt w którym będzie utworzony nasz komponent. Tworzona jest lokalizacja obiektu w podanym punkcie. Współrzędne podawane są w następującej kolejności: X, Y.
Następnie podawana jest nazwa przycisku (wyjaśnione to zostanie w dalszej części), rozmiar (tworzony jest obiekt w sensie wizualnym), określany jest kolejny numer podczas przełączania między kontrolkami przy użyciu TAB. Później ustawiamy wyświetlany tekst, oraz określenie czy tło ma być rysowane wg styli wizualnych. Na samym końcu dodawana jest metoda w przypadku wykonania kliknięcia – dla zdarzenia CLICK. Dodawana z tego względu, że może być wywołanych wiele metod.
Tworzony jest nowy uchwyt/odnośnik dla zdarzenia (EventHandler jest delegatem, czyli wskaźnikiem) – mówiąc prościej, do zdarzenia Click przypisywany jest wskaźnik funkcji która ma zostać wywołana (jako parametr przekazujemy funkcję/metodę która ma obsłużyć to zdarzenie)

Przechodzimy jeszcze pod koniec tej metody. W sektorze odpowiedzialnym za utworzenie formularza odnajdujemy następujące fragmenty kodu:

            this.Controls.Add(this.label1);
            this.Controls.Add(this.textBox1);
            this.Controls.Add(this.button2);
            this.Controls.Add(this.button1);

Linijki kodu odpowiedzialne one są za dodanie naszych kontrolek do formularza, a dokładanie do kolekcji formularza, czyli takiej tablicy, która może w sposób dynamiczny mieć zmienianą wielkość – w kolekcji łatwiejszy sposób są dodawane i usuwane wartości. Wszystkie kontrolki, które tworzymy (czy to z poziomu projektanta formularza, czy dynamicznie) znajdują się w takiej kolekcji. Niekoniecznie musi być to kolekcja formularza, może znaleźć się również kolekcja tzw. kontenerów – GroupBox, Panel, itp.

Czyli już wiemy na czym polega utworzenie nowej kontrolki. Przedstawię teraz w punktach kolejność postępowania w przypadku tworzenia nowej kontrolki:

  1. Deklaracja nowej kontrolki – tworzymy nową zmienną (której typem jest kontrolka)
  2. Przypisanie do naszej zmiennej obiektu kontrolki (wywołanie konstruktora)
  3. Ustawienie położenia kontrolki, oraz jej rozmiaru
  4. Przypisanie jej nazwy
  5. Pozostała konfiguracja kontrolki
  6. Dodanie kontrolki do kolekcji np. formularza

W taki sposób będziemy tworzyć również w sposób dynamiczny nasze kontrolki.

Tworzenie dynamiczne kontrolek będzie wykonywane po podaniu liczby w textbox i naciśnięciu przycisku Utwórz. Kod, który znajduje się pod tym przyciskiem wygląda następująco:

        private void button2_Click(object sender, EventArgs e)
        {
            int liczba_elementow = Convert.ToInt16(textBox1.Text); //Konwersja wpisanej wartości do textboxa na int

            for (int i = 0; i < liczba_elementow; i++)
            {
                //tworzymy nową kontrolkę - textbox, button, oraz label
                TextBox ntextbox = new TextBox();
                Button nbutton = new Button();
                Label nlabel = new Label();

                //umieszczanie na ekranie danej kontrolki
                ntextbox.Location = new System.Drawing.Point(12, 110 + 26 * i);
                nbutton.Location = new System.Drawing.Point(118, 108 + 26 * i);
                nlabel.Location = new Point(213, 113 + 26 * i);

                //określenie rozmiarów kontrolki
                ntextbox.Size = new System.Drawing.Size(100, 20);
                nbutton.Size = new System.Drawing.Size(75, 23);
                nlabel.Size = new Size(35, 13);

                //przypisanie nazwy danej kontrolce
                ntextbox.Name = "d_box_" + i.ToString();
                nbutton.Name = "d_button_" + i.ToString();
                nlabel.Name = "d_label_" + i.ToString();

                //konfiguracja danej kontrolki - wyswietlany tekst, ewentualne ustawienia
                nbutton.Text = "Przepisz";   //nazwa przycisku
                nlabel.AutoSize = true;      //ustawienie automatycznego dopasowania szerokości

                //ustawienie zdarzenia dla przycisków
                nbutton.Click += new System.EventHandler(Nbutton_Click);

                //dodanie kontrolek do formularza (do kolekcji - tablicy)
                Controls.Add(ntextbox);
                Controls.Add(nbutton);
                Controls.Add(nlabel);

            }
        }

Większość kodu została wyjaśniona w komentarzach, bądź omówiona wcześniej.
Można jednak zauważyć że dla określenia położenia i rozmiaru kontrolki użyłem innego sposobu przypisania parametrów. Chciałem w ten sposób pokazać, że dzięki zdefiniowaniu na początku przestrzeni nazw System.Drawing nie musimy za każdym razem pisać skąd dana metoda ma zostać zaczerpnięta. Przydaje się to jednak w tedy, gdy w różnych przestrzeniach (klasach) mamy podobno brzmiące metody – w tedy zastosowanie pełnych nazw powoduje uniknięcia błędu (pełnych nazw, czyli z jakiej przestrzeni/klasy jest zaczerpnięta dana metoda/funkcja).

Jak to jest z tą nazwą tej kontrolki? Otóż gdy tworzymy dynamicznie np. przycisk w kodzie musimy jakoś nazwać nasz nowy obiekt np. nbutton, dla którego będziemy ustawiać parametry. Dla jednego przycisku nie byłoby problemu, ale dla większej ilości musielibyśmy utworzyć więcej zmiennych – tylko to się mija z dynamicznym tworzeniem kontrolek. Dlatego gdy tworzymy w sposób dynamiczny nową kontrolkę najpierw wykorzystujemy nazwę którą zdefiniowaliśmy w programie (tworzymy tylko jedną zmienną dla jednego typu – bo po co więcej!?), aby łatwiej się konfigurowało ten i następny komponent (tworzony jest taki wskaźnik na dany obiekt, który w danej chwili jest konfigurowany), a w kolejnych czynności zmieniana jest dynamicznie nazwa tego komponentu. Pod tą nazwą znajduje się adres (indeks w kolekcji/tabeli) który wskazuje na nasz obiekt (kontrolkę) który jest teraz konfigurowany, więc nazwa ta może być zmieniana – zmieniając nazwę nie zmieniamy adresu gdzie przechowywana jest nasza kontrolka. Trochę to jest pokręcone, ale dzięki parametrowi Name, twórcy tego komponentu umożliwili w łatwy sposób możliwość zmiany nazwy tej kontrolki w sposób dynamiczny, oraz w prosty sposób odnoszenie się do jej właściwości. Po dodaniu kontrolki do kolekcji np. formularza, aby się do niej odwołać, należy wykorzystać nazwę, jaka została przypisana poprzez parametr Name.
Dla kontrolek utworzony w projektancie formularza, parametr Name raczej nie będzie zmieniany w sposób dynamiczny podczas działania programu – bo po co, jak i tak w dalszej części programu nazwa tej kontrolki zostanie niezmieniona i każda próba odwołania się do tej kontrolki (która będzie miała już zmienioną nazwę) może spowodować błąd programu.
Mam nadzieję, że opisałem to dość zrozumiale – jakby co to pisać.

Musimy jeszcze napisać funkcję/metodę która będzie wykonywana po naciśnięciu na przycisk, który został utworzony dynamicznie. Umieścimy ją po obsłudze przycisku Utwórz. Będzie ona miała następująco postać:

        private void Nbutton_Click(object sender, EventArgs e)
        {
            //odczytanie numeru przycisku który spowodował wywołanie tego zdarzenia
            string nr_kontrolki = (sender as Button).Name.Remove(0, 9);

            //tworzymy nowe obiekty - dokładnie uchwyty do istniejących obiektów (poprzez wyszukanie FIND)
            TextBox ptextbox = (TextBox)this.Controls.Find("d_box_" + nr_kontrolki, false).First();
            Label plabel = this.Controls.Find("d_label_" + nr_kontrolki, false).First() as Label;

            //przepisanie zawartości
            plabel.Text = ptextbox.Text;

            //usunięcie przycisku który spowodował to zdarzenie - usunięcie z kolekcji
            Controls.Remove(sender as Button);

        }

Również w tym przypadku zaczerpnąłem z gotowego rozwiązania, które daje nam Visual – typy jakie są przekazywane do tej metody, oraz typ jaki zwraca ta metoda – a dokładnie że nic nie zwraca;).
Co jest przekazywane do tej metody:
Poprzez zmienną sender przekazywany jest typ object, który informuje użytkownika, czyli nas z którego komponentu nastąpiło wywołanie tego zdarzenia. Dzięki temu mamy możliwość odwołania się do wszystkich właściwości tego komponentu. Drugim parametrem jest EventArgs, który przechowuje informacje o zdarzeniu – prawdę mówiąc jeszcze dokładnie nie wiem do czego on może służyć więc pominę opis tego parametru.

Aby można było odwołać się do kontrolki, która wywołała to zdarzenie, należy wykorzystać operator as (jako, tak jak), który umożliwi konwersję – w wolnym tłumaczeniu obiekt sender traktujemy jako przycisk button (dla tego rozważanego przypadku). Dzięki temu możliwy jest dostęp do wszystkich właściwości kontrolki, która spowodowała wywołanie tego zdarzenia.

Wróćmy jeszcze na chwilę do poprzedniego kodu. Tam jako nazwę dla danej kontrolki użyłem specjalnie dłuższego tekstu, który zakończony jest kolejnym numer – który jest ten sam dla każdej grupy kontrolek (w tym przypadku grupa kontrolek dla mnie to label, button i textbox). Można było użyć samego numeru co by ułatwiło identyfikację która grupa została wywołana – ale chciałem wykorzystać i sprawdzić metody które znajdują się w klasie string.
Tak więc w pierwszej kolejności w obsłudze przycisku musimy odczytać która grupa jest wywołana. Dokonujemy tego poprzez wycięcia słowa „d_button_” które ma 9 znaków – użyjemy w tym celu metody Remove, do której należy przekazać indeks od którego ma zacząć usuwać znaki, oraz ilość znaków do usunięcia. Tak przygotowaną informację przepisujemy do zmiennej nr_kontrolki, która jest typu string – nie jest konieczna konwersja na int, z tego względu, że w dalszej części programu składane będą nazwy kolejnych kontrolek.

Następnie tworzone są dwa nowe obiekty – label i textbox. Do nazw tych obiektów przypisujemy kontrolki, które zostały utworzone przez nas w sposób dynamiczny. Aby tego dokonać należy wykorzystać metodę Find, która powoduje wyszukanie w danej kolekcji kontrolki o podanej nazwie. Drugim parametrem jest wartość typu bool, która określa czy mają zostać również wyszukane kontrolki tzw. potomne (w naszym przypadku nie, więc ustawiamy false). Metoda ta zwraca w postaci tabeli wszystkie odpowiadające kontrolki. Można w łatwy sposób poprzez metodę First(), zwrócić pierwszą wartość tej tablicy. Należy jeszcze przekonwertować na odpowiednią kontrolkę dany obiekt – tutaj przedstawiłem dwa sposoby: poprzez jawne rzutowani i z zastosowaniem as (as w przypadku gdy nie uda się przekonwertować danych nie spowoduje błędu, tylko wpisze do zmiennej null, czego nie można powiedzieć o innych sposobach konwersji typów).
Następnie są przepisywane dane z jednej kontrolki do drugiej.
Na samym końcu usuwany jest przycisk który nacisnęliśmy – jest on usuwany całkowicie z kolekcji i nie mamy do niego dostępu w żaden sposób.

Ustawiamy jeszcze dla formularza właściwość AutoScroll na true – dzięki czemu pojawią nam się paski przewijania w przypadku gdy kontrolki wyjdą poza nasz formularz. Zmiany tej dokonujemy w projektancie formularza we właściwościach naszego Form1.

Chyba to wszystko. Mam nadzieję, że udało mi się w prosty sposób (ale pewnie zakręcony) wytłumaczyć niektóre aspekty dynamicznego tworzenia kontrolek. Możliwe, że czasami za bardzo zakręciłem opisem, za co przepraszam.

W przypadku jakichś błędów, czy pytań pisać. Każda forma krytyki będzie motywująca;)

Jeszcze w załączniku przesyłam cały program wraz z komentarzami, aby każdy mógł sprawdzić jak to działa.

Pobierz “Cs_02_dynamiczne_kontrolki.zip”

Cs_02_dynamiczne_kontrolki.zip – Pobrano 270 razy – 59,16 KB

P.S.
Podobny wpis został również umieszczony przeze mnie na forum microgeek – zachęcam do rejestracji i częstych wizyt.

Dodaj komentarz

Twój adres e-mail nie zostanie opublikowany. Wymagane pola są oznaczone *