关于WPF:WPF源码阅读-InkCanvas选中笔迹

6次阅读

共计 5176 个字符,预计需要花费 13 分钟才能阅读完成。

本文接上一篇 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));
    }
    // ...
}
正文完
 0