Custom Behaviors – UWP

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 the later).

To do the draggable behavior, the straight forward solution is to write the logic in the code behind and hook up the required 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 the concept “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 a Blank App (Windows) and click ok.

Once the project is created we will need to add a reference to the Behaviors SDK. From the nuget package manager search for “XamlBehaviors” and install “Microsoft.Xaml.Behaviors.Uwp.Managed”

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;
 var fe = AssociatedObject as FrameworkElement;
 if (fe != null)
 {
 fe.PointerPressed += fe_PointerPressed;
 fe.PointerReleased += fe_PointerReleased;
 }
 }
 }


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

4- 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;
 }

5- 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. What you can do is add the namespace in xaml and attach the behavior to the element:



 <Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
 <Grid 
 xmlns:Custom="using:Microsoft.Xaml.Interactivity" xmlns:Custom1="using:SampleCustomBehaviors.Behaviors"
 Background="Red" Width="250" Height="250">
 <Custom:Interaction.Behaviors>
 <Custom1:ElementDraggableBehavior/>
 </Custom:Interaction.Behaviors>
 </Grid>
 </Grid>

But there’s a cooler way to add behaviors is Blend.

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

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

 

Let’s add a 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.

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 get the complete source code from this github repository:
    https://github.com/TareqAteik/SampleUWPBehaviros
    If you have any question, please reach me out on twitter @tareqateik

Leave a Reply

Your email address will not be published. Required fields are marked *