WPF – rotacja, transformacja i przechylanie

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):

  1. <Window x:Class=”wpfTransformations.MainWindow”
  2.         xmlns=”http://schemas.microsoft.com/winfx/2006/xaml/presentation”
  3.         xmlns:x=”http://schemas.microsoft.com/winfx/2006/xaml”
  4.         Title=”MainWindow” Height=”466″ Width=”1000″>
  5.     <Grid>
  6.         <!– Rotating –>
  7.         <Button x:Name=”r_00_00″ Content=”0,0″ Height=”23″ HorizontalAlignment=”Left” Margin=”69,64,0,0″
  8.                 VerticalAlignment=”Top” Width=”75″ RenderTransformOrigin=”0,0″>
  9.             <Button.RenderTransform >
  10.                 <RotateTransform Angle=”33″ x:Name=”RotateTransform”/>
  11.             </Button.RenderTransform>
  12.         </Button>
  13.  
  14.         <Button x:Name=”r_10_00″ Content=”1,0″ Height=”23″ HorizontalAlignment=”Left” Margin=”255,64,0,0″
  15.                 VerticalAlignment=”Top” Width=”75″ RenderTransformOrigin=”1,0″>
  16.             <Button.RenderTransform>
  17.                 <RotateTransform Angle=”33″/>
  18.             </Button.RenderTransform>
  19.         </Button>
  20.  
  21.         <Button x:Name=”r_10_10″ Content=”1,1″ Height=”23″ HorizontalAlignment=”Left” Margin=”447,64,0,0″
  22.                 VerticalAlignment=”Top” Width=”75″ RenderTransformOrigin=”1,1″>
  23.             <Button.RenderTransform>
  24.                 <RotateTransform Angle=”33″/>
  25.             </Button.RenderTransform>
  26.         </Button>
  27.  
  28.         <Button x:Name=”r_00_10″ Content=”0,1″ Height=”23″ HorizontalAlignment=”Left” Margin=”631,64,0,0″
  29.                 VerticalAlignment=”Top” Width=”75″ RenderTransformOrigin=”0,1″>
  30.             <Button.RenderTransform>
  31.                 <RotateTransform Angle=”33″/>
  32.             </Button.RenderTransform>
  33.         </Button>
  34.  
  35.         <Button x:Name=”r_05_05″ Content=”0.5,0.5″ Height=”23″ HorizontalAlignment=”Left” Margin=”803,64,0,0″
  36.                 VerticalAlignment=”Top” Width=”75″ RenderTransformOrigin=”0.5,0.5″>
  37.             <Button.RenderTransform>
  38.                 <RotateTransform Angle=”33″/>
  39.             </Button.RenderTransform>
  40.         </Button>
  41.         <!– Scaling –>
  42.           <Button x:Name=”s_00_00″ Content=”Button” Height=”23″ HorizontalAlignment=”Left” Margin=”69,179,0,0″
  43.                 VerticalAlignment=”Top” Width=”75″ RenderTransformOrigin=”0,0″>
  44.             <Button.RenderTransform>
  45.                 <ScaleTransform ScaleX=”.6″ ScaleY=”-.6″ />
  46.             </Button.RenderTransform>
  47.         </Button>
  48.         <Button x:Name=”s_00_10″ Content=”Button” Height=”23″ HorizontalAlignment=”Left” Margin=”255,179,0,0″
  49.                 VerticalAlignment=”Top” Width=”75″ RenderTransformOrigin=”0,1″>
  50.             <Button.RenderTransform>
  51.                 <ScaleTransform ScaleX=”.6″ ScaleY=”-.6″ />
  52.             </Button.RenderTransform>
  53.         </Button>
  54.         <Button x:Name=”s_10_10″ Content=”Button” Height=”23″ HorizontalAlignment=”Left” Margin=”447,179,0,0″
  55.                 VerticalAlignment=”Top” Width=”75″ RenderTransformOrigin=”1,1″>
  56.             <Button.RenderTransform>
  57.                 <ScaleTransform ScaleX=”.6″ ScaleY=”-.6″ />
  58.             </Button.RenderTransform>
  59.         </Button>
  60.         <Button x:Name=”s_10_00″ Content=”Button” Height=”23″ HorizontalAlignment=”Left” Margin=”631,179,0,0″
  61.                 VerticalAlignment=”Top” Width=”75″ RenderTransformOrigin=”1,0″>
  62.             <Button.RenderTransform>
  63.                 <ScaleTransform ScaleX=”.6″ ScaleY=”-.6″ />
  64.             </Button.RenderTransform>
  65.         </Button>
  66.         <Button x:Name=”s_05_05″ Content=”Button” Height=”23″ HorizontalAlignment=”Left” Margin=”803,179,0,0″
  67.                 VerticalAlignment=”Top” Width=”75″ RenderTransformOrigin=”0.5,0.5″>
  68.             <Button.RenderTransform>
  69.                 <ScaleTransform ScaleX=”.6″ ScaleY=”-.6″ />
  70.             </Button.RenderTransform>
  71.         </Button>
  72.         <!– Skewing –>
  73.           <Button x:Name=”sk_00_00″ Content=”Button” Height=”23″ HorizontalAlignment=”Left” Margin=”69,315,0,0″
  74.                 VerticalAlignment=”Top” Width=”75″ RenderTransformOrigin=”0,0″>
  75.             <Button.RenderTransform>
  76.                 <SkewTransform AngleX=”33″ AngleY=”3″ />
  77.             </Button.RenderTransform>
  78.         </Button>
  79.         <Button x:Name=”sk_10_00″ Content=”Button” Height=”23″ HorizontalAlignment=”Left” Margin=”255,315,0,0″
  80.                 VerticalAlignment=”Top” Width=”75″ RenderTransformOrigin=”1,0″>
  81.             <Button.RenderTransform>
  82.                 <SkewTransform AngleX=”33″ AngleY=”3″ />
  83.             </Button.RenderTransform>
  84.         </Button>
  85.         <Button x:Name=”sk_10_10″ Content=”Button” Height=”23″ HorizontalAlignment=”Left” Margin=”447,315,0,0″
  86.                 VerticalAlignment=”Top” Width=”75″ RenderTransformOrigin=”1,1″>
  87.             <Button.RenderTransform>
  88.                 <SkewTransform AngleX=”33″ AngleY=”3″ />
  89.             </Button.RenderTransform>
  90.         </Button>
  91.         <Button x:Name=”sk_00_10″ Content=”Button” Height=”23″ HorizontalAlignment=”Left” Margin=”631,315,0,0″
  92.                 VerticalAlignment=”Top” Width=”75″ RenderTransformOrigin=”0,1″>
  93.             <Button.RenderTransform>
  94.                 <SkewTransform AngleX=”33″ AngleY=”3″ />
  95.             </Button.RenderTransform>
  96.         </Button>
  97.         <Button x:Name=”sk_05_05″ Content=”Button” Height=”23″ HorizontalAlignment=”Left” Margin=”803,315,0,0″
  98.                 VerticalAlignment=”Top” Width=”75″ RenderTransformOrigin=”0.5,0.5″>
  99.             <Button.RenderTransform>
  100.                 <SkewTransform AngleX=”33″ AngleY=”3″ />
  101.             </Button.RenderTransform>
  102.         </Button>
  103.     </Grid>
  104. </Window>

Tak wygląda code-behind i trzy wątki odpowiedzialne, za aktualizację kontrolek (też bez szału):

  1. using System;
  2. using System.Windows;
  3. using System.Windows.Controls;
  4. using System.Windows.Media;
  5.  
  6. namespace wpfTransformations
  7. {
  8. using System.Threading;
  9.  
  10. /// <summary>
  11. /// Interaction logic for MainWindow.xaml
  12. /// </summary>
  13. public partial class MainWindow : Window
  14. {
  15.     // delegates to handle UI controls update from background threads
  16.     public delegate void SetAngleD(float f, Button b);
  17.     public delegate void SetScaleD(float sx, float sy, Button b);
  18.     public delegate void SetSkewD(float ax, float ay, Button b);
  19.  
  20.     // helper methods to update the controls    
  21.     public void SetAngle(float f, Button b)
  22.     {
  23.         RotateTransform r = new RotateTransform(f);
  24.         b.RenderTransform = r;
  25.     }
  26.  
  27.     public void SetScale(float sx, float sy, Button b)
  28.     {
  29.         ScaleTransform st = new ScaleTransform(sx, sy);
  30.         b.RenderTransform = st;
  31.     }
  32.  
  33.     public void SetSkew(float ax, float ay, Button b)
  34.     {
  35.         SkewTransform sk = new SkewTransform(ax, ay);
  36.         b.RenderTransform = sk;
  37.     }
  38.  
  39.     public MainWindow()
  40.     {
  41.         InitializeComponent();
  42.  
  43.         // This one will take care of rotation of the elements
  44.         Thread tRotatingThread = new Thread(
  45.             () =>
  46.             {
  47.                 float angle = 33.0f;
  48.                 while (true)
  49.                 {
  50.                     SetAngleD d = new SetAngleD(this.SetAngle);
  51.                     r_00_00.Dispatcher.Invoke(d, new object[] { angle, r_00_00 });
  52.                     r_00_10.Dispatcher.Invoke(d, new object[] { angle, r_00_10 });
  53.                     r_10_10.Dispatcher.Invoke(d, new object[] { angle, r_10_10 });
  54.                     r_10_00.Dispatcher.Invoke(d, new object[] { angle, r_10_00 });
  55.                     r_05_05.Dispatcher.Invoke(d, new object[] { angle, r_05_05 });
  56.  
  57.                     angle += 1.0f;
  58.                     if (angle > 360.0f)
  59.                     {
  60.                         angle = 0.0f;
  61.                     }
  62.  
  63.                     Thread.Sleep(10);
  64.                 }
  65.             });
  66.         tRotatingThread.IsBackground = true;
  67.  
  68.         // This one will play with scaling properties of controls
  69.         Thread tScalingThread = new Thread(
  70.             () =>
  71.             {
  72.                 float sxm = 0.019f;
  73.                 float sym = -0.011f;
  74.  
  75.                 float sx = 0.6f;
  76.                 float sy = -0.6f;
  77.  
  78.                 while (true)
  79.                 {
  80.                     SetScaleD d = new SetScaleD(this.SetScale);
  81.                     s_00_00.Dispatcher.Invoke(d, new object[] { sx, sy, s_00_00 });
  82.                     s_10_00.Dispatcher.Invoke(d, new object[] { sx, sy, s_10_00 });
  83.                     s_10_10.Dispatcher.Invoke(d, new object[] { sx, sy, s_10_10 });
  84.                     s_00_10.Dispatcher.Invoke(d, new object[] { sx, sy, s_00_10 });
  85.                     s_05_05.Dispatcher.Invoke(d, new object[] { sx, sy, s_05_05 });
  86.  
  87.                     if (Math.Abs(sx) > 2.1f)
  88.                     {
  89.                         sxm *= -1.0f;
  90.                     }
  91.  
  92.                     if (Math.Abs(sy) > 2.1f)
  93.                     {
  94.                         sym *= -1.0f;
  95.                     }
  96.  
  97.                     sx += sxm;
  98.                     sy += sym;
  99.                     Thread.Sleep(30);
  100.                 }
  101.             });
  102.  
  103.         tScalingThread.IsBackground = true;
  104.  
  105.  
  106.         // This one will skew them grrrrr
  107.         Thread tSkewingThread = new Thread(
  108.             () =>
  109.             {
  110.                 float ax = 33.0f;
  111.                 float ay = 3.0f;
  112.  
  113.                 while (true)
  114.                 {
  115.                     SetSkewD d = new SetSkewD(this.SetSkew);
  116.                     sk_00_00.Dispatcher.Invoke(d, new object[] { ax, ay, sk_00_00 });
  117.                     sk_10_00.Dispatcher.Invoke(d, new object[] { ax, ay, sk_10_00 });
  118.                     sk_10_10.Dispatcher.Invoke(d, new object[] { ax, ay, sk_10_10 });
  119.                     sk_00_10.Dispatcher.Invoke(d, new object[] { ax, ay, sk_00_10 });
  120.                     sk_05_05.Dispatcher.Invoke(d, new object[] { ax, ay, sk_05_05 });
  121.  
  122.                     ax += 1.0f;
  123.                     if (ax > 90.0f)
  124.                     {
  125.                         ax = 0.0f;
  126.                     }
  127.  
  128.                     ay += 1.0f;
  129.                     if (ay > 60.0f)
  130.                     {
  131.                         ay = 0.0f;
  132.                     }
  133.  
  134.                     Thread.Sleep(30);
  135.                 }
  136.             });
  137.  
  138.         tSkewingThread.IsBackground = true;
  139.  
  140.  
  141.         // Because everyone of them is set as background thread
  142.         // They should end nicely at the end of application
  143.         tRotatingThread.Start();
  144.         tScalingThread.Start();
  145.         tSkewingThread.Start();
  146.     }
  147.  
  148. }
  149. }

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.

2 thoughts on “WPF – rotacja, transformacja i przechylanie

  1. 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

  2. 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.

Dodaj komentarz