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.
hej,
Cieszę się, że nie próżnujesz
Też chciałem zrobić kilka przykładów ale jestem zbyt leniwy. Mam nadzieje, że zdążę bo zostało jeszcze trochę czasu.
Odnośnie akcji (Action()) to wydaje mi się łatwiejsze niż użycie delegatów:
sk_00_00.Dispatcher.Invoke(new Action(() =>
{
SkewTransform sk = new SkewTransform(ax, ay);
b.RenderTransform = sk;
}
));
Jeśli chodzi o lambde. W Silverlighcie mamy Dispatcher.BeginInvoke który przyjmuje lambde więc zapis bedzie jeszcze bardziej skrócony.
Fajnie byłoby umieszczać przykłady osadzone w przeglądarce za pomocą XBAP. Masz taką możliwość?
Pozdrawiam
Damian
Co do XBAP nie mam lub nie wiem, że mam. Są jakieś wymagania co do tego? Blog sam z siebie stoi na googlowym blogerze, nie posiadam tez własnej strony/serwera bo nie mam takiej potrzeby.