Posts Tagged ‘C#’

Dynamiczne tworzenie interfejsu użytkownika przy pomocy refleksji.

Author: Jarek Kożdoń (jarek.kozdon) | luty 19th, 2011
avatar

Niedawno, przy okazji rozmyślania nad konstrukcją interfejsu użytkownika dla narzędzia tworzonego dla własnych potrzeb, przypomniał mi się pewien projekt, nad którym miałem okazję pracować. Projekt nie był wielki, ale naszpikowany ciekawymi rozwiązaniami, m.in. LINQ (zarówno dla bazy danych jak i LINQ to XML), SQL Server Compact Edition, przetwarzanie bardzo sporych XMLi. Było również jedno rozwiązanie, które szczególnie zapadło mi w pamięci – kontrolka do edycji danych obiektów, co ciekawe – dla wszystkich obiektów bazodanowych (a było ich trochę, nie setki – ale powiedzmy kilkanaście) była tylko i wyłącznie jedna kontrolka. Nasuwa się tutaj pytanie „ale jak?!, przecież to musiało mieć milion linii kodu!”. Ale odpowiedź brzmi „wcale nie”. Z pomocą przyszła refleksja…

Zamieszczam więc niniejszym skromny instruktarz, który być może kogoś zainspiruje, do kodu kontrolki obecnie dostępu nie posiadam, przedstawię więc bardziej ideę jako propozycje alternatywy dla typowego myślenia o edycji właściwości naszych obiektów.

1. Co to jest refleksja?

Dla tych co wiedzą – przypomnienie (bądź akapit do przeskrolowania), dla pozostałych – kilka podstawowych informacji.

Refleksja to mechanizm pozwalający na pozyskiwanie informacji o klasach, czy kodzie skompilowanych już klas (na przykład właśnie wykonywanego programu, czy też odczytanych przez niego binarek).

W celu użycia refleksji w projekcie .NETowym należy użyć przestrzeń nazw System.Reflection. Dzięki temu będziemy mieli dostęp do podstawowych klas refleksji czyli: Type, MethodInfo, ProprtyInfo, EventInfo, FieldInfo, ConstructorInfo… Każda odpowiada jednemu z elementów tworzących klasy.

Żeby pobrać informacje dotyczące obiektu danej klasy wywołać należy na nim metodę .GetType() dziedziczoną z bazowej klasy Object.

2. Skąd wiemy co wyświetlać?

Metod na pozyskanie interesującej nas informacji jest co najmniej kilka. Możemy pozyskać informacje o klasie wczytując plik binarny i w nim szukając, możemy szukać po nazwie klasy i jej przestrzeni nazw, ale zdecydowanie najłatwiejszym sposobem jest wywołanie wspomnianej powyżej metody GetType() na obiekcie, który nas interesuje.

Co wyświetlać? O tym decydujemy już wyłącznie my! Powinniśmy jednak coś założyć, w tym przykładzie wyświetlać chciałbym wszystkie publiczne Property obiektów.

Skoro już wiemy jak pobrać typ obiektu oraz wiemy co będziemy wyświetlać pozostaje wyłącznie pobrać listę, nie jest to nic trudnego, wystarczy wywołać metodę GetProperties() klasy Type, która zwraca tablicę obiektów PropertyInfo danego typu. Stworzymy więc taki kod:

var type = ob.GetType();
var properties = type.GetProperties();

kod ten nie spełnia jednak naszego założenia, bowiem zwrócone zostaną wszystkie publiczne properties (domyślne działanie nie zwraca prywatnych właściwości!) łącznie z tymi statycznymi (chcemy tylko właściwości obiektu). W celu pozyskania odpowiedniej listy użyć należy przeładowania metody GetProperties wraz z argumentem w postaci wartości enuma BindingFlags (dopuszczając bitowe dodawanie wielu wartości)

var type = ob.GetType();
var properties = type.GetProperties(BindingFlags.Public | BindingFlags.Instance);

Lista gotowa!

3. A co jeżeli jakiegoś pola NIE chcę wyświetlać?

Nie, nie trzeba w takim przypadku robić dziwnych warunków czy aby dane property jest jednym z wykluczonych, czy nie, dużo bardziej uniwersalnym sposobem jest użycie atrybutu. Można działać w 2 strony – stworzyć atrybut informujący o tym, że dane pole powinno być wyświetlane, bądź atrybut informujący o tym, że jego edycja nie jest potrzebna. Pokażę drugie rozwiązanie, jako bardziej ogólne i bardziej do mnie przemawiające, poza tym pozwalające w dalszej drodze edytować wartości obiektów nie należących do danego projektu.

Najpierw klasa atrybutu:

public class HiddenValueAttribute : Attribute
{
}

Po czym umieszczamy atrybut przy Property, którego nie chcemy widzieć:

[HiddenValue]
public
string HiddenName { get set }

a teraz modyfikacja naszej metody pobierającej listę do wyświetlenia (dodajemy również jej nazwę):

public IEnumerable<PropertyInfo> GetPropertiesToDisplay(object ob)

{
var type = ob.GetType();
var
properties = type.GetProperties(BindingFlags.Public | BindingFlags.Instance);
var
returnValue = from PropertyInfo p in properties where p.GetCustomAttributes(typeof(HiddenValueAttribute), true).Length == 0 select p;
}

Jak widać dodana została linijka wyszukująca tylko te PropertyInfo, które wśród swoich atrybutów nie mają tego o typie HiddenValueAttribute, w celu tym użyta zostało użyte zapytanie LINQ wywołujące metodę GetCustomAttributes, która zwraca tablicę atrybutów spełniających warunki. Warunki podawane w argumentach to typ atrybutu (opcjonalny parametr, w drugim przeładowaniu metody nie występuje) oraz czy brać pod uwagę dziedziczone atrybuty.

4. Jak wyświetlać?

Przechodzimy teraz do najistotniejszego punktu – wyświetlanie. Założymy, że wyświetlamy kontrolki na obiekcie klasy Panel o nazwie… panel!

Metoda wyświetlająca kosztować będzie nas najwięcej pracy – tutaj generalnie przewidzieć będziemy musieli wszystkie potencjalnie wyświetlane typy oraz obsługę każdego z nich.

Ale powoli.

Zacznijmy od organizacji naszego panelu, proponuję wyświetlać wartości w wierszach, w każdym etykieta z nazwą i odpowiednia kontrolka dla wartości – taka para dla każdej Property naszego obiektu. Czyli na przykład:

private void CreateControlsForProps(IEnumerable<PropertyInfo> properties, object ob)
{

foreach
(var prop in properties)
{

Panel
row = new Panel();
row.Dock =
DockStyle.Top;
row.Height = 36;

this.panel.Controls.Add(row);
row.Controls.Add(
this.GetLabelForProperty(prop));

if (prop.PropertyType == typeof(String))
{

row.Controls.Add(
this.GetInputForString(prop));
}

else
if (prop.PropertyType == typeof(Int32))
{

row.Controls.Add(
this.GetInputForNumber(prop));
}

else
if (prop.PropertyType == typeof(Color))
{

row.Controls.Add(
this.GetInputForColor(prop));
}

else
if (prop.PropertyType.IsEnum)
{

row.Controls.Add(
this.GetInputForEnum(prop));
}

else

{

row.Controls.Add(
new Label()
{

Text =
„Niestety, typu „ + prop.PropertyType.Name + ” jeszcze nie obsługujemy.”,
Left = row.Controls[0].Right + 15,

AutoSize =
true,
Anchor = Anchor |
AnchorStyles.Right
});
}
}
}

Tak przygotowany kod wystarczy „tylko” uzupełnić o metody tworzące odpowiednie kontrolki dla wprowadzania wartości i etykiety. Podam przykład dla klasy String, dla pozostałych typów odsyłam to pokładów własnej inwencji (nie jest to nic trudnego!):

private Control GetInputForString(PropertyInfo prop)
{

TextBox tb = new TextBox();tb.Left = 75;
tb.Anchor = tb.Anchor | AnchorStyles.Right;

return
tb;
}

i dla etykiet:

private Control GetLabelForProperty(PropertyInfo prop)
{

Label l = new Label();
l.Text = prop.Name;

return
l;
}

5. A co z wartościami?!

Bardzo dobre pytanie… jak widać… nic. Ale przecież nie po to tworzymy naszą kontrolkę do wyświetlania wartości, żeby wartości nie wyświetlać…

Dlatego konieczna będzie mała modyfikacja powyższego kodu. Zacznijmy od podstaw – jak w ogóle pobrać wartość Property obiektu, którego typu nie znamy z początku, zresztą sami przecież nie wiemy co to za Property (więc rzutowanie, czy wpisywanie nazwy Property po kropce nie jest możliwe)? Nic trudnego, w naszym kodzie mamy już obiekt opisujący Property, potrzebujemy jeszcze obiekt typu, którego dane Property dotyczy – a potem już tylko wywołanie metody GetValue:

prop.GetValue(ob, null);

W naszym kodzie więc potrzebujemy obiektu, którego wartość chcemy pobierać, modyfikacje będą więc następujące:

Metoda tworząca kontrolkę do edycji/wyświetlania wartości:

private Control GetInputForString(PropertyInfo prop, object ob)
{

TextBox
tb = new TextBox();
tb.Text = (
String)prop.GetValue(ob, null);tb.Left = 75;
tb.Anchor = tb.Anchor | AnchorStyles.Right;

return
tb;
}

I co za tym idzie uzupełnienie wywołań w głównej pętli wyświetlającej nasze wartości:

row.Controls.Add(this.GetInputForString(prop, ob));

i podobnie dla wszystkich pozostałych metod dotyczących Property.

6. Ale opis jest brzydki…

Fakt, opisy w tym momencie stanowią nazwy pól w klasie – brak spacji, często niewłaściwy język i skrótowe nazwy niewiele mówią potencjalnemu użytkownikowi. Tutaj z pomocą znowu przyjść nam mogą atrybuty.

Zacznijmy więc od zadeklarowania odpowiedniego (powinien zawierać przeładowany konstruktor oraz musi zawierać Property dla wyświetlanej wartości):

public class VisualDisplayAttribute : Attribute
{

public
string VisualDisplay { get; set; }

public VisualDisplayAttribute(string VisualDisplay)
:
base()
{

this
.VisualDisplay = VisualDisplay;
}
}

Następnie niewygodne Property „ozdabiamy” takim atrybutem:

[VisualDisplay("Tło")]
public
Color Background { get; set; }

I modyfikujemy tworzenie etykiety:

private Control GetLabelForProperty(PropertyInfo prop)
{

Label
l = new Label();
var
attributes = prop.GetCustomAttributes(typeof(VisualDisplayAttribute), true);
if
(attributes.Length > 0)
{

l.Text = ((
VisualDisplayAttribute)attributes[0]).VisualDisplay;
}

else

{
l.Text = prop.Name;
}

return
l;
}

7. Czy można zrobić pola tylko do odczytu? Albo obowiązkowe?

Jak najbardziej – proponowane rozwiązanie to odpowiedni zestaw atrybutów. Np. nowy atrybut ReadOnlyValueAttribute, którego posiadanie przez Property badamy w identyczny sposób jak dla wcześniejszych przykładów i w przypadku znalezienia ustawiamy np. wartość enabled tworzonej kontrolki na false.

Dzięki odpowiedniemu zestawowi atrybutów możemy praktycznie dowolnie sterować całą naszą kontrolką – od możliwości edycji danej wartości, po kolor tła kontrolki, czy jej kontenera – granicą jest chyba tylko nasza wyobraźnia. Faktem jest, że pewne rzeczy dużo łatwiej było by zaprojektować w „tradycyjny” sposób, ustawiając kontrolki w designerze, niż odczytywać wartości z atrybutów pól klasy, po czym przeliczać je na wartości, które „ręcznie” trzeba ustawić w kontrolkach, granicę opłacalności wyznaczyć musimy sobie sami.

8. Zaraz, zaraz… miała być edycja!

I nikt o niej nie zapomniał. Jak łatwo zauważyć – wartości co prawda odczytujemy… ale ich już nie zmieniamy… metod jest kilka, ale wszystkie wymagają jednego – zapamiętania lub pozyskania obiektu PropertyInfo oraz obiektu, który wyświetlamy / edytujemy. Kiedy już owe obiekty mamy, wystarczy nam… jak zwykle jedna linijka… a dokładniej wywołanie metody SetValue klasy PropertyInfo

Zmodyfikujmy raz jeszcze nasz kod – do pól Tag paneli przypiszemy odpowiednie informacje (dla obiektu panel naturalny będzie obiekt na panelu wyświetlany, dla panelu wiersza – PropertyInfo, którego wiersz dotyczy):

private void CreateControlsForProps(IEnumerable<PropertyInfo> properties, object ob)
{

this
.panel.Tag = ob;
foreach
(var prop in properties)
{

Panel
row = new Panel();
row.Tag = prop;

Dodajmy obsługę zdarzenia zmiany wartości kontrolki edytującej Property, na przykład:

private Control GetInputForString(PropertyInfo prop, object ob)
{

TextBox
tb = new TextBox();
tb.Text = (
String)prop.GetValue(ob, null);
tb.Left = 75;

tb.TextChanged +=
new EventHandler(tb_TextChanged);
return
tb;
}

void tb_TextChanged(object sender, EventArgs e)
{

TextBox
tb = sender as TextBox;
PropertyInfo
prop = tb.Parent.Tag as PropertyInfo;
object
ob = tb.Parent.Parent.Tag;

prop.SetValue(ob, tb.Text, null);
}

Gotowe!

Zdaję sobie sprawę, że posługiwanie się polem Tag bywa mętne i trochę utrudnia analizę kodu następcom, ale zostało użyte raczej jako przykład niż wzór – osobiście proponowałbym np. podziedziczyć po panelu i stworzyć klasę panelu wiersza z odpowiednim, dedykowanym Property dla PropertyIfno itp. Takie rozwiązanie będzie dużo bardziej czytelne no i nikt nam wtedy wartości nie nadpisze, gdy stwierdzi, że fajnie by było dla niego przechować coś w Tagu

9.Przecież refleksja jest wolna!

Tak, refleksja działa wolniej niż pobierania i ustawianie wartości „normalnymi” odwołaniami do właściwości obiektów, jednak stworzenie i wyświetlenie jednej kontrolki zajmuje nieporównanie więcej czasu… więc nie stanowi to w tym przypadku żadnego argumentu, ani nie powinno nas to napełniać obawami.

10. Plus i minus, to jedyne co słyszę – czyli kiedy stosować?

Zdecydowanym plusem takiego rozwiązania jest jego elastyczność, można wyświetlić w ten sposób wszystko. Dosłownie. Wystarczy spojrzeć na Visualowe komponenty do edycji obiektów – PropertyGrid’y, które mają bardzo szerokie możliwości, a działają na dokładnie takiej samej zasadzie. Będzie to rozwiązanie szczególnie cenne, kiedy klasy wyświetlane zmieniają się co chwila, albo interfejs powstaje w celu edycji danych, których jeszcze nie znamy.

Kolejnym plusem jest modna „reusability”, jeżeli stworzymy dobrze taką kontrolkę oraz zestaw atrybutów użycie ich w kolejnym projekcie będzie dziecinnie proste – w końcu nie mamy żadnych odwołań do klas encji biznesowych, czy jakichkolwiek innych klas, które chcemy wyświetlać.

Oczywiście są i minusy…
M.in. dużo cięższe debugowanie wymuszone przez użytą refleksję.

Brak możliwości podejrzenia jak wygląda kontrolka w trybie design, jedyna możliwość to uruchomienie aplikacji na przykładowych danych.

Monotonność interfejsu i dużo trudniejsze modyfikacje wyglądu kontrolek pod konkretne Propery. Bez napracowania się nad atrybutami i ich interpretacją każde użycie kontrolki do wyświetlania danych wyglądać będzie identycznie.

Można zadać sobie pytanie po co tak się męczyć? przecież są wspomniane już PropertyGrid’y, są dobre do edycji danych Gridy. Ciężko temu argumentowi zaprzeczyć, to prawda. Tym niemniej tych gridów nie zmodyfikujemy, nie powiemy im co mają wyświetlać, a co nie w tak łatwy sposób itp. To rozwiązanie da nam pełną dowolność, której być może będziemy potrzebować.

public IEnumerable<PropertyInfo> GetPropertiesToDisplay(object ob)

{

var type = ob.GetType();

var properties = type.GetProperties(BindingFlags.Public | BindingFlags.Instance);

var returnValue = from PropertyInfo p in properties where p.GetCustomAttributes(

Jak w projekcie skorzystać z bazy SQLite

Author: Krzysztof Wawoczny (Krzysztof Wawoczny) | kwiecień 27th, 2010
avatar

Zazwyczaj w tworzonych projektach trzeba korzystać z bazy danych, wybór konkretnej zależy od rodzaju projektu – dla ćwiczebnych aplikacji, polecam SQLite. Cała konfiguracja nie powinna zająć więcej czasu niż ugotowanie jajek.

Po pierwsze należy ściągnąć i zainstalować pakiet ADO.NET z http://sqlite.phxsoftware.com/ (lub skorzystać z innego ;))

Po drugie do projektu dodajemy referencję do System.Data.SQLite z pakietu ADO.NET (kopiujemy do katalogu System.Data.SQLite.dll)

Po trzecie dodajemy do projektu plik *.config (opis na http://msdn.microsoft.com/en-us/library/ms184658%28VS.80%29.aspx) oraz w pliku *.config wpisujemy:



  
    
      
      
    
  

Po czwarte należy wyposażyć się w jakiegoś managera bazy danych, polecam wtyczkę do przeglądarki firefox http://code.google.com/p/sqlite-manager/

Po piąte wyklikujemy prostą bazę danych

Po szóste – gotowe ; )

Teraz test naszej bazy:

      DbProviderFactory fact = DbProviderFactories.GetFactory("System.Data.SQLite");
      using (DbConnection cnn = fact.CreateConnection())
      {
        cnn.ConnectionString = "Data Source=test.db3";
        cnn.Open();
      }

Jeśli nie dostaliśmy żadnego błędu to cała konfiguracja przebiegła pomyślnie.

Najprostszy sposób na zalogowanie użytkownika

Author: Krzysztof Wawoczny (Krzysztof Wawoczny) | kwiecień 26th, 2010
avatar

Obecnie, w niemal każdej aplikacji biznesowej wymagane jest zalogowanie się, by w pełni móc korzystać z możliwości, jakie taka aplikacja daje, lub by wogóle móc cokolwiek zrobić. Jakiś czas temu zrobiłem krótki research po sieci w poszukiwaniu jakiegoś sprytniejszego rozwiązania niż to, które używam w swoich aplikacjach. Cóż… Dziwni są ludzie, którzy zatrudniają do obrony swojego zamku legiony, a pozwalają do niej wejść zwykłym OR 1=1 (to tak na marginesie), nie rozumię też rozwiązania, które zostało pozytywnie ocenione na jednym z for programistycznych, a wygląda ono tak:
W aplikacji sprawdzamy, czy ustawiony jest w bazie znacznik informujący o zalogowaniu, jeśli nie, to po zalogowaniu generujemy go, a usuwamy, gdy użytkownik zamknie aplikację… A co, jeśli zwiesi się komp, zabraknie prądu albo wydarzy się coś innego? Przecież po wystąpieniu tak nieoczekiwanego zdarzenia wartość w bazie może się nie zmienić, a wtedy każdy, kto skorzysta z komputera będzie zalogowany. Naprawdę nie rozumię wielu tych wielkich idei, dlatego na razie nadal korzystam z bardzo prostego rozwiązania, które wygląda następująco:

W aplikacji w sekcji odpowiedzialnej za przechowanie predefiniowanych zmiennych globalnych (jak np. nazwa bazy, string wyświetlany w górnej belce aplikacji etc.) ustawiam sobie zmienną, określającą, czy użytkownik jest zalogowany (domyślnie ustawioną na false) oraz metodę pozwalającą zmienić wartość tej zmiennej:

public class GlobalAppVariables
{
    /// Globalna zmienna, której wartość jest ustwaiona na 1, gdy użytkownik się zaloguje.
    static int _logged = 0;

    /// Dostęp/zmiana wartości globalnej zmiennej _logged
    public static int Logged
    {
      get
      {
        return _logged;
      }
      set
      {
        _logged = value;
      }
    }
}

Samo logowanie polega na wywołaniu metody, która połączy się z bazą, sprawdzi, czy dla podanych parametrów istnieje wpis i na tej podstawie zwróci wynik pozytywny/negatywny, co z kolei pozwoli mi ustawić wartość zmiennej _logged w następujący sposób:

private void button1_Click(object sender, EventArgs e)
{
    object result = Queries.StartSession(this.loginTxt.Text, this.PasswordTxt.Text);
   //Queries to osobna klasa, w której przechowywane są metody odpowiedzialne za operacje na bazie
    if (result != null)
    {
        this.Close(); //zamknięcie okna logowania
        GlobalAppVariables.Logged = 1; // w tej chwili wiem, że już mam zalogowanego użytkownika
    }
    else
    {
       //info o nieudanym logowaniu
    }
}

Jeśli już mam zalogowanego użytkownika, mogę odblokować domyślnie wyłączone opcje, lub zrobić coś innego.
Jeśli nie potrzebujesz wymyślnego systemu logowania skorzystaj z przedstawionego sposobu. Jest on wystarczający dla wielu nieskomplikowanych aplikacji. AVE!

Dynamiczne generowanie kontrolek

Author: Krzysztof Wawoczny (Krzysztof Wawoczny) | kwiecień 24th, 2010
avatar

W czasie pracy nad swoim małym projektem w C#, stanąłem przed problemem dynamicznego generowania kontrolek na formie. Nie chciałem, aby cały kod za to odpowiedzialny znajdował się w zdarzeniu kliknięcia w button, wiec stworzyłem przykładową klasę, którą nazwałem ControlGenerator.
klasa
Generalnie, trzeba by ją jakoś połączyć z główną formą aplikacji (w projekcie Form1 – standardowe nazewnictwo, gdyż więcej form nie przewiduję). W klasie ControlGenerator utworzyłem zmienną typu Form1 służącą za łącznik klasa – forma:

public class ControlGenerator
{
    /// Prywatna zmienna typu Form1
    private Form1 pf;
   //...
}

Następnie, konstruktor klasy musi posiadać takie przypisanie zmienej pf, aby wskazywała ona na referencję instancji mojej Form1 (trochę to może zamieszane, więc kawałek kodu wyjaśni, co miałem na myśli):

public ControlGenerator(Form1 f1)
{
  pf = f1;
}

Tak właśnie wygląda konstruktor, o którym była mowa, a jak to wygląda ‘na żywo’ jest ukazane poniżej.

W formie głównej aplikacji (Form1) po kliknięciu w jeden z przycisków powinny wygenerować się kontrolki, inicjuję więc obiekt klasy ControlGenerator w następujący sposób:

private void button1_Click(object sender, EventArgs e)
{
    ControlGenerator generate = new ControlGenerator(this);
   // właśnie tu do konstruktora przekazuje referencję do Form1 za pomocą this
}

Skoro moje połączenie klasa – forma jest już aktywne, to pora napisać metodę generującą kontrolki. Do owej metody powinna być przekazana informacja jak wiele kontrolek utworzyć. W poniższym przykładzie ograniczę się do wygenerowania TextBoxów z Labelkami zawartymi w GroupBoxie (które mogą być oczywiście dodatkowo osadzone na jakimś panelu, czy czymkolwiek się chce).
Gotowa funkcja prezentuje się następująco:

    public void GenerateRegisterControls(int number, string text)
    {
      int[] textboxes = new int[number];
      string[] labels = { "Imię", "Nazwisko", "PESEL", "Ulica", "Numer budynku",
                              "Miasto", "Kod pocztowy", "Województwo", "Telefon" };
      int i = 0, move = 0;

      //tworzę GroupBoxa, do którego wrzucę tworzone kontrolki (opis metody w następnym listingu)
      GroupBox box = GenerateGroupBoxControl(text); 

      foreach (int element in textboxes)
      {
        TextBox textbox = new TextBox(); //tworzę nowego TextBoxa
        Label label = new Label(); //tworzę nowy Label
        //lokalizacja TextBoxa
        textbox.Location = new System.Drawing.Point(200, 70 + move);
        textbox.Name = "textbox" + i.ToString();
        textbox.Size = new System.Drawing.Size(360, 20 + move); //wymiary TextBoxa
        //Rozmiar czcionki w TextBoxie - funkcja nie zostanie przedstawiona
        //w tym wpisie
        textbox.Font = SetFontForElement(40, textbox.Font);
        textbox.TabIndex = i;
        textbox.Visible = true;
        //automatyczna zmiana rozmiarów TextBoxa przy zmianie rozmiaru okna
        textbox.Anchor = AnchorStyles.Left | AnchorStyles.Top | AnchorStyles.Right;
        textbox.BorderStyle = BorderStyle.FixedSingle; //styl ramki
        //lokalizacja Labela
        label.Location = new System.Drawing.Point(20, 80 + move);
        label.Name = "label" + i.ToString();
        //nazwa Labela wzięta z wcześniej utworzonej tablicy stringów
        label.Text = labels[i];
        label.Font = SetFontForElement(28, label.Font);
        label.Size = new System.Drawing.Size(160, 28);
        label.Visible = true;

        box.Controls.Add(textbox); //dodaję do GroupBoxa utworzone TextBoxy
        box.Controls.Add(label); //dodaję do GroupBoxa utworzone Labele

        i++;
        move += 50;
      }
      //dodaję GroupBoxa z kontrolkami do panelu w kontenerze Form1
      pf.splitContainer1.Panel2.Controls.Add(box);
    }

Jak widać funkcja nie jest specjalnie skomplikowana, a tablicę z nazwami labelek można by przekazać jako parametr – jednak nie ma potrzeby w tym wpisie rozmieniać się na drobne. Tworzenie TextBoxów i Labelek można by dodatkowo zostawić osobnym funkcjom wywołanym w GenerateRegisterControls, jak np. GenerateGroupBoxControl, która wygląda następująco:

    private GroupBox GenerateGroupBoxControl(string text)
    {
      GroupBox box = new GroupBox();

      box.Name = "GroupBoxRegister";
      box.Text = text;
      box.Location = new System.Drawing.Point(1, 9);
      box.Size = new System.Drawing.Size(pf.splitContainer1.Panel2.Width - 20, pf.splitContainer1.Panel2.Height - 20);
      box.Anchor = AnchorStyles.Left | AnchorStyles.Top | AnchorStyles.Right | AnchorStyles.Bottom;

      return box;
    }

Samo wygenerowanie kontrolek po kliknięciu w jeden z przycisków wygląda następująco:

    private void button1_Click(object sender, EventArgs e)
    {
      ControlGenerator generate = new ControlGenerator(this);
      //9 TextBoxów i 9 Labelek, GroupBox będzie opatrzony tekstem Rejestracja
      generate.GenerateRegisterControls(9, "Rejestracja");
    }

Efekt:
efekt

Jeśli ktoś dopiero zaczyna swoją przygodę z C#, to informacje, które przedstawiłem z pewnością się przydadzą, albo przynajmniej wskażą drogę poszukiwań w google. AVE!