• WPF源码阅读 -- InkCanvas选中笔迹


    本文接上一篇WPF源码阅读 -- InkCanvas选择模式,本文介绍笔迹的选择过程及选中后的高亮显示方法,文中若有理解错误的地方,欢迎大家指正。选择效果如下图所示:

    InkCanvas是WPF中用于墨迹书写的控件,其具有书写、选择、擦除等模式。根据上图,可以看出笔迹的选择功能由如下三部分组成:

    1. 选择笔迹(Lasso Stroke)
    2. 动态选择
    3. 选中后高亮显示

    本文将首先介绍选择模式的激活过程,然后介绍如上三部分内容WPF是如何实现的。

    选择模式的激活

    从图中可以看出,切换到选择模式后,鼠标按下移动绘制的效果为黄色点状虚线(Lasso),根据Lasso及一定的算法进行笔迹的选中与取消选中。

    先看InkCanvas切换到选择模式后的动作。切换到选择模式后,EditingMode改变,调用OnEditingModeChanged方法,该方法调用RaiseEditingModeChanged方法。RaiseEditingModeChanged方法中,调用了_editingCoordinator.UpdateEditingState方法,并通过OnEditingModeChanged引发事件。

    切换到EditingCoordinator类,可以看到依次调用UpdateEditingState() -> ChangeEditingBehavior() -> PushEditingBehavior()。UpdateEditingState方法调用GetBehavior方法拿到新Behavior(SelectionEditor)。PushEditingBehavior方法会吊销之前的Behavior,并激活新的Behavior,即SelectionEditor会被激活。

    切换到SelectionEditor类,在其OnActivate方法中监听了事件,在OnAdornerMouseButtonDownEvent方法中,调用了EditingCoordinator.ActivateDynamicBehavior方法激活了LassoSelectionBehavior。至此,选择模式已激活,并将随着设备移动绘制Lasso。

    // InkCanvas
    private static void OnEditingModeChanged(DependencyObject d, 
        DependencyPropertyChangedEventArgs e)
    {
        ( (InkCanvas)d ).RaiseEditingModeChanged(
                                    new RoutedEventArgs(
                                        InkCanvas.EditingModeChangedEvent, d));
    }
    
    private void RaiseEditingModeChanged(RoutedEventArgs e)
    {
        Debug.Assert(e != null, "EventArg can not be null");
        _editingCoordinator.UpdateEditingState(false /* EditingMode */);
        this.OnEditingModeChanged(e);
    }
    
    // EditingCoordinator
    internal void UpdateEditingState(bool inverted)
    {
        // ... EditingBehavior newBehavior = GetBehavior(ActiveEditingMode); ...
        ChangeEditingBehavior(newBehavior);
        // ...
    }
    
    private void ChangeEditingBehavior(EditingBehavior newBehavior)
    {
        // ...    
        PushEditingBehavior(newBehavior);
        // ...
    }
    
    private void PushEditingBehavior(EditingBehavior newEditingBehavior)
    {
        // ... behavior.Deactivate(); ...
        newEditingBehavior.Activate();
    }
    
    // SelectionEditor
    private void OnAdornerMouseButtonDownEvent(object sender, MouseButtonEventArgs args)
    {
        // ...
        EditingCoordinator.ActivateDynamicBehavior(EditingCoordinator.LassoSelectionBehavior, 
            args.StylusDevice != null ? args.StylusDevice : args.Device);
    }
    

    选择笔迹(Lasso Stroke)

    LassoSelectionBehavior继承自StylusEditingBehavior,随着设备的移动,会调用AddStylusPoints方法。该方法会调用StylusInputBegin、StylusInputContinue方法。StylusInputBegin会调用StartLasso方法,该方法创建了LassoHelper对象,该对象将绘制Lasso。

    // StylusEditingBehavior
    void IStylusEditing.AddStylusPoints(StylusPointCollection stylusPoints, bool userInitiated)
    {
        // ...
        if ( !EditingCoordinator.UserIsEditing )
        {
            EditingCoordinator.UserIsEditing = true;
            StylusInputBegin(stylusPoints, userInitiated);
        }
        else
            StylusInputContinue(stylusPoints, userInitiated);
    }
    
    // LassoSelectionBehavior
    private void StartLasso(List<Point> points)
    {
        // ...  
        _lassoHelper = new LassoHelper();
        // ...
    }
    
    // LassoHelper
    public const double  MinDistanceSquared     = 49.0;
    const double  DotRadius                     = 2.5;
    const double  DotCircumferenceThickness     = 0.5;
    const double  ConnectLineThickness          = 0.75;
    const double  ConnectLineOpacity            = 0.75;
    static readonly Color DotColor              = Colors.Orange;
    static readonly Color DotCircumferenceColor = Colors.White;
    private void AddLassoPoint(Point lassoPoint)
    {
        // ...
        dc.DrawEllipse(_brush, _pen, lassoPoint, DotRadius, DotRadius);
        // ...
    }
    

    动态选择

    LassoSelectionBehavior中有一个IncrementalLassoHitTester对象,该对象实现Lasso的hit-testing。当选中的笔迹有变化时,会引发其SelectionChanged事件,该事件参数LassoSelectionChangedEventArgs包含SelectedStrokes集合(动态选中的笔迹)与DeselectedStrokes集合(动态取消选中的笔迹)。该事件响应函数调用InkCanvas的UpdateDynamicSelection方法,进而实现笔迹的动态选中与取消选中。

    // LassoSelectionBehavior
    private void StartLasso(List<Point> points)
    {
        // ...
        _incrementalLassoHitTester = 
            this.InkCanvas.Strokes.GetIncrementalLassoHitTester(_percentIntersectForInk);
        _incrementalLassoHitTester.SelectionChanged += new LassoSelectionChangedEventHandler(OnSelectionChanged);
        // ...
        if (0 != lassoPoints.Length)
            _incrementalLassoHitTester.AddPoints(lassoPoints);
        // ...
    }
    
    private void OnSelectionChanged(object sender, LassoSelectionChangedEventArgs e)
    {
        this.InkCanvas.UpdateDynamicSelection(e.SelectedStrokes, e.DeselectedStrokes);
    }
    
    // InkCanvas
    internal void UpdateDynamicSelection(StrokeCollection strokesToDynamicallySelect, StrokeCollection strokesToDynamicallyUnselect)
    {
        // ...
        if (strokesToDynamicallySelect != null)
        {
            foreach (Stroke s in strokesToDynamicallySelect)
            {
                _dynamicallySelectedStrokes.Add(s);
                s.IsSelected = true;
            }
        }
    
        if (strokesToDynamicallyUnselect != null)
        {
            foreach (Stroke s in strokesToDynamicallyUnselect)
            {
                System.Diagnostics.Debug.Assert(_dynamicallySelectedStrokes.Contains(s));
                _dynamicallySelectedStrokes.Remove(s);
                s.IsSelected = false;
            }
        }
    }
    

    选中高亮

    从上文中可以看出,选中后的笔迹其IsSelected属性为true。直接看Stroke的DrawCore方法,如下代码中的_drawAsHollow的值由IsSelected决定。可以看出WPF绘制笔迹高亮的方式其实是绘制了两次Geometry,先绘制宽点的,再绘制窄点的,两个Geometry重叠后就形成了高亮效果。根据WPF注释描述,采用这种方法的效率是GetOutlinePathGeometry方法的五倍。

    protected virtual void DrawCore(DrawingContext drawingContext, DrawingAttributes drawingAttributes)
    {
        //...
        if (_drawAsHollow == true)
        {
            Matrix innerTransform, outerTransform;
            DrawingAttributes selectedDA = drawingAttributes.Clone();
            selectedDA.Height = Math.Max(selectedDA.Height, DrawingAttributes.DefaultHeight);
            selectedDA.Width = Math.Max(selectedDA.Width, DrawingAttributes.DefaultWidth);
            CalcHollowTransforms(selectedDA, out innerTransform, out outerTransform);
    
            selectedDA.StylusTipTransform = outerTransform;
            SolidColorBrush brush = new SolidColorBrush(drawingAttributes.Color);
            brush.Freeze();
            drawingContext.DrawGeometry(brush, null, GetGeometry(selectedDA));
    
            selectedDA.StylusTipTransform = innerTransform;
            drawingContext.DrawGeometry(Brushes.White, null, GetGeometry(selectedDA));
        }
        // ...
    }
    
    转载请注明出处,欢迎交流。
  • 相关阅读:
    Mac安装zookeeper
    征途
    vue-配置文件
    数组去重
    判断身份证
    判断邮箱
    判断手机号是否正确
    JS 时间格式转换
    打印 print-js
    自"愚"自乐的云服务器
  • 原文地址:https://www.cnblogs.com/louzixl/p/14624173.html
Copyright © 2020-2023  润新知