Komponenty wizualne i XAML
Informacje o module
Opis modułu
W tym rozdziale poznasz strukturę komponentów wizualnych Silverlight:
kształty, kontrolki i kontenery. Nauczysz się podstaw manipulacji
własnościami komponentów z użyciem składni XAML. Nauczysz się
manipulowad układami współrzędnych.
Cel modułu
Moduł ma za zadanie wprowadzid strukturę komponentów SIlverlight w
sposób funkcjonalny. Wprowadza się podstawy składni XAML. Szczegółowo
omawiana jest manipulacja układami współrzędnych: tworzenie i składanie.
Uzyskane kompetencje
Po zrealizowaniu modułu będziesz:
wiedział jak wygląda organizacja komponentów Silverlight oraz jakie
funkcjonalności wyodrębniono,
potrafił manipulowad własnościami wizualnymi komponentów z
poziomu XAML,
rozumiał zasady rządzące wyświetlaniem komponentów wizualnych.
Wymagania wstępne
Przed przystąpieniem do pracy z tym modułem powinieneś:
znad podstawy składni XML
rozumied zasady dziedziczenia w projektowaniu obiektowym
pamiętad czym jest prostokątny układ współrzędnych oraz czym są
współrzędne punktu w takim układzie
Przygotowanie teoretyczne
Przykładowy problem
Załóżmy, że musisz szybko stworzyd mały edytor który pozwoli rozmieścid na białej kartce zdjęcia i
teksty. Użytkownik powinien mied możliwośd przesuwania, obracania i skalowania tych elementów.
Jeśli dopiero rozpoczynasz przygodę z Silverlight, to bezwzględnie najważniejsze jest poznanie
struktury komponentów wizualnych oraz zasad nimi rządzących.
Silverlight 4 daje Tobie do dyspozycji ponad 60 kontrolek. Mimo tego, że kontrolki te realizują różne
zadania posiadają ważne wspólne cechy. Poznanie tych cech pozwoli Ci łatwo projektowad
rozwiązania wykorzystując już przygotowane elementy.
Podstawy teoretyczne (czyli od ogółu do szczegółu)
Praca z XAML
Tworzenie kodu interfejsu aplikacji przy pomocy XAML pozwala ująd przy pomocy jednego wspólnego
standardu takie cechy jak:
Struktura komponentów (logiczna i wizualna),
Style oraz wzorce kontrolek,
Automatycznie tworzony kod działający w tle,
Możliwośd importowania wzorców z narzędzi stworzonych dla grafików (jak Expression Blend).
Jak wiesz z poprzedniego rozdziału kod XAML najprostszej aplikacji Silverlight rozpoczyna się od
<Application
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" …>
</Application>
Natomiast kod głównej strony (kontrolki) rozpoczyna się od
<UserControl
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x=http://schemas.microsoft.com/winfx/2006/xaml …>
</UserControl>
Cechą wspólną są przestrzenie nazw. Przestrzeo nazw presentation zawiera definicje
najważniejszych elementów Silverlight, np.: wzorce językowe kontrolek, atrybutów.
Oznacza to, że interpreter przetwarzając wiersz (umieszczony w znaczniku UserControl)
<Button Name=”button1”>Przycisk</Button>
Poza samą analizą składni (znacznik otwarty – znacznik zamknięty) sprawdzi czy znacznik Button
może posiadad własnośd Name. Maszyna uruchomieniowa Silverlight strukturze znacznik – dziecko
nada znacznie tworząc elementy interfejsu użytkownika.
Podobne efekt możemy uzyskad w C#:
Button bt = new Button();
bt.Name = “button1”;
TextBlock tb = new TextBlock();
Tb.Text = “Przycisk”;
bt.Content = tb;
Podobieostwo występuję tylko na pierwszy rzut oka, natychmiast pojawia się wątpliwośd co do
kontekstu stworzenia przycisku. Kod XAML ze względu na strukturę element nadrzędny - element
podrzędny potrafi określid strukturę kontrolka-rodzic/kontrolka-dziecko, wiążąc kontrolki w strukturę
drzewa wizualnego.
Zmiana zawartości drzewa wizualnego (zmiana rozmieszczenia kontrolek) możliwa jest przez edycję
kodu XAML i nie wymaga specjalnie przygotowanego środowiska uruchomieniowego.
Elastycznośd interfejsu użytkownika uzyskujemy dzięki dobrze zaprojektowanemu zbiorowi kontrolek
oraz możliwości ich kontroli. Kontrola komponentów odbywa się przez system własności (szerokośd,
wysokośd, czcionka, kolor itp.:). Kolejne rozdziały opisują własności podstawowe kontrolek Silverlight.
Dla programisty XAML ważny jest sposób definiowania tych własności. Trzeba pamiętad, że finalnie
przypisanie dowolnej wartości wymagało będzie stworzenia przez Silverlight odpowiednich obiektów.
Przypisywanie atrybutowe wartości polega na dostępie do własności jak do atrybutu XML i
przypisaniu jej wartości w postaci łaocucha znaków, tzn.:
<znacznik atrybut=”wartość” … />
Silverlight znając typ atrybutu dokona w tym przypadku konwersji łaocucha (o ile jest możliwa), np.:
<Button Background=”Yellow” />
Drugą metodą przypisania wartości jest użycie składni własności, tzn.:
<znacznik>
<znacznik.własność>
Wartość możliwa do budowy jako obiekt
</znacznik.własność>
</znacznik>
Wcześniejszy przykład napisad można dokładniej, mianowicie:
<Button>
<Button.Background>
<SolidColorBrush Color="Yellow"/>
</Button.Background>
</Button>
Porównanie obu przykładów pozwala wnioskowad jak działa konwerter przypisania atrybutowego.
Konwerter ten rozpoznając łaocuch jako literał związany z kolorem podstawowym tworzy w sposób
niewidoczny obiekt typu SolidColorBrush,a następnie jego własności Color przypisuje kolor
odpowiadający łaocuchowi Yellow.
Przestrzeo nazw XAML dostępna dzięki przedrostkowi x: pozwala w szczególności nadad nazwę
dowolnemu elementowi XAML dzięki konstrukcji x:Name, np.:
<SolidColorBrush x:Name="kolor_wypelnienia" Color="Yellow"/>
Więcej cech języka XAML poznasz w pozostałych modułach. Zaprezentowany tutaj zarys w zupełności
wystarcza aby rozpocząd przygodę z Silverlight 4.
Struktura projektowa Silverlight
Wszystkie komponenty wizualne w Silverlight posiadają wspólne cechy. Cech te ujawnia struktura
dziedziczenia typów podstawowych:
DependencyObject
UIElement
FrameworkElement
Rys. 1 Typy podstawowe
Podstawowy typ, czyli DependencyObject, pozwala połączyd kontrolki (oraz własności) ze
specyficznym dla Silverlight systemem własności zależnych (temat ten rozwinięty zostanie w module
4 – Własności zależne i doczepiane).
Klasa UIElement zawiera podstawowe cechy odpowiedzialne za pracę z wyglądem i aranżacją
kontrolek. Daje również podstawę do przetwarzania interakcji kontrolek z użytkownikiem.
Ostatni w podstawowej hierarchii dziedziczenia jest typ FrameworkElement. Typ ten rozszerza
możliwości aranżacji komponentów, w szczególności dodaje mechanizm kontroli zasobów i
przydzielania stylów, lokalizacji językowej oraz łączenia danych. Ważna własnośd eksponowana przez
FrameworkElement to własnośd Name, dzięki niej możesz nadad kontrolce unikatową nazwę, np.
<Button
Name=”przycisk_start”
>START</Button>
Nazwa ta na etapie projektowania widoczna jest z poziomu kodu C#. Można również odnaleźd żądaną
kontrolkę dzięki metodzie FindName klasy FrameworkElement.
Komponenty w Silverlight można aranżowad w dowolny sposób, w edytorze możesz umiejscowid
jedną kontrolkę na drugiej. Pochodne klasy FrameworkElement, które widzisz w przyborniku,
podzielone są, przede wszystkim, w zależności od sposobu zarządzania zawartością. Zarządzanie
zawartością powinieneś tutaj rozumied jako sposób dostępu do kontrolek zdefiniowanych jako
kontrolki-dzieci.
Z poprzedniego modułu pamiętasz poniższy szkielet wyglądu twojej aplikacji:
<UserControl …>
<Grid …>
<Button … />
<TextBox … />
</Grid>
</UserControl>
Próba zadeklarowania więcej niż jednej kontrolki-dziecka w komponencie UserControl,
powodowała informację o już istniejącej zawartości. Komponent Grid natomiast nie posiada takich
ograniczeo, może posiadad tyle dzieci ile potrzebujesz. Zachowanie to wynika ze struktury Silverlight.
Przyjrzyjmy się podstawowym typom dziedziczącym z FrameworkElement:
FrameworkElement
Control
Shape
Panel
UserControl
ContentControl
Grid
StackPanel
Button
Rys. 2 Typy rozszerzone
Typ Shape:
Typ bazowy służący do rysowania kształtów (jak prostokąty, linie, elipsy),
Nie może zawierad żadnych kontrolek-dzieci.
Typ Control:
Typ bazowy kontrolek które mogą posiadad co najwyżej jedno dziecko,
Dziecko nie zawsze jest publicznie dostępne.
Typ Panel:
Podstawowy typ służący zarządzaniu kolekcjami dzieci,
Typy pochodne odpowiedzialne są za aranżację wyglądu kontrolek-dzieci.
Dzieci komponentów to dowolne elementy typu UIElement.
W przypadku klasy Panel dostęp do kolekcji dzieci uzyskujemy przez własnośd Children typu
UIElementCollection.
Poniższy kod w XAML:
<Grid …>
<Button … />
<TextBox … />
</Grid>
Równoważnie zapisad można w postaci:
Grid gr = new Grid();
Button bt = new Button();
TextBox tb = new TextBox();
gr.Children.Add(bt);
gr.Children.Add(tb);
Operacje dodawania do kolekcji są prawidłowe, gdyż zarówno klasa Button jak i TextBox pośrednio
dziedziczą z klasy UIElement.
Typy UserControl i ContentControl z punktu widzenia projektowego odróżnia zakres
widoczności własności Content, która przechowuje kontrolkę-dziecko. Własnośd ta w klasie
UserControl jest własnością chronioną, natomiast jest to własnośd publiczna w klasie
ContentControl. Oznacza to, że użytkownik komponentu który projektujesz nie będzie miał
możliwości zmiany jego zawartości gdyż jest to pochodna typu UserControl (jest to cecha
pożądana, gdyż logika działania twojego komponentu może wymagad danych zależnych od
użytkownika). Z drugiej strony nie ma najmniejszego problemu, aby na powierzchni przycisku (klasa
Button) umieścid obrazek oraz napis (a nawet inne przyciski), np.:
<Button Height="72" Width="234" >
<StackPanel Name="sp_1" Orientation="Horizontal">
<Button Width="100" Height="30" Content="Przycisk"> </Button>
<StackPanel Name="sp_2" >
<Image Source="silver.png" Width="30" Height="30"></Image>
<TextBlock Width="100">podpis</TextBlock>
</StackPanel>
</StackPanel>
</Button>
Własności Content przycisku przypisywany jest obiekt typu StackPanel nazwany sp_1, przypisanie
to jest oczywiście prawidłowe gdyż StackPanel dziedziczy z UIElement. Panel o nazwie sp_1
posiada dwójkę dzieci - przycisk oraz kolejny StackPanel nazwany sp_2. Panel o nazwie sp_2
posiada dwójkę dzieci tj.: kontrolkę Image oraz TextBlock.
Zarządzanie rozmieszczeniem
Twoja aplikacja Silverlight tuż po uruchomieniu, a jeszcze przed narysowaniem interfejsu musi ustalid
layout (wygląd, rozmieszczenie komponentów), można zapytad dlaczego? Spędzamy przecież czas
projektując layout i nie powinien się on zmienid. Tak jest w istocie, pamiętad jednak musisz, że
aplikacja Silverlight może dowiedzied się jaki posiada rozmiar w przeglądarce dopiero po inicjalizacji.
Oznacza to, że wiedząc ile miejsca przydzielono mu na stronie system zarządzania wyglądem winien
uzgodnid położenia wszystkich komponentów w zależności od zadanego rozmiaru.
Ten sposób postępowania dotyczy również składowych naszego interfejsu użytkownika, mianowicie:
każdy rodzic powinien uzgodnid ze swoimi dziedmi ich wymiary i położenie. Są one pośrednio zależne
od miejsca przeznaczonego dla rodzica.
Proces rozmieszczania dzieli się na dwie fazy:
Odpytywanie kontrolek-dzieci o rozmiar (faza Measure),
Ustalanie pozycji kontrolek-dzieci (faza Arrange).
Pierwsza faza, to faza w której rodzic odpytuje kontrolki-dzieci o rozmiar którego wymagają do
odrysowania. Odpytywane są wszystkie dzieci z wyjątkiem tych które posiadają własnośd
Visibility ustawioną na false. Oczywiście gdy kontrolka-dziecko jest np. pochodną klasy Panel
przegląda swoje dzieci w ten sam sposób oraz uwzględnia sposób ich rozmieszczenia. Na podstawie
tak uzyskanych informacji żąda pewnego obszaru do wyświetlenia swojej zawartości.
Rodzic
Dziecko
szerokość
w
ys
ok
oś
ć
szerokość
w
ys
ok
oś
ć
Rys. 3 Zapotrzebowanie kontrolki na obszar
Faza druga to faza która na podstawie zebranych informacji przydziela się miejsce oraz kontrolkom.
Co się stanie gdy kontrolka-rodzic będzie miała zbyt mało miejsca na wyświetlenie swojego dziecka?
Rodzic
Dziecko
szerokość
w
ys
o
ko
ść
szerokość
w
ys
o
ko
ść
Rys. 4 Przycinanie
W takim przypadku dziecko zostaje obcięte (ang. Clipping) do powierzchni rodzica.
Klasa UIElement która implementuje podstawy mechanizmu rozmieszczania komponentów
posługuje się wyłącznie strukturą typu Size do opisu żądanego miejsca (własnośd DesiredSize),
nie posiada jednak żadnych własności które mogłyby mied wpływ na tę wielkośd. Jest tak dlatego, że
oczekiwany rozmiar może zostad określony dla kontrolek które przechowują tekst albo obrazek.
Bezpośredni potomek klasy UIElement czyli klasa FrameworkElement wprowadza własności które
pozwalają ustalid zarówno wysokośd jak i szerokośd kontrolki, są to własności Width oraz Height.
Dodatkowe własności ActualWidth oraz ActualHeight zawierają informacje o rzeczywistych
rozmiarach kontrolki po jej odrysowaniu (mogą byd różne niż Width i Height jeśli np.: rodzic
kontrolki zdecyduje o jej rozciągnięciu).
Własnośd Margin pochodząca z klasy FrameworkElement jest przykładem własności która ma
wpływ na wielkośd komponentu na ekranie. W najprostszym scenariuszu użycia margines pozwala
zdefiniowad odstępy pomiędzy kontrolkami.
Rodzic
Dziecko 1
Dziecko 2
Góra (top)
Dół (bottom)
Lewy
(left)
Prawy
(right)
Rys. 5 Marginesy
Zależnie od tego, czy zawartośd kontrolki posiada wymuszony rozmiar (przez zawartośd, albo jawne
zadanie szerokości i wysokości) marginesy mogą wymusid zmniejszenie kontrolki albo powiększenie
obszaru potrzebnego do odrysowania. Możesz to zaobserwowad, pisząc:
<StackPanel Height="64" Width="220">
<Button Content="Przycisk A"/>
<Button
Margin="10 10 10 10"
Content="Przycisk B"/>
</StackPanel>
Rys. 6 Rezultat
Ponieważ wyświetlany na powierzchni przycisku tekst (niejawnie stworzony obiekt typu TextBlock)
posiada niewielką długośd, drugi przycisk zaakceptował mniejszą szerokośd. Natomiast wysokośd
potrzebnego do wyświetlenia obszaru została powiększona o odpowiednio margines górny i dolny.
Gdy rodzic kontrolki posiada więcej miejsca aniżeli potrzebują jego kontrolki dzieci musi zdecydowad
gdzie umiejscowid kontrolki na swojej powierzchni. Decyzja podejmowana jest na podstawie wartości
VerticalAlignment
oraz
HorizontalAlignment
(eksponowanych
przez
klasę
FrameworkElement).
Wartości dopuszczalne dla VerticalAlignment to:
Top – do górnej krawędzi,
Center – środek kontrolki w środku ojca,
Bottom – do dolnej krawędzi,
Stretch – rozciągnięcie tzn.: górna krawędź wyrównana do górnej krawędzi ojca, dolna
wyrównana do dolnej krawędzi.
Domyślną wartością VerticalAlinement jest Stretch.
Wartości dopuszczalne dla HorizontalAlignment to:
Left – do lewej krawędzi,
Center – środek kontrolki w środku ojca,
Right – do dolnej krawędzi,
Stretch – rozciągnięcie tzn.: lewa krawędź wyrównana do lewej krawędzi ojca, prawa
wyrównana do prawej krawędzi ojca.
Domyślną wartością HorizontalAlignment jest Stretch.
Omówione wcześniej własności uważane są za mniej lub bardziej standardowe, a ich odpowiedniki
znasz zapewne z innych bibliotek komponentów wizualnych (jak chodby Windows Form). Dalej
zajmiemy się unikatowymi własnościami Silverlight, który czynią mechanizm rozmieszczania
kontrolek jeszcze bardziej elastycznym.
W klasie UIElement zdefiniowano dwie arcyciekawe własności, mianowicie:
RenderTransform – zawiera układ współrzędnych kontrolki (dwuwymiarowy),
Projection – zawiera układ współrzędnych kontrolki (trójwymiarowy).
Obie własności przechowują układ współrzędnych w postaci macierzy o strukturze
,
Gdzie t – jest wektorem wierszowym przesunięcia środka układu współrzędnych, 0 jest wektorem
kolumnowym złożonym z samych zer, natomiast
jest podmacierzą kwadratową która
przechowuje współrzędne osi układu współrzędnych w postaci wierszowej. Innymi słowy jest to
afiniczny układ współrzędnych. Taka postad układu współrzędnych pozwala w szczególności oddzielid
przesuwanie kontrolki od obracania. Mianowicie jeśli współrzędne wierzchołka w kontrolki
przechowujemy w wektorze
, wtedy przekształcenie
,
możemy przeczytad jako przedstawienie położenia w układzie współrzędnych R przesuniętym o t.
Własność RenderTransform
Macierz RenderTransform jest formatu 3x3. Macierz ta w swojej naturalnej postaci dostępna jest
przez własności klasy Matrix, mianowicie: M11, M12, M21, M22, OffsetX, OffsetY. Ich
znaczenie jest następujące:
,
Oczywiście elementów które mają ustalone wartości typ Matrix nie eksponuje (ostatnia kolumna).
Przykładowe przypisanie
<StackPanel Background="Beige" Height="64"
Width="220">
<Button Content="Przycisk" >
<Button.RenderTransform>
<MatrixTransform >
<MatrixTransform.Matrix >
<Matrix M11="0" M12="1" M21="1" M22="0"
OffsetX="100" OffsetY="-75"/>
</MatrixTransform.Matrix>
</MatrixTransform>
</Button.RenderTransform>
</Button>
</StackPanel>
Rys. 7 Rezultat
Pokazuje, że gdy RenderTransform wyprowadza kontrolkę-dziecko poza obszar widoczności
rodzica, Silverlight nie będzie dziecka obcinał do powierzchni rodzica. Dodatkowo zauważmy, że
przycisk nie posiada ustalonej szerokości. Została ona zao ustalona w procesie Arrange jako równa
szerokości rodzica (zostanie to wyjaśnione dalej), dopiero wtedy kontrolka podlega przekształceniu
układu współrzędnych.
Domyślną wartością RenderTransform jest macierz identycznościowa, tj. macierz postaci.:
,
Definiuje ona prostokątny układ współrzędnych którego środek znajduje się w lewym górnym
narożniku kontrolki, mianowicie:
X
Y
Rys. 8 Domyślny układ współrzędnych
W przeciwieostwie do układów współrzędnych które znasz, w Silverlight oś Y ma zwrot w dół.
Manipulacja współczynnikami macierzy jest uciążliwa dlatego wprowadzono zbiór klas które
ułatwiają zmianę układu współrzędnych w standardowych scenariuszach użycia, Są to mianowicie:
Przesunięcie – TranslateTransform,
Obrót – RotateTransform,
Skalowanie – ScaleTransform,
Gięcie – SkewTransform,
Typowe złożenie – CompositeTransform.
W tabeli zebrano przykłady dla przycisku,
Nazwa
Przykład
Rezultat
TranslateTransform
<Button.RenderTransform>
<
TranslateTransform X ="30" Y ="-10"
/>
</Button.RenderTransform>
RotateTransform
<Button.RenderTransform>
<
RotateTransform Angle="45"
/>
</Button.RenderTransform>
ScaleTransform
<Button.RenderTransform>
<
ScaleTransform
ScaleX="0.5" ScaleY="4.5"
/>
</Button.RenderTransform>
SkewTransform
<Button.RenderTransform>
<
SkewTransform
AngleX="30" AngleY="30"
/>
</Button.RenderTransform>
Dla wszystkich tych przekształceo poza przesunięciem zdefiniowane są własności CenterX oraz
CenterY, odpowiadają one za położenie punktu względem którego przekształcenie jest
wykonywane.
Każda z uproszczonych form definiowania układu współrzędnych, pozwala inicjalizowad macierz. Jeśli
te cztery przekształcenia nie wystarczą do realizacji zadania, możemy je składad. Służy do tego klasa
TransformGroup. Zawiera ona kolekcję składowych przekształceo (własnośd Children), w XAML
inicjalizacja może wyglądad tak:
<Button.RenderTransform>
<TransformGroup >
<RotateTransform Angle="45" />
<TranslateTransform X="50" />
</TransformGroup>
</Button.RenderTransform>
Składanie przekształceo to nic innego jak iloczyn odpowiednich macierzy. Ponieważ iloczyn nie
zawsze jest przemienny to składając przekształcenia musimy zachowad szczególną uwagę. Dwa
złożenia (różniące się tylko kolejnością mogą wywoład drastycznie różne efekty).
<Button.RenderTransform>
<TransformGroup >
<RotateTransform Angle="45" />
<TranslateTransform X="50" />
</TransformGroup>
</Button.RenderTransform>
<Button.RenderTransform>
<TransformGroup >
<TranslateTransform X="50" />
<RotateTransform Angle="45" />
</TransformGroup>
</Button.RenderTransform>
Efekt ten łatwo wytłumaczyd posługując się zapisem macierzowym. Oznaczmy przez R macierz
obrotu, a przez T macierz przesunięcia, wektor x oznaczał będzie wektor położenia.
Pierwsza wersja równoważna jest wykonaniu mnożenia postaci xRT, to znaczy najpierw x wyrażony
jest w obróconym układzie a następnie środek układu ulega przesunięciu.
Druga wersja przekształcenia równoważna jest mnożeniu xTR, czyli punkt x najpierw wyrażony jest w
układzie przesuniętym i tak uzyskane współrzędne zostają wyrażone w układzie obróconym.
Wspomniane wcześniej przekształcenie CompositeTransform pozwala skrócid zapis w XAML (bez
konieczności użycia TransformGroup), typowego przypadku użycia, czyli porządku:
1. Skalowanie,
2. Gięcie
3. Obrót,
4. Przesunięcie.
Klasa CompositeTransform zachowuje powyższą kolejnośd oraz eksponuje własności przekształceo
składowych (skrótowy zapis skraca kod z sześciu linii do jednej), np.:
<CompositeTransform Rotation=”45” TranslateX=”30” SkewX=”10” ScaleY=”1.5” />
Własnośd RenderTransform jest własnością dziedziczoną. Układy współrzędnych kontrolek-dzieci
składane są automatycznie z układem współrzędnych rodzica. Oznacza to, że dzieci zawsze będą
wyrażone w układzie współrzędnych rodzica. Pokazuje to poniższy przykład
<StackPanel Background="Beige" Height="80" Width="220">
<StackPanel.RenderTransform>
<RotateTransform Angle="45" />
</StackPanel.RenderTransform>
<Button Content="Przycisk A" />
<Button Content="Przycisk B" >
<Button.RenderTransform>
<TranslateTransform X="50" />
</Button.RenderTransform>
</Button>
<Button Content="Przycisk C" />
</StackPanel>
Rezultat jest już łatwy do przewidzenia, mianowcie:
Rys. 9 Dziedziczenie układu współrzędnych
Własność Projection
Format macierzy Projection to 4x4. Strukturę tej macierzy ujawnia typ Matrix3D obudowany
klasą Matrix3DProjection. Struktura macierzy przechowującej przekształcenie układu jest bardzo
podobna do struktury omawianej wcześniej macierzy 3x3, mianowicie:
,
Eksponowana jest ostatnia kolumna (co jest ważne z punktu widzenia rzutowania), dostępna jest
dodatkowa współrzędna przesunięcia (własnośd OffsetZ) oraz macierz przechowująca współrzędne
osi układu jest formatu 3x3.
Ponownie operowad można każdej współrzędnej tej macierzy, mianowicie:
<Button.Projection>
<Matrix3DProjection ProjectionMatrix="2, 0, 0, 0,
0, 2, 0, 0,
0, 0, 1, 0,
10, 0, 10, 1"/>
</Button.Projection>
Spowoduje wydłużenie wersorów na osi X i Y (rozciągnięcie), dodatkowo środek umieszczony
zostanie w punkcie o współrzędnych (10,0,10).
W Silverlight 4 nie wyeksponowano interfejsu pozwalającego składad przekształcenia trójwymiarowe,
oraz interfejsu pozwalającego tworzyd macierze o zadanych własnościach (translacja, rotacja,
skalowanie, macierz rzutowa itp.). Bez trudu można dokonad tego samodzielnie gdyż typ Matrix3D
implementuje operator mnożenia macierzy. Po przykładowe rozwiązania spójrz (Materiały
dodatkowe….). Mimo ogromu możliwości stojących za własnymi przekształceniami 3D tematu tego
nie rozwiniemy gdyż wykracza poza ramy niniejszych materiałów.
Podobnie jak w przypadku RenderTransform do manipulacji macierzą Projection stworzono
dostarczono prostą klasę PlaneProjection. Klasa ta pomaga zrealizowad typowe scenariusze
użycia.
Klasa PlaneProjection eksponuje 12 własności pozwalających kontrolowad wynikową macierz
rzutową. Niestety nie eksponuje własności odpowiedzialnej za kontrolę rozwarcia trójwymiarowej
bryły widzenia. Własności podzielid można na cztery grupy:
Kontrola obrotów – RotationX, RotationY, RotationZ,
Kontrola
środka
obortu
–
CenterOfRotationX,
CenterOfRotationY,
CenterOfRotationZ,
Kontrola położenia środka układu w układzie globalnym (środek w centrum ekranu) –
GlobalOffsetX, GlobalOffsetY, GlobalOffsetZ,
Kontrola położenia środka układu w układzie lokalnym (środek w centrum kontrolki) –
LocalOffsetX, LocalOffsetY, LocalOffsetZ.
O ile pierwsze działanie dwóch pierwszych grup jest jasne, to różnica pomiędzy lokalnym a globalnym
przesunięciem jest subtelna. Spójrzmy na następujący fragment:
<StackPanel VerticalAlignment="Center">
<Image Width="200" Source="Tulips.jpg">
<Image.Projection>
<PlaneProjection
RotationY="60"/>
</Image.Projection>
</Image>
<Image Width="200" Source="Penguins.jpg">
<Image.Projection>
<PlaneProjection
RotationY="60"/>
</Image.Projection>
</Image>
</StackPanel>
Łatwo zauważyd, że oba obrazy rozmieszczone przez StackPanel jeden pod drugim posiadają
wspólną oś obrotu. Przypisując jednakową wartośd przesunięcia odpowiednio lokalnemu i
globalnemu przesunięciu X, czyli:
<StackPanel VerticalAlignment="Center">
<Image Width="200" Source="Tulips.jpg">
<Image.Projection>
<PlaneProjection
LocalOffsetX="100"
RotationY="60"/>
</Image.Projection>
</Image>
<Image Width="200" Source="Penguins.jpg">
<Image.Projection>
<PlaneProjection
GlobalOffsetX="100"
RotationY="60"/>
</Image.Projection>
</Image>
</StackPanel>
Rezultat winien byd przejrzysty. Przesunięcie lokalne stosowane jest przed wykonaniem obrotu
(pozwala umiejscowid środek układu współrzędnych niekoniecznie w centrum kontrolki), natomiast
przesunięcie globalne po wykonaniu obrotu (pozwala umiejscowid obiekt w przestrzeni).
Interakcja z użytkownikiem
System służący budowie interfejsów użytkownika byłby niepełny gdyby nie pozwalał na interakcję
użytkownika ze zbiorem komponentów. Jak zostało wspomniane na początku rozdziału podstawy
tego interfejsu wprowadza klasa UIElement. Interakcja kodu Silverlight z użytkownikiem odbywa się
przy pomocy systemu zdarzeo. Zdarzenie to z jednej strony specjalny typ delegacji (patrz słowo
kluczowe event języka C#) a z drugiej to proces wykonywania kodu w pewnych szczególnych
sytuacjach (np. użytkownik wcisnął lewy przycisk myszy gdy kursor znajdował się na powierzchnią
kontrolki).
Delegowanie kodu wykonywanego przy wyzwoleniu zdarzenia można dokonad na co najmniej dwa
sposoby. Pierwszy to zwyczajowe delegowanie z poziomu języka C# (z użyciem operatora +=), np.
Button1.MouseEnter += Obsluga_zdarzenia;
Delegację możemy oczywiście usunąd z uzyciem operatora -=, mianowicie:
Button1.MouseEnter -= Obsluga_zdarzenia;
Drugim sposobem jest przypisanie nazwy metody odpowiedniej własności kontrolki w sposób
atrybutowy, np.:
<Button MouseEnter=”Obsluga_zdarzenia” … />
Visual Studio wspiera ten typ przypisania obsługi pozwalając automatycznie utworzyd namiastkę
kodu metody obsługi zdarzenia (zapewnia to niezbędną zgodnośd typów).
Oddelegowany kod, wykonany będzie w chwili gdy zaistnieje pewna szczególna sytuacja (związana
np. ze związkiem kursora myszy oraz komponentu, albo naciśnięciem przycisku klawiatury). Proces
ten nazywa się wyzwoleniem zdarzenia. Jeśli o wyzwoleniu zdarzenia poinformowane zostaną
również inne kontrolki (np. bezpośredni rodzic kontrolki) mówimy o rozgłaszaniu zdarzenia (ang.
Event Routing). Rozgłaszanie zdarzeo jest bardzo ważną cechą Silverlight dokładne omówienie tego
zagadnienia pozostawiamy jednak na później. Na potrzeby tego rozdziału możesz przyjąd, że
zdarzenie wyzwalane jest na kontrolce której dotyczy.
Zdarzenia eksponowane przez UIElement, dotyczące interakcji kursora myszy oraz kontrolek
zebrano w poniższej tabeli.
MouseEnter
wyzwalane gdy kursor myszy znalazł się nad powierzchnią kontrolki
(wcześniej był poza)
MouseLeave
Wyzwalane gdy kursor myszy opuścił powierzchnie kontrolki
(wcześniej znajdował się nad powierzchnią)
MouseLeftButtonDown
Wyzwalane gdy wciśnięto lewy przycisk myszy oraz kursor znajduje się
nad powierzchnią kontrolki
MouseLeftButtonUp
Wyzwalane gdy puszczono lewy przycisk myszy nad powierzchnią
kontrolki
MouseMove
Wyzwalane gdy kursor porusza się nad powierzchnią kontrolki
MouseRightButtonUp
Wyzwalane gdy puszczono prawy przycisk myszy oraz kursor znajduje
się nad powierzchnią kontrolki
MouseRightButtonDown Wyzwalane gdy wciśnięto prawy przycisk myszy oraz kursor znajduje
się nad powierzchnią kontrolki
MouseWheel
Wyzwalane gdy zmieniła się pozycja rolki myszy oraz kursor znajduje
się nad powierzchnią kontrolki
Istnieje jeszcze jedno specjalne zdarzenie związane z kursorem myszy, mianowicie
LostMouseCapture. Zdarzenie to związane jest z procesem przechwytywania kursora myszy.
Proces ten przydatny jest w zadaniach związanych z manipulacją w czasie rzeczywistym pozycjami
kontrolek, zauważ mianowicie gdy łapiesz za boczny pasek do przesuwania dokumentów w
przeglądarce i przesuwasz kursor myszy zmiana położenia kursora raportowana jest do kontrolki
nawet wtedy, gdy kursor znajduje się poza jej powierzchnią. Gdy zwalniasz przycisk sytuacja wraca do
normalności.
Aby nad dowolną kontrolką przechwycid kursor myszy powinieneś wykonad metodę CaptureMouse.
Metoda ta zwraca wartośd logiczną informując czy udało się przechwycid kursor myszy. Proces ten
może się nie powieśd gdy np.: nie jest wciśnięty lewy przycisk myszy lub inna kontrolka przechwyciła
kursor myszy i jeszcze nie zwolniła. Od momentu przechwycenia kursora zachowanie myszy
raportowane jest kontrolce nawet gdy kursor nie znajduje się nad jej powierzchnią.
Aby zwolnid przechwytywanie kursora należy wywoład metodę ReleaseCaptureMouse. Zdarzenie
LostMouseCapture wywoływane jest właśnie w odpowiedzi na utratę przechwyconego kursora
myszy.
Kursor myszy nie jest jedynym elementem mogącym wywoływad reakcje kontrolek Silverlight. Innym
interfejsem jest użycie klawiatury. Pomiędzy kontrolkami możemy nawigowad z użyciem klawiatury
(np. przycisk TAB lub kursor numeryczny). W jednej chwili może byd aktywna tylko jedna kontrolką
mówimy wtedy że posiada fokus.
W tabeli zebrano zdarzenia związane z tym procesem.
GotFocus
Wyzwalane gdy fokus został ustawiony na kontrolkę
LostFocus
Wyzwalane gdy kontrolka utraciła fokus
Gdy kontrolka posiada fokus oraz wciśnięto przycisk klawiatury inny niż specjalny (inny niż służący do
zmiany elementu posiadającego fokus) wyzwalane są następujące zdarzenia:
KeyDown
Wyzwalane gdy naciśnięto przycisk
KeyUp
Wyzwalane gdy zwolniono przycisk
Klasa FrameworkElement wprowadza dwa dodatkowe typy zdarzeo:
Loaded
Wyzwalane gdy zainicjalizowana kontrolka została dołączona do
drzewa obiektów twojej aplikacji
Unloaded
Wyzwalane gdy kontrolka została wycięta z drzewa komponentów
wizualnych
Zdarzenia te są niezmiernie ważne dla zapewnienia spójności działania aplikacji. Silverlight tworząc
kontrolki dołącza je do wspólnego drzewa komponentów. W tej strukturze rodzic jest elementem
nadrzędnym swoich dzieci. Silverlight rozpoczyna tworzenie obiektów na podstawie np. kodu XAML
od elementów które nie posiadają dzieci (nazywanych liśdmi). Jedną z ważnych przesłanek do
wyzwolenia zdarzenia Loaded jest to, że wszystkie dzieci kontrolki zostały poprawnie utworzone
(każda w szczególności wyzwoliła zdarzenie Loaded). Wtedy kontrolka-rodzic może wyzwolid
zdarzenie Loaded, informujące o tym, iż została poprawnie utworzona. Temat ten rozwiniemy w
kolejnych modułach.
Gdy kontrolka usunięta zostaje z drzewa wyświetlania wyzwalane jest zdarzenie Unloaded,
następnie każda jej kontrolka-dziecko wyzwala zdarzenie Unloaded itd. Standardowy scenariusz
użycia tego zdarzenia jest następujący: jeśli kontrolka korzystała z danych pochodzących od
zewnętrznej usługi (serwisu), delegowanie kodu w odpowiedzi na zajście zdarzenia Unloaded
zdarzenia pozwala na zamknięcie połączenia z tym serwisem.
Pozostałe zdarzenia standardowe omówione zostaną w następnych modułach.
Funkcjonalna lista komponentów
Poznałeś już podstawowe możliwości kontrolek. Wraz z Silverlight 4 dostajesz do dyspozycji ponad 60
różnych komponentów. Wszystkie SA nie tylko gotowe do użycia ale można dostosowad je do
własnych potrzeb. Na bazie tych kontrolek tworzyd można nowe bardziej skomplikowane bądź
realizujące specyficzne cele.
Poniżej zestawiono wybrane grupy komponentów realizujące typowe scenariusze użycia.
Przyciski:
Nazwa
Opis
Button
Definiuje wyzwala zdarzenie Click
RepeatButton
Specjalny przycisk który powtarza wyzwolenie zdarzenia Click w
krótkich odstępach czasu
HyperlinkButton
Specjalny przycisk umożliwiający nawigację do zadane strony
Komponenty grupujące:
Nazwa
Opis
Canvas
Umożliwia jawne pozycjonowanie kontrolek-dzieci
StackPanel
Pozwala układad kontrolki zależnie od orientacji (pozioma – pionowa)
Grid
Pozwala komponowad kontrolki w regularnej postaci (wiersze – kolumny)
Komponenty dekorujące:
Nazwa
Opis
Border
Pozwala zdefiniowad obramowanie kontrolki-dziecka
Viewbox
Wymusza wypełnienie (byd może z rozciągnięciem) swojej zawartości
Komponenty nawigacyjne:
Nazwa
Opis
Page
Kontrolka pomocnicza pozwalająca obudowad zawartośd do postaci
możliwej w nawigacji przez kontrolkę Frame
Frame
Pozwala nawigowac między swoimi stronami (kontrolki Page) przy
pomocy metod GoBack oraz GoForward
Wyświetlanie grafiki:
Nazwa
Opis
Image
Dzięki własności Source może wyświetlac obrazy
MultiScaleImage
Wprowadza możliwośc oglądania obrazu z różną skalą szczegółowości
MediaElement
Pozwala odtwarzad dźwięki oraz obraz wideo
Komponenty informacyjne
Nazwa
Opis
TextBlock
Pozwala wyświetlad porcje tekstu
ProgressBar
Pozwala raportowad postęp przy pomocy paska
RichTextBox
Pozwala wyświetlad tekst formatowany
Label
Pozwala wyświetlad objaśnienia
Tekstowe komponenty wejściowe:
Nazwa
Opis
TextBox
Pozwala wyświetlad i edytowad proste fragmenty tekstu
PassswordBox
Pozwala wprowadzad i ukrywad ważne informacje tekstowe
RichTextBox
Pozwala wyświetlad i edytowad złożone fragmenty tekstu
WebBrowser
Pozwala wyświetlad i nawigowad w dokumentach HTML
Powyższa lista nie wyczerpuje wszystkich możliwości, a cechy poszczególnych komponentów
wyjaśnimy w kolejnych rozdziałach.
Przykładowe rozwiązanie
Projektując narzędzie do projektowania książek zawierających zdjęcia z wakacji chcemy dad
użytkownikom prosty przejrzysty interfejs pozwalający na manipulacje elementami strony foto
książki. Nasze zadanie to szybko zbudowad prototyp takiego interfejsu użytkownika.
Prototypowy interfejs do aranżacji zdjęd na stronie powinien:
Pozwalad przesuwad zdjęcie (zarówno przy pomocy kursora myszy, jak i przy pomocy
wyspecjalizowanych przycisków),
Pozwalad na obrót i skalowanie zdjęcia względem środka elementu (przy pomocy przycisków).
Załóżmy, że elementy pozycje elementów strony fotoksiążki (obrazy, teksty), będziemy kontrolowali
przy pomocy przekształceo: skalowania, obrotu oraz przesunięcia (w tej kolejności). Przekształcenia
te będą złożone razem przy pomocy TransformGroup. Finalne przekształcenie przypisane będzie do
własności RenderTransform.
Ponieważ elementy strony fotoksiążki będą tworzone dynamicznie złą praktyką byłoby nadawanie im
unikatowych nazw. Lepszym pomysłem jest stworzenie specjalnej klasy która związana będzie z
operacjami na elemencie strony. Nazwijmy tę klasę Kontroler. W swej najprostszej postaci jej
szkielet wyglądad może następująco:
public class Kontroler
{
private static RotateTransform rotacja;
private static TranslateTransform translacja;
private static ScaleTransform skala;
private static FrameworkElement lastselected = null;
…
}
Potrzebne przekształcenia będziemy przechowywali w odpowiednich zmiennych statycznych.
Dodatkowo przechowywany będzie ostatnio wybrany element.
Pierwsza ważna metoda pomocnicza klasy Kontroler powinna pozwolid dołączyd odpowiednie
przekształcenia do dowolnego FrameworkElement. Ma ona postad:
private void Dolacz_Uklad(FrameworkElement elem)
{
TransformGroup tg = new TransformGroup();
ScaleTransform sc = new ScaleTransform();
…
tg.Children.Add(sc);
RotateTransform rt = new RotateTransform();
…
tg.Children.Add(rt);
TranslateTransform tt = new TranslateTransform();
…
tg.Children.Add(tt);
elem.RenderTransform = tg;
}
Przekształcenia tworzone są w odpowiedniej kolejności. Ich własności można ustalid np. w sposób
losowy.
Załóżmy, że element po wybraniu będzie posiadał przezroczystośd (własnośd Opacity) ustawioną na
75%. Wbrew pozorom najpierw winniśmy zaimplementowad operacje odpowiedzialne za
odznaczenie elementu mianowicie:
public static void CleanTransforms()
{
if (lastselected != null)
{
lastselected.Opacity = 1.0;
lastselected = null;
}
rotacja = null;
translacja = null;
skala = null;
}
Teraz możemy przystąpid do implementacji podstawowych operacji związanych z wyborem nowego
elementu, tj.:
Odznaczenie elementu zaznaczonego,
Zaznaczenie nowego elementu wraz z wydobyciem potrzebnych przekształceo.
Służyła będzie temu metoda RegisterElementTransforms, o szkielecie:
public static void RegisterElementTransforms(UIElement elem)
{
CleanTransforms();
if (elem != null && elem.RenderTransform is TransformGroup)
{
if (elem is FrameworkElement)
{
lastselected = elem as FrameworkElement;
lastselected.Opacity = 0.75;
}
TransformGroup tg = elem.RenderTransform as TransformGroup;
foreach (Transform tran in tg.Children)
{
if (tran is TranslateTransform) translacja = tran as
TranslateTransform;
if (tran is RotateTransform) rotacja = tran as RotateTransform;
if (tran is ScaleTransform) skala = tran as ScaleTransform;
}
}
}
Metoda ta najpierw usuwa wszystkie przekształcenia oraz przywraca przeźroczystośd elementu
wcześniej wybranego (o ile był wybrany). Następnie zapisuje referencje do nowego elementu oraz
zmienia jego przezroczystośd. Zauważmy, że wyróżniany obiekt musi posiadad RenderTransform
typu TransformGroup. Ostatnim krokiem jest przegląd i wydobycie potrzebnych przekształceo.
Możemy teraz zaimplementowad metody pomocnicze odpowiedzialne za:
Przesuwanie (Lewo, Prawo, Góra, Dół), np.:
public static void PrzesunLewo()
{
if (translacja != null)
translacja.X -= 1;
}
Obracanie, np.:
public static void ObrocLewo()
{
if (rotacja != null)
rotacja.Angle -= 1;
}
Skalowanie, np.:
public static void Powieksz()
{
if (skala != null)
{
skala.ScaleX += 0.01;
skala.ScaleY += 0.01;
}
}
Dodatkowo będziemy potrzebowali metody która pomoże zrealizowad poruszanie obiektu przy
pomocy kursora myszy, mianowicie:
public static void AddToElementPosition(Point p)
{
if (translacja != null)
{
translacja.X += p.X;
translacja.Y += p.Y;
}
}
Skoro potrzebne zaplecze jest już gotowe to możemy przystąpid do budowy części wizualnej
interfejsu.
Podzielmy powierzchnię na dwie części. Cześd lewa wyświetlała będzie przyciski edycyjne (kontrola
położenia, obrotu oraz skali), częśd prawa natomiast będzie zawierała powierzchnię wyświetlającą
zdjęcia oraz napisy. Do podziału użyjemy panelu typu Grid, w XAML wyglądad może to następująco:
<Grid x:Name="LayoutRoot" Background="White" >
<Grid.RowDefinitions>
<RowDefinition />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="200" />
<ColumnDefinition />
</Grid.ColumnDefinitions>
…
<Grid>
Mamy więc dwie kolumny oraz jeden wiersz. Lewa kolumna posiada ustaloną szerokośd. Przyciski
sterujące zbierzmy w panelu typu StackPanel (domyślnie układa on elementy pionowo). Wyboru
położenia kontrolki w panelu typu Grid dokonujemy przyklejając do obiektu własności
Grid.Column oraz Grid.Row. Więcej o doczepianiu własności dowiesz się w module własności
zależne i doczepiane. W tej chwili ważne jest to, że jeśli bezpośrednie dziecko panelu Grid posiada te
własności to zostanie umieszczone w odpowiednim miejscu siatki, poza tym panelem własności te są
ignorowane.
Umiejscowiony panel z przyciskami wyglądad może tak:
<StackPanel Grid.Row="0" Grid.Column="0" VerticalAlignment="Center"
HorizontalAlignment="Center" Height="Auto">
<Button Margin="10 10 10 10" Click="Button_Click">Dodaj</Button>
<TextBlock>Pozycja:</TextBlock>
<StackPanel Orientation="Horizontal" >
<RepeatButton Width="65" Height="65" Margin="5 0 5 5">
Góra</RepeatButton>
<RepeatButton Width="65" Height="65" Margin="5 0 5 5”>
Dół</RepeatButton>
</StackPanel>
<StackPanel Orientation="Horizontal" >
<RepeatButton Width="65" Height="65" Margin="5 0 5 5">
Lewo</RepeatButton>
<RepeatButton Width="65" Height="65" Margin="5 0 5 5">
Prawo</RepeatButton>
</StackPanel>
<TextBlock>Obrót:</TextBlock>
<StackPanel Orientation="Horizontal" >
<RepeatButton Width="65" Height="65" Margin="5 0 5 5" >
w Lewo</RepeatButton>
<RepeatButton Width="65" Height="65" Margin="5 0 5 5">
w Prawo</RepeatButton>
</StackPanel>
<TextBlock>Skala:</TextBlock>
<StackPanel Orientation="Horizontal" >
<RepeatButton Width="65" Height="65" Margin="5 0 5 5">
Powiększ</RepeatButton>
<RepeatButton Width="65" Height="65" Margin="5 0 5 5">
Zmniejsz</RepeatButton>
</StackPanel>
</StackPanel>
Przycisk Dodaj służył będzie to testowego rozmieszczania elementów.
Prawa częśd interfejsu to powierzchnia na której rozmieszczamy zdjęcia, w tym celu użyjemy
kontrolki Canvas dekorowanej czarnym obramowaniem, mianowicie:
<Border Grid.Column="1" Grid.Row="0"
Width="500" Height="500" HorizontalAlignment="Center"
VerticalAlignment="Center"
BorderThickness="2" BorderBrush="Black">
<Canvas Background="Gray" Name="kartka" />
</Border>
Powinniśmy uzyskad rezultat zbliżony do następującego:
Rys. 10 Finalny layout
Byd może wygląd nie należy do najpiękniejszych, niemniej grafik z użyciem Microsoft Expression
Blend może łatwo uczynid go wspaniałym. Nasze zadanie to nadad interfejsowi pełną funkcjonalnośd.
W odpowiedzi na naciśnięcie przycisku Dodaj testowo stwórzmy obrazek albo linię tekstu (losowo).
Powinniśmy obsłużyd zdarzenie Click, na przykład w pokazany poniżej sposób:
private void Button_Click(object sender, RoutedEventArgs e)
{
FrameworkElement elem = null;
if (rd.Next() % 2 == 0)
{
Image img = new Image();
…
elem = img;
}
else
{
TextBlock tb = new TextBlock();
…
elem = tb;
}
Kontroler.Dolacz_Uklad(elem);
elem.MouseLeftButtonDown += Wybrano_Obrazek;
elem.MouseMove += RuchMyszy;
elem.MouseLeftButtonUp += KoniecEdycji;
kartka.Children.Add(elem);
}
Po stworzeniu obiektu dobudowujemy ustalamy mu żądany zestaw przekształceo przygotowaną
wcześniej metodą Dolacz_Uklad klasy Kontroler. Kolejny krok to dodanie obsługi zdarzeo
związanych z kursorem myszy. Tak udekorowany obiekt dodajemy do panelu kontrolek o nazwie
kartka. Obsługa zdarzeo wciśnięto lewy przycisk, poruszono myszą oraz zwolniono lewy przycisk
myszy, winna zapewnid ruch obiektu oraz rejestrację jego przekształceo.
Metoda Wybrano_Obrazek może mied postad:
private Point pozycja;
private bool isMouseDown = false;
private void Wybrano_Obrazek(object sender, MouseButtonEventArgs e)
{
Kontroler.RegisterElementTransforms(e.OriginalSource as UIElement);
pozycja = e.GetPosition(kartka);
isMouseDown = true;
}
Oczywiście w pierwszej kolejności wydobywamy z elementu wszystkie potrzebne przekształcenia
(dzięki wcześniej przygotowanej metodzie RegisterElementTransforms) następnie zapamiętujemy
pozycję ekranową wciśnięcia przycisku myszy oraz notujemy fakt wciśnięcia przycisku. Względem
zapamiętanej pozycji kursora będziemy liczyli przesunięcia obiektu.
Po zwolnieniu przycisku nie czyścimy układu współrzędnych (nie wywołujemy CleanTransforms) gdyż
byd może użytkownik wykona jakieś operacje z użyciem panelu bocznego przycisków. Kod metody
KoniecEdycji może byd zatem bardzo prosty:
private void KoniecEdycji(object sender, MouseButtonEventArgs e)
{
isMouseDown = false;
}
Odpowiedniej uwagi wymaga natomiast metoda RuchMyszy:
private void RuchMyszy(object sender, MouseEventArgs e)
{
if (isMouseDown)
{
Point mousePos = e.GetPosition(kartka);
Point delta = new Point(mousePos.X - pozycja.X,
mousePos.Y - pozycja.Y);
Kontroler.AddToElementPosition(delta);
pozycja = mousePos;
}
}
Ponieważ metoda zdarzenie MouseMove będzie wyzwolone nad elementem nawet gdy nie wciśnięto
nad nim przycisku myszy, winniśmy sprawdzid czy takowy przycisk został wciśnięty. Jeśli tak,
znajdujemy przesunięcie względem ostatnio zapamiętanej pozycji kursora. Nakazujemy przesunięcie
wybranego w chwili wciśnięcia lewego przycisku myszy elementu. Ostatni krok to jako zapamiętanie
bieżącej pozycji kursora myszy.
Metody Wybrano_Obrazek, RuchMyszy oraz KoniecEdycji przypisz dodatkowo do obiektu kartka.
Pozwoli to między innymi odznaczyd element (gdy wciśnięto przycisk na kartce ale nie na obrazku).
Zauważ, że niezależnie nad jakim elementem zdarzenie zostanie obsłużone, to zmiana pozycji będzie
dotyczyła elementu wybranego w chwili wciśnięcia lewego przycisku myszy!
Po uruchomieniu stworzonego kodu powinieneś uzyskad następujący rezultat:
Rys. 11 Ostateczny interfejs
Działa wybór oraz przesuwanie elementów strony. Pozostaje dołączyd funkcjonalnośd prawego
panelu edycyjnego. Wciśnięcie przycisku łączymy z odpowiednią metodą kontrolera, np.:
private void obroc_lewo(object sender, RoutedEventArgs e)
{
Kontroler.ObrocLewo();
}
Ponieważ zastosowaliśmy przyciski typu RepeatButton to zaznaczony obiekt będzie obracał się tak
długo jak długo będziemy utrzymywali przycisk w stanie wciśniętym (zdarzenie Click będzie
wyzwalane w krótkich odstępach czasu).
Możemy teraz obrócid zaprojektowaną stronę foto książki z użyciem własności Projection.
Testowy interfejs jest już gotowy i pozwala rozmieszczad elementy w obrębie pojedynczej kartki.
Porady praktyczne
Pamiętaj, że rozdział ten nie wyczerpał informacji o własnościach dołączonych z Silverlight
kontrolek. Przed użyciem kontrolki pamiętaj aby zapoznad się z jej możliwościami
Niektóre panele mogą ignorowad własności wyrównania, np. Grid z ustaloną szerokością
kolumn
Częstym błędem jest kopiowanie fragmentów kodu XAML zawierającego nazwane elementy.
Elementy musza mied unikatowe nazwy.
Składanie przekształceo poprzedź rozpisaniem ciągu przekształceo na kartce, aż do momentu
gdy będziesz pewien wyniku. Eksperymenty wprowadzają zamieszanie gdy liczba przekształceo
przekracza dwa.
Do obcinania elementów wystających poza stronę możesz użyd własności Clip, mianowicie po
zmianie rozmiaru wyzwalane jest zdarzenie SizeChanged, możesz dopisad obsługę
następującej postaci:
private void kartka_SizeChanged(object sender, SizeChangedEventArgs e)
{
RectangleGeometry rg = new RectangleGeometry();
rg.Rect = new Rect(0, 0, e.NewSize.Width, e.NewSize.Height);
kartka.Clip = rg;
}
Uwagi dla studenta
Jesteś przygotowany do realizacji laboratorium jeśli:
Rozumiesz podstawy składni XAML,
Rozumiesz zasady kompozycji rodzic-dziecko,
Potrafisz kontrolowad pozycję kontrolki,
Umiesz składad przekształcenia,
Umiesz wykorzystad system zdarzeo,
Potrafisz nazywad ważne elementy interfejsu użytkownika,
Potrafisz nazwad klasy inne niż typu FrameworkElement,
Wiesz jak działa przekształcenie rzutowe.
Pamiętaj o zapoznaniu się z uwagami i poradami zawartymi w tym module. Upewnij się, że rozumiesz
omawiane w nich zagadnienia. Jeśli masz trudności ze zrozumieniem tematu zawartego w uwagach,
przeczytaj ponownie informacje z tego rozdziału i zajrzyj do notatek z wykładów.
Laboratorium podstawowe
Zadanie 1 (czas realizacji 25 minut)
Zbliżają się targi fotograficzne a twoja firma uzyskała zlecenie na budowę prototypowego interfejsu
do budowy kartek z życzeniami. Użytkownik interfejsu posiadając zdjęcia powinien mied możliwośd
umieścid zdjęcie na jednej stronie kartki z życzeniami, a na drugiej tekstu życzeo. Kartka drukowana
będzie na dołączonej do komputerów twojego klienta drukarce dwustronnej. Prototyp nie musi mied
możliwości fizycznego drukowania.
Zadanie 2 (czas realizacji 20 minut)
Po sukcesie poprzedniego produktu, klient z branży fotograficznej zamówił w Twojej firmie interfejs
do przetwarzania łamanych kartek z życzeniami. Do wymagao dołączył podgląd kartki w 3D.
Laboratorium rozszerzone
Zadanie 1 (czas realizacji 45 minut)
Stwórz aplikację Silverlight w której kontrolki będą odsuwały się od kursora myszy.
Wskazówka. Wykorzystaj metodę TranformTo do uzyskania współrzędnych w układzie związanych
z inną kontrolką.
Zadanie 2 (czas realizacji 45 minut)
Napisz aplikację Silverlight służącą do stworzenia kartek z życzeniami. Aplikacja ta konfigurowalna
jest z poziomu parametrów przekazanych z HTML. Konfigurowad można zarówno typ produktu (patrz
laboratorium podstawowe) jak i jego rozmiary.
Zadanie 3 (czas realizacji 90 minut)
Stwórz interfejs który pozwoli rozmieszczad zdjęcia na stronach książki o zadanej liczbie stron.
Zadanie 4 (czas realizacji 90 minut)
Stwórz interfejs który pozwoli rozmieszczad zdjęcia w foto-książce oraz na jej okładce.