Artykuł

maj 15 2011
0

WPF Tutorial - wprowadzenie

Do grona rzeczy, które wyróżniają aplikację, już na pierwszy rzut oka, najczęściej z pewnością możemy zaliczyć jej GUI, czyli innymi słowy wartstwę prezentacji. W .Necie, praktycznie od zawsze, GUI tworzyło się za pomocą dość wygodnych Windows Forms, które za pomocą metody Drag and Drop (Przeciągnij i upuść) pozwalały na szybkie tworzenie wizualnego obszaru naszej aplikacji, często bez napisania nawet jednej linijki kodu. Istotą takiego podejścia, było użycie absolutnego pozycjonowania elementów, względem całej formatki. Tymczasem w innych językach programowania, np. w Javie od zawsze istniały różnego rodzaju menadżery układów, które pozwalały np. na grupowanie elementów pionowo, poziomo, czy np. w układach tabelarycznych.

Tytułowy WPF (Framework, który został wprowadzony w .Net 3.0) to właśnie taki trochę ukłon, w stronę podejścia zaserwowanego w Javie. Znika całkowicie pozycjonowanie absolutne, a na znaczeniu zyskują wszelakie menadżery układów oraz właściwości typu Padding i Margin. Znika również plik, w którym Visual Studio generowało wszystkie właściwości układu wizualnego, czyli znany designer. Co dostajemy w zamian? Nowe podejście, które wykorzystuje do projektowania GUI język XAML (Microsoftowa pochodna XML), bardziej przypomina tworzenie stron internetowych, niż aplikacji okienkowych znanych z Windows Forms. Tak więc zacznijmy od początku;)

Tworzenie pierwszej aplikacji i XAML

XAML, jest skrótem od ang. wyrażenia eXtensible Application Markup Language i jest używany do projektowania układów wizualnych w Windows. Tak jak wspomniałem we wstępie, jest to pochodna/rozszerzenie klasycznego XML. XAML, jest wykorzystywany do projektowania wszystkich elementów graficznych, które tworzone są w WPFie, czyli np. okien, ramek, kontrolek itp.

W WPFie, zmienia się również domyślny komponent wizualny aplikacji. W Windows Forms, tworzyliśmy formatki, które po kompilacji, stawały się oknami naszej aplikacji. W WPFie, projektujemy z kolei okna... Jak nie trudno się domyślić, oba elementy formatka i okno wzieły swoje nazwy od wybranego podejścia tworzenia GUI.

Uzbrojeni w podstawową wiedzę, możemy przystąpić do utworzenia naszego pierwszego projektu w WPF. Uruchamiamy Visual Studio (jak wspomniałem wcześniej, WPF funkcjonuje od .Net 3.0, czyli potrzebny jest Visual Studio w wersji co najmniej 2008), z menu File wybieramy opcję New project. W nowym oknie, które się pojawi, wybieramy opcję WPF Application.

Utworzony i otwarty zostanie nasz pierwszy projekt aplikacji WPF:)

W aplikacji, domyślnie pojawią się okna App.xaml oraz MainWindow.xaml. App.xaml, jest odpowiednikiem tego, co w Windows Forms, zawarte było w pliku Program.cs, czyli pozwala na uruchomienie głównego okna MainWindow.xaml. Przejdźmy do szybkiej analizy pliku App.xaml:

<Application x:Class="MyFirstWpfApplication.App"
         xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
         xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
         StartupUri="MainWindow.xaml">
    <Application.Resources></Application.Resources>
</Application>

Elementem root, tego pliku, jest element Application. To właśnie w tym miejscu, aplikacja rozpoczyna swój byt. W kolejnym kroku, pojawia się atrybut x:Class. Wartość do niego przypisana, oznacza plik zawierający code behind wraz z jego namespacem (MyFirstWpfApplication - w tym przypadku) - z definicji plik nazywa się tak samo jak wybrany plik xaml, ale otrzymuje dodatkowo rozszerzenie .cs (jeśli programowaliście kiedyś w ASP.NET, to działo się tam właśnie dokładnie tak samo). W tym przypadku, plik ten zawierać będzie jedynie definicję klasy (przynajmniej na początku).

Kolejne dwa atrybuty, odnoszą się do przestrzeni nazw, z których korzysta xaml, pozycje te, są obowiązkowe. Ostatnim atrybutem, jest StartupUri, który określa, jakie okno ma zostać otwarte w dalszym kroku. Domyślnie, jest to właśnie plik MainWindow.xaml. Atrybutu tego możemy się pozbyć, a kod uruchamiający nowe okno umieścić w pliku code-behind, za pomocą przeciążonej metody OnStartup:

protected override void OnStartup(StartupEventArgs e)
{
    base.OnStartup(e);
    this.StartupUri = new Uri("MainWindow.xaml", UriKind.Relative);
}

Ostatnim elementem pliku App.xaml, jest deklaracja globalnych zasobów dla aplikacji. Możemy ten element pozostawić tak jest, możemy je tutaj zdefiniować ręcznie, bądź też możemy się odwołać do pliku, który znajduje się w strukturze naszego projektu:

<Application.Resources>
        <ResourceDictionary>
            <ResourceDictionary.MergedDictionaries>
                <ResourceDictionary Source="Themes/Generic.xaml"/>
            </ResourceDictionary.MergedDictionaries>
        </ResourceDictionary>
</Application.Resources>

W tym przypadku, aplikacja będzie próbowała pozyskać zasoby zawarte w pliku Themes/Generic.xaml.

Skoro już wiemy jak uruchomić plik MainWindow.xaml, to czas przejść do jego analizy:

<Window x:Class="MyFirstWpfApplication.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="MainWindow" Height="350" Width="525">
    <Grid>
        
    </Grid>
</Window>

W tym przypadku mamy do czynienia z oknem, co dobitnie określa element główny Window pliku xaml. Pierwsze trzy atrybuty już znamy, nowością są ostatnie trzy, które w tym przypadku, są odpowiednikiem właściwości dobrze znanych z Windows Forms. Swoją drogą dalej funkcjonują one jako właściwości, również w WPF, z tymże teraz dopisują się do konkretnych elementów drzewa xaml:) Właściwości/atrybuty, może ustawiać zarówno z kodu jak i z okienka Properties. Myślę, że wszystkie atrybuty są tu raczej klarowne;)

Wewnątrz elementu Window, znajduje się element Grid, który jest jednym z układów pozwalających na rozmieszczenie elementów w aplikacji. Zanim przejdziemy do omówienia menadżerów układów, warto wspomnieć o jeszcze jednej ważnej rzeczy.

Element Grid umieszczony wewnątrz węzła Window, tworzy jego zawartość (z ang. Content). Zawartość danego elementu możemy zaprezentować poprzez zagnieżdżenie (tak jak w tym przypadku), lub poprzez atrybut Content. Ważny w tym przypadku jest również fakt, że dla elementów, których zawartość nie jest wyliczalna (czyli innych niż wszelkie listy, menadżery układu itp.), możemy umieścić tylko jeden element zawartości. Dla elementu Window, z reguły będzie to jakiś menadżer układu - chyba, że nasza aplikacja składa się np. tylko z jednego przycisku, ale jest to raczej bardzo abstrakcja wizja;).

Podobnym zachowaniem jak okno, cechuje się np. przycisk (Button). Wewnątrz możemy umieścić jeden element, czyli np. tekst czy obrazek. Jeśli chcemy umieścić w przycisku obrazek i tekst, musimy je zawrzeć np. w jednym z paneli.

Choć brzmi to trochę abstrakcyjnie, ale w tym tkwi właśnie elastyczność WPF związana z projektowaniem interfejsu. Jeśli coś wydaje się niewykonalne, za pomocą konwencjonalnych operacji, warto spróbować tych mniej konwencjonalnych rozwiązań, a być może się uda:)

Menadżery układów - panele

WPF posiada pięć różnych rodzajów paneli, a po polsku lepszym określeniem byłby zwrot - menadżerów układu. Do utworzenia widoku naszej aplikacji, możemy użyć dowolnego z nich, choć nie wszystkie z nich nadają się tak samo dobrze, do określonych zadań.

Nic nie stoi jednak na przeszkodzie, by w jednej aplikacji współistniało razem wiele różnych układów. Np. Grid, który rozplanowuje pomniejsze panele, Wrap panel, który rozlokowuje przyciski funkcyjne itd.

Omówię teraz pokrótce każdy z layoutów, przedstawię ich kluczowe cechy oraz zastosowanie.

Grid

Grid, pozwala na tworzenie tabelarycznych układów widoku, w których każdy element umieszczany jest względem określonego wiersza i kolumny. Jego głównym przeznaczeniem, jest sposobność utworzenie podstawowej struktury okna. Jedną z najfajniejszych jego opcji, jest możliwość automatycznego skalowania wysokości i szerokości określonych komórek. Dzięki temu, łatwo możemy tworzyć skalowalne layouty.

Aby pracować z Gridem, należy skorzystać ze znacznika Grid. Następnie, powinniśmy zdefiniować wiersze oraz kolumny, które będą tworzyć naszego grida. Dla wierszy i kolumn, powinniśmy również zdefiniować odpowiednio ich wysokości oraz szerokości. Wartości dla obu właściwości, możemy definiować na trzy różne sposoby:

  • Używając jednostek logicznych (wartości liczbowych)
  • Używając opcji auto, która pozwoli na automatycznie dopasowanie rozmiaru komórki, na podstawie wysokości/szerokości elementu w niej zawartego
  • Używając *. Gwiazdka pozwala na zajęcie całej wolnej przestrzeni, która jest dostępna po przydzieleniu innych elementów. Jeśli kilka kolumn bądź wierszy, korzysta z gwiazdki, to dodatkowa przestrzeń zostanie rozdzielona proporcjonalnie

O ile to tylko możliwe, powinniśmy używać jednostek logicznych, ponieważ jest to najbardziej optymalne i najszybsze podejście.

Po zdefiniowaniu wierszy i kolumn, należy nadać przypisać numer wiersza oraz kolumny, wszystkim elementom mającym znajdować się w gridzie. Oczywiście jeśli w określonej komórce, znajdować się będzie inny panel, to numer wiersza i kolumny przypisujemy tylko jemu, a nie elementom w nim się znajdującym. Innymi słowy, stosujemy dziedziczenie bezpośrednie. Spójrzmy na przykład:

<Window x:Class="MyFirstWpfApplication.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="MainWindow" Height="150" Width="250">
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto" />
            <RowDefinition Height="Auto" />
            <RowDefinition Height="*" />
        </Grid.RowDefinitions>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="Auto" />
            <ColumnDefinition Width="Auto" />
            <ColumnDefinition Width="*" />
        </Grid.ColumnDefinitions>
        <Button Content="button1" Grid.Row="0" Grid.Column="0" Margin="5"/>
        <Button Content="button2" Grid.Row="0" Grid.Column="1" Margin="5"/>
        <Button Content="button3" Grid.Row="0" Grid.Column="2" Margin="5" 
            Grid.RowSpan="2"/>
        <Button Content="button4" Grid.Row="1" Grid.Column="0" Margin="5"/>
        <Button Content="button5" Grid.Row="1" Grid.Column="1" Margin="5"/>
        <Button Content="button6" Grid.Row="2" Grid.Column="0" Margin="5"/>
        <Button Content="button7" Grid.Row="2" Grid.Column="1" Margin="5" 
            Grid.ColumnSpan="2"/>
    </Grid>
</Window>

Większość elementów w tym momencie powinna być już jasna, dzięki opisowi przedstawionemu wyżej. Przyjrzyjmy się nowościom. Nowością, na pewno będzie tutaj kontrolka przycisku (Button), która, w tym przypadku, posłuży nam jako wypełniacz dla komórek. Każdy przycisk, posiada właściwość Margin, równą 5. Dzięki czemu, pomiędzy przyciskami, znajduje się trochę więcej przestrzeni (o marginesach, powiem więcej w kolejnym wpisie poświęconym kontrolkom). Oprócz tego, pojawiły się również dwie nowe właściwości grida, które możemy używać bezpośrednio w elementach, czyli RowSpan oraz ColumnSpan.

Jeśli tworzyłeś kiedykolwiek strony WWW w HTML, powinieneś poczuć się teraz trochę bardziej swobodniej, bo jest to dokładnie ta sama konstrukcja, jaką można znaleźć w HTMLu.

RowSpan, pozwala zając elementowi, więcej niż jeden wiersz. Element zajmie tyle wierszy, ile zostało określonych właściwością RowSpan. Analogicznie z właściwością ColumnSpan, tylko że w tym przypadku, sprawy dotyczą kolumn.

Jeśli w naszym układzie, przycisk znalazłby się w wierszu i kolumnie 0 i ustawiony by dla niego RowSpan oraz ColumnSpan równe 3, to zająłby by on cały obszar naszego okna.

Grid posiada o wiele więcej różnych możliwości i właściwości, ale te wymienione powyżej powinny z pewnością wystarczyć na początek:)

StackPanel

StackPanel, często wykorzystywany jest do rozplanowywania przycisków. Domyślnie, umieszcza on elementy pionowo, jeden pod drugim:

<Window x:Class="MyFirstWpfApplication.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="MainWindow" Width="250">
    <StackPanel>
        <Button Content="button1" Margin="5" />
        <Button Content="button2" Margin="5" />
        <Button Content="button3" Margin="5" />
        <Button Content="button4" Margin="5" />
        <Button Content="button5" Margin="5" />
    </StackPanel>
</Window>

Jak widać, użycie StackPanela jest dużo prostsze niż Grida. Efekt powyższego kodu, prezentuje się następująco:

Warto wiedzieć, że StackPanel może funkcjonować również w orientacji poziomej. Aby tak się stało, w znaczniku otwierającym wystarczy ustawić właściwość Orientation:

<StackPanel Orientation="Horizontal">
...
</StackPanel>

WrapPanel

WrapPanel, działa podobnie do StackPanela, kluczowa różnica, to słowo Wrap (z ang. zawijaj), które oznacza, że elementy użyte w tym panelu, zawijają się, w momencie kiedy kończy się miejsce.

Jak to wygląda w praktyce? Jeśli WrapPanel, ma orientację poziomą (tak jest właśnie domyślnie), to elementy dodawane są poziomo. Jeśli elementów jest więcej, niż może pomieścić dany poziom, to przechodzą one do następnej linii. Działa to dokładnie tak, jak funkcja Zawijanie wierszy w Windowsowym notatniku. Spójrzmy na poniższy kod:

<Window x:Class="MyFirstWpfApplication.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="MainWindow" Width="250">
    <WrapPanel>
        <Button Content="button1" Margin="5" />
        <Button Content="button2" Margin="5" />
        <Button Content="button3" Margin="5" />
        <Button Content="button4" Margin="5" />
        <Button Content="button5" Margin="5" />
    </WrapPanel>
</Window>

Podobnie jak w przypadku StackPanela, myślę że kod jest na tyle intuicyjny i jasny, że nie wymaga żadnych wyjaśnień. Efekt prezentuje się następująco:

Przyciski zostały zawinięte, ponieważ nie zmieściły się w jednej linii. Do czego można wykorzystać WrapPanel? Przyda się on w sytuacji, kiedy tworzymy elastyczny, skalowalny layout, który powinien dopasowywać się do różnych rozdzielczości ekranu komputera.

DockPanel

DockPanel, działa bardzo podobnie jak właściwość Dock znana z Windows Forms. Pozwala on na umieszczenie elementów, w czterech różnych kierunkach naszego okna. Dokowanie, stosujemy do konkretnych elementów umieszczonych wewnątrz DockPanelu. Robimy to w podobny sposób, jak ustawialiśmy wiersz oraz kolumnę w Gridzie.

Przy okazji, warto wspomnieć, że odwoływanie się do nadrzędnego elementu, jest znanym trickiem w WPF i pozwala w bieżącym elemencie na wykorzystanie dodatkowych właściwości związanych z elementem nadrzędnym - w tym przypadku, na zarządzanie układem.

Spójrzmy na kod implementujący DockPanel:

<Window x:Class="MyFirstWpfApplication.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="MainWindow" Width="250" Height="250">
    <DockPanel>
        <Button Content="Top" DockPanel.Dock="Top" />
        <Button Content="Bottom" DockPanel.Dock="Bottom" />
        <Button Content="Left" DockPanel.Dock="Left" />
        <Button Content="Right" DockPanel.Dock="Right" />
        <Button Content="Fill" />
    </DockPanel>
</Window>

Przeanalizujmy kod pokrótce. Scenariusz jest standardowy, panel i grupa przycisków. Cztery z pięciu przycisków posiadają właściwość DockPanel.Dock. Może ona przyjmować cztery wartości:

  • Top - element pozycjonowany do góry
  • Bottom - element pozycjonowany do dołu
  • Left - element pozycjonowany do lewej
  • Right - element pozycjonowany do prawej

W tym przypadku, kolejność rozmieszczenia elementów ma znaczenie, ponieważ kolejne elementy, pozycjonują się również względem poprzednich elementów.

To o czym warto wiedzieć, to fakt, że nie musimy ustawiać właściwości dokowania (w takim przypadku element dopasuje się do wolnej przestrzeni i pozostałych elementów), lub też możemy powtarzać określony rodzaj dokowania (w takiej sytuacji, przyciski skierowane np. do lewej, będą dążyć w tym właśnie kierunku).

Canvas

Ostatnim z układów, które serwuje nam WPF, jest Canvas. Spodoba się on szczególnie osobom, którym ciężko się odzwyczaić od Windows Forms. Nie oferuje on co prawda pozycjonowania absolutnego dla elementów znajdujących się wewnątrz panelu, ale relatywne. Co w praktyce, że jeśli zastosujemy Canvas, jako główny panel, to pozycjonowanie relatywne, będzie równe pozycjonowaniu absolutnemu.

Spójrzmy jak Canvas sprawuje się w praktyce:

<Window x:Class="MyFirstWpfApplication.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="MainWindow" Width="150" Height="140">
    <Canvas>
        <Button Content="Button1" Canvas.Top="0" Canvas.Left="0" />
        <Button Content="Button2" Canvas.Top="20" Canvas.Left="20" />
        <Button Content="Button3" Canvas.Top="40" Canvas.Left="40" />
        <Button Content="Button4" Canvas.Bottom="20" Canvas.Right="20" />
        <Button Content="Button5" Canvas.Bottom="0" Canvas.Right="0" /> 
    </Canvas>
</Window>

Podobnie jak w innych układach, również w tym przypadku korzystamy z odziedziczonych właściwości. Jak nietrudno się domyślić, po analizie powyższego kodu, element możemy pozycjonować względem czterech kierunków:

  • Top - wskazujemy jak daleko od górnej krawędzi, ma znajdować się element
  • Bottom - wskazujemy jak daleko od dolnej krawędzi, ma znajdować się element
  • Left - wskazujemy jak daleko od lewej krawędzi, ma znajdować się element
  • Right - wskazujemy jak daleko od prawej krawędzi, ma znajdować się element

Wybrane właściwości, mogą się wzajemnie wykluczać. Jeśli zastosujemy Canvas.Top w jednym elemencie, to nie zadziała Canvas.Bottom. Analogicznie sprawy mają się w przypadku pary Canvas.Left oraz Canvas.Right. Decyduje kolejność zapisania właściwości w wybranym elemencie.

Do czego może nam się przydać taki layout? Może on znaleźć zastosowanie, kiedy chcemy, by dany element na pewno znalazł się w takiej, a nie innej lokalizacji i nie umiemy zaprojektować wyglądu używając pozostałych układów. W praktyce, programiści WPF raczej marginalizują użycie tego panelu.

Podsumowanie

Dziś dowiedzieliśmy się kilku ważnych rzeczy:

  • Dowiedzieliśmy się czym jest WPF
  • Poznaliśmy podstawowe różnice między Windows Forms a WPF
  • Poznaliśmy język XAML
  • Dowiedzieliśmy się, jak tworzone są okna w WPF
  • Poznaliśmy menadżery układów

Z wiedzą zdobytą w niniejszym wpisie, możemy coś już sobie pokodować, ale nie będzie to raczej niestety nic funkcjonalnego. Sytuacja, powinna się zmienić diametralnie, po przeczytaniu kolejnej części wpisu, w której zamierzam powiedzieć w głównej mierze o kontrolkach. A konkretniej, o ich właściwościach, ich pozycjonowaniu w układzie wizualnym oraz obsłudze zdarzeń.

Jeśli podoba Ci się ten wpis, sprawdź inne części tutoriala WPF.

Następna część

Data ostatniej modyfikacji: 23.09.2014, 22:05.

Podoba Ci się ten wpis? Powiedz o tym innym!

Send to Kindle

Komentarze

blog comments powered by Disqus