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.
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)
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(...) {
...
...
...
}
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(...) {
...
...
...
}
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.
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
XML – wprowadzenie prawidłowy plik XML
<!-- komentarz -->
<lista>
<osoba>
Kowalski </osoba>
<osoba imię="Piotr" nazwisko="Nowak">
<telefon numer="0123456789"/>
</osoba>
</lista>
znacznik zamykający
atrybuty
znacznik pusty znacznik otwierający
XML – wprowadzenie nieprawidłowy plik XML
<lista>
<osoba>Nowak</Osoba>
<osoba>
<adres>
</osoba>
</adres>
</lista>
<osoba>
Kowalski
</osoba>
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>
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)
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ę
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();
} } }
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>
</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).
<StackPanel>
<Window ...>
<StackPanel>
<Button>jeden</Button>
<Button>dwa</Button>
<Button>trzy</Button>
<Button>cztery</Button>
</StackPanel>
</Window>
<StackPanel>
<Window ...>
<StackPanel Orientation="Horizontal">
<Button>jeden</Button>
<Button>dwa</Button>
<Button>trzy</Button>
<Button>cztery</Button>
</StackPanel>
</Window>
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
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ę!
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
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
<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>
<DockPanel>
<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!
Dołączone właściwości
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>
</Window>
elementy umieszczamy w polach siatki
krok pierwszy – zdefiniowanie siatki
Tylko do testu
Złożone właściwości
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>
<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
<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
<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
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>
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
A jak zaprojektujemy nasze okienko?
<Window ...
Title="Obliczenia" Height="200" Width="300"
FontSize="14">
...
</Window>
<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
<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
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”
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