Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Create a movable usercontrol in WPF part II

0.00/5 (No votes)
10 Oct 2008 1  
introduces how to create a moving control in WPF

Introduction

This is a second part of creating a movable usercontrol in WPF. I assume you have read the part I (even though that was poorly written).  I have made a lot of improvement to the file in that article. In this part, you can download the latest base MovabaleControl  class and two controls that inherit it.

Background 

In HCI (human-computer interaction), we use Fitts' Law to measure the performance of selecting a target. It is based on D (the distance between the cursor and the selecting target), and W (the width of the target), and it calculates the average time taken to complete the selection.

Now, when study fall to a moving target selection, things are becoming more interesting.

The goal of this series is to build up a WPF application to perform a test on moving target selection.

We already have built this base class, now we can create some different moving targets, so we can find out if there is any technique we can use to enhance the moving target selection performance.

Normal Target

The first thing is, we need a really simple and base moving target, without any technique (aid) that could facilitate the target selection. Well, this is easy.

Suppose our base class has a namespace WPFTest.Targets and is called MovableControl.

In this NormalTarget xaml, we can define

<local:MovableUserControl x:Class="WPFTest.Targets.NormalTarget"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Height="40" Width="100" xmlns:local="clr-namespace:WPFTest.Targets">
    <Grid>
        <Rectangle Width="100" Height="40" Stroke="Black" Focusable="True" Name="TextRect" Fill="LightGray"></Rectangle>
        <TextBlock Text="Normal Target" Margin="0" HorizontalAlignment="Center" VerticalAlignment="Center" Name="TargetText"></TextBlock>
    </Grid>
</local:MovableUserControl>

The xmlns:local is a self-defined tag and it's pointing to the namespace we are after. Then we can reference our own UserControl.

Now in the code behind, we only need very simple code:

    public partial class NormalTarget : MovableUserControl
    {
        public NormalTarget()
        {
            InitializeComponent();
        }

        public override string Text
        {
            get { return TargetText.Text; }
            set { TargetText.Text = value; TargetText.HorizontalAlignment = HorizontalAlignment.Center; }
        }

        protected override void OnMouseLeftButtonUp(MouseButtonEventArgs e)
        {
		base.OnMouseLeftButtonUp(e);
		MouseClicked = true;
        }

        public override void Start()
        {
            if (this.IsTarget)
            {
                this.TextRect.Fill = Brushes.Yellow;
            }
            base.Start();
        }
    }

 In the Base class, I have two properties:

  1. MouseClicked - indicates this target is clicked, because we want the inherited class to tell us this is a valid mouse click, in case we have some special technique handle the click differently
  2. IsTaget - indicates this is a target to be selected. It's used in a test case that there are multiple moving targets. 

You can see from the code, I changed the control background color if it's a target. 

ExpandedClick Technique

This is a simple technique that I created might potentially enhance the selection performance. If you ever used MacOS or had any experience in HCI, you must have known there is something called "Expanded Target". It's just like the MacOS task bar, when your mouse is approaching the target, the target itself will grow bigger so that the user can easily select it.

The difference between this "ExpandedClick" and "Expanded Target" is, "ExpandedClick" control doesn't grow the size, but expand the clickable area. It looks as follows:

ExpandedClick.jpg 

The yellow part is the control itself, the blue is the background, but the white part between is actually clickable. So when the cursor is approaching this target, it will expand the clickable area so that the user can easily select it.

Let's look at the XAML code first:

<local:MovableUserControl x:Class="WPFTest.Targets.ExpandedClick"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Height="80" Width="160" xmlns:local="clr-namespace:WPFTest.Targets">
    <Grid>
        <Rectangle Width="160" Height="80" Fill="Transparent" Name="BackRect"></Rectangle>
        <Rectangle Width="100" Height="40" Stroke="Black" Focusable="True" Name="TextRect" Fill="LightGray"></Rectangle>
        <TextBlock Text="Expanded Target" Margin="38,32,40,32" Name="TargetText"></TextBlock>
    </Grid>
</local:MovableUserControl> 

It's very similar to the Normal target; however, it has one more Rectangle that is the clickable area. By default, it's transparent; when the mouse is hover, it will change the color.

The code behind will be like:

	public partial class ExpandedClick : MovableUserControl
	{
		// these two values define the allowable offset of this control, because the control itself actually is larger than 
		// what the user can see due to the clickable area is expanded 
		private int horizontalOffset = 0;
		private int verticalOffset = 0;

		public ExpandedClick()
		{
			InitializeComponent();
		}

		private Brush m_ExpandedBackground = Brushes.Azure;
		public Brush BackgroundBrush
		{
			get { return m_ExpandedBackground; }
			set { m_ExpandedBackground = value; }
		}

		public override string Text
		{
			get { return TargetText.Text; }
			set { TargetText.Text = value; TargetText.TextAlignment = TextAlignment.Center; }
		}

		protected override void OnMouseEnter(MouseEventArgs e)
		{
			BackRect.Fill = BackgroundBrush;
			base.OnMouseEnter(e);
		}

		protected override void OnMouseLeave(MouseEventArgs e)
		{
			BackRect.Fill = Brushes.Transparent;
			base.OnMouseLeave(e);
		}

		protected override void OnMouseDown(MouseButtonEventArgs e)
		{
			BackRect.Fill = Brushes.Transparent;
			base.OnMouseDown(e);
		}

		protected override void OnMouseLeftButtonUp(MouseButtonEventArgs e)
		{
			base.OnMouseLeftButtonUp(e);
			BackRect.Fill = BackgroundBrush;
			this.TextRect.Fill = Brushes.LightGray;
			MouseClicked = true;
		}

		public override void Start()
		{
			if (this.IsTarget)
			{
				TextRect.Fill = Brushes.Yellow;
			}

			base.Start();
		}

		private void precal()
		{
			if (horizontalOffset == 0 && verticalOffset == 0)
			{
				// do the pre-calculation in here
				horizontalOffset = (int)(this.Width - TextRect.Width) / 2;
				verticalOffset = (int)(this.Height - TextRect.Height) / 2;
			}
		}

		public override int LeftOffset
		{
			get
			{
				precal();
				return -horizontalOffset;
			}
		}

		public override int TopOffset
		{
			get
			{
				precal();
				return -verticalOffset;
			}
		}

		public override int RightOffset
		{
			get
			{
				precal();
				return horizontalOffset;
			}
		}

		public override int BottomOffset
		{
			get
			{
				precal();
				return verticalOffset;
			}
		}
	}

It's a little more compared to the normal target. First, in the base control, I defined four properties:

  1. LeftOffset
  2. TopOffset
  3. BottomOffset
  4. RightOffset

They are used to detect if the control hits the boundary. If yes, we need to re-calculate where the control is heading in the next interval. The detection code is like:

 	if (currentX + this.ActualWidth >= parentWidth + RightOffset ||
		currentY + this.ActualHeight >= parentHeight + BottomOffset ||
		currentX <= LeftOffset ||
		currentY <= TopOffset)
	{
		restart = true;
	}

In this control, we don't want the user see such a large "block" on the screen, because the actual visual target part doesn't include the clickable area. Hence, we want to tell the parent, here is a small offset you can go, don't bounce yet.

Points of Interest     

Next time I will show you another technique I created. Then I will introduce how to create a platform to run the experiment.

Oh, right, the source code is here Download MovableTargets.zip - 5.21 KB

History  

10/10/2008 - first edition

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here