本文接上一篇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));
}
// ...
}
轉載請註明出處,本文鏈接:https://www.uj5u.com/net/273210.html
標籤:.NET技术
