Kontrolki Windows Forms i GDI+

.NET zawiera wiele elementów związanych z tworzeniem bogatego interfejsu użytkownika w aplikacjach Windows. W wersji 2.0 wprowadzono wiele nowych kontrolek, rozbudowano istniejące, a także pojawiły się mechanizmy ułatwiające np. pracę w tle czy automatyczne rozmieszczanie elementów w formie.

.NET zawiera wiele elementów związanych z tworzeniem bogatego interfejsu użytkownika w aplikacjach Windows. W wersji 2.0 wprowadzono wiele nowych kontrolek, rozbudowano istniejące, a także pojawiły się mechanizmy ułatwiające np. pracę w tle czy automatyczne rozmieszczanie elementów w formie.

Wszystkie obiekty związane z programowaniem GUI w Windows zawarto w przestrzeni System.Windows.Forms. Są dostępne do aplikacji konsolowych oraz oczywiście "normalnych" aplikacji Windows, ale jeżeli się uruchomi np. MessageBox w aplikacji Web Forms (czyli aplikacji WWW), to okno dialogowe wyświetlone zostanie na serwerze (oczywiście jeśli są ustawione odpowiednie uprawnienia).

Pojemnikiem, w którym umieszczane są kontrolki Windows Forms, musi być klasa, która dziedziczy po Control. W szczególności może to być klasa Form odpowiadająca formie (okno w Windows). To, czy dany obiekt będzie zwykłym oknem (modeless), oknem modalnym, klientem MDI czy oknem-pojemnikiem MDI zależy od sposobu wywołania Form i właściwości ustawionych w tej klasie.

Istotną właściwością formy i kontrolki jest Localizable. Jeżeli zostanie ustawiona na true, to forma może mieć interfejs w różnych językach. Sposób postępowania powinien być następujący: gdy właściwość Language jest równa Default, należy przełączyć Localizable na true, dodać (lub mieć dodane) kontrolki i określić teksty - napisy na przyciskach, pozycje menu itp. To, co zostanie zaprojektowane, jest tzw. językiem neutralnym - wybieranym, gdy klient ma ustawiony język nieuwzględniony w aplikacji. Następnie można wybrać np. angielski - English (United States). Spowoduje to zdefiniowanie struktury danego języka. Jeżeli teraz przełączymy się na inny język i zmienimy jakiś tekst w formie, to będzie on widoczny tylko po uruchomieniu aplikacji z wybranym odpowiednim CultureUI (a jeżeli będzie wybrany CultureUI niedostępny do danej formy lub aplikacji, pojawi się język domyślny, czyli to, co zostało zaprojektowane przy wybranym Language równym Default). Równocześnie w projekcie pojawią się dodatkowe pliki, np. Form2.en-US.resx i Form2.pl-PL.resx oprócz standardowego Form2.resx.

Warto wiedzieć, co się stanie, gdy wrócimy do ustawienia Default i w kontrolce zmienimy tekst jeszcze niezlokalizowany, to znaczy niezmieniony, gdy w formie został wybrany inny język. Wtedy zmiany będą "propagowane" na wszystkie inne języki. Wspomniane pliki .resx dla danej lokalizacji są właściwie listą zmian. Po otwarciu takiego pliku w zwykłym edytorze XML zobaczymy np.:

<data name="button2.Text">

<value xml:space="preserve">

Tekst do pokazania w pl-PL

</value>

</data>

a potem w kodzie wygenerowanym przez projektanta (Form2.Designer.cs):

System.ComponentModel.ComponentResourceManager

resources = new System.ComponentModel.

ComponentResourceManager(typeof(Form2));

...

resources.ApplyResources(this.button2,"button2");

co powoduje, że zasoby właściwe dla wybranego języka są "nakładane" na dany przycisk.

Warto pamiętać, że do formy z Localizable ustawionym na true nowe kontrolki można dodawać tylko wtedy, gdy wybrany jest język Default.

Jeżeli chcemy programowo zmieniać ustawienia językowe, możemy użyć np.:

Thread.CurrentThread.CurrentUICulture =

new CultureInfo("pl-PL");

Oprócz UI warto też ustawić Thread.CurrentThread.CurrentCulture. Spowoduje to, że np. operacje na łańcuchach (porównania itp.) będą wykonywane zgodnie z danymi ustawieniami.

Aby utworzyć własną kontrolkę, trzeba dodać nowy typ projektu (który utworzy klasę dziedziczącą po UserControl). Wtedy można dodawać na obszarze projektanta inne kontrolki, definiować właściwości itp. Warto dodać, że to jest opcja, z której można skorzystać, nie tylko tworząc skomplikowaną kontrolkę. Jeżeli np. w wielu miejscach aplikacji jest wprowadzany (czy pokazywany) adres, to można zdefiniować własną kontrolkę, zawierającą pola TextBox, w które wprowadza się lub w nich wyświetla nazwę ulicy, kod pocztowy, miasto. Potem można używać jej wszędzie tam, gdzie trzeba coś zrobić z adresem.

Inny typ obiektów to tzw. komponenty - obiekty dziedziczące po Component. Nie są "pełnymi" kontrolkami, ale mogą pokazywać pewne elementy w formie. Komponentem może być coś, co wykona zadanie "w tle" albo składnik (jak ToolTip), który zmienia działanie kontrolek zawartych w formie (w tym wypadku definiuje podpowiedź pojawiającą się nad innymi obiektami).

W przestrzeni nazw System.Drawing znajdują się funkcje pozwalające rysować przy użyciu obiektów GDI+. Jest to bazowa biblioteka do grafiki 2D w Windows. Pozwala tworzyć dowolne rysunki, operować na bitmapach, wczytywać pliki JPG, obsługiwać czcionki, definiować regiony, tekstury itp.

W stosunku do "starego" GDI na platformie Win32, GDI+ jest zaawansowanym silnikiem graficznym.

W stosunku do "starego" GDI na platformie Win32, GDI+ jest zaawansowanym silnikiem graficznym.

Gdy tworzymy np. pióro do rysowania (obiekt Pen), to de facto zużywamy jakiś zasób niezarządzany - z systemu operacyjnego alokujemy odpowiednią strukturę Windows. Dlatego bardzo ważne jest stosowanie schematu using (albo wywołanie funkcji Dispose dla danego obiektu), aby jawnie powiedzieć, kiedy dany obiekt nie jest już potrzebny. Jeżeli tego nie zrobimy, to mimo że prawdopodobnie pamięć i zasób zostaną kiedyś zwolnione, Windows wcześniej zgłosi, że brakuje zasobów GDI.

W .NET 2.0 wprowadzono klasy (BufferedGraphics, BufferedGraphicsContext) ułatwiające korzystanie z tzw. mechanizmu podwójnego buforowania. Pozwala on tak rysować obiekty, że użytkownik nie widzi "migotania" podczas zmian. Jest to dosyć powszechnie używana technika, która polega na wykorzystaniu dwóch obrazów - rysujemy w niewidzialnym buforze, a potem szybko przełączamy to, co widzi użytkownik.

Warto przyjrzeć się możliwościom kontrolek w Windows Forms. Większość ma pewne wspólne właściwości. Anchor określa, z którymi krawędziami pojemnika będzie związana kontrolka (pojemnika - czyli albo formy, albo takiej kontrolki, która może zawierać inne elementy). Jeżeli będzie związana np. z lewą i prawą, to w miarę zwiększania rozmiaru formy zwiększy się rozmiar danego elementu. AutoSize określa, że rozmiar kontrolki będzie dostosowany do zawartości. Ta funkcja przydaje się np. do tworzenia etykiet (Label), gdy nie znamy ostatecznej wielkości tekstu. Dock określa, czy dana kontrolka może być dokowana w pojemniku (co oznacza, że np. krawędzie będą "sklejone" ze sobą), a Fill - że kontrolka będzie wypełniała cały obszar pojemnika.

Właściwości Location, Margin, Padding, Size - ustalają położenie, odstępy i rozmiar kontrolki (lub marginesy wokół wartości w niej prezentowanej). Visible określa, czy dana kontrolka jest widoczna na ekranie - jeżeli przypiszemy wartość false właściwości Visible kontenera, to znikną wszystkie kontrolki w nim zawarte. Enabled decyduje, czy kontrolka może reagować na zdarzenia, natomiast ReadOnly sprawia, że kontrolka może reagować na zdarzenia, ale użytkownik nie może zmienić jej wartości.

Uniwersalną właściwością jest Tag. Przechowa wartość dowolnego typu i będzie pojemnikiem na pomocnicze informacje (takie jak klucz główny, referencja do obiektu biznesowego itp.).

W .NET 2.0 pojawiła się także właściwość GenerateMember, która określa, czy projektant będzie dla danego elementu generował składową klasy. Jeżeli nie odwołujemy się do danej kontrolki z kodu, nie jest to potrzebne.

W wypadku kontrolek-list można zdecydować, że każdy element będzie rysowany przy użyciu kodu napisanego przez programistę - jeżeli włączy się opcję OwnerDraw. Wtedy dla każdego elementu będzie zgłoszone zdarzenie DrawItem. Inne kontrolki mają analogiczny mechanizm do rysowania całego elementu.

Warto też wiedzieć o bardzo pożytecznych metodach - BeginUpdate i EndUpdate. Służą do tymczasowego blokowania odrysowywania kontrolek (standardowo po każdej zmianie właściwości automatycznie jest ona uwzględniana w widoku danej kontrolki).

Te metody można również zastosować do pojemnika - w tym formy (obowiązuje wówczas wszystkie kontrolki w danym obiekcie). Zatem gdy zamierzamy ustawiać w kodzie dużo właściwości kontrolek, np. wypełniać je danymi z bazy, warto na początku wywołać BeginUpdate a na końcu EndUpdate. Cały proces trwa krócej, a użytkownik nie ogląda "odrysowującego" się ciągle interfejsu.

Każda kontrolka zawiera także elementy z grupy Accessibility. Pozwalają one określić, w jaki sposób dany element interfejsu współpracuje z mechanizmami wspierającymi niepełnosprawnych, np. screen readerami, które "czytają" interfejs aplikacji niewidomym.


Zobacz również