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:
- 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
- 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:
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
{
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)
{
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:
- LeftOffset
- TopOffset
- BottomOffset
- 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