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