1. Wprowadzenie do WPF i XAML. Tworzenie interfejsu użytkownika
Transcription
1. Wprowadzenie do WPF i XAML. Tworzenie interfejsu użytkownika
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 Co to jest programowanie zdarzeniowe? Jak powinna wyglądać aplikacja okienkowa? Krok pierwszy – tworzenie interfejsu użytkownika (wizualne lub w kodzie). 4/38 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 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. 7/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. 8/38 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 XML – wprowadzenie nieprawidłowy plik XML <lista> <osoba>Nowak</Osoba> <osoba> <adres> </osoba> </adres> </lista> <osoba> Kowalski </osoba> 11/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> </Window> 12/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" Title="Okienko" Height="200" Width="300" FontSize="14"> <Grid> <Button Margin="30"> Nie dotykać </Button> </Grid> </Window> 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 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" ...>... </Window> 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ę 14/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" ...>... </Window> 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(); } } } 15/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 16/38 Układy zawartości <Window ... Title="Okienko" Height="200" Width="300" FontSize="14"> <Grid> <Button Margin="30"> Nie dotykać </Button> </Grid> </Window> 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 <StackPanel> <Window ...> <StackPanel> <Button>jeden</Button> <Button>dwa</Button> <Button>trzy</Button> <Button>cztery</Button> </StackPanel> </Window> 18/38 <StackPanel> <Window ...> <StackPanel Orientation="Horizontal"> <Button>jeden</Button> <Button>dwa</Button> <Button>trzy</Button> <Button>cztery</Button> </StackPanel> </Window> 19/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> </Window> VerticalAlignment HorizontalAlignment 20/38 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> </Window> marginesy sąsiadujących kontrolek sumują się! 21/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> </Window> VerticalContentAlignment HorizontalContentAlignment 22/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> </Window> Padding steruje odstępem od zawartości kontrolki 23/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> </Window> 24/38 <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> </Window> kolejność elementów ma znaczenie! 25/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 26/38 <Grid> <Window ...> <Grid ShowGridLines="True"> <Grid.RowDefinitions> <RowDefinition/> <RowDefinition/> </Grid.RowDefinitions> <Grid.ColumnDefinitions> <ColumnDefinition/> <ColumnDefinition/> <ColumnDefinition/> </Grid.ColumnDefinitions> ... ... </Grid> </Window> Tylko do testu Złożone właściwości elementy umieszczamy w polach siatki krok pierwszy – zdefiniowanie siatki 27/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> 28/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> </Window> krok drugi – rozmieszczenie elementów 29/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> </Window> element może rozciągać się na więcej niż jedno pole siatki 30/38 <Grid> <Window ...> <Grid> <Grid.RowDefinitions> <RowDefinition/> <RowDefinition/> </Grid.RowDefinitions> <Grid.ColumnDefinitions> <ColumnDefinition Width="auto"/> <ColumnDefinition Width="*"/> <ColumnDefinition Width="70"/> </Grid.ColumnDefinitions> ... ... </Grid> </Window> Width dla kolumn Height dla wierszy 31/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> 32/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 33/38 A jak zaprojektujemy nasze okienko? <Window ... Title="Obliczenia" Height="200" Width="300" FontSize="14"> ... </Window> 34/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> </Window> 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 35/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> </Window> Label – etykieta TextBox – pole tekstowe IsReadOnly – tylko do odczytu przycisk zajmuje cały wiersz 36/38 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