Jest to przykład aplikacji desktopwej WPF z zastosowaniem języka XAML oraz C#. Jej głównym celem jest zaprezentowanie podstawowych animacji z użyciem klasy Storyboard. W aplikacji wszystkie wykorzystane grafiki są obiektami wektorowymi. Ich użycie w WPF zostało obsłużone za pomocą zewnętrznej biblioteki SharpVectors (https:/home/mihowpl/domains/mihow.pl/public_html/github.com/ElinamLLC/SharpVectors)
Aplikacja składa się z jednego okna, w którym w nagłówku widać przyciski kontrolne, natomiast w centralnej części umieszczona została symboliczna grafika Pasa Transmisyjnego (oraz Kół Zębatych) z umieszonym na nim Kartonem.
Głównym kontenerem jest kontrolka Grid, która zawiera w sobie definicję dla 3 wierszy.
- W pierwszym z wierszy umieszczona jest inna kontrolka Grid, na której to rozplanowane są przyciski kontrolne oraz kontrolka Border, która z kolei służy jako kontener do kontrolki Thumb, która to ma zadanie umozliwienie funkcjonalności „drag” dla całego okna aplikacji.
<Button x:Name="_play" Grid.Column="0" Margin="3" Background="Transparent" Style="{StaticResource Hoverless}" Click="_play_Click" >
<svgc1:SvgViewbox Source="Resources/play.svg" />
</Button>
<Button x:Name="_stop" Grid.Column="0" Margin="3" Background="Transparent" Style="{StaticResource Hoverless}" Click="_stop_Click" >
<svgc1:SvgViewbox Source="Resources/stop.svg"/>
</Button>
<Button x:Name="_exit" Grid.Column="2" Margin="3" Background="Transparent" Style="{StaticResource Hoverless}" Click="_exit_Click" >
<svgc1:SvgViewbox Source="Resources/exit.svg" />
</Button>
<Border
Height="{Binding ActualHeight, ElementName=_header, Mode=OneWay}"
Width="473"
Grid.Column="1"
x:Name="headerBorder"
VerticalAlignment="Center"
HorizontalAlignment="Center"
Background="Transparent"
BorderThickness="1,1,1,1"
BorderBrush="Transparent" Margin="5,-5,2,0">
<Grid>
<Thumb
x:Name="headerThumb"
Opacity="0"
Background="{x:Null}"
Foreground="{x:Null}"
DragDelta="headerThumb_DragDelta"/>
</Grid>
</Border>
- Na drugim z wierszy głównej kontrolki Grid znajduje się kontrolka Button (z zastosowaniem odpowiedniego Stylu, który ukrywa tło przycisku oraz domyślny efekt podświetlania przycisku gdy znajduje się nad nim kursor myszki). Kontrolka ta jest „przetransformowana” o -140px w osi X w celu ustawienia Kartonu na początku Pasa Transmisyjnego. Dodatkowo transformacja położenia kontrolki posiada nadane imię, dzięki czemu będzie można się do niej odnieść w Code Behind.
Aby wyświetlić samą grafikę Kartonu zamiast domyślnego wyglądu kontrolki Button wykorzystana jest wspomniana wcześniej zewnętrzna biblioteka SharpVectors, a dokładnie kontrolka SvgViewbox umożliwiająca użycie grafiki wektorowej.
<Button Grid.Row="1" x:Name="_box" Background="Transparent" Margin="5" Style="{StaticResource CogButton}">
<Button.RenderTransform>
<TranslateTransform x:Name="_positionTransform_case" X="-140"/>
</Button.RenderTransform>
<svgc1:SvgViewbox Source="/Resources/box.svg"/>
</Button>
- Na trzecim, ostatnim z wierszy znajdują się dwie „nakładające” się na siebie kontrolki. W celu uzyskania efektu opasania Kół Zębatych poprzez Pas Transmisyjny, niezbędne było użycie kontroli UserControl z efektem przezroczystego tła, w środku której znajduje się grafika wektorowa samego Pasa Transmisyjnego. Koła Zębate zgrupowane są za pomocą kontroli Grid i tak samo jak Karton posiadają transformację, lecz trochę bardziej złożoną. Przede wszystkim posiadają transformację swojego środka masy, który został przeniesiony dokładnie do środka samego obiektu, oraz posiadają nazwaną transformację obrotu, która zostanie użyta w Code Behind.
<UserControl x:Name="_belt" Grid.Row="2" Background="Transparent">
<svgc1:SvgViewbox Source="/Resources/belt.svg"/>
</UserControl>
<Grid x:Name="_conveyor" Grid.Row="2" Background="Transparent" >
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Button x:Name="_cog1" Grid.Column="1" Margin="15" Style="{StaticResource CogButton}" RenderTransformOrigin=".5,.5">
<Button.RenderTransform>
<RotateTransform x:Name="_rotateTransform_cog1"/>
</Button.RenderTransform>
<svgc1:SvgViewbox Source="/Resources/cog.svg"/>
</Button>
<Button x:Name="_cog2" Grid.Column="2" Margin="15" Style="{StaticResource CogButton}" RenderTransformOrigin=".5,.5">
<Button.RenderTransform>
<RotateTransform x:Name="_rotateTransform_cog2"/>
</Button.RenderTransform>
<svgc1:SvgViewbox Source="/Resources/cog.svg"/>
</Button>
<Button x:Name="_cog3" Grid.Column="3" Margin="15" Style="{StaticResource CogButton}" RenderTransformOrigin=".5,.5">
<Button.RenderTransform>
<RotateTransform x:Name="_rotateTransform_cog3"/>
</Button.RenderTransform>
<svgc1:SvgViewbox Source="/Resources/cog.svg"/>
</Button>
<Button x:Name="_cog4" Grid.Column="4" Margin="15" Style="{StaticResource CogButton}" RenderTransformOrigin=".5,.5">
<Button.RenderTransform>
<RotateTransform x:Name="_rotateTransform_cog4"/>
</Button.RenderTransform>
<svgc1:SvgViewbox Source="/Resources/cog.svg"/>
</Button>
<Button x:Name="_cog5" Grid.Column="5" Margin="15" Style="{StaticResource CogButton}" RenderTransformOrigin=".5,.5">
<Button.RenderTransform>
<RotateTransform x:Name="_rotateTransform_cog5"/>
</Button.RenderTransform>
<svgc1:SvgViewbox Source="/Resources/cog.svg"/>
</Button>
</Grid>
Po uruchomieniu animacji Karton przesuwa się w prawo imitując poruszanie się po Pasie Transmisyjnym. Imitacja ruchu Pasa Transmisyjnego pokazana jest za pomoca obracających się Kół Zębatych umieszczonych „wewnątrz” Pasa Transmisyjnego. Po dotarciu Kartonu do prawej granicy Pasa, następuje animacja płynnej zmiany przezroczystości Kartonu, co ma imitować jego „zniknięcie”. Po czym Karton pojawią się po lewej stronie Pasa w animacji odwrotnej – następuje zwiększanie przezroczystości Kartonu az do całkowitego się pojawienia.
Spójrzmy teraz na Code Behind.
W konstruktorze Głównego Okna naszej aplikacji znajdują się metody niezbędne do inicjalizacji animacji.
- InitializeRotateAnimation jest to uniwersalna metoda, która została zastosowana do wszystkich Kół Zębatych w celu utworzenia obiektu Storyboard oraz dodaniu do tego obiektu animacji DoubleAnimation w celu obrotu Koła Zębatego. Dodatkowo umieszczona jest tutaj obsługa Eventu Completed, który jest każdorazowo wykonywany, gdy element Storyboard wykona zadeklarowaną na nim animację.
private void InitializeRotateAnimation(ref Button givenButton, ref RotateTransform givenTransform)
{
Storyboard storyboard = new Storyboard
{
Duration = new Duration(TimeSpan.FromSeconds(2))
};
DoubleAnimation rotateAnimation = new DoubleAnimation()
{
To = givenTransform.Angle + 360,
Duration = storyboard.Duration
};
Storyboard.SetTarget(rotateAnimation, givenButton);
Storyboard.SetTargetProperty(rotateAnimation, new PropertyPath("(UIElement.RenderTransform).(RotateTransform.Angle)"));
storyboard.Children.Add(rotateAnimation);
Resources.Add(givenButton.Name + "_Storyboard", storyboard);
((Storyboard)Resources[givenButton.Name + "_Storyboard"]).Completed += new EventHandler(myanim_Completed);
}
- InitializeCaseAnimation jest to metoda, która ma na celu zadeklarowanie animacji dla obiektu Karton. Animacja tego obiektu składa się tak naprawdę z czterech animacji na czterech oddzielnych obiektach Storyboard.
- Animacja przemieszczania się Kartonu w prawą stronę
/home/mihowpl/domains/mihow.pl/public_html/Move Animation from middle to the edge
Storyboard storyboard1 = new Storyboard
{
Duration = new Duration(TimeSpan.FromSeconds(4))
};
DoubleAnimation moveAnimation = new DoubleAnimation()
{
To = givenTransform.X + 280,
From = givenTransform.X,
Duration = storyboard1.Duration
};
Storyboard.SetTarget(moveAnimation, caseButton);
Storyboard.SetTargetProperty(moveAnimation, new PropertyPath("(UIElement.RenderTransform).(TranslateTransform.X)"));
storyboard1.Children.Add(moveAnimation);
Resources.Add(caseButton.Name + "_Storyboard1", storyboard1);
((Storyboard)Resources[caseButton.Name + "_Storyboard1"]).Completed += new EventHandler(caseAnim_Completed1);
- Animacja „znikania” kartonu po dotarciu do prawej krawędzie Pasa Transmisyjnego
/home/mihowpl/domains/mihow.pl/public_html/Opacity animation on the Right Edge
Storyboard storyboard2 = new Storyboard
{
Duration = new Duration(TimeSpan.FromMilliseconds(500))
};
DoubleAnimation caseDoneAnmiation = new DoubleAnimation()
{
To = 0,
Duration = storyboard2.Duration
};
Storyboard.SetTarget(caseDoneAnmiation, caseButton);
Storyboard.SetTargetProperty(caseDoneAnmiation, new PropertyPath("Opacity"));
storyboard2.Children.Add(caseDoneAnmiation);
Resources.Add(caseButton.Name + "_Storyboard2", storyboard2);
((Storyboard)Resources[caseButton.Name + "_Storyboard2"]).Completed += new EventHandler(caseAnim_Completed2);
- Animacja „pojawienia” się Kartonu (po zniknięciu) po lewej stronie Pasa Transmisyjnego
/home/mihowpl/domains/mihow.pl/public_html/Appear Animation - new Case
Storyboard storyboard3 = new Storyboard
{
Duration = new Duration(TimeSpan.FromMilliseconds(500))
};
DoubleAnimation caseNewAnmiation = new DoubleAnimation()
{
To = 1,
Duration = storyboard3.Duration
};
Storyboard.SetTarget(caseNewAnmiation, caseButton);
Storyboard.SetTargetProperty(caseNewAnmiation, new PropertyPath("Opacity"));
storyboard3.Children.Add(caseNewAnmiation);
Resources.Add(caseButton.Name + "_Storyboard3", storyboard3);
((Storyboard)Resources[caseButton.Name + "_Storyboard3"]).Completed += new EventHandler(caseEnd_Completed3);
- Oraz faktyczne „przemieszczenie” Kartonu na początek Pasa Transmisyjnego po osiągnięciu skrajnej prawej strony Pasa
/home/mihowpl/domains/mihow.pl/public_html/Transition to Left Edge of the Belt
Storyboard storyboard4 = new Storyboard
{
Duration = new Duration(TimeSpan.FromMilliseconds(1))
};
DoubleAnimation caseStartAnimation = new DoubleAnimation()
{
From = givenTransform.X,
To = givenTransform.X-280,
Duration = storyboard4.Duration
};
Storyboard.SetTarget(caseStartAnimation, caseButton);
Storyboard.SetTargetProperty(caseStartAnimation, new PropertyPath("(UIElement.RenderTransform).(TranslateTransform.X)"));
storyboard4.Children.Add(caseStartAnimation);
Resources.Add(caseButton.Name + "_Storyboard4", storyboard4);
((Storyboard)Resources[caseButton.Name + "_Storyboard4"]).Completed += new EventHandler(caseStart_Completed4);
Po odpowiednim zadeklarowaniu Animacji pozostaje jednie kwestia ich odpowiedniej obsługi, która to została wykonana za pomocą wywołań funkcji Storyboard.Begin, Storyboard.Pause czy Storyboard.Resume.
Całe rozwiązanie wrac z plikami źródłowymi do pobrania na licencji Open Source na moim GitHub
(https:/home/mihowpl/domains/mihow.pl/public_html/github.com/JazwinskiMichal/WpfAnimationDemo)