Introduction
In this article, I'll show items-switch between two lists with visual representation. I'll do it under MVVM pattern, performing View-ViewModel-View-ViewModel passes while preserving decoupling.
I'll use the Async-Ctp's Awaitable Task for async waiting in the ViewModel. Along the way, I'll show a technique to achieve Bezier-Animation in Silverlight.
Background
Over the last few years, I've come across an understandable misconception regarding the WPF/Silverlight use/necessity/role of animation.
Many programmers, usually with Winforms substantial background tends to regard animation as a decorative addition to the old set of UI. Judging from the abundance of jumping, spinning, color-changing controls, this misconception is fairly understandable. While, in fact, Animation is actually an important additional channel that allows the application to communicate with the user, thus conveying important information regarding the application's state and activities (with or without correspondence to the user actions).
On one occasion, I came across a company that 'held' this wrong point of view, and decided to take upon myself the task of convincing them about the crucial role animation has in a modern UI. Since this company dealt with Basketball (yes, the game...) management-related software, I was looking for a scenario in that area, that would prove my point.
What I came up with was - the 'substitution' management scenario. In short: two lists interface(Five, Bench) that allow the application's operator to perform items(Players
) switch between them. Drag&Drop solution, although intuitive, ruled out due to operational ease-of-use and quick response reasons. So, I came up with the 'select player on each list' triggered switch solution.
The nice thing about this scenario is that it is almost impossible to perform under winforms (in an actual UI understandable manner), and it is fairly easy using WPF/Silverlight's animation.
Code Description
Two logical lists of players in ViewModel (five, bench) binded to two Listboxes in the View respectively. When the ViewModel detects Mutual (both Five & Bench) selection, it raises an event that tells the View to commence the 'switch-animation': The View Constructs & Displays the appropriate Arrows and performs the animation. When finished, it notifies the Model which 'Commits' the logical switch in its lists and stores the switch-data for Undo purposes.
Using the Code
MVVM Meets Async-Ctp
The flow can be illustrated like this:
As explained above, the 'Switch Task' actually starts when the ViewModel
identifies that two players (one on each list) got selected. Then it performs the StoreAndPerformeSwitch
subroutine.
private async void StoreAndPerformeSwitch(Player SwitchBP, Player SwitchFP)
This soubroutine starts with 'waiting' for the 'Visual switch' to perform and finish:
await TaskEx.Run(() =>PerformSwitch());
All PerformSwitch
does is raise BeginSwitchAnimation
Event, and wait (its thread) for the ManualResetEvent
'gate' to get open (set),
It does (get open) when the ViewSwitchEnded
method gets called from the View (when it finishes the animation).
Then it commits the Logical-Switch and stores the switch-data into the undo stack.
SwitchData sd = new SwitchData() { BenchPlayer = SwitchBP, FivePlayer = SwitchFP };
...
sd.BenchPlayer.IsSelected = false;
sd.FivePlayer.IsSelected = false;
int InxInFive = Five.IndexOf(sd.FivePlayer);
int InxInBench = Bench.IndexOf(sd.BenchPlayer);
Bench[InxInBench] = Players[Players.IndexOf(sd.FivePlayer)];
Five[InxInFive] = Players[Players.IndexOf(sd.BenchPlayer)];
if (!OnUndo) stckSwitches.Push(new SwitchData()
{ BenchPlayer = Bench[InxInBench], FivePlayer = Five[InxInFive] });
Finally, it 'renews' the UndoCommand
- this causes it to ReEvaluate
its IsEnabled
state based on the Undo-Stack state.
UndoCommand = new DelegateCommand(Undo, CanUndo);
ArrowPath
Originally, this was a Custom-shape in the WPF's version of this project. This custom-path is responsible for drawing the Arrow, and, along the way, constructs a matching Bezier curve for the movement animation.
Bezier Animation
Although path/curved-line animations in Silverlight are usually done by fragmenting the curve and animating from one fragment to the next, here, we can use a much simpler approach: Bezier
is actually a mathematical formula to describe a curve upon giving four control points. Conveniently, it can return x,y position on its curve for any zero-to-one input value. So, all we actually need to do is 'DoubleAnimation
' values from zero-to-one, put it into our Bezier formula and get the X,Y we need for positioning our animated element. As the preceding lines imply, there should be an Object
that, on the one hand, will hold the Bezier formula, and, on the other will receive animated values.
In a 'perfect world', we will construct an object with two AttachedProperties
:
Zero2One
(double
)
Bezier
(AnimatedBezier
)
We'll attach it to the targeted element (on which we want to set the corresponding x,y). Something like that (pseudo):
<TranslateTransform x:Name="ttSTF" Bezier={Binding ElementName="
ArrowWithMovementBezier", Path="MovementBezier" }/>
Then, we'll only need DoubleAnimation
with Storyboard.TargetName="ttSTF"
and something like that - Storyboard.TargetProperty="(local:OurObject.Zero2One)"
.
Inside the Zero2One
dependency-property-changed subroutine, we'll set the attached-object's x,y values by calculating against the Bezier formula.
Unfortunately, as it seems, property-path to Custom-Attached-Property is not supported!
i.e., - you can have this propertyPath
- '...(Canvas.Left
)', but, you can't do this - '...(some_namespace:MyCanvas.MyLeft
)'
* if any of the readers knows how to set PropertyPath
to a Custom-AttachedProperty
, please share.
In 'real life' - there are several ways to overcome this obstacle, I chose to create an inert-proxy Element:
<local:Bezier2DoubleAnimationMediator x:Name="mediator2ttSTB"
Target="{Binding ElementName=ttSTB}" AnimBezier="{Binding ElementName=SwitchToBenchArrow,
Path=AnimBezier}"/>
This element has Zero2OneValue
dp, AnimBezier
dp, as the previous solution, but has an additional Target
dp.
Now the animation acts on this element's Zero2OneValue
DependencyProperty
, and consequently, it will change its Target's X,Y values.
History
- 24th November, 2011: Initial version