• WPF源码阅读 -- InkCanvas选择模式


    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);
        // ...
    }
    
    转载请注明出处,欢迎交流。
  • 相关阅读:
    libusb 示例
    里不是吧、
    ibeacon UUID
    Centos7系统下Docker开启认证的远程端口2376配置教程
    Consul 快速入门
    docker: Error response from daemon: Get https://registry-1.docker.io/v2/: net/http: request canceled
    Docker 启动容器时,报错 WARNING:IPv4 forwarding is disabled. Networking will not work. 的解决办法
    【基线检查】(高)基线检查--禁用local-infile选项(访问控制)
    PyCharm 上安装 Package(以 pandas 为例)
    Python time模块和datetime模块
  • 原文地址:https://www.cnblogs.com/louzixl/p/14557668.html
Copyright © 2020-2023  润新知