创建墨迹输入控件

可以创建动态和静态呈现墨迹的自定义控件。 也就是说,在用户绘制笔划时呈现墨迹,使墨迹看起来像是从触笔中“流出”的一样,并在将墨迹添加到控件(通过触笔、从剪贴板粘贴或从文件加载)后显示墨迹。 若要动态呈现墨迹,控件必须使用 DynamicRenderer。 要静态呈现墨迹,必须重写触笔事件方法(OnStylusDownOnStylusMoveOnStylusUp)以收集 StylusPoint 数据、创建笔触并将它们添加到 InkPresenter(用于在控件上呈现墨迹)。

本主题包含以下小节:

如何:收集触笔点数据并创建墨迹笔划

若要创建收集和管理墨迹笔划的控件,请执行以下操作:

  1. Control 或派生自 Control(例如 Label)的类之一派生类。

    using System;
    using System.Windows.Ink;
    using System.Windows.Input;
    using System.Windows.Input.StylusPlugIns;
    using System.Windows.Controls;
    using System.Windows;
    
    class InkControl : Label
    {
    
    }
    
  2. 向类添加 InkPresenter,并将 Content 属性设置为新的 InkPresenter

    InkPresenter ip;
    
    public InkControl()
    {
        // Add an InkPresenter for drawing.
        ip = new InkPresenter();
        this.Content = ip;
    }
    
  3. 通过调用 AttachVisuals 方法将 DynamicRendererRootVisual 附加到 InkPresenter,并将 DynamicRenderer 添加到 StylusPlugIns 集合。 这允许 InkPresenter 在控件收集触笔点数据时显示墨迹。

    public InkControl()
    {
    
        // Add a dynamic renderer that
        // draws ink as it "flows" from the stylus.
        dr = new DynamicRenderer();
        ip.AttachVisuals(dr.RootVisual, dr.DrawingAttributes);
        this.StylusPlugIns.Add(dr);
    }
    
  4. 重写 OnStylusDown 方法。 在此方法中,通过调用 Capture 来捕获触笔。 通过捕获触笔,即使触笔离开控件的边界,控件也会继续接收 StylusMoveStylusUp 事件。 这不是严格强制的,但若要获得良好的用户体验,则始终需要这样做。 创建新的 StylusPointCollection,以收集 StylusPoint 数据。 最后,将初始 StylusPoint 数据集添加到 StylusPointCollection

    protected override void OnStylusDown(StylusDownEventArgs e)
    {
        // Capture the stylus so all stylus input is routed to this control.
        Stylus.Capture(this);
    
        // Allocate memory for the StylusPointsCollection and
        // add the StylusPoints that have come in so far.
        stylusPoints = new StylusPointCollection();
        StylusPointCollection eventPoints =
            e.GetStylusPoints(this, stylusPoints.Description);
    
        stylusPoints.Add(eventPoints);
    }
    
  5. 重写 OnStylusMove 方法并将 StylusPoint 数据添加到之前创建的 StylusPointCollection 对象。

    protected override void OnStylusMove(StylusEventArgs e)
    {
        if (stylusPoints == null)
        {
            return;
        }
    
        // Add the StylusPoints that have come in since the
        // last call to OnStylusMove.
        StylusPointCollection newStylusPoints =
            e.GetStylusPoints(this, stylusPoints.Description);
        stylusPoints.Add(newStylusPoints);
    }
    
  6. 重写 OnStylusUp 方法并使用 StylusPointCollection 数据创建一个新 Stroke。 将创建的新 Stroke 添加到 InkPresenterStrokes 集合,并释放触笔捕获。

    protected override void OnStylusUp(StylusEventArgs e)
    {
        if (stylusPoints == null)
        {
            return;
        }
    
        // Add the StylusPoints that have come in since the
        // last call to OnStylusMove.
        StylusPointCollection newStylusPoints =
            e.GetStylusPoints(this, stylusPoints.Description);
        stylusPoints.Add(newStylusPoints);
    
        // Create a new stroke from all the StylusPoints since OnStylusDown.
        Stroke stroke = new Stroke(stylusPoints);
    
        // Add the new stroke to the Strokes collection of the InkPresenter.
        ip.Strokes.Add(stroke);
    
        // Clear the StylusPointsCollection.
        stylusPoints = null;
    
        // Release stylus capture.
        Stylus.Capture(null);
    }
    

如何:使控件能够接受鼠标输入

如果将上述控件添加到应用程序,运行它并使用鼠标作为输入设备,你会注意到笔划不会持久保留。 要在将鼠标用作输入设备时保留笔画,请执行以下操作:

  1. 重写 OnMouseLeftButtonDown 并创建一个新的 StylusPointCollection。获取事件发生时鼠标的位置,使用点数据创建一个 StylusPoint,并将 StylusPoint 添加到 StylusPointCollection

    protected override void OnMouseLeftButtonDown(MouseButtonEventArgs e)
    {
    
        base.OnMouseLeftButtonDown(e);
    
        // If a stylus generated this event, return.
        if (e.StylusDevice != null)
        {
            return;
        }
    
        // Start collecting the points.
        stylusPoints = new StylusPointCollection();
        Point pt = e.GetPosition(this);
        stylusPoints.Add(new StylusPoint(pt.X, pt.Y));
    }
    
  2. 重写 OnMouseMove 方法。 获取事件发生时鼠标的位置,并使用点数据创建一个 StylusPoint。 将 StylusPoint 添加到之前创建的 StylusPointCollection 对象。

    protected override void OnMouseMove(MouseEventArgs e)
    {
    
        base.OnMouseMove(e);
    
        // If a stylus generated this event, return.
        if (e.StylusDevice != null)
        {
            return;
        }
    
        // Don't collect points unless the left mouse button
        // is down.
        if (e.LeftButton == MouseButtonState.Released ||
            stylusPoints == null)
        {
            return;
        }
    
        Point pt = e.GetPosition(this);
        stylusPoints.Add(new StylusPoint(pt.X, pt.Y));
    }
    
  3. 重写 OnMouseLeftButtonUp 方法。 使用 StylusPointCollection 数据创建一个新 Stroke,并将创建的新 Stroke 添加到 InkPresenterStrokes 集合。

    protected override void OnMouseLeftButtonUp(MouseButtonEventArgs e)
    {
    
        base.OnMouseLeftButtonUp(e);
    
        // If a stylus generated this event, return.
        if (e.StylusDevice != null)
        {
            return;
        }
    
        if (stylusPoints == null)
        {
            return;
        }
    
        Point pt = e.GetPosition(this);
        stylusPoints.Add(new StylusPoint(pt.X, pt.Y));
    
        // Create a stroke and add it to the InkPresenter.
        Stroke stroke = new Stroke(stylusPoints);
        stroke.DrawingAttributes = dr.DrawingAttributes;
        ip.Strokes.Add(stroke);
    
        stylusPoints = null;
    }
    

综合运用

以下示例是在用户使用鼠标或笔时收集墨迹的自定义控件。

using System;
using System.Windows.Ink;
using System.Windows.Input;
using System.Windows.Input.StylusPlugIns;
using System.Windows.Controls;
using System.Windows;
// A control for managing ink input
class InkControl : Label
{
    InkPresenter ip;
    DynamicRenderer dr;

    // The StylusPointsCollection that gathers points
    // before Stroke from is created.
    StylusPointCollection stylusPoints = null;

    public InkControl()
    {
        // Add an InkPresenter for drawing.
        ip = new InkPresenter();
        this.Content = ip;

        // Add a dynamic renderer that
        // draws ink as it "flows" from the stylus.
        dr = new DynamicRenderer();
        ip.AttachVisuals(dr.RootVisual, dr.DrawingAttributes);
        this.StylusPlugIns.Add(dr);
    }

    static InkControl()
    {
        // Allow ink to be drawn only within the bounds of the control.
        Type owner = typeof(InkControl);
        ClipToBoundsProperty.OverrideMetadata(owner,
            new FrameworkPropertyMetadata(true));
    }

    protected override void OnStylusDown(StylusDownEventArgs e)
    {
        // Capture the stylus so all stylus input is routed to this control.
        Stylus.Capture(this);

        // Allocate memory for the StylusPointsCollection and
        // add the StylusPoints that have come in so far.
        stylusPoints = new StylusPointCollection();
        StylusPointCollection eventPoints =
            e.GetStylusPoints(this, stylusPoints.Description);

        stylusPoints.Add(eventPoints);
    }

    protected override void OnStylusMove(StylusEventArgs e)
    {
        if (stylusPoints == null)
        {
            return;
        }

        // Add the StylusPoints that have come in since the
        // last call to OnStylusMove.
        StylusPointCollection newStylusPoints =
            e.GetStylusPoints(this, stylusPoints.Description);
        stylusPoints.Add(newStylusPoints);
    }

    protected override void OnStylusUp(StylusEventArgs e)
    {
        if (stylusPoints == null)
        {
            return;
        }

        // Add the StylusPoints that have come in since the
        // last call to OnStylusMove.
        StylusPointCollection newStylusPoints =
            e.GetStylusPoints(this, stylusPoints.Description);
        stylusPoints.Add(newStylusPoints);

        // Create a new stroke from all the StylusPoints since OnStylusDown.
        Stroke stroke = new Stroke(stylusPoints);

        // Add the new stroke to the Strokes collection of the InkPresenter.
        ip.Strokes.Add(stroke);

        // Clear the StylusPointsCollection.
        stylusPoints = null;

        // Release stylus capture.
        Stylus.Capture(null);
    }

    protected override void OnMouseLeftButtonDown(MouseButtonEventArgs e)
    {

        base.OnMouseLeftButtonDown(e);

        // If a stylus generated this event, return.
        if (e.StylusDevice != null)
        {
            return;
        }

        // Start collecting the points.
        stylusPoints = new StylusPointCollection();
        Point pt = e.GetPosition(this);
        stylusPoints.Add(new StylusPoint(pt.X, pt.Y));
    }

    protected override void OnMouseMove(MouseEventArgs e)
    {

        base.OnMouseMove(e);

        // If a stylus generated this event, return.
        if (e.StylusDevice != null)
        {
            return;
        }

        // Don't collect points unless the left mouse button
        // is down.
        if (e.LeftButton == MouseButtonState.Released ||
            stylusPoints == null)
        {
            return;
        }

        Point pt = e.GetPosition(this);
        stylusPoints.Add(new StylusPoint(pt.X, pt.Y));
    }

    protected override void OnMouseLeftButtonUp(MouseButtonEventArgs e)
    {

        base.OnMouseLeftButtonUp(e);

        // If a stylus generated this event, return.
        if (e.StylusDevice != null)
        {
            return;
        }

        if (stylusPoints == null)
        {
            return;
        }

        Point pt = e.GetPosition(this);
        stylusPoints.Add(new StylusPoint(pt.X, pt.Y));

        // Create a stroke and add it to the InkPresenter.
        Stroke stroke = new Stroke(stylusPoints);
        stroke.DrawingAttributes = dr.DrawingAttributes;
        ip.Strokes.Add(stroke);

        stylusPoints = null;
    }
}

使用其他插件和 DynamicRenderers

与 InkCanvas 一样,自定义控件可以具有自定义 StylusPlugIn 和其他 DynamicRenderer 对象。 将这些对象添加到 StylusPlugIns 集合。 StylusPlugInCollectionStylusPlugIn 对象的顺序会影响墨迹的呈现外观。 假设你有一个名为 dynamicRendererDynamicRenderer 和一个名为 translatePlugin 的自定义 StylusPlugIn,用于偏移来自触笔的墨迹。 如果 translatePluginStylusPlugInCollection 中的第一个 StylusPlugIn,而 dynamicRenderer 是第二个,则“流动”的墨迹将随着用户移动笔而偏移。 如果 dynamicRenderer 是第一个,而 translatePlugin 是第二个,则在用户抬起笔之前,墨迹不会偏移。

结论

可以通过重写触笔事件方法来创建一个收集和呈现墨迹的控件。 通过创建自己的控件、派生自己的 StylusPlugIn 类并将其插入 StylusPlugInCollection 中,可以实现几乎任何可使用数字墨迹想象的行为。 你可以在 StylusPoint 数据生成时访问该数据,借此机会自定义 Stylus 输入并按照适合应用程序的方式在屏幕上呈现该输入。 由于你对 StylusPoint 数据的访问权限非常低,因此可以使用应用程序的最佳性能实现墨迹收集和呈现。

另请参阅