Silverlight 3 Navigation Drag and Drop Actions

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.



kick it on DotNetKicks.com

A Mouse Wheel Blend Behavior

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.

 

kick it on DotNetKicks.com

Attaching Silverlight 3 Behaviors in C#

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

 

kick it on DotNetKicks.com

Silverlight 3 Behaviors : Double Click 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>

 

kick it on DotNetKicks.com

Resuing types in Silverlight Service References

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.

kick it on DotNetKicks.com