Sunday, October 18, 2009

Behaviors Library for WPF

In my previous post, Behaviors in WPF with Expression Blend 3, I explained some basics about behaviors with Expression Blend 3. So now, I decided to start working on a library that will combine (hopefully) lots of behaviors for everyday use. I started with several behaviors for Window objects.

Here is what the library contains for now:

  • WindowDragMoveBehavior – When attached to a window, this behavior allows the window to be moved with the mouse, but not just by dragging the title bar of the window, as is the default behavior, but by clicking on any point of the window, and then moving the mouse. This is useful when you have skinned apps and you don’t actually have the title bar.
  • AnimatedShowHideElementBehavior – Animates the showing or hiding of a framework element (works best for window elements now). Supports several types of animations:
      • Fade In
      • Fade Out
      • Zoom In
      • Zoom Out
      • Skew

Using the library is simple, just add a reference to the .dll file in your project and Expression Blend will start showing the new behaviors in the assets:

assets

Then, just drag and drop the behavior to an element to start using it:

attached

And you’ll see the settings of the behavior in Blend:

properties

Of course, this library is very small now, and i promise that I will add more and more behaviors when I have the time. Any suggestions will be appreciated.

Download the behaviors library

You can download the behaviors library and a small demo project from this link.

Behaviors in WPF with Expression Blend 3

In Expression Blend 3, there is this new cool thing, called behaviors. In very simple words, these are special forms of objects which are attached to another object (usually an element in the UI) and perform some actions thus defining the behavior of the element. The cool thing is that behaviors are reusable, and can be created in libraries and used in many projects. They're not tied to a specific application logic. Another nice thing is that once created, behaviors are extremely easy to attach to an element in XAML.

To demonstrate the power of behaviors, I decided to create a very simple pong-like game ( a game with a ball that is moving around the screen and you have to keep it from escaping) and I used 2 custom behaviors. So let's go through the process.



Getting Started

Creating a custom behaviors is pretty simple. In Expression Blend, go to File -> New Item, and select Behavior.



Then, the code-file of the behavior will be opened. You can edit in Blend of course, but when it comes to writing code, I prefer Visual Studio.

The structure of a behavior
Blend will define a skeleton for your behavior. The most important methods inside are the OnAttached() and the OnDetaching() overrides. As suggested by the name, the OnAttached() method executes when the behavior is being attached to the element, so you should do some initializations there. In the OnDetaching() method, you can provide cleanup. Also in the behavior class you can define properties that control some settings. These properties will be displayed in the Properties section in Expression Blend when the behavior is selected.

The behaviors for my game
In my demo game, I need two types of objects. First type is the player controlled bars (four of them) that move on each side of the screen following the movement of the mouse. The second type is the ball that moves constantly and bounces off the player controlled bars. So as you can guess, I need two behaviors.

So let's see the first behavior I defined. I named it FollowMouseBehavior. Here is the C# code.




using System;
using System.Collections.Generic;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Shapes;
using System.Windows.Interactivity;
using System.ComponentModel;
using Microsoft.Expression.Interactivity.Core;


namespace WpfBehaviorsSample
{
public class FollowMouseBehavior : Behavior<DependencyObject>
{
// add some private fields
private Panel parentPanel = null;
private Window parentWindow = null;

// add some properties for the behavior
public bool LockXAxis { get; set; }
public bool LockYAxis { get; set; }
public bool IsLimitedToParentPanel { get; set; }
public bool IsEnabled { get; set; }

public FollowMouseBehavior()
{
this.IsEnabled = true;
IsLimitedToParentPanel = true;
}

protected override void OnAttached()
{
base.OnAttached();

if (!DesignerProperties.GetIsInDesignMode(this) && this.AssociatedObject is UIElement)
{
// find the window and the panel in wich the current object is placed
parentWindow = Common.GetAncestorOfType(this.AssociatedObject, typeof(Window)) as Window;
parentPanel = Common.GetAncestorOfType(this.AssociatedObject, typeof(Panel)) as Panel;
if (parentPanel == null || parentWindow == null) return;

// add a handler for the mouse move event
parentWindow.PreviewMouseMove += new MouseEventHandler(parentPanel_PreviewMouseMove);
}
}

protected override void OnDetaching()
{
base.OnDetaching();

// Cleanup
if (!DesignerProperties.GetIsInDesignMode(this) && this.AssociatedObject is UIElement && parentPanel != null)
{
parentPanel.PreviewMouseMove -= parentPanel_PreviewMouseMove;
}
}



void parentPanel_PreviewMouseMove(object sender, MouseEventArgs e)
{
if (!IsEnabled) return;

// When the mouse is moved, make the attatched element to follow it

Point position = e.GetPosition(parentPanel);

if (parentPanel is Canvas)
{
double posX = LockXAxis ? Canvas.GetLeft(this.AssociatedObject as UIElement) : position.X;
double posY = LockYAxis ? Canvas.GetTop(this.AssociatedObject as UIElement) : position.Y;
if (IsLimitedToParentPanel && this.AssociatedObject is FrameworkElement)
{
double pW = parentPanel.ActualWidth;
double pH = parentPanel.ActualHeight;
double cW = (this.AssociatedObject as FrameworkElement).ActualWidth;
double cH = (this.AssociatedObject as FrameworkElement).ActualHeight;

if (posX > pW - cW) posX = pW - cW;
if (posY > pH - cH) posY = pH - cH;
}

Canvas.SetLeft(this.AssociatedObject as UIElement, posX);
Canvas.SetTop(this.AssociatedObject as UIElement, posY);
}
}

}
}


The code is pretty simple, so I don't think I need to explain it in depth. First of all we find in what window the element is placed, and we add an event handler to this window's mouse move event. Then on mouse move, we just get the mouse coordinates, and change the objects coordinates to follow, according to the settings we defined (for example lock X or Y axis. that causes the element to move only horizontally or vertically, of course it can be moved both horizontally and vertically).

OK, let's look at the second behavior. This will cause an element (in our case, the pong ball) to move around the screen and when it hits another object, to change it's velocity. So this is the MovingBouncingBehavior.


using System;
using System.Collections.Generic;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Shapes;
using System.Windows.Interactivity;
using System.ComponentModel;
using System.Windows.Threading;
using Microsoft.Expression.Interactivity.Core;

namespace WpfBehaviorsSample
{
public class MovingBouncingBehavior : Behavior<DependencyObject>
{
private static Random random = new Random();
private Panel parentPanel = null;
private Window parentWindow = null;
private DispatcherTimer timerAnimation = null;
private Vector velocity = new Vector(0, 0);

public double MinVelocityX { get; set; }
public double MinVelocityY { get; set; }
public double MaxVelocityX { get; set; }
public double MaxVelocityY { get; set; }
public event EventHandler LeftParentBounds;

public bool IsEnabled { get; set; }

private TimeSpan animationInterval = TimeSpan.FromMilliseconds(10);
public TimeSpan AnimationInterval
{
get { return animationInterval; }
set { animationInterval = value; CreateTimer(); }
}

public MovingBouncingBehavior()
{
AnimationInterval = TimeSpan.FromMilliseconds(10);
MinVelocityX = 1.0;
MinVelocityY = 1.0;
MaxVelocityX = 10.0;
MaxVelocityY = 10.0;
}

protected override void OnAttached()
{
base.OnAttached();
if (!DesignerProperties.GetIsInDesignMode(this) && this.AssociatedObject is UIElement)
{
parentWindow = Common.GetAncestorOfType(this.AssociatedObject, typeof(Window)) as Window;
parentPanel = Common.GetAncestorOfType(this.AssociatedObject, typeof(Panel)) as Panel;
if (parentPanel == null || parentWindow == null) return;

velocity = new Vector(GetRandomVelocity(true), GetRandomVelocity(false));
if (timerAnimation != null) timerAnimation.Start();
}
}

protected override void OnDetaching()
{
base.OnDetaching();
if (!DesignerProperties.GetIsInDesignMode(this) && this.AssociatedObject is UIElement && parentPanel != null)
{
if (timerAnimation != null) timerAnimation.Stop();
}
}

private double GetRandomVelocity(bool x)
{
if (x)
{
return MinVelocityX + random.NextDouble() * (MaxVelocityX - MinVelocityX);
}
else
{
return MinVelocityY + random.NextDouble() * (MaxVelocityY - MinVelocityY);
}
}

private void CreateTimer()
{
if (timerAnimation == null)
{
timerAnimation = new DispatcherTimer();
this.timerAnimation.Tick += new EventHandler(timerAnimation_Tick);
}
timerAnimation.Interval = AnimationInterval;
}

void timerAnimation_Tick(object sender, EventArgs e)
{
if (!IsEnabled) return;
if (parentPanel is Canvas && this.AssociatedObject is FrameworkElement)
{
FrameworkElement ui = this.AssociatedObject as FrameworkElement;
Point pointNow = new Point(Canvas.GetLeft(ui), Canvas.GetTop(ui));
Point pointMoved = pointNow + velocity;

// check for collisions

bool collisionFound = false;

int cycleSafetyCounter = 50;
do
{
cycleSafetyCounter--;
pointMoved = pointNow + velocity;
Rect rc = new Rect(pointMoved, new Size(ui.ActualWidth, ui.ActualHeight));
collisionFound = false;

// scan all children of the canvas
foreach (UIElement child in parentPanel.Children)
{
if (child is FrameworkElement && child != ui)
{
Rect rcChild = new Rect(Canvas.GetLeft(child), Canvas.GetTop(child),
(child as FrameworkElement).ActualWidth, (child as FrameworkElement).ActualHeight);
if (rcChild.IntersectsWith(rc))
{
rcChild.Intersect(rc);
bool shouldReverseX = random.Next(0, 2) % 2 == 0;
bool shouldReverseY = random.Next(0, 2) % 2 == 0;
velocity = new Vector(
GetRandomVelocity(true) * (shouldReverseX ? -1 : 1),
GetRandomVelocity(false) * (shouldReverseY ? -1 : 1));
collisionFound = true;
break;
}
}
}
}
while (collisionFound && cycleSafetyCounter > 0);

Canvas.SetLeft(ui, pointMoved.X);
Canvas.SetTop(ui, pointMoved.Y);

if (pointMoved.X < 0 || pointMoved.Y < 0 || pointMoved.X > parentPanel.ActualWidth
|| pointMoved.Y > parentPanel.ActualHeight)
{
if (LeftParentBounds != null)
{
LeftParentBounds(this, EventArgs.Empty);
}
}
}
}
}
}

This behavior also has some settings. The ting it does, it creates a timer and then on each tick it moves the element to which it's attached, and checks for collisions. When a collision is found, the velocity is changed. Again, let's not go through the logic, the post is about behaviors, not about making a pong game.

Attaching a behavior to an element
As I said before, attaching behaviors is pretty simple. Now you'll see how it's done.



See? Easy. Just set which behavior to use and change some settings (or, leave the dafault values, of course). Now our element (rectangle in this case) has a behavior. When we run the application and move the mouse, we see that this rectangle moves with it. You can add this behavior to any number of objects, and they'll all behave alike. All that's left to do is attatch the MovingBouncingBehavior to the ball element, edit some brushes (because, the application has to look nice, of course), and run.
Few lines of code, and you have a working game. Pretty powerful things, behaviors.
Thanks for reading. If you want the full demo application, check out the link below.

Download Sample Application

You can download the sample from this link.