【翻译】Pro.Silverlight.5.in.CSharp.4th.Edition - 第四章 依赖属性和路由事件 02

目录:点击这里

上一篇:【翻译】Pro.Silverlight.5.in.CSharp.4th.Edition - 第四章 依赖属性和路由事件 01

鼠标处理

MouseLeftButtonDown事件是Silverlight应用程序中使用非常频繁的事件。不过,除它以外,鼠标事件方面的处理仍然还有很多需要我们学习的内容。本章接下来的内容中,我们会学到如何对鼠标右键点击、鼠标移动和鼠标滚轮的滚动做出反应。我们还会学到如何捕获鼠标(这样我们就能在鼠标移出去后仍然可以继续处理鼠标事件),还会学到如何模拟拖拽以及改变鼠标的光标。

右键点击

默认情况下,在Silverlight应用程序的任何位置点击右键都会弹出一个Silverlight菜单。这个菜单包含一个名为“Silverlight”的命令,点击此命令会弹出一个卡片式的窗口,这个窗口用于修改Silverlight的配置。另外,如果应用程序被配置过支持浏览器外运行(详见第21章),那么这个弹出的Silverlight菜单会多出一条安装应用程序的命令。

      可能我们会需要在某些元素(甚至整个窗口)上处理右键点击事件以便能提供一些特定的功能。举个栗子,当用户右键点击一个特定的元素的时候,可能我们是希望显示出这个元素多对应的各种命令的自定义菜单。尽管Silverlight并不自带菜单控件,不过Silverlight Toolkit中提供了(http://silverlight.codeplex.com),我们可以在任何控件的右键处理的时候使用这个菜单控件。

      不过,还是有个问题:即使显示了自定义的菜单,但是MouseRightButtonDown事件还是会向上冒泡到应用程序的顶层,这还是会导致Silverlight显示它的标准系统菜单。如果想只显示我们自定义的菜单,将系统菜单隐藏起来,我们必须将MouseRightButton事件设置为已处理状态,换句话说,也就是将MouseButtonEventArgs.Handled属性设置为true,这样就可以抑制右键点击事件以及系统菜单的弹出。

双击和三击

Silverlight在某些方面遵循了web用户界面的便利性。比如说,Silverlight应用程序注重了鼠标点击但是忽视了双击,这是由Windows所倡导的鼠标快速点击两下的技术。而且实际上,在Silverlight5之前的版本中都不直接支持双击效果。

      如果我们想要实现双击,最好是对那些次要的任务使用这个效果。比如,双击能给有经验的用户提供便利,而对那些经验不足的用户仍然可以使用它们习惯的单击菜单或者控件来达到相同的效果。

      包括快速的连续点击数次,元素的每次点击都会触发Click事件。如果要监测到双击,我们需要检查MouseButtonEventArgs对象的ClickCount这个属性。如果ClickCount的值是1,这表示是一个普通的单击。如果ClickCount的值是2,则表示本次点击是一个双击动作的第二次点击。(通常来说,这意味是在第一次点击后的500毫秒以内执行了第二次点击,当然,关于这个时间间隔的具体指主要还是看操作系统中鼠标的相关设置。)

      下面是一个示例:

 

private void SomethingClicked(object sender, MouseButtonEventArgs e)
{
  if (e.ClickCount == 1)
  {
    // 监测到一次点击.
    // 当然这可能是一次双击动作中的第一次点击.
    lblClickCount.Text = "Clicks: 1";
  }
  else (e.ClickCount == 2)
  {
    //双击动作中的第二次点击.
    lblClickCount.Text = "Clicks: 2";
  }
}

      三击,这个概念虽然不常见,但的确是个潜在的快捷操作(枪王之王里头古天乐 = =)。文字处理软件和网页浏览器经常让用户通过三击这样的操作来完成大段文本块的选取。(比如双击可能是选中一个词语,而三击则将整段文字都选中,这在word中一试便知)。很明显三击的意思就是鼠标快速的连续点击三下。和双击一样,Silverlight中通过ClickCount这个属性来捕获双击效果。

      在捕获不同类型的点击经常会出现一些让我们头疼的现象(比如同时捕获单击和双击,或者同时捕获双击和三击)。最终完成的是一个动作还是会有额外的多余动作,这主要取决于代码是如何写的。比如当用户完成一次三击操作的时候,我们的代码中可能是在检测到ClickCount为2的时候做了一个操作,并且检测到ClickCount为3的时候又做了另一个操作,尽管这都属于同一个三击事件的一部分。

      为了避免这个问题,必须确保重叠的动作之间不存在冲突。就以文本的选择威力,如果是设计合理的点击行为,应该要考虑到在用户通过三击选择文本的时候具体发生了什么。这种情况下,第二次点击的时候会触发双击效果(这时候的效果是选中了一个词),那么下一次点击发生的时候触发了三击效果(这时候选取的内容扩大到了整个段落)。这种方式的运行效果是合情合理的,因为三击行为相当于是对双击行为的增强。但是如果双击行为是做一些其他完全不相干的事情(比如启动另一个程序、删除当前被点击的内容等等),那么紧接下来发生的三击行为就会变得混乱。

鼠标移动

除了MouseLeftButtonDown、MouseLeftButtonUp、MouseRightButtonDown、MouseRightButtonUp这四个明显的鼠标点击事件以外,Silverlight还提供了鼠标移动的时候可以触发的事件。这些事件是MouseEnter(鼠标移动到元素上的时候触发)、MouseLeave(鼠标移出元素的时候触发)、MouseMove(鼠标在元素上移动的时候触发)。

      这些事件给代码提供了同一个信息:一个MouseEventArgs对象。这个对象包含一个重要的内容:可以指明鼠标相对于选择的元素的坐标信息的GetPosition()方法。下面这个示例代码展示了鼠标指针当前的位置:

private void MouseMoved(object sender, MouseEventArgs e)
{
  Point pt = e.GetPosition(this);
  lblInfo.Text =
   String.Format("You are at ({0},{1}) in page coordinates", pt.X, pt.Y);
}

 

      这种情况下,坐标值是参照整个页面区域(浏览器标题栏的下方所有区域)的左上角来定的。


提示:要在布局容器中接受到鼠标移动事件,必须保证容器的Background属性是一个非空的值——比如填充为纯白色。


鼠标滚轮

如今基本所有的电脑的鼠标都有了滚轮。利用滚轮,我们可以完成一些相关的响应动作。唯一的原则是要保证对鼠标滚轮的支持是一个有用的额外功能,而不是应用程序必须的要素。因为毕竟还是有不少用户用不了滚轮(比如没插鼠标的笔记本电脑或者平板)或者直接不想用滚轮。

      通过MouseWheelEventArgs.Delta属性,鼠标滚轮事件可以获取到上次滚轮事件发生的时候滚轮所滚动的值信息。通常,鼠标滚轮的每一个刻度的值都是120,因此鼠标滚轮的一个单次滚动调整给程序传入的Delta值就是120。正数表示表示鼠标滚轮朝远离用户的方向滚动,负数表示鼠标滚轮超考虑用户的方向滚动。用大俗话说就是正数表示往上滚动,负数表示往下滚动。

      为了更好地认识这其中的远离,我们看看图4-5所示的示例界面。这个示例中,用户可以通过操作鼠标滚轮来缩放图中的Grid信息。

图4-5 使用鼠标的滚轮实现缩放

      这个示例用到了我们在第3章中学过的两个控件——ScrollViewer和Viewbox,Viewbox的作用是实现内容的缩放,ScrollViewer的作用则是当页面内容浏览器窗口尺寸的时候可以滚动来查看整个界面。

<UserControl x:Class="RoutedEvents.MouseWheelZoom"
  xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
  xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
  MouseWheel="Page_MouseWheel">
  <ScrollViewer VerticalScrollBarVisibility="Auto"
  HorizontalScrollBarVisibility="Auto">
    <Viewbox x:Name="viewbox" Height="250" Width="350">
      <Grid Background="White" Height="250" Width="350">
        ...
      </Grid>
    </Viewbox>
  </ScrollViewer>
</UserControl>

 

      请注意:Viewbox的硬编码尺寸和它内部的Grid的尺寸一样。这样就能保证Viewbox不会去执行任何初始缩放动作——这样在应用程序最初启动的时候,Grid的尺寸就是其原始设置的尺寸。

      当用户滚动鼠标滚轮的时候,MouseWheel事件处理程序会检查delta值并且按比例调整Viewbox的Width和Height这两个属性的值,然后ViewBox会相应的扩张或者收缩,与此同时Viewbox内部的所有元素会跟着进行伸缩调整:

private void Page_MouseWheel(object sender, MouseWheelEventArgs e)
{
  // Delta 是120单位值, 因此除以110后得到比例因子为1.09
  // 换句话说, 鼠标滚轮滚动一个刻度,Viewbox控件会扩大或者缩小9%
  double scalingFactor = (double)e.Delta / 110;
  // 检查滚动滚动的方式.
  if (scalingFactor > 0)
  {
    // 扩大 viewbox.
    viewbox.Width *= scalingFactor;
    viewbox.Height *= scalingFactor;
  }
  else
  {
    // 缩小 viewbox.
    viewbox.Width /= -scalingFactor;
    viewbox.Height /= -scalingFactor;
  }
}

 

      有些控件已经自身就包含了MouseWheel事件的处理,因此它们已经内嵌了对鼠标滚轮的支持(虽然Viewbox不属于其中)。比如TextBox、ComboBox、ListBox、DataGrid和ScrollViewer这些控件在用户滚动鼠标滚轮的时候就会相应的滚动动作发生。另外日历控件Calender也支持滚动,效果是月份的切换。

捕获鼠标

一般来说,元素每次收到鼠标按键按下的事件后,紧随其后会再收到对应的鼠标按键弹起的事件。不过,这并不是总如此。比如说,我们点中一个元素后保持鼠标按键按住不放,然后将鼠标指针移出这个元素,这种情况下,元素就不会接收到鼠标按键弹起的事件。

      在某些情况下,我们可能需要监测到鼠标弹起事件,即使这个鼠标弹起事件是在鼠标移出元素之外后发生的。为了实现这个效果,我们需要通过调用相应元素的MouseCapture()方法来将鼠标捕获住。(MouseCapture()是定义在基类UIElement中的,因此所有的Silverlight元素都可以使用这个方法)。在调用这个方法之后,元素就可以继续接收到MouseLeftButtonDown和MouseLeftButtonUp事件,直到元素失去了对鼠标的捕获。让元素失去鼠标捕获有两个方式:首先,可以通过再次调用MouseCapture()方法并且传入null参数,这样就可以让控件主动放弃对鼠标的捕获;第二个方式是用户点击应用程序之外的部分——比如另一个程序、或者浏览器的菜单、或者当前web页面中其它的HTML内容都可以。在元素失去了鼠标的捕获的时候会激活LostMouseCapture事件。

      一旦某个元素捕获了鼠标,这时候其他元素将接收不到鼠标事件了。这意味着用户将不能点击页面其它地方的按钮、或者点击文本输入框进行编辑,等等。鼠标捕获这个技术有时候用于创建支持拖拽和尺寸可变的元素。

一个鼠标事件的示例

我们通过一个示例来将前面所讲过的鼠标事件相关的概念糅合到一起(并且还了解一些关于控件动态生成方面的内容)。

       图4-6展示的这个Silverlight应用程序可以让我们在Canvas上画小圆圈并且能移动。每次点击Canvas就会出现一个红圆圈;点住并拖拽就可以移动这个小圆圈;如果是点住圆圈,那么圆圈的颜色就会从红色变为绿色,鼠标释放的时候,圆圈的颜色就会变成橘色。可以增加的圆圈数量以及拖拽的次数都没有限制。

图4-6 拖拽形状


备注:这个示例演示的是一个由我们的自定义的代码实现的“模拟”拖拽效果。作为对比,一个“真实”的拖拽特性依赖于操作系统所提供的功能。Silverlight确实包含真实的拖拽特性,不过其适用范围比较有限——从电脑上拖拽文件到Silverlight窗口中。第18章会详述基于文件的拖拽特性。


 

      每个圆圈都是Ellipse对象的一个实例,Ellipse是2D绘图的一个基础组成部分——一个着色的形状。很明显,我们不可能在XAML标记中定义我们所需要的全部圆形;相反,我们需要一种方式能够在每次用户点击Canvas画布的时候能自动生成Ellipse对象。

      创建一个Ellipse对象本身并不是特别困难的事情——毕竟,我们可以像其它.NET对象那样将它实例化出来即可:设置它的各个属性、注册需要的事件处理程序。我们甚至还可以使用SetValue()方法来设置相关的附加属性以使得圆圈在Canvas处于一个正确的位置。不过,还有另外一个细节要考虑的——我们需要找到一种方式能将Ellipse放置在Canvas中。当然这容易,因为Canvas类提供了一个Children集合用于存放所有的子元素。一旦我们将元素添加到这个集合中,这个圆圈就会出现在Canvas里面。

      本例的XAML页面中注册了Canvas.MouseLeftButtonDown事件。另外还设置了Canvas.Background属性,因为默认情况下Canvas的背景是透明的,而如果背景透明的话,是无法捕获鼠标事件的。除此之外,再没有定义其它元素了。

<Canvas x:Name="parentCanvas" MouseLeftButtonDown="canvas_Click" Background="White">
</Canvas>

      在对应的code-behind类中,我们需要两个成员变量来记录当前是否正在发生一个拖动圆圈的操作。

// 当圆形拖动的时候坐记录
private bool isDragging = false;
// 当圆形被点击的时候,记录下点击发生的时候准确的坐标位置
private Point mouseOffset;

 

      下面是点击Canvas创建圆形的对应代码:

private void canvas_Click(object sender, MouseButtonEventArgs e)
{
  // 如果用户并不是在拖动圆形,那么就创建一个新的圆形
  if (!isDragging)
  {
    // 将圆形的直径设置为50像素,并且填充为红色
    Ellipse ellipse = new Ellipse();
    ellipse.Fill = new SolidColorBrush(Colors.Red);
    ellipse.Width = 50;
    ellipse.Height = 50;
    // 将鼠标的位置设置为新画的圆形的远点
    Point point = e.GetPosition(this);
    ellipse.SetValue(Canvas.TopProperty, point.Y - ellipse.Height/2);
    ellipse.SetValue(Canvas.LeftProperty, point.X - ellipse.Width/2);
    // 监测鼠标左键点击事件
    ellipse.MouseLeftButtonDown += ellipse_MouseDown;
    // 将ellipse增加到Canvas.中
    parentCanvas.Children.Add(ellipse);
  }
}

 

      这段代码除了创建圆形之外,还包括点击圆形的时候所响应的事件处理程序。这个事件处理程序改变了圆形的颜色并且启动了圆形的拖拽操作。

private void ellipse_MouseDown(object sender, MouseButtonEventArgs e)
{
  // 拖拽模式.
  isDragging = true;
  Ellipse ellipse = (Ellipse)sender;
  //获取鼠标相对于圆形的坐标位置
  // 圆形左上角的坐标是 (0,0).
  mouseOffset = e.GetPosition(ellipse);
  // 改变圆形的颜色.
  ellipse.Fill = new SolidColorBrush(Colors.Green);
  // 一些相关事件的注册.
  ellipse.MouseMove += ellipse_MouseMove;
  ellipse.MouseLeftButtonUp += ellipse_MouseUp;
  //获取鼠标,使用这个方式,
  //就算用户猛地将鼠标拖出圆形之外也可以收到MouseMove事件
  ellipse.CaptureMouse();
}

 

      直到MouseMove事件发生,圆形才会真正移动。这种情况下,圆形的Canvas.Left和Canvas.Top这两个附加属性的值就会相应地改变,然后圆形就开始移动到新的位置上。圆形的坐标位置基于用户一开始点击鼠标的时候的光标位置,然后圆形会随着鼠标的移动而同步移动,直到用户放开鼠标左键。

private void ellipse_MouseMove(object sender, MouseEventArgs e)
{
  if (isDragging)
  {
    Ellipse ellipse = (Ellipse)sender;
    // 获取圆形相对于Canvas的位置
    Point point = e.GetPosition(parentCanvas);
    // 移动圆形
    ellipse.SetValue(Canvas.TopProperty, point.Y - mouseOffset.Y);
    ellipse.SetValue(Canvas.LeftProperty, point.X - mouseOffset.X);
  }
}

 

      当鼠标左键释放的时候,有这些工作要进行处理修改圆形的颜色、释放鼠标的捕获以及停止对MouseMove和MouseUp这两个事件的监听。用户可以再次点住圆形然后将整个过程再跑一遍。

private void ellipse_MouseUp(object sender, MouseButtonEventArgs e)
{
  if (isDragging)
  {
    Ellipse ellipse = (Ellipse)sender;
    // 改变圆形的颜色.
    ellipse.Fill = new SolidColorBrush(Colors.Orange);
    // 注销对鼠标事件的监听.
    ellipse.MouseMove -= ellipse_MouseMove;
    ellipse.MouseLeftButtonUp -= ellipse_MouseUp;
    ellipse.ReleaseMouseCapture();
    isDragging = false;
  }
}

 

鼠标的光标

在应用程序中我们经常会碰到需要根据程序处于繁忙状态或者要展示不同的工作状态的情况来显示不同的鼠标光标。使用FrameworkElement类下的Cursor属性我们可以给任意元素设置鼠标指针。

      每一种光标都代表一种System.Windows.Input.Cursor对象。获取Cursor对象最简单的方法是使用System.Windows.Input命名空间下的Cursors这个静态属性,其中 包括了标准的Windows鼠标,比如沙漏、手形、尺寸调整的箭头等等。下面的代码示例就是给当前页面设置鼠标为沙漏状态:

this.Cursor = Cursors.Wait;

 

      经过上面的设置后,当我们将鼠标移动到当前页面中的时候,鼠标的指针就会变成我们熟悉的沙漏图标(Windows xp中)或者漩涡图标(Windows 7,原书写的是Vista,还是改成7吧)。


备注:Cursors的各个属性对应的鼠标的图标取决于当前系统中的鼠标相关设置。如果用户已经自定义了系统的标准鼠标,那么应用程序所展现的鼠标样式也相应地选择这些自定义的鼠标设置。


      如果是在XAML中使用设置鼠标,我们不必直接使用Cursors类。那是因为针对Cursor属性的类型转换器可以识别属性名称并且检索到Cursors类中相应的Cursor对象。这就意味着我们可以写出类似如下的XAML标记来实现当用户将鼠标移动到这个按钮上的时候展示一个“帮助”光标(一个箭头和一个问号的组合光标):

<Button Cursor="Help" Content="Help Me"></Button>

 

      光标的设置是可以覆盖的。这种情况下就存在一个设置的优先级问题了。比如说,我们给一个包含了多个按钮的页面设置了一个鼠标光标A,给其中一个按钮设置了光标B,那么当鼠标移动到这个特定的按钮上的时候,光标会变成B;而在页面的其它区域所显示出来的光标则是A。


提示:与WPF不同,Silverlight不支持自定义鼠标光标。不过,我们可以将鼠标光标隐藏起来(将Cursor属性设置为Cursors.None),然后在鼠标的指针附近设置一个小图标即可。(相应的技术实现的代码在前面部分已经展示过)


 

转载于:https://www.cnblogs.com/xtechnet/archive/2012/08/31/Silverlight5_Chapter04_Part02.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值