W ramach uczestnictwa w kursie WPF czytam ksiązkę “WPF 4 Unleashed” i przygotowuje jakieś przykłady sprawdzające co tam pan autor pisze w tej książce. Jeden z początkowych rozdziałów (tak, dopiero rozpoczynam kurs i książkę) poświęcony jest tematyce transformacji kontrolek, w związku z tym napisałem prosty przykład ukazyjący omawiane tranformacje. W zasadzie testuje tylko trzy z pięciu, ponieważ przesunięcie obiektów nie jest zbyt widowiskowe, a wykorzystanie macierzy nie jest na moją głowę. Zostają tylko obracanie, skalowanie i przechylanie.
Do implementacji wykorzystałem trzy wątki, a jak wszyscy wiemy .NET nie lubi, jak ktoś dobiera się do kontrolek z innego wątku niż głównego. Wykorzystany został tutaj obiekt Dispatcher z WPF, który ułatwia sprawę. Najbardziej denerwującą rzeczą okazał się brak możliwości skorzystania z wyrażeń lambda do implementacji ciała metody zajmującej się aktualizacją kontrolek. Trzeba było skorzystać z delegatów. Na stackoverflow widziałem przykład z rzutowaniem lambdy na Action. Po krótkiej i przegranej walce z przykładem, postanowiłem że zostanę przy delegatach.
Przykład jest na tyle prosty, że nie pokuszę się o wrzucanie go na jakiś serwer, zostanie pokazany światu tu i tylko tu.
Na początek UI (bez szału):
- <Window x:Class=”wpfTransformations.MainWindow”
 - xmlns=”http://schemas.microsoft.com/winfx/2006/xaml/presentation”
 - xmlns:x=”http://schemas.microsoft.com/winfx/2006/xaml”
 - Title=”MainWindow” Height=”466″ Width=”1000″>
 - <Grid>
 - <!– Rotating –>
 - <Button x:Name=”r_00_00″ Content=”0,0″ Height=”23″ HorizontalAlignment=”Left” Margin=”69,64,0,0″
 - VerticalAlignment=”Top” Width=”75″ RenderTransformOrigin=”0,0″>
 - <Button.RenderTransform >
 - <RotateTransform Angle=”33″ x:Name=”RotateTransform”/>
 - </Button.RenderTransform>
 - </Button>
 - <Button x:Name=”r_10_00″ Content=”1,0″ Height=”23″ HorizontalAlignment=”Left” Margin=”255,64,0,0″
 - VerticalAlignment=”Top” Width=”75″ RenderTransformOrigin=”1,0″>
 - <Button.RenderTransform>
 - <RotateTransform Angle=”33″/>
 - </Button.RenderTransform>
 - </Button>
 - <Button x:Name=”r_10_10″ Content=”1,1″ Height=”23″ HorizontalAlignment=”Left” Margin=”447,64,0,0″
 - VerticalAlignment=”Top” Width=”75″ RenderTransformOrigin=”1,1″>
 - <Button.RenderTransform>
 - <RotateTransform Angle=”33″/>
 - </Button.RenderTransform>
 - </Button>
 - <Button x:Name=”r_00_10″ Content=”0,1″ Height=”23″ HorizontalAlignment=”Left” Margin=”631,64,0,0″
 - VerticalAlignment=”Top” Width=”75″ RenderTransformOrigin=”0,1″>
 - <Button.RenderTransform>
 - <RotateTransform Angle=”33″/>
 - </Button.RenderTransform>
 - </Button>
 - <Button x:Name=”r_05_05″ Content=”0.5,0.5″ Height=”23″ HorizontalAlignment=”Left” Margin=”803,64,0,0″
 - VerticalAlignment=”Top” Width=”75″ RenderTransformOrigin=”0.5,0.5″>
 - <Button.RenderTransform>
 - <RotateTransform Angle=”33″/>
 - </Button.RenderTransform>
 - </Button>
 - <!– Scaling –>
 - <Button x:Name=”s_00_00″ Content=”Button” Height=”23″ HorizontalAlignment=”Left” Margin=”69,179,0,0″
 - VerticalAlignment=”Top” Width=”75″ RenderTransformOrigin=”0,0″>
 - <Button.RenderTransform>
 - <ScaleTransform ScaleX=”.6″ ScaleY=”-.6″ />
 - </Button.RenderTransform>
 - </Button>
 - <Button x:Name=”s_00_10″ Content=”Button” Height=”23″ HorizontalAlignment=”Left” Margin=”255,179,0,0″
 - VerticalAlignment=”Top” Width=”75″ RenderTransformOrigin=”0,1″>
 - <Button.RenderTransform>
 - <ScaleTransform ScaleX=”.6″ ScaleY=”-.6″ />
 - </Button.RenderTransform>
 - </Button>
 - <Button x:Name=”s_10_10″ Content=”Button” Height=”23″ HorizontalAlignment=”Left” Margin=”447,179,0,0″
 - VerticalAlignment=”Top” Width=”75″ RenderTransformOrigin=”1,1″>
 - <Button.RenderTransform>
 - <ScaleTransform ScaleX=”.6″ ScaleY=”-.6″ />
 - </Button.RenderTransform>
 - </Button>
 - <Button x:Name=”s_10_00″ Content=”Button” Height=”23″ HorizontalAlignment=”Left” Margin=”631,179,0,0″
 - VerticalAlignment=”Top” Width=”75″ RenderTransformOrigin=”1,0″>
 - <Button.RenderTransform>
 - <ScaleTransform ScaleX=”.6″ ScaleY=”-.6″ />
 - </Button.RenderTransform>
 - </Button>
 - <Button x:Name=”s_05_05″ Content=”Button” Height=”23″ HorizontalAlignment=”Left” Margin=”803,179,0,0″
 - VerticalAlignment=”Top” Width=”75″ RenderTransformOrigin=”0.5,0.5″>
 - <Button.RenderTransform>
 - <ScaleTransform ScaleX=”.6″ ScaleY=”-.6″ />
 - </Button.RenderTransform>
 - </Button>
 - <!– Skewing –>
 - <Button x:Name=”sk_00_00″ Content=”Button” Height=”23″ HorizontalAlignment=”Left” Margin=”69,315,0,0″
 - VerticalAlignment=”Top” Width=”75″ RenderTransformOrigin=”0,0″>
 - <Button.RenderTransform>
 - <SkewTransform AngleX=”33″ AngleY=”3″ />
 - </Button.RenderTransform>
 - </Button>
 - <Button x:Name=”sk_10_00″ Content=”Button” Height=”23″ HorizontalAlignment=”Left” Margin=”255,315,0,0″
 - VerticalAlignment=”Top” Width=”75″ RenderTransformOrigin=”1,0″>
 - <Button.RenderTransform>
 - <SkewTransform AngleX=”33″ AngleY=”3″ />
 - </Button.RenderTransform>
 - </Button>
 - <Button x:Name=”sk_10_10″ Content=”Button” Height=”23″ HorizontalAlignment=”Left” Margin=”447,315,0,0″
 - VerticalAlignment=”Top” Width=”75″ RenderTransformOrigin=”1,1″>
 - <Button.RenderTransform>
 - <SkewTransform AngleX=”33″ AngleY=”3″ />
 - </Button.RenderTransform>
 - </Button>
 - <Button x:Name=”sk_00_10″ Content=”Button” Height=”23″ HorizontalAlignment=”Left” Margin=”631,315,0,0″
 - VerticalAlignment=”Top” Width=”75″ RenderTransformOrigin=”0,1″>
 - <Button.RenderTransform>
 - <SkewTransform AngleX=”33″ AngleY=”3″ />
 - </Button.RenderTransform>
 - </Button>
 - <Button x:Name=”sk_05_05″ Content=”Button” Height=”23″ HorizontalAlignment=”Left” Margin=”803,315,0,0″
 - VerticalAlignment=”Top” Width=”75″ RenderTransformOrigin=”0.5,0.5″>
 - <Button.RenderTransform>
 - <SkewTransform AngleX=”33″ AngleY=”3″ />
 - </Button.RenderTransform>
 - </Button>
 - </Grid>
 - </Window>
 
Tak wygląda code-behind i trzy wątki odpowiedzialne, za aktualizację kontrolek (też bez szału):
- using System;
 - using System.Windows;
 - using System.Windows.Controls;
 - using System.Windows.Media;
 - namespace wpfTransformations
 - {
 - using System.Threading;
 - /// <summary>
 - /// Interaction logic for MainWindow.xaml
 - /// </summary>
 - public partial class MainWindow : Window
 - {
 - // delegates to handle UI controls update from background threads
 - public delegate void SetAngleD(float f, Button b);
 - public delegate void SetScaleD(float sx, float sy, Button b);
 - public delegate void SetSkewD(float ax, float ay, Button b);
 - // helper methods to update the controls
 - public void SetAngle(float f, Button b)
 - {
 - RotateTransform r = new RotateTransform(f);
 - b.RenderTransform = r;
 - }
 - public void SetScale(float sx, float sy, Button b)
 - {
 - ScaleTransform st = new ScaleTransform(sx, sy);
 - b.RenderTransform = st;
 - }
 - public void SetSkew(float ax, float ay, Button b)
 - {
 - SkewTransform sk = new SkewTransform(ax, ay);
 - b.RenderTransform = sk;
 - }
 - public MainWindow()
 - {
 - InitializeComponent();
 - // This one will take care of rotation of the elements
 - Thread tRotatingThread = new Thread(
 - () =>
 - {
 - float angle = 33.0f;
 - while (true)
 - {
 - SetAngleD d = new SetAngleD(this.SetAngle);
 - r_00_00.Dispatcher.Invoke(d, new object[] { angle, r_00_00 });
 - r_00_10.Dispatcher.Invoke(d, new object[] { angle, r_00_10 });
 - r_10_10.Dispatcher.Invoke(d, new object[] { angle, r_10_10 });
 - r_10_00.Dispatcher.Invoke(d, new object[] { angle, r_10_00 });
 - r_05_05.Dispatcher.Invoke(d, new object[] { angle, r_05_05 });
 - angle += 1.0f;
 - if (angle > 360.0f)
 - {
 - angle = 0.0f;
 - }
 - Thread.Sleep(10);
 - }
 - });
 - tRotatingThread.IsBackground = true;
 - // This one will play with scaling properties of controls
 - Thread tScalingThread = new Thread(
 - () =>
 - {
 - float sxm = 0.019f;
 - float sym = -0.011f;
 - float sx = 0.6f;
 - float sy = -0.6f;
 - while (true)
 - {
 - SetScaleD d = new SetScaleD(this.SetScale);
 - s_00_00.Dispatcher.Invoke(d, new object[] { sx, sy, s_00_00 });
 - s_10_00.Dispatcher.Invoke(d, new object[] { sx, sy, s_10_00 });
 - s_10_10.Dispatcher.Invoke(d, new object[] { sx, sy, s_10_10 });
 - s_00_10.Dispatcher.Invoke(d, new object[] { sx, sy, s_00_10 });
 - s_05_05.Dispatcher.Invoke(d, new object[] { sx, sy, s_05_05 });
 - if (Math.Abs(sx) > 2.1f)
 - {
 - sxm *= -1.0f;
 - }
 - if (Math.Abs(sy) > 2.1f)
 - {
 - sym *= -1.0f;
 - }
 - sx += sxm;
 - sy += sym;
 - Thread.Sleep(30);
 - }
 - });
 - tScalingThread.IsBackground = true;
 - // This one will skew them grrrrr
 - Thread tSkewingThread = new Thread(
 - () =>
 - {
 - float ax = 33.0f;
 - float ay = 3.0f;
 - while (true)
 - {
 - SetSkewD d = new SetSkewD(this.SetSkew);
 - sk_00_00.Dispatcher.Invoke(d, new object[] { ax, ay, sk_00_00 });
 - sk_10_00.Dispatcher.Invoke(d, new object[] { ax, ay, sk_10_00 });
 - sk_10_10.Dispatcher.Invoke(d, new object[] { ax, ay, sk_10_10 });
 - sk_00_10.Dispatcher.Invoke(d, new object[] { ax, ay, sk_00_10 });
 - sk_05_05.Dispatcher.Invoke(d, new object[] { ax, ay, sk_05_05 });
 - ax += 1.0f;
 - if (ax > 90.0f)
 - {
 - ax = 0.0f;
 - }
 - ay += 1.0f;
 - if (ay > 60.0f)
 - {
 - ay = 0.0f;
 - }
 - Thread.Sleep(30);
 - }
 - });
 - tSkewingThread.IsBackground = true;
 - // Because everyone of them is set as background thread
 - // They should end nicely at the end of application
 - tRotatingThread.Start();
 - tScalingThread.Start();
 - tSkewingThread.Start();
 - }
 - }
 - }
 
Na koniec aplikacji w akcji (odrobina szału jest):
Pierwszy rząd guzików jest obracany, kolejny skalowany, a ostatni przechylany. Chyba lepiej to wygląda gdy działa, po wklejeniu do msvc powinien śmigać bez zająknięcia.
To tyle. Do zapamiętania: Dispatcher nie przyjmie lamby i nie walcz z tym, od razu korzystaj z delegatów. Chyba, że wiesz o co chodzi z tym Action() i wiesz jak z tego skorzystać (wiesz? – napisz w komentarzu), albo masz inne rozwiązanie. Podobno w 4.5 można z każdego wątku aktualizować UI, jeszcze nie sprawdziłem.
ps. Rozpieszczam was tym zawijaniem wierszy.
		
		