Tematy zajęć: 1. Wprowadzenie do WPF i XAML. Tworzenie interfejsu użytkownika. 2. Posługiwanie się podstawowymi kontrolkami. 3. Własności i zdarzenia w WPF. 4. Zadania aplikacji. Okna. 5. Polecenia. Zasoby. 6. Wiązanie danych. 7. Konwersja, walidacja, szablony, widoki. 8. Style, drzewa, menu. 9. Dokumenty i drukowanie. 10. Kształty, transformacje, pędzle, geometria, rysowanie. 11. Animacje. 12. Szablony kontrolek. Kontrolki użytkownika. 13. Grafika 3d. 14. Interfejs oparty na stronach. 1/38
Literatura i oprogramowanie: 1. Matthew MacDonald, Pro WPF in C# 2010: Windows Presentation Foundation in.net 4 2. Adam Nathan, WPF 4 Unleashed 3. Andrew Troelsen, Język C# I platform.net 4. Stephen C. Perry, C# I.NET 5. Jesse Liberty, C#. Programowanie 6. Charles Petzold, Applications = Code + Markup, a guide to the Microsoft Windows Presentation Foundation 7. Matthew A. Stoecker, Microsoft.NET Framework 3.5 Windows Presentation Foundation 8. http://msdn.com (wszystko na temat programowania w C# i nie tylko) 9. http://codeguru.pl, http://codeproject.com (artykuły na temat programowania w C# i nie tylko) 10. Visual Studio 2008 lub 2010 w dowolnej wersji (wraz z dostępnym z niego systemem pomocy MSDN) 3/38
4/38 Co to jest programowanie zdarzeniowe? Jak powinna wyglądać aplikacja okienkowa? Krok pierwszy tworzenie interfejsu użytkownika (wizualne lub w kodzie).
Co to jest programowanie zdarzeniowe? Jak powinna wyglądać aplikacja okienkowa? Krok drugi obsługa zdarzeń. (chcemy, aby akcja użytkownika spowodowała wykonanie naszego kodu) void Oblicz(...) {......... } 5/38
Co to jest programowanie zdarzeniowe? Jak powinna wyglądać aplikacja okienkowa? Krok trzeci interakcja z kontrolkami. (chcemy odczytać wprowadzone przez użytkownika dane, wykonać obliczenia i wyświetlić wynik) void Oblicz(...) {......... } 6/38
7/38 WPF - Windows Presentation Foundation API wyższego poziomu Nowy system graficzny i API (interfejs programowania aplikacji) Windowsów. Najbardziej radykalna zmiana interfejsu od czasów Win95. Wcześniej API opierało się na User32 (wygląd i zachowanie okien i kontrolek) oraz GDI/GDI+ (odpowiadało za rysowanie). Różnorodne frameworki (np. Window Forms, MFC) zapewniały dodatkową abstrakcję, pomagając redukować złożoność, dodając nowe możliwości; ale pod tym zawsze kryło się User32 i GDI/GDI+ ze swymi ograniczeniami.
8/38 WPF Wykorzystuje DirectX do rysowania okien i zawartości. Niezależność od rozdzielczości (oparcie się na jednostkach logicznych =1/96 cala) umożliwia łatwe skalowanie i dopasowanie do rozdzielczości ekranu. Ułożenie kontrolek: dynamiczne, oparte na zawartości (dopasowują się do swojej zawartości oraz dostępnego miejsca). Obiektowy model rysowania oparty na grafice wektorowej. Wsparcie dla mediów i grafiki 3D. Deklaratywne tworzenie animacji. Style i szablony pozwalają dopasowywać formatowanie i sposób renderowania elementów interfejsu. Deklaratywne *) tworzenie interfejsu użytkownika (XAML) pozwala oddzielić wygląd interfejsu od kodu. *) Deklaratywne czyli nie opisujemy kroków prowadzących do rozwiązania (algorytmu), a jedynie samo rozwiązanie. Nie mówmy jak ma być coś zrobione, ale co ma być zrobione.
XAML Extensible Application Markup Language oparty na XMLu*) (deklaratywny) język do tworzenia interfejsu definiuje ułożenie (oraz inne cechy) kontrolek w oknie pozwala na podział pracy pomiędzy programistów i grafików (twórców interfejsu) XAML nie jest obowiązkowy to samo można zrobić w kodzie, ale wymaga to większego nakładu pracy można korzystać z narzędzi wizualnych do generowania plików XAML (np. Microsoft Expression Blend) warto znać XAMLa aby móc w pełni wykorzystywać możliwości WPFa cel oddzielenie tworzenia interfejsu od programowania (mechanizmów) np. w Windows Forms narzędzia wizualne tworzą kod c# nie ma łatwej współpracy (a logika i tak zawsze po stronie programisty (animacje, etc.)) *) XML uniwersalny język znaczników do strukturalnego reprezentowania danych 9/38
XML wprowadzenie prawidłowy plik XML <!-- komentarz --> znacznik otwierający <lista> <osoba> Kowalski </osoba> <osoba imię="piotr" nazwisko="nowak"> <telefon numer="0123456789"/> </osoba> </lista> atrybuty znacznik pusty znacznik zamykający 10/38
11/38 XML wprowadzenie nieprawidłowy plik XML <lista> <osoba>nowak</osoba> <osoba> <adres> </osoba> </adres> </lista> <osoba> Kowalski </osoba>
12/38 XAML każdy znacznik odpowiada określonemu elementowi interfejsu (i konkretnej klasie.net powoduje utworzenie obiektu danej klasy) możliwe jest zagnieżdżanie używane do określenia zawierania np. jednych elementów w innych ustawianie właściwości przez atrybuty <Window... Title="Okienko" Height="200" Width="300" FontSize="14"> <Grid> <Button Margin="30"> Nie dotykać </Button> </Grid>
Element <Window> okno aplikacji <Window x:class="windowsapplication1.window1" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Title="Okienko" Height="200" Width="300" FontSize="14"> <Grid> <Button Margin="30"> Nie dotykać </Button> </Grid> Przykładowe atrybuty okna: Title tekst na pasku tytułowym Height wysokość (rozmiar w jednostkach logicznych) Width szerokość FontSize rozmiar czcionki używanej przez wszystkie kontrolki w oknie Każdy atrybut odpowiada konkretnej własności klasy Window uwaga: tylko jeden element korzenia (nadrzędny) 13/38
14/38 Element <Window> okno aplikacji <Window x:class="windowsapplication1.window1" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"...>... Przestrzenie nazw określają, skąd pochodzą klasy (znaczniki), którymi się posługujemy. (uwaga: te deklaracje można umieścić gdziekolwiek (jako atrybuty), ale dobra praktyka każe umieszczać je w korzeniu) xmlns=" " zawiera wszystkie klasy WPFa, kontrolki, wszystko do tworzenia elementów interfejsu deklarowana bez prefiksów, jest domyślna dla dokumentu xmlns:x=" " XAMLowa przestrzeń nazw zawiera różne przydatne narzędzia i elementy XAMLa; zmapowana tu do prefiksu x - czyli wymaga podania <x:nazwaelementu> gdy chcemy coś z niej wziąć te przestrzenie nazw nie odpowiadają dokładnie przestrzeniom z.net, gdyż dla ułatwienia i uproszczenia zawarto większość w zbiorczej - a definiowane są przez URI aby różni dostawcy nie wchodzili sobie w drogę
15/38 Element <Window> okno aplikacji <Window x:class="wpfapplication1.window1" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"...>... atrybut x:class w oknie pozwala połączyć klasę z tworzonym interfejsem (prefiks x oznacza, że ten atrybut to element przestrzeni nazw XAMLa a nie WPF) // zawartość pliku Window1.xaml.cs: namespace WpfApplication1 { public partial class Window1 : Window { public Window1() { InitializeComponent(); } } }
16/38 nazywanie elementów ważne, aby posłużyć się obiektem, np. <Button Name="button1"...>... </Button> odpowiada to: Button button1; i pozwala na: button1.content = Dzień dobry ; uwaga: element nie musi mieć nazwy poste właściwości (i konwersja typów) <TextBox Name="pole" VerticalAlignment="Stretch" HorizontalAlignment="Stretch" FontFamily="Verdana" FontSize="24" Foreground="Green"... > atrybuty ustawiają właściwości obiektu
Układy zawartości <Window... Title="Okienko" Height="200" Width="300" FontSize="14"> <Grid> <Button Margin="30"> Nie dotykać </Button> </Grid> Okno może zawierać tylko jeden element. Aby umieścić ich więcej musimy wykorzystać specjalny element, który posłuży za kontener dla innych kontrolek. jest on odpowiedzialny za ułożenie kontrolek w oknie; dba o dopasowanie kontrolek do zawartości oraz do dostępnego miejsca elementy nie powinny mieć ustalonego rozmiaru ani położenia (współrzędnych) o te aspekty będzie dbał kontener różne rodzaje (typy) kontenerów będą kierować się różną logiką rozmieszczania elementów Wszystkie kontenery dziedziczą z (abstrakcyjnej) klasy Panel. Dodaje ona właściwość Children jest to kolekcja elementów zawartych w panelu (pierwszy poziom zagnieżdżenia). 17/38
18/38 <StackPanel> <Window...> <StackPanel> <Button>jeden</Button> <Button>dwa</Button> <Button>trzy</Button> <Button>cztery</Button> </StackPanel>
19/38 <StackPanel> <Window...> <StackPanel Orientation="Horizontal"> <Button>jeden</Button> <Button>dwa</Button> <Button>trzy</Button> <Button>cztery</Button> </StackPanel>
20/38 Przy pomocy atrybutów możemy sterować ułożeniem kontrolek: <Window...> <StackPanel Orientation="Horizontal"> <Button VerticalAlignment="Top">jeden</Button> <Button VerticalAlignment="Center">dwa</Button> <Button VerticalAlignment="Bottom">trzy</Button> <Button VerticalAlignment="Stretch">cztery</Button> </StackPanel> VerticalAlignment HorizontalAlignment
Margin dodaje odstęp od krawędzi kontenera i sąsiednich elementów: <Window...> <StackPanel> <Button HorizontalAlignment="Left" Margin="5"> jeden</button> <Button HorizontalAlignment="Right" Margin="5"> dwa</button> <Button Margin="5">trzy</Button> <Button Margin="15, 5">cztery</Button> <Button Margin="30, 5, 15, 0">pięć</Button> </StackPanel> marginesy sąsiadujących kontrolek sumują się! 21/38
22/38 Podobnie sterujemy ułożeniem zawartości wewnątrz kontrolki: <Window...> <StackPanel> <Button HorizontalContentAlignment="Left"> jeden</button> <Button HorizontalContentAlignment="Right"> dwa</button> <Button HorizontalContentAlignment="Center"> trzy</button> <Button>cztery</Button> </StackPanel> VerticalContentAlignment HorizontalContentAlignment
23/38 Podobnie sterujemy ułożeniem zawartości wewnątrz kontrolki: <Window...> <StackPanel> <Button HorizontalAlignment="Center"> jeden</button> <Button HorizontalAlignment="Center" Padding="5"> dwa</button> <Button HorizontalAlignment="Center" Padding="15, 5"> trzy</button> <Button HorizontalAlignment="Center" Padding="30, 0, 15, 5">cztery</Button> </StackPanel> Padding steruje odstępem od zawartości kontrolki
24/38 <WrapPanel> <Window...> <WrapPanel> <Button Margin="5">jeden</Button> <Button Margin="5">dwa</Button> <Button Margin="5">trzy</Button> <Button Margin="5">cztery</Button> <Button Margin="5">pięć</Button> <Button Margin="5">sześć</Button> </WrapPanel>
<DockPanel> Dołączone właściwości <Window...> <DockPanel> <Button DockPanel.Dock="Top">jeden</Button> <Button DockPanel.Dock="Left">dwa</Button> <Button DockPanel.Dock="Right">trzy</Button> <Button DockPanel.Dock="Bottom">cztery</Button> <Button DockPanel.Dock="Top">pięć</Button> <Button DockPanel.Dock="Right">sześć</Button> <Button>siedem</Button> </DockPanel> kolejność elementów ma znaczenie! 25/38
26/38 uwaga: dołączone właściwości są to właściwości, które mogą dotyczyć jakiejś kontrolki, ale są zdefiniowane w innej klasie każda kontrolka ma swoje własne właściwości, ale też jakaś inna kontrolka czy element interfejsu (np. kontener) może chcieć udekorować ją własnymi specyficznymi właściwościami kontrolka umieszczona w kontenerze zyskuje dodatkowe właściwości określa się: TypDenifiujący.NazwaWłaściwości działa to jako: DockPanel.SetDockPanel(button, Dock.Top); co faktycznie tłumaczy się na: button.setvalue(dockpanel.dockproperty, Dock.Top) jest to właściwość potrzebna DockPanelowi, ale faktycznie przechowywana jest w Buttonie
<Grid> <Window...> <Grid ShowGridLines="True"> <Grid.RowDefinitions> <RowDefinition/> <RowDefinition/> </Grid.RowDefinitions> <Grid.ColumnDefinitions> <ColumnDefinition/> <ColumnDefinition/> <ColumnDefinition/> </Grid.ColumnDefinitions>...... </Grid> Tylko do testu Złożone właściwości elementy umieszczamy w polach siatki krok pierwszy zdefiniowanie siatki 27/38
28/38 co to jest? złożona właściwość gdy prosta to za mało; wtedy zamiast: <Grid Name="grid1" RowDefinitions = "...">... </Grid> dajemy: <Grid Name="grid1"> <Grid.RowDefinitions>... </Grid.RowDefinitions >... </Grid>
29/38 <Grid> <Window...> <Grid> <Grid.RowDefinitions>...</Grid.RowDefinitions> <Grid.ColumnDefinitions>...</Grid.ColumnDefinitions> <Button>jeden</Button> <Button Grid.Row="1">dwa</Button> <Button Grid.Column="2">trzy</Button> <Button Grid.Row="1" Grid.Column="1">cztery</Button> </Grid> krok drugi rozmieszczenie elementów
30/38 <Grid> <Window...> <Grid> <Grid.RowDefinitions>...</Grid.RowDefinitions> <Grid.ColumnDefinitions>...</Grid.ColumnDefinitions> <Button Grid.ColumnSpan="2">jeden</Button> <Button Grid.Row="1">dwa</Button> <Button Grid.Column="2" Grid.RowSpan="2">trzy</Button> <Button Grid.Row="1" Grid.Column="1">cztery</Button> </Grid> element może rozciągać się na więcej niż jedno pole siatki
31/38 <Grid> <Window...> <Grid> <Grid.RowDefinitions> <RowDefinition/> <RowDefinition/> </Grid.RowDefinitions> <Grid.ColumnDefinitions> <ColumnDefinition Width="auto"/> <ColumnDefinition Width="*"/> <ColumnDefinition Width="70"/> </Grid.ColumnDefinitions>...... </Grid> Width dla kolumn Height dla wierszy
32/38 Kontenery można dowolnie zagnieżdżać: <Grid> <Grid.ColumnDefinitions> <ColumnDefinition Width="auto"/> <ColumnDefinition Width="auto"/> <ColumnDefinition/> </Grid.ColumnDefinitions> <StackPanel>... </StackPanel> <StackPanel Grid.Column="1" Orientation="Horizontal">... </StackPanel> <WrapPanel Grid.Column="2">... </WrapPanel> </Grid>
33/38 Ciekawe cechy Grida: GridSplitter pozwala na dynamiczną (przez użytkownika) zmianę rozmiaru kolumn/ wierszy Grida SharedSizeGroup pozwala dopasować rozmiar kolumny lub wiersza do innej kolumny lub wiersza (również w innym Gridzie) Inne ciekawe rodzaje kontenerów: UniformGrid podobne do Grida, lecz wymusza jednakowy rozmiar komórek tabeli Canvas pozwala wyłącznie na rozmieszczanie bezwzględne (w ustalonych współrzędnych) InkCanvas podobne do Canvas, ale pozwala też na przyjmowanie wejścia ze stylusa (rysika), pozwala np. na rysowanie na powierzchni okna lub rozpoznawanie gestów
34/38 A jak zaprojektujemy nasze okienko? <Window... Title="Obliczenia" Height="200" Width="300" FontSize="14">...
35/38 <Window...> <Grid> <Grid.RowDefinitions> <RowDefinition Height="auto"/> <RowDefinition Height="auto"/> <RowDefinition Height="auto"/> <RowDefinition Height="auto"/> </Grid.RowDefinitions> <Grid.ColumnDefinitions> <ColumnDefinition Width="auto"/> <ColumnDefinition Width="*"/> </Grid.ColumnDefinitions> </Grid> cztery wiersze, dwie kolumny wysokość wierszy dopasowuje się do zawartości szerokość pierwszej kolumny dopasowuje się do zawartości szerokość kolumny z polem tekstowym rozciągnięta
36/38 <Window...> <Grid> <Grid.RowDefinitions>...</Grid.RowDefinitions> <Grid.ColumnDefinitions>...</Grid.ColumnDefinitions> <Label Margin="5">Pierwsza liczba:</label> <Label Margin="5" Grid.Row="1">Druga liczba:</label> <Label Margin="5" Grid.Row="2">Wynik:</Label> <TextBox Margin="5" Grid.Column="1"/> <TextBox Margin="5" Grid.Row="1" Grid.Column="1"/> <TextBox Margin="5" Grid.Row="2" Grid.Column="1" IsReadOnly="True"/> <Button Grid.Row="3" Grid.ColumnSpan="2" Margin="5" Padding="10,3" HorizontalAlignment="Right"> Oblicz</Button> </Grid> Label etykieta TextBox pole tekstowe IsReadOnly tylko do odczytu przycisk zajmuje cały wiersz
Jak dodać obsługę zdarzeń? również przy pomocy atrybutów: <Button Click="Oblicz"...>Oblicz</Button> // zawartość pliku Window1.xaml.cs: namespace WpfApplication1 { public partial class Window1 : Window { public Window1() { InitializeComponent(); } private void Oblicz(object sender, RoutedEventArgs e) { } } } Ta funkcja zostanie wywołana, gdy użytkownik naciśnie przycisk Oblicz 37/38
Jak odczytać dane z okna w kodzie programu? najpierw musimy nazwać elementy, do których chcemy mieć dostęp: <TextBox Name="pierwsze".../> <TextBox Name="drugie".../> <TextBox Name="wynik"... IsReadOnly="True"/> następnie w pliku *.cs: private void Oblicz(object sender, RoutedEventArgs e) { int a = int.parse(pierwsze.text); int b = int.parse(drugie.text); int c = a + b; wynik.text = c.tostring(); } pierwsze.text zawartość pola tekstowego int.parse(...) konwersja tekstu na liczbę c.tostring() konwersja zmiennej na tekst 38/38