详解WPF的InkCanvas选择模式

 更新时间:2021年04月02日 10:09:11   作者:louzi  
这篇文章主要介绍了WPF InkCanvas选择模式的相关资料,帮助大家更好的理解和学习使用c# wpf,感兴趣的朋友可以了解下

InkCanvas是WPF中进行墨迹绘制的控件,本文介绍下InkCanvas控件是如何进行选择操作的。文中有误的地方希望大家进行批评指正。

InkCanvas的选择效果

使用WPF可以轻松实现白板功能,只需要添加一个InkCanvas控件。修改InkCanvas的EditingMode属性可以控制InkCanvas的操作模式,如书写、选择、擦除等模式。
如下demo在窗口中添加一个InkCanvas,然后添加一个Button实现书写与选择模式的切换。

// xaml
<Grid>
  <InkCanvas x:Name="inkCanvas"/>
  <Button x:Name="btnChangeMode" Content="select" Click="Button_Click"
      Width="50" Height="30" HorizontalAlignment="Left" VerticalAlignment="Bottom" Margin="10"/>
</Grid>

// cs
private void Button_Click(object sender, RoutedEventArgs e)
{
	if(btnChangeMode.Content == "select")
	{
    inkCanvas.EditingMode = InkCanvasEditingMode.Select;
    btnChangeMode.Content = "write";
	}
	else
	{
    inkCanvas.EditingMode = InkCanvasEditingMode.Ink;
    btnChangeMode.Content = "select";
	}
}

运行demo,书写后点击按钮进行选择,可以看到InkCanvas的选择操作如下图所示:

从图中可以看出,InkCanvas的选择效果有如下特点:

  1. 选中后笔迹高亮;
  2. 选中后显示选择框;
  3. 拖动选择框,选择框随着鼠标移动,但选择的笔迹并未移动。

接下来看下WPF是如何实现这种选择操作的。

InkCanvas选择模式的实现

首先,InkCanvas的编辑功能(书写、擦除、选择等)是通过EditingCoordinator管理的,该类包含一系列的EditingBehavior,实现选择过程的为LassoSelectionBehavior类,实现选择后对选择框操作的为SelectionEditor与SelectionEditingBehavior。本文主要介绍选择后对选择框的操作过程,选择过程以及笔迹的高亮显示打算单独写一篇文章进行介绍。

在InkCanvas中,与选择功能相关的对象有InkCanvasSelection、InkCanvasSelectionAdorner及InkCanvasFeedbackAdorner。后两者为装饰器,装饰器的介绍可参考官方文档。

先看InkCanvasSelectionAdorner类,直接看其OnRender方法,代码如下。首先绘制了选择框的背景,然后绘制了选择框(矩形虚线效果),最后绘制了选择框上的9个小矩形按钮。按钮可以进行拖动调节,具体实现逻辑可以看代码,本文不赘述。

protected override void OnRender(DrawingContext drawingContext)
{
  DrawBackground(drawingContext);

  Rect rectWireFrame = GetWireFrameRect()
  if(!rectWireFrame.IsEmpty)
  {
    drawingContext.DrawRectangle(null, _adornerBorderPen, rectWireFrame);

    DrawHandles(drawingContext, rectWireFrame);
  }
}

再看InkCanvasFeedbackAdorner类,同样看OnRender方法,代码如下。其仅绘制了矩形虚线选择框,通过这两个类的OnRender方法,结合上文中的动画,可以知道选中后使用InkCanvasSelectionAdorner进行装饰,对选择框的操作(拖动)使用InkCanvasFeedbackAdorner进行装饰。

protected override void OnRender(DrawingContext drawingContext)
{
  drawingContext.DrawRectangle(null, _adornerBorderPen,
    new Rect(CornerResizeHandleSize / 2, CornerResizeHandleSize / 2,
    _frameSize.Width - CornerResizeHandleSize, _frameSize.Height - CornerResizeHandleSize));
}

接下来看下这两个Adorner是对谁进行装饰的,首先看InkCanvas的OnPreApplyTemplate方法,代码如下。注释部分是InkCanvas的Visual Tree,可以了解到InkCanvas的内部结构。再看下SelectionAdorner的初始化,可以看出是对InnerCanvas进行装饰,InnerCanvas是InkCanvas的内部容器,放置笔迹及其它UIElement。SelectionAdorner添加了对ActiveEditingMode的绑定,当Mode为None时,隐藏,否则显示。FeedbackAdorner的装饰对象通过其构造函数可以看出,也是装饰的InnerCanvas。

internal override void OnPreApplyTemplate()
{
  base.OnPreApplyTemplate();

  // Build our visual tree here.
  // <InkCanvas>
  //   <AdornerDecorator>
  //     <InkPresenter>
  //       <InnerCanvas/>
  //       <ContainerVisual/>
  //       <HostVisual/>
  //     </InkPresenter>
  //     <AdornerLayer>
  //       <InkCanvasSelectionAdorner/>
  //       <InkCanvasFeedbackAdorner/>
  //     </AdornerLayer>
  //   </AdornerDecorator>
  // </InkCanvas>

  if(_localAdornerDecorator == null)
  {
    _localAdornerDecorator = new AdornerDecorator();
    InkPresenter inkPresenter = InkPresenter;

    AddVisualChild(_localAdornerDecorator);
    _localAdornerDecorator.Child = inkPresenter;
    inkPresenter.Child = InnerCanvas;

    _localAdornerDecorator.AdornerLayer.Add(SelectionAdorner);
  }
}

internal InkCanvasSelectionAdorner SelectionAdorner
{
  get
  {
    if(_selectionAdorner == null)
    {
      _selectionAdorner = new InkCanvasSelectionAdorner(InnerCanvas);

      Binding activedEditingModeBinding = new Binding();
      activedEditingModeBinding.Path = new PropertyPath(InkCanvas.ActiveEditingModeProperty);
      activedEditingModeBinding.Mode = BindingMode.OneWay;
      activedEditingModeBinding.Source = this;
      activedEditingModeBinding.Converter = new ActiveEditingMode2VisibilityConverter();
      _selectionAdorner.SetBinding(UIElement.VisibilityProperty, activedEditingModeBinding);
    }

    return _selectionAdorner;
  }
}

// InkCanvasFeedbackAdorner
internal InkCanvasFeedbackAdorner(InkCanvas inkCanvas)
  : base((inkCanvas != null ? inkCanvas.InnerCanvas : null))
  {...}

最后,我们看下对选择框进行的操作是如何实现的。选择后会激活SelectionEditingBehavior,在其OnActivate方法中,绑定了SelectionAdorner的MouseMove/MouseUp/LostMouseCapture事件,并调用InkCanvasSelection.StartFeedbackAdorner()方法对FeedbackAdorner进行初始化,将其添加到AdornerLayer中。然后通过响应MouseMove,调用InkSelection.UpdateFeedbackAdorner()方法更新FeedbackAdorner的位置。最后在MouseUp响应中释放FeedbackAdorner。删减代码如下,具体的实现逻辑可以看WPF源码。

protected override void OnActive()
{
  // ...
  InkCanvas.InkCanvasSelection.StartFeedbackAdorner(_selectionRect, _hitResult);

  InkCanvas.SelectionAdorner.AddHandler(Mouse.MouseUpEvent, new MouseButtonEventHandler(OnMouseUp));
  InkCanvas.SelectionAdorner.AddHandler(Mouse.MouseMoveEvent, new MouseEventHandler(OnMouseMove));
  InkCanvas.SelectionAdorner.AddHandler(Mouse.LostMouseCaptureEvent, new MouseEventHandler(OnLostMouseCapture));
}

private void OnMouseMove(object sender, MouseEventArgs args)
{
  // ...
  InkCanvas.InkCanvasSelection.UpdateFeedbackAdorner(newRect);
  // ...
}

以上就是详解WPF的InkCanvas选择模式的详细内容,更多关于WPF的InkCanvas选择模式的资料请关注脚本之家其它相关文章!

相关文章

  • C#异步下载文件

    C#异步下载文件

    这篇文章主要介绍了C#异步下载文件的相关资料,需要的朋友可以参考下
    2016-01-01
  • C#中父窗口和子窗口之间控件互操作实例

    C#中父窗口和子窗口之间控件互操作实例

    这篇文章主要介绍了C#中父窗口和子窗口之间控件互操作的方法,实例分析了父窗口与子窗口互操作的技巧,需要的朋友可以参考下
    2015-01-01
  • Unity3D UGUI实现缩放循环拖动卡牌展示效果

    Unity3D UGUI实现缩放循环拖动卡牌展示效果

    这篇文章主要为大家详细介绍了Unity3D UGUI实现缩放循环拖动展示卡牌效果,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2019-02-02
  • WINFORM 窗体间的传值实现解析

    WINFORM 窗体间的传值实现解析

    这篇文章主要介绍了WINFORM 窗体间的传值实现解析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
    2019-09-09
  • C#实现将千分位字符串转换成数字的方法

    C#实现将千分位字符串转换成数字的方法

    这篇文章主要介绍了C#实现将千分位字符串转换成数字的方法,很适合初学者更好的理解C#字符串原理,需要的朋友可以参考下
    2014-08-08
  • 轻松学习C#的String类

    轻松学习C#的String类

    轻松学习C#的String类,小编也是第一次接触C#的String类,感兴趣的小伙伴们可以参考一下,大家一起学习
    2015-11-11
  • WPF实现动画效果(一)之基本概念

    WPF实现动画效果(一)之基本概念

    这篇文章介绍了WPF实现动画效果之基本概念,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2022-06-06
  • Unity3D开发之获取所有的子对象的方法详解

    Unity3D开发之获取所有的子对象的方法详解

    这篇文章主要为大家详细介绍了三种Unity3D中获取所有的子对象(child)的方法,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下
    2023-01-01
  • C# Hashtable/Dictionary写入和读取对比详解

    C# Hashtable/Dictionary写入和读取对比详解

    本文中将从基础角度讲解HashTable、Dictionary的构造和通过程序进行插入读取对比
    2013-11-11
  • 解决Unity urp级联阴影接缝问题

    解决Unity urp级联阴影接缝问题

    通过从unity内部函数中抽几个出来改造,强制取某个裁切球的级联阴影映射,通过案例给大家详细介绍,文中给出了完整的urp shader代码,对Unity级联阴影知识感兴趣的朋友一起看看吧
    2021-06-06

最新评论