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

如何在 Silverlight 2 中实现自定义光标

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.78/5 (8投票s)

2008年12月30日

CPOL

4分钟阅读

viewsIcon

54164

downloadIcon

529

本文介绍了如何在 Silverlight 中实现自定义光标。

引言

Silverlight 2 只支持一小部分默认光标类型。WPF (Windows Presentation Foundation) 为开发人员提供了扩展的默认光标类型集,此外,为 WPF 应用程序创建自定义光标也是一项简单的任务。

然而,在 Silverlight 2 中模拟自定义光标却相当简单。有不同的实现方法来显示光标。本文介绍的解决方案基于 Popup 类和一组预先定义好的自定义光标图像,这使得使用起来很方便。让我们先来看一张截图,展示一个绿色矩形上带有自定义光标类型(十字准星)。

SilverlightCustomCursorsScreenshot1.JPG

自定义光标实现与一个 UI 元素堆栈协同工作,所有元素都使用不同的光标,并且在滚动查看器内。上面显示的页面的 XAML 代码如下:

 ...
<Grid Background="White">
  <ScrollViewer Width="400" Height="400" ... >
    <Grid>
      <Rectangle parago:CursorSet.ID="SizeAll" Fill="LightCoral" Width="600" ... />
      <Rectangle parago:CursorSet.ID="Cross" Fill="Red" Width="150" Height="150" ... />
      <Rectangle parago:CursorSet.ID="Hand" Fill="Blue" Width="130" Height="130" ... />
      <Rectangle parago:CursorSet.ID="IBeam" Fill="Yellow" Width="100" Height="100" ... />
      <Rectangle parago:CursorSet.ID="Cross" Fill="Green" Width="50" Height="50" ... />
    </Grid>
  </ScrollViewer>
</Grid>
...

代码显示,设置自定义光标就像在 Silverlight 2 中设置默认和内置光标一样简单。像 Hand 或 IBeam 这样的标准光标类型也可以定义为 ID,并会在内部转换为设置 Cursor 属性。因此,从开发者的角度来看,设置 UI 元素光标只有一种技术可以使用,无需在 XAML 代码中添加任何额外的元素。

这是如何实现的?有一个名为 CursorSet 的类,它实现了一个名为 ID 的附加属性。在此实现中使用附加属性可以查询 UI 元素的光标设置。CursorSet 类维护一个静态光标 ID 列表、一个名为 ActiveElements 的活动元素列表(带有附加的光标 ID),以及一个用于显示实际光标图像的静态 popup 和 canvas 实例。

public class CursorSet
{
  const string ImagePath = "/Resources/Cursors/";

   internal static Popup Popup;
   internal static Canvas AdornerLayer;
   internal static List<string> IDs;
   internal static Dictionary<FrameworkElement, 
            ContentControl> ActiveElements;

  #region public string ID (attached)
  
  public static string GetID(DependencyObject d)
  {
    return (string)d.GetValue(IDProperty);
  }

  public static void SetID(DependencyObject d, string id)
  {
    d.SetValue(IDProperty, id);
  }

  public static readonly DependencyProperty IDProperty = 
                DependencyProperty.RegisterAttached(
    "ID",
    typeof(string),
    typeof(CursorSet),
    new PropertyMetadata(new PropertyChangedCallback(OnIDPropertyChanged)));

  static void OnIDPropertyChanged(DependencyObject d, 
              DependencyPropertyChangedEventArgs e)
  {
    FrameworkElement element = d as FrameworkElement;
    string id = e.NewValue as string;

    if(IsValidID(id))
    {
      Cursor cursor;

      if(IsSystemType(id, out cursor))
      {
        element.Cursor = cursor;
        RemoveMouseEventHandlers(element);

        if(ActiveElements.ContainsKey(element))
          ActiveElements.Remove(element);
      }
      else
      {
        AddMouseEventHandlers(element);
        ContentControl control = CreateControl(id);

        if(ActiveElements.ContainsKey(element))
          ActiveElements[element] = control;
        else
          ActiveElements.Add(element, control);
      }
    }
    else
    {
      element.Cursor = null;
      RemoveMouseEventHandlers(element);

      if(ActiveElements.ContainsKey(element))
        ActiveElements.Remove(element);
    }
  }

  #endregion


  ...
}

有关 Silverlight 中附加属性的更多信息,请参阅 MSDN 文档,或 SilverlightShow.net 网站上的这篇精彩文章。

附加属性 ID 的更改处理起着重要作用。当一个光标 ID 被新分配给一个 UI 元素(parago:CursorSet.ID="Cross")或者它已更改时,会调用 OnIDPorpertyChanged 方法。该方法首先检查定义的 ID 是否有效,然后检查 ID 是否是默认光标类型的名称。如果 ID 被识别为默认光标,则该方法将设置 UI 元素的 Cursor 属性,并移除所有现有的鼠标事件处理程序注册;否则,将为该元素创建一个 ContentControl 类的新实例,并将所需的自定义光标 ID 作为图像。然后将该实例添加到 ActiveElements 列表中,CursorSet 类会注册该 UI 元素的 MouseEnterMouseMoveMouseLeave 事件。

对于每个自定义 ID,都必须有一个 PNG 图像存在于字符串常量 ImagePath 定义的文件夹中。目前样本项目中只有两个光标可用,但很容易扩展自定义光标的集合。

由于我们需要将光标渲染在所有其他元素之上,并且独立于这些元素的布局,因此使用内置的 Popup 类非常完美。CusorSet popup 本身包含一个 canvas 元素作为子元素,用于通过坐标设置光标图像的位置。Popup 的大小应完全填充 Silverlight 内容区域。因此,CursorSetOnContentResized 方法注册到 Application.Current.Host.Content.Resized 事件,并相应地更新 popup 及其 canvas 的大小。

...
static void OnContentResized(object sender, EventArgs e)
{
  if(AdornerLayer != null)
  {
    AdornerLayer.Width = Application.Current.Host.Content.ActualWidth;
    AdornerLayer.Height = Application.Current.Host.Content.ActualHeight;
  }
}

static void EnsurePopup()
{
  if(Popup == null || AdornerLayer == null)
  {
    AdornerLayer = new Canvas()
    {
      IsHitTestVisible = false,
      Width = Application.Current.Host.Content.ActualWidth,
      Height = Application.Current.Host.Content.ActualHeight
    };

    Popup = new Popup
    {
      IsHitTestVisible = false,
      Child = AdornerLayer
    };
  }
}

...

上面的列表还显示了 EnsurePopup 方法,该方法最初创建 popup 和 canvas 实例。重要的是要注意,必须将 IsHitTestVisible 属性设置为 false,以避免与底层元素发生冲突。

MouseEnterMouseMoveMouseLeave 事件的鼠标事件处理程序定义如下:

...

static void OnElementMouseEnter(object sender, MouseEventArgs e)
{
  EnsurePopup();

  FrameworkElement element = sender as FrameworkElement;
  ContentControl control = ActiveElements[element];

  element.Cursor = Cursors.None;
  AdornerLayer.Children.Add(control);

  Point p = e.GetPosition(null);
  Canvas.SetTop(control, p.Y);
  Canvas.SetLeft(control, p.X);

  Popup.IsOpen = true;
}

static void OnElementMouseMove(object sender, MouseEventArgs e)
{
  FrameworkElement element = sender as FrameworkElement;
  ContentControl control = ActiveElements[element];

  Point p = e.GetPosition(null);
  Canvas.SetTop(control, p.Y);
  Canvas.SetLeft(control, p.X);
}

static void OnElementMouseLeave(object sender, MouseEventArgs e)
{
  FrameworkElement element = sender as FrameworkElement;
  ContentControl control = ActiveElements[element];

  element.Cursor = null;
  AdornerLayer.Children.Remove(control);

  Popup.IsOpen = false;
}

...

MouseEnter 事件处理程序 OnElementMouseEnter 中,首先,代码将通过调用 EnsurePopup 方法(见上面的代码)来确保 popup 实例存在并已设置。由于事件处理程序仅为自定义光标注册,而不是为默认和内置光标注册,因此该方法将从 ActiveElements 列表中获取 ContentControl 实例(在设置 ID 属性时生成),并将其添加到 popup 的名为 AdornerLayer 的 canvas 中。接下来,光标图像的位置将被调整为当前鼠标指针位置,并且 UI 元素的 Cursor 属性将被设置为 None 值,以避免出现多个光标。然后,popup 将被显示。

MouseMove 事件处理程序 OnElementMouseMove 将根据当前鼠标位置更新 popup canvas 中光标图像的位置。一旦鼠标指针离开 UI 元素,带有光标图像的 ContentControl 将从 popup canvas 中移除,并且 popup 本身将关闭。

就是这样。

如何在 Silverlight 2 中实现自定义光标 - CodeProject - 代码之家
© . All rights reserved.