Click here to Skip to main content
12,999,845 members (55,062 online)
Click here to Skip to main content
Add your own
alternative version

Stats

28K views
481 downloads
17 bookmarked
Posted 10 Oct 2008

Create a movable usercontrol in WPF part II

, 10 Oct 2008
Rate this:
Please Sign up or sign in to vote.
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, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

Share

About the Author

Hong Zhang (Sowen)
Software Developer ImageComponent.NET
Canada Canada
I am a full-time employee @ a large corporation, a part-time master student @ University of Manitoba, and a part-time freelance developer @ ImageComponent.NET, and a full-time husband, a part-time house cleaner, law mower, and so on. God, I don't have time for my Wii!

You may also be interested in...

Comments and Discussions

 
GeneralGood Article Pin
rameshKumar171727-Dec-13 5:43
memberrameshKumar171727-Dec-13 5:43 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

Permalink | Advertise | Privacy | Terms of Use | Mobile
Web01 | 2.8.170624.1 | Last Updated 10 Oct 2008
Article Copyright 2008 by Hong Zhang (Sowen)
Everything else Copyright © CodeProject, 1999-2017
Layout: fixed | fluid