共计 5176 个字符,预计需要花费 13 分钟才能阅读完成。
本文接上一篇 WPF 源码浏览 — InkCanvas 抉择模式,本文介绍笔迹的抉择过程及选中后的高亮显示办法,文中若有了解谬误的中央,欢送大家斧正。抉择成果如下图所示:
InkCanvas 是 WPF 中用于墨迹书写的控件,其具备书写、抉择、擦除等模式。依据上图,能够看出笔迹的抉择性能由如下三局部组成:
- 抉择笔迹(Lasso Stroke)
- 动静抉择
- 选中后高亮显示
本文将首先介绍抉择模式的激活过程,而后介绍如上三局部内容 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));
}
// ...
}