July 1, 2009 21:49 by Nigel Sampson
So far I've covered building a Trigger "Double Click Trigger" and a
Behavior "Mouse Wheel Behavior" so all that's left are Actions. Actions
I believe are probably the most useful of the three sorts of Behaviors
in Silverlight, the sheer amount of variation possible when composing
these with events provides a lot of richfunctionality to the Blend user. The real power comes from having all this functionality at your drag and drop disposal, by quickly wiring this all together it leaves you more time for true functionality of your RIA.
For
the Actions I want to create today I'm going to concentrate on the new
navigation system in Silverlight 3. If you haven't taken a look at it I
suggest you watch the video at silverlight.net.
In the default examples we have to wire up the button clicks to change
the current page ourselves. For a designer throwing together a
prototype this can be awkward and slows everything down. Let's create a
couple of Actionsthat'll speed this process up.
To begin with
we need an Action that tells the Frame to navigate to a certain Uri.
For this we need to associate the Action with two different elements,
one, the element that will ultimately trigger the Action and two, the
Frame that will do the navigation.
This is achieved by using the TargetedTriggerAction<T> abstract class. This allows us to define a Target of the Action where T is the constraint of the Target (and not the AssociatedObject). You can see how some of this works at "Targeting Other Elements via the Blend 3 UI".
So for our Action to navigate on the Frame we inherit from TargetedTriggerAction<Frame>.
The rest of the code is pretty simple, we expose a Uri property and
when the action is invoked we call the Navigate method on the Target
Frame.
public class NavigateFrameAction : TargetedTriggerAction<Frame>
{
public Uri Uri
{
get;
set;
}
protected override void Invoke(object parameter)
{
Target.Navigate(Uri);
}
}
The other Action we're going to require is one that sits in our
navigation Pages and tells the containing Frame to do the navigation.
In theory we could build this using a TargetedTriggerAction<Page> but this relies on the Action being the same .xaml
as the Page in order to target it. We could end up having multiple
levels of controls so we'll need to walk to the visual tree ourselves
to find the containing page. Thanks toVisualTreeHelper this is a pretty simple task. Our Navigation action inherits from TriggerAction<UIElement> as we want to make this as applicable as possible. Again pretty simple code, on Invoke we locate the Page and use its NavigationService to trigger the Navigation.
public class NavigateAction : TriggerAction<DependencyObject>
{
public Uri Uri
{
get;
set;
}
protected override void Invoke(object parameter)
{
var page = FindContainingPage(AssociatedObject);
if(page == null)
throw new InvalidOperationException("Could not find the containing System.Windows.Controls.Page in the visual tree.");
page.NavigationService.Navigate(Uri);
}
protected static Page FindContainingPage(DependencyObject associatedObject)
{
var current = associatedObject;
while(!(current is Page))
{
current = VisualTreeHelper.GetParent(current);
if(current == null)
return null;
}
return (Page)current;
}
}
Using them is very simple, drag either action onto the element you want triggering the page navigation. Type in the uri you want to navigate to and you're done.
June 24, 2009 13:01 by Nigel Sampson
One fairly useful piece of functionality missing from Silverlight is
the use of the Mouse Wheel, thankfully there's a lot of code out there
about how to use the Html Bridge to receive DOM events and bring in
Mouse Wheel functionality.
The trouble is that with these examples you need to wire up all the events and manually deal with the mouse wheel.
Silverlight
/ Blend Behaviors can help with this quite a bit by encapsulating all
the required functionality in a simple drag and drop component.
As
I've discussed before Blend Behaviors come in three flavors, Triggers,
Actions and Behaviors. Triggers and Actions are great when you want to
have both the action and trigger asseparate composable items but sometimes both the trigger and action are so intertwined that it doesn't make sense for them to be separated, this is where Behaviors come in, they really are pre-packaged pieces of functionality.
For this example we'll use the MouseWheelHelper
class from the Deep Zoom Composer projects as this is one I've used a
lot and fits nicely with what we'll be doing. The code for it is below.
public class MouseWheelHelper
{
private static Worker MouseWheelWorker;
private bool isMouseOver;
public MouseWheelHelper(UIElement element)
{
if(MouseWheelWorker == null)
MouseWheelWorker = new Worker();
MouseWheelWorker.Moved += HandleMouseWheel;
element.MouseEnter += HandleMouseEnter;
element.MouseLeave += HandleMouseLeave;
element.MouseMove += HandleMouseMove;
}
public event EventHandler<MouseWheelEventArgs> Moved;
private void HandleMouseWheel(object sender, MouseWheelEventArgs args)
{
if(isMouseOver)
Moved(this, args);
}
private void HandleMouseEnter(object sender, EventArgs e)
{
isMouseOver = true;
}
private void HandleMouseLeave(object sender, EventArgs e)
{
isMouseOver = false;
}
private void HandleMouseMove(object sender, EventArgs e)
{
isMouseOver = true;
}
private class Worker
{
public Worker()
{
if(!HtmlPage.IsEnabled)
return;
HtmlPage.Window.AttachEvent("DOMMouseScroll", HandleMouseWheel);
HtmlPage.Window.AttachEvent("onmousewheel", HandleMouseWheel);
HtmlPage.Document.AttachEvent("onmousewheel", HandleMouseWheel);
}
public event EventHandler<MouseWheelEventArgs> Moved;
private void HandleMouseWheel(object sender, HtmlEventArgs args)
{
double delta = 0;
var eventObj = args.EventObject;
if(eventObj.GetProperty("wheelDelta") != null)
{
delta = ((double)eventObj.GetProperty("wheelDelta")) / 120;
if(HtmlPage.Window.GetProperty("opera") != null)
delta = -delta;
}
else if(eventObj.GetProperty("detail") != null)
{
delta = -((double)eventObj.GetProperty("detail")) / 3;
if(HtmlPage.BrowserInformation.UserAgent.IndexOf("Macintosh") != -1)
delta = delta * 3;
}
if(delta == 0 || Moved == null)
return;
var wheelArgs = new MouseWheelEventArgs(delta);
Moved(this, wheelArgs);
if(wheelArgs.Handled)
args.PreventDefault();
}
}
}
The
Behavior code is pretty simple, we're not adding any startling pieces
of functionality here, the value we get is from setting this up as
something that anyone using Blend can quickly add to their applications.
We'll be attaching our Behavior to any elements of ScrollViewer so we inherit from Behavior<ScrollViewer>. When the viewer is attached we create a MouseWheelHelper for the viewer and attach to the helper's Moved event. We also clean up the helper if the behavior is detached.
public class MouseWheelScrollBehavior : Behavior<ScrollViewer>
{
private MouseWheelHelper helper;
protected override void OnAttached()
{
base.OnAttached();
helper = new MouseWheelHelper(AssociatedObject);
helper.Moved += OnMouseWheelMoved;
}
private void OnMouseWheelMoved(object sender, MouseWheelEventArgs e)
{
var offset = AssociatedObject.VerticalOffset;
AssociatedObject.ScrollToVerticalOffset(offset + (e.Delta * -10));
}
protected override void OnDetaching()
{
base.OnDetaching();
helper.Moved -= OnMouseWheelMoved;
}
}
On
the mouse wheel moved event scroll the viewer by the scaled delta (you
can change this constant in order to speed up or slow down the scroll
speed).
From Blend you can now drag this Behavior from the Asset Library on to any ScrollViewer you want mouse wheel scrolling on. Sadly ListBox
doesn't contain the appropriate scrolling methods to quickly add this
in, however there should be nothing stopping you creating a new
template forListBox that has this Behavior attached to the internal ScrollViewer.
June 17, 2009 11:45 by Nigel Sampson
For the most part we'll be adding or manipulating Silverlight / WPF
Behaviors from Blend, but occasionally we'll need to manage these from
code. Overall it's a pretty simple process. The main class we need to
deal with is Microsoft.Expression.Interactivity.Interaction, from here
we can retrieve the Behaviors and Triggers collections for a DependencyObject.
To attach a standard Behavior we use the following code.
var scrollBehavior = new MouseWheelScrollBehavior();
var behaviours = Interaction.GetBehaviors(Viewer);
behaviours.Add(scrollBehavior);
To remove an attached behavior we use the following.
behaviours.Remove(scrollBehavior);
Managing
triggers and actions are a two step process. In Blend elements have
triggers and triggers have actions. You can have multiple actions per
trigger, and multiple triggers per element. First we create our trigger
and our action, we then add our action to the trigger's actions
collection and finally the trigger to the element's triggers collection.
var trigger = new RandonTimerTrigger();
var action = new MessageBoxAction();
trigger.Actions.Add(action);
var triggers = Interaction.GetTriggers(Viewer);
triggers.Add(trigger);
Removing an action from a trigger or a trigger from an element is similar process to behaviors.
triggers.Remove(trigger);
June 10, 2009 11:24 by Nigel Sampson
I've been come really enamoured with the possibilities of Silverlight /
WPF Behaviors, broadly they can be divided into three types, Triggers,
Actions and Behaviors. The ability for non-programmers to compose
behaviour and interactivity in Blend is very interesting.
Triggers
as the name suggests are objects the ultimately trigger other Actions,
because they're separated in this way it means you can compose
different combinations of Triggers and Actions for a richer user
experience. For this post we'll look at filling a gap in Silverlight by
creating a Double Click Trigger which can be used to invoke all sorts
of actions, and all through Blend!.
To create a trigger we need
to reference the assemble "Microsoft.Expression.Interactivty", if
you've installed the Blend 3 Preview in to the default location then
you can find this assembly in "C:\Program Files\Microsoft
Expression\Blend 3 Preview\Libraries\Silverlight". Triggers inherit
from a class named TriggerBase<T> where T is the type of element
the trigger can be attached to. This means that if our trigger is only
appropriate to say Buttons then we would inherit from
TriggerBase<Button>.
For our trigger we need access to the "MouseLeftButtonDown" event and
we want to be able to apply this to as many different elements as we
can so we'll inherit from TriggerBase<UIElement>. The two
important methods when creating all Silverlight 3 Behaviours is the
OnAttached and OnDetaching methods, this is where we can start to
interact with the element (AssociatedObject) we've been dropped on to.
In our case we want to attach to the MouseLeftButtonDown event and to
be a good citizen we'll clean up after ourselves on detaching.
public class DoubleClickTrigger : TriggerBase<UIElement>
{
private readonly DispatcherTimer timer;
public DoubleClickTrigger()
{
timer = new DispatcherTimer
{
Interval = new TimeSpan(0, 0, 0, 0, 200)
};
timer.Tick += OnTimerTick;
}
protected override void OnAttached()
{
base.OnAttached();
AssociatedObject.MouseLeftButtonDown += OnMouseButtonDown;
}
protected override void OnDetaching()
{
base.OnDetaching();
AssociatedObject.MouseLeftButtonDown -= OnMouseButtonDown;
if(timer.IsEnabled)
timer.Stop();
}
private void OnMouseButtonDown(object sender, MouseButtonEventArgs e)
{
if(!timer.IsEnabled)
{
timer.Start();
return;
}
timer.Stop();
InvokeActions(null);
}
private void OnTimerTick(object sender, EventArgs e)
{
timer.Stop();
}
}
The rest of the code is pretty simple, when a user clicks the
associated object we start a timer that runs for 200ms, if we record
another click while the timer is running then we call InvokeActions.
This triggers any actions the user has attached to our trigger.
To use our new Trigger we drag on any Action from our assets panel on
to our element, in my little example I'm using a test MessageBoxAction.
Then under the properties of the action we change the trigger from the
default EventTrigger to our new DoubleClickTrigger.

The xaml is pretty readable too.
<Rectangle Height="80" Width="130" Canvas.Left="63" Canvas.Top="102">
<i:Interaction.Triggers>
<cx:DoubleClickTrigger>
<cx:MessageBoxAction Message="Test"/>
</cx:DoubleClickTrigger>
</i:Interaction.Triggers>
</Rectangle>
June 4, 2009 19:33 by Nigel Sampson
A question I see a lot on sites like Stack Overflow and the Silverlight
forums is around sharing or reusing types in WCF Service References,
either between multiple services or between the client and server.
When
you create a Service Reference in a Silverlight project Visual Studio
will generate proxies for the Service and Data Contracts. These are
partial classes so if you want to extend them with some logic (or an
interface for dependency injection) you can pretty easily. This has a
few problems, if you have multiple services that use the same data
contracts then each will have it's own proxies. For instance an
OrderService and a ProductService will often share the Product type.
Simply generating service references to both of those will cause dual
Product types in your Silverlight client.
Thankfully Visual
Studio has some tooling support to deal with this, however it has some
tricks to make it work correctly. When you create a Service Reference
there is an advanced option titled "Reuse types in referenced
assemblies" with some options about which references to use. The first
trick is that it's referenced assemblies and therefore will not use types in the assembly of the project you're adding the reference to.
So which types will it use:
- Types in assemblies referenced by your project.
- Types with the same name and namespace.
- It doesn't matter if the client side type has the DataContract and DataMember attributes.
- It doesn't matter if the client side type has the INotifyPropertyChanged interface (although I recommend it does).
It
appears it looks for types that have the same Full Name as the
DataContract type. It doesn't appear to examine the actual signature of
the type, so if you're writing your own client types there could be a
problem there if you're not careful.
So now that we know which
types "Reuse types in referenced assemblies" will use how can be put
this to use. As it only uses types in referenced assemblies we need an
assembly (usually a Silverlight Class Library project) to add these
types to. Normally this isn't a problem I think only once have I needed
to add a project for just this purpose.
Now while you can create
your own types for the Silverlight Service References I prefer to share
the source code for these between the client and server. The method I
use is "Add Existing Item" - "Add As Link" in Visual Studio. That way the code is only written once and defined on both the client and server. This could cause some problems with having the different CLR versions but for most service objects the shouldn't be a problem.There are lots of articles out there about sharing code between WPF and Silverlight so find the best method for you.