WPF源碼閱讀 -- InkCanvas選中筆跡

本文接上一篇WPF源碼閱讀 -- InkCanvas選擇模式,本文介紹筆跡的選擇過程及選中後的高亮顯示方法,文中如有理解錯誤的地方,歡迎你們指正。選擇效果以下圖所示:html

image

InkCanvas是WPF中用於墨跡書寫的控件,其具備書寫、選擇、擦除等模式。根據上圖,能夠看出筆跡的選擇功能由以下三部分組成:算法

  1. 選擇筆跡(Lasso Stroke)
  2. 動態選擇
  3. 選中後高亮顯示

本文將首先介紹選擇模式的激活過程,而後介紹如上三部份內容WPF是如何實現的。函數

選擇模式的激活

從圖中能夠看出,切換到選擇模式後,鼠標按下移動繪製的效果爲黃色點狀虛線(Lasso),根據Lasso及必定的算法進行筆跡的選中與取消選中。this

先看InkCanvas切換到選擇模式後的動做。切換到選擇模式後,EditingMode改變,調用OnEditingModeChanged方法,該方法調用RaiseEditingModeChanged方法。RaiseEditingModeChanged方法中,調用了_editingCoordinator.UpdateEditingState方法,並經過OnEditingModeChanged引起事件。spa

切換到EditingCoordinator類,能夠看到依次調用UpdateEditingState() -> ChangeEditingBehavior() -> PushEditingBehavior()。UpdateEditingState方法調用GetBehavior方法拿到新Behavior(SelectionEditor)。PushEditingBehavior方法會弔銷以前的Behavior,並激活新的Behavior,即SelectionEditor會被激活。code

切換到SelectionEditor類,在其OnActivate方法中監聽了事件,在OnAdornerMouseButtonDownEvent方法中,調用了EditingCoordinator.ActivateDynamicBehavior方法激活了LassoSelectionBehavior。至此,選擇模式已激活,並將隨着設備移動繪製Lasso。orm

// 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。htm

// 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方法的五倍。blog

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));
    }
    // ...
}
相關文章
相關標籤/搜索