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:
- RotateTransform – obrót o podany kąt (w stopniach) i podany środek obrotu (punkt)
- 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ą
- 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
- 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 Image – arrowImage 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 :)