Transformacje w WPF

W lipcu przyszło mi wykonywać dla pewnej firmy projekt łudząco przypominający zastosowanie Microsoft Surface. W mojej gestii leżało napisać program, który na podstawie przyjmowanych od panelu dotykowego współrzędnych będzie manipulował (transformował) obrazki. Ot, taki terminal, na którym klienci mogą sobie w przyjemny sposób pooglądać zdjęcia produktu. Przy okazji tego projektu zasięgnąłem poważnej lekcji WPF-a i długo zmagałem się z transformacjami graficznych obiektów – dlatego stwierdziłem, że warto napisać o paru aspektach tego zagadnienia :)

Wygodne transformowanie obiektów graficznych w WPF-ie to jedno z dobrodziejstw, które owa technologia oferuje, w szczególności jeśli zestawić ją z WinForms i GDI. Transformować możemy obiekty wszystkich klas, które dziedziczą po UIElement. Każdy taki obiekt posiada 2 właściwości: RenderTransform i LayoutTransform do której przypisujemy naszą transformacje. Czym różnią się te właściwości? Można o tym przeczytać tu albo uruchomić krótkie demo (koniecznie w IE), dostępne tu.

Teraz może nieco o transformacjach. Dostępnych typów transformacji jest cztery:

  1. RotateTransform – obrót o podany kąt (w stopniach) i podany środek obrotu (punkt)
  2. ScaleTransform – skalowanie, w punkcie skalowania, w dwóch płaszczyznach (X i Y) o podanej skali (oddzielnie X i Y); wartości >1 powiększają a wartości należące do przedziału (0;1) pomniejszają
  3. SkewTransform – „skośne” transformowanie o podane kąty (X i Y) oraz środek „skosowania” ;) – służy często do tworzenia iluzji trójwymiarowości
  4. TranslateTransform – najzwyklejsza translacja o wektor – wymagane są dwie współrzędne wektora

Więcej polecam poczytać więcej na MSDN, są nawet ładne rysunki :)

Przechodząc do rzeczy, aplikowanie transformacji jest bajecznie proste. Załózmy iż mamy obiekt typu ImagearrowImage i chcemy go przesunąć o wektor [50,82]. Wystarczy jedynie napisać:

arrowImage.RenderTransform = new TranslateTransform(50,82);

i gotowe :)

W taki sposób możemy jednak dodać tylko jedną transformację. Co jeśli chcemy zaaplikować naszemu obiektowi całą grupę transformacji? Wystarczy wtedy skorzystać z klasy TransformGroup:

TransformGroup transformGroup = new TransformGroup();

i teraz:

transformGroup.Children.Add(rotateTransform);

transformGroup.Children.Add(translateTransform);

transformGroup.Children.Add(scaleTransform);

pozostaje zaaplikować grupę transformacji:

arrowImage.RenderTransform = transformGroup;

tadam! Gotowe :)

Na koniec chciałbym zaznaczyć, że transformacje mają na siebie wpływ np. pomniejszenie obiektu o połowe powoduje iż translacja o wektor [8,0] jest tak naprawdę translacją o wektor [4,0]. Chyba, że zrobimy najpierw translacje, a potem pomniejszanie. Tutaj należy pamiętać, iż dodawanie transformacji nie zawsze jest przemienne: kolejność translacji nie ma znaczenia – wynik zawsze będzie taki sam; co innego np. z obrotem. Dokumentacja niestety nie jest zbyt wyczerpująca w tej kwestii. Osobiście męczyłem się tydzień, żeby zrozumieć cały mechanizm współdziałania między transformacjami – trzeba być naprawdę bardzo uważnym. Dla przykładu – najbardziej „aktualne” (te które mają być zastosowane najpóźniej) transformacje należy wstawiać na początku, a nie na końcu kolekcji.

transformGroup.Children.Add(rotateTransform); transformGroup.Children.Add(translateTransform);

Taki kod spowoduje najpierw translacje, a potem obrót. Jeśli więc chcemy uzyskać efekt wielokrotnej transformacji przydatne staje się coś takiego:

transformGroup.Children.Insert(0, pewnaTransformacja);

To by było na tyle. Jeszcze nie jestem pewien, czy mogę, ale być może wspomniana aplikacja a’la Surface zostanie opublikowana na moim blogu :) Tymczasem polecam spojrzenie na podobną aplikację – napisaną w Silverlighcie. Jej kod źródłowy jest cennym przykładem stosowania transformacji :)