65.9K
CodeProject 正在变化。 阅读更多。
Home

创建可移动的用户控件(WPF 第二部分)

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.13/5 (5投票s)

2008年10月10日

CPOL

3分钟阅读

viewsIcon

37385

downloadIcon

588

介绍如何在 WPF 中创建移动控件


引言

这是在WPF中创建可移动用户控件的第二部分。我假设您已经阅读了第一部分(即使写得很差)。  我对该文章中的文件做了一些改进。 在本部分中,您可以下载最新的基类MovabaleControl  和两个继承它的控件。

背景 

在人机交互(HCI)中,我们使用费茨定律来衡量选择目标的性能。 它基于 D(光标与选择目标之间的距离)和 W(目标的宽度),并计算完成选择所花费的平均时间。

现在,当研究陷入移动目标选择时,事情变得越来越有趣。

本系列的目标是构建一个WPF应用程序,以对移动目标选择进行测试。

我们已经构建了这个基类,现在我们可以创建一些不同的移动目标,这样我们就可以找出是否有任何技术可以用来增强移动目标选择的性能。

普通目标

首先,我们需要一个非常简单和基本的移动目标,没有任何可能促进目标选择的技术(辅助)。 好吧,这很简单。

假设我们的基类有一个命名空间 WPFTest.Targets,并被称为 MovableControl。

在这个 NormalTarget xaml 中,我们可以定义

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

xmlns:local 是一个自定义的标签,它指向我们所需要的命名空间。然后我们可以引用我们自己的 UserControl。

现在在后台代码中,我们只需要非常简单的代码

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

 在基类中,我有两个属性

  1. MouseClicked - 指示单击了此目标,因为我们希望继承类告诉我们这是一个有效的鼠标单击,以防我们有一些特殊的技术来处理不同的单击
  2. IsTaget - 指示这是一个要选择的目标。它用于存在多个移动目标的测试用例中。 

您可以从代码中看到,如果它是一个目标,我更改了控件的背景颜色。 

ExpandedClick 技术

这是一项我创建的简单技术,可能会增强选择性能。如果您使用过 MacOS 或有任何HCI经验,您一定知道有一个叫做“扩展目标”的东西。 它就像 MacOS 任务栏,当您的鼠标靠近目标时,目标本身会变大,以便用户可以轻松选择它。

这个 "ExpandedClick" 和 "扩展目标" 之间的区别在于,"ExpandedClick" 控件不会增大尺寸,而是扩展可点击区域。 它看起来如下所示

ExpandedClick.jpg 

黄色部分是控件本身,蓝色是背景,但中间的白色部分实际上是可点击的。 因此,当光标接近此目标时,它将扩展可点击区域,以便用户可以轻松选择它。

让我们先看看 XAML 代码

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

它与普通目标非常相似;但是,它还有一个 Rectangle,即可点击区域。 默认情况下,它是透明的;当鼠标悬停时,它会更改颜色。

后台代码将是这样的

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

与普通目标相比,它稍微多一点。 首先,在基类控件中,我定义了四个属性

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

它们用于检测控件是否碰到边界。 如果是,我们需要重新计算控件在下一个时间间隔内的方向。 检测代码是这样的

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

在这个控件中,我们不希望用户在屏幕上看到一个这么大的“块”,因为实际的视觉目标部分不包括可点击区域。 因此,我们想告诉父级,这里有一个小偏移量你可以去,先不要反弹。

值得关注的点

下次我将向您展示我创建的另一项技术。 然后我将介绍如何创建一个运行实验的平台。

哦,对了,源代码在这里 下载 MovableTargets.zip - 5.21 KB

历史  

2008年10月10日 - 第一版

© . All rights reserved.