Custom Behaviors (Draggable Element)

Before I start talking about behaviors and what they do, lets start by understanding a little bit about the problem we have.

So lets say that you have some logic you want to write for a UI element, for example you want that element to be resizable using mouse/touch, or maybe you want to enable that element to be draggable (This post will explain how to do that).

To do the draggable behavior, the straight forward solution is to write the logic in the code behind and hook up the needed events (pointer pressed, pointer moved and pointer released) for that UI element and apply the translate x and y.

Doing this will work fine for a small project (a very tiny one). But this will lead to a different issues and here I list few:

  • This will break the MVVM pattern implementation of not having logic in your code behind (or at least it’s a good practice to eliminate reduce the amount of code in the page code behind)
  • If you have couple of UI elements in different pages, then you have to write that logic in every page. Also in case you want to change that logic, you have to maintain the changes in all pages
  • Registering/Unregistering events might lead to a memory leak and to a weird behavior (which is hard to track).
  • Probably you will use that logic again and again in different projects. Copying and pasting sometimes are the developer’s worst enemy.

So there must be a better way to do this. Behaviors.

What is “Behaviors”

Behaviors is a way to package up some logic (f.e the code to handle dragging the element), and then attach it to the UI element using XAML.

Even though we call them “Behaviors” but there are actually three types of them:

Behaviors

Basically they are some logic you want to package and add to your elements so you can extend the functionality  (f.e the draggable capability)

Triggers

Triggers are the logic to detect when the action should happen. The SDK comes with two types of triggers EventTriggerBehavior & DataTriggerBehavior. These two triggers should be more than enough. But its still possible to write your own custom triggers.
Triggers will always have an Actions collection. Once the trigger is fired, you want some actions to be executed.

Actions

Actions are some logic you want to run based on some trigger. They are  are always paired with some Trigger. Each trigger can have one or more actions to be executed.

Creating a custom behavior

Now that we have a basic idea about the behaviors, lets see how they work. Start Visual Studio and create a new project. Select from templates Windows Apps, then Blank App (Windows) (Notice that you can select Universal Apps, the behaviors will work on both Windows & Windows Phone apps). Click ok

image

Once the project is created we will need to add a reference to the Behaviors SDK. From your solution explorer, right click on the References and click on “Add Reference”

image

From the Reference Manager window, from the left side select the Windows 8.1 expander, Extensions, then tick the “Behaviors SDK (XAML)” and finally click OK. (Notice if you’re create a Universal Apps project that you will need to add this SDK for both project Windows & Windows Phone).

image

This step will add two assemblies to your project, Microsoft.Xaml.Interactivity.dll which will help us to create our own custom behavior and Microsoft.Xaml.Interactions.dll which will get us couple of ready out of box behaviors.

Once you add the SDK, you will get couple of ready out of the box behaviors that you can attach to your elements. The easiest way to play with behaviors is to reopen our project with Blend. Right click on the solution and click on “Open in Blend”

image

From Blend, click on the “Assets” tab, then “Behaviors”, you will find 10 ready behaviors that you can use

image

In our case, we want to make the element draggable. So we need to create our own custom behavior.  Go back to Visual Studio, and add a new class “ElementDraggableBehavior.cs”

image

To create a behavior, we need to inherit one class (DependencyObject) , and implement an interface (IBehavior)

public class ElementDraggableBehavior : DependencyObject,IBehavior
    {
        public DependencyObject AssociatedObject
        {
            get { throw new NotImplementedException(); }
        }

        public void Attach(DependencyObject associatedObject)
        {
            throw new NotImplementedException();
        }

        public void Detach()
        {
            throw new NotImplementedException();
        }
    }

The “AssociatedObject” property will be used to reference the object which uses the Behavior.
The other two methods will be called once the behavior is attached/detached to the element.

First, lets remove the throw exception line from the AssociatedObject getter, and add the setter

       public DependencyObject AssociatedObject
        {
            get;
            set;
        }

Now, we need to save a reference to the associated object once the behavior is attached to it. The Attach method will be called, and will pass the object we need

public void Attach(DependencyObject associatedObject)
        {
            if ((associatedObject != AssociatedObject) && !Windows.ApplicationModel.DesignMode.DesignModeEnabled)
            {
                AssociatedObject = associatedObject;
            }
        }

As this behavior will be attached through XAML,  we want to make sure that this code will not run in the design time by adding to the if statement the condition (!DesignMode.DesignModeEnabled).

The Detach method will be called once the behavior is detached from the element. This method will be used to clean up any resource used and to unregister the event. We didn’t use any resource or register any events, so we just  need to remove the throw exception line

public void Detach()
        {
        }

Now we need to write the actual implementation of the draggable feature. The implementation will be like this:

1- On the Attach method. Register the event PointerPressed, this event will be fired once the user touch/click on the element. Then we register another event, this time PointerReleased. This event will be fired once the user is done dragging the element.

Notice how we casted the AssociatedObject to a FrameworkElement so we can access the events

public void Attach(DependencyObject associatedObject){    if ((associatedObject != AssociatedObject) && !Windows.ApplicationModel.DesignMode.DesignModeEnabled)    {        AssociatedObject = associatedObject;        var fe = AssociatedObject as FrameworkElement;        if (fe != null)        {            fe.PointerPressed += fe_PointerPressed;            fe.PointerReleased += fe_PointerReleased;        }    }

}

2- The event handler “fe_PointerPressed” will get the parent item. Then it will register the PointerMoved event to track the user’s finger/mouse’s position.

        UIElement parent = null;
        Point prevPoint;
        int pointerId = -1;
        void fe_PointerPressed(object sender, PointerRoutedEventArgs e)
        {
            var fe = AssociatedObject as FrameworkElement;
            parent = (UIElement)fe.Parent;
            if (!(fe.RenderTransform is TranslateTransform))
                fe.RenderTransform = new TranslateTransform();
            prevPoint = e.GetCurrentPoint(parent).Position;
            parent.PointerMoved += move;
            pointerId = (int)e.Pointer.PointerId;
        }

3- The handler for the PointerMoved event will move the element (by changing the X & Y of the TranslateTransform)

        private void move(object o, PointerRoutedEventArgs args)
        {
            if (args.Pointer.PointerId != pointerId)
                return;

            var fe = AssociatedObject as FrameworkElement;
            var pos = args.GetCurrentPoint(parent).Position;
            var tr = (TranslateTransform)fe.RenderTransform;
            tr.X += pos.X - prevPoint.X;
            tr.Y += pos.Y - prevPoint.Y;
            prevPoint = pos;
        }

5- The handler for the PointerReleased will simply unregister the move method (the one we registered on the PointerPressed event)

        void fe_PointerReleased(object sender, PointerRoutedEventArgs e)
        {
            var fe = AssociatedObject as FrameworkElement;
            if (e.Pointer.PointerId != pointerId)
                return;
            parent.PointerMoved -= move;
            pointerId = -1;
        }

6- Finally we clean up and unregister any events we registered on the Attach method. On the Deatch method, we unregister both events; PointerPressed & PointerReleased

public void Detach()
        {
            var fe = AssociatedObject as FrameworkElement;
            if (fe != null)
            {
                fe.PointerPressed -= fe_PointerPressed;
                fe.PointerReleased -= fe_PointerReleased;
            }
            parent = null;
            AssociatedObject = null;
        }

Now that we have our Draggable behavior ready, we can go ahead and start using it. The easiest way to add behaviors is Blend.

Right click on the project again and click on “Open in Blend”

Notice this time Blend has 11 Behaviors. The magic of reflections, Blend went through our project’s assemblies and found our custom behavior

image

Let’s add two rectangle elements to our grid, and apply the custom behavior.

From the toolbox in the left side, click on the rectangle button, and draw two rectangles. Drag and drop the “ElementDraggableBehavior” from the Assets window to the two rectangles.

image

Run the project. You can easily drag and drop your elements around inside the root Grid.

What else you can do:

  • You can do some visual improvements for the dragging expierence. For example, you can change the Opacity on PointerPressed to 0.7, and change it back to 1.0 on PointerReleased.

  • This behavior respects the order of the elements in the Grid. The second rectangle will always be rendered over the first. In case you want the draggable element to be the one in the top you can do the following in the PointerPressed handler:

            var panel = parent as Windows.UI.Xaml.Controls.Panel;
            panel.Children.Remove(fe);
            panel.Children.Add(fe);
    
  • This behavior is always on, it would be a good idea to add a dependency property (IsEnabled). so you can control when the element is draggable or not. Notice that you should use dependency property instead of a normal property, so it can be bindable and to be changeable from the visual states.

  • Its always a good idea to package all your behaviors code in a PCL library, so they can be easily accessible from different projects and different platforms.

You can download the source code from this link:

http://1drv.ms/1souCMZ

You can always find me on Twitter @TareqAteik

Tareq Ateik

Read more posts by this author.