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.