Поделиться через


Создание элемента управления рукописным вводом

Обновлен: Ноябрь 2007

Можно создать настраиваемый элемент управления, динамически и статически отображающий рукописный ввод. То есть рукописные данные отображаются при вводе пользователем штриха, визуально рукописный ввод «выплывает» из-под пера планшета и отображается после того, как добавляется в элемент управления, либо с помощью пера планшета, из буфер обмена, либо при загрузке из файла. Для динамического отображения рукописного ввода, элемент управления должен использовать DynamicRenderer. Для статического отображения рукописных данных необходимо переопределить методы событий пера (OnStylusDown, OnStylusMove и OnStylusUp) для сбора данных StylusPoint и создания штрихов, и добавить их в InkPresenter (который отображает рукописные данные в элементе управления).

В этом разделе содержатся следующие подразделы:

  • Практическое руководство. Сбор данных о точках пера и создание рукописных штрихов

  • Практическое руководство. Включение приема элементом управления ввода при помощи мыши

  • Сборка

  • Использование дополнительных подключаемых модулей и DynamicRenderer

  • Заключение

Практическое руководство. Сбор данных о точках пера и создание рукописных штрихов

Для создания элемента управления, собирающего и управляющего штрихами рукописного ввода выполните следующие действия.

  1. Создайте класс, производный от Control или от одного из классов, производных от Control, например, Label.

    Imports System
    Imports System.Collections.Generic
    Imports System.Text
    Imports System.Windows.Ink
    Imports System.Windows.Input
    Imports System.Windows.Input.StylusPlugIns
    Imports System.Windows.Controls
    Imports System.Windows
    
    
    ...
    
    
    Class InkControl
        Inherits Label
    
    
    ...
    
    
    End Class 'StylusControl
    
    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.

    Private ip As InkPresenter
    
    Public Sub New()
        ' Add an InkPresenter for drawing.
        ip = New InkPresenter()
        Me.Content = ip
    
    End Sub
    
    InkPresenter ip;
    
    public InkControl()
    {
        // Add an InkPresenter for drawing.
        ip = new InkPresenter();
        this.Content = ip;
    }
    
  3. Присоедините RootVisual для DynamicRenderer к InkPresenter, вызвав метод AttachVisuals, и добавьте DynamicRenderer в коллекцию StylusPlugIns. Это позволит InkPresenter отображать рукописные данные как данные точек пера, собираемые элементом управления.

    Public Sub New()
    
    
    ...
    
    
        ' Add a dynamic renderer that 
        ' draws ink as it "flows" from the stylus.
        dr = New DynamicRenderer()
        ip.AttachVisuals(dr.RootVisual, dr.DrawingAttributes)
        Me.StylusPlugIns.Add(dr)
    
    End Sub
    
    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. При перехвате пера элемент управления будет продолжать получать события StylusMove и StylusUp даже в том случае, если перо покидает границы элемента управления. Это не является строго обязательным, но почти всегда желательно для повышения удобства работы пользователя. Создайте новый объект StylusPointCollection для сбора данных StylusPoint. Наконец, добавьте исходный набор данных StylusPoint в StylusPointCollection.

    Protected Overrides Sub OnStylusDown(ByVal e As StylusDownEventArgs)
        ' Capture the stylus so all stylus input is routed to this control.
        Stylus.Capture(Me)
    
        ' Allocate memory for the StylusPointsCollection and
        ' add the StylusPoints that have come in so far.
        stylusPoints = New StylusPointCollection()
        Dim eventPoints As StylusPointCollection = e.GetStylusPoints(Me, stylusPoints.Description)
    
        stylusPoints.Add(eventPoints)
    
    End Sub 'OnStylusDown
    
    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 Overrides Sub OnStylusMove(ByVal e As StylusEventArgs)
    
        If stylusPoints Is Nothing Then
            Return
        End If
    
        ' Add the StylusPoints that have come in since the 
        ' last call to OnStylusMove.
        Dim newStylusPoints As StylusPointCollection = e.GetStylusPoints(Me, stylusPoints.Description)
        stylusPoints.Add(newStylusPoints)
    
    End Sub 'OnStylusMove
    
    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 и создайте новый Stroke с данными StylusPointCollection. Добавьте новый созданный объект Stroke в коллекцию Strokes для InkPresenter и освободите перо.

    Protected Overrides Sub OnStylusUp(ByVal e As StylusEventArgs)
        ' Allocate memory for the StylusPointsCollection, if necessary.
        If stylusPoints Is Nothing Then
            Return
        End If
    
        ' Add the StylusPoints that have come in since the 
        ' last call to OnStylusMove.
        Dim newStylusPoints As StylusPointCollection = e.GetStylusPoints(Me, stylusPoints.Description)
        stylusPoints.Add(newStylusPoints)
    
        ' Create a new stroke from all the StylusPoints since OnStylusDown.
        Dim stroke As New Stroke(stylusPoints)
    
        ' Add the new stroke to the Strokes collection of the InkPresenter.
        ip.Strokes.Add(stroke)
    
        ' Clear the StylusPointsCollection.
        stylusPoints = Nothing
    
        ' Release stylus capture.
        Stylus.Capture(Nothing)
    
    End Sub 'OnStylusUp
    
    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 Overrides Sub OnMouseLeftButtonDown(ByVal e As MouseButtonEventArgs)
    
        MyBase.OnMouseLeftButtonDown(e)
    
        ' If a stylus generated this event, return.
        If Not (e.StylusDevice Is Nothing) Then
            Return
        End If
    
        ' Start collecting the points.
        stylusPoints = New StylusPointCollection()
        Dim pt As Point = e.GetPosition(Me)
        stylusPoints.Add(New StylusPoint(pt.X, pt.Y))
    
    End Sub 'OnMouseLeftButtonDown
    
    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 Overrides Sub OnMouseMove(ByVal e As MouseEventArgs)
    
        MyBase.OnMouseMove(e)
    
        ' If a stylus generated this event, return.
        If Not (e.StylusDevice Is Nothing) Then
            Return
        End If
    
        ' Don't collect points unless the left mouse button
        ' is down.
        If e.LeftButton = MouseButtonState.Released Then
            Return
        End If
    
        If stylusPoints Is Nothing Then
            Return
        End If
    
        Dim pt As Point = e.GetPosition(Me)
        stylusPoints.Add(New StylusPoint(pt.X, pt.Y))
    
    End Sub 'OnMouseMove
    
    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. Создайте новый объект Stroke с данными StylusPointCollection и добавьте новый созданный объект Stroke в коллекцию Strokes из InkPresenter.

    Protected Overrides Sub OnMouseLeftButtonUp(ByVal e As MouseButtonEventArgs)
    
        MyBase.OnMouseLeftButtonUp(e)
    
        ' If a stylus generated this event, return.
        If Not (e.StylusDevice Is Nothing) Then
            Return
        End If
    
        If stylusPoints Is Nothing Then
            stylusPoints = New StylusPointCollection()
        End If
    
        Dim pt As Point = e.GetPosition(Me)
        stylusPoints.Add(New StylusPoint(pt.X, pt.Y))
    
        ' Create a stroke and add it to the InkPresenter.
        Dim stroke As New Stroke(stylusPoints)
        stroke.DrawingAttributes = dr.DrawingAttributes
        ip.Strokes.Add(stroke)
    
        stylusPoints = Nothing
    
    End Sub 'OnMouseLeftButtonUp 
    
    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;
    
    }
    

Сборка

В следующем примере представлен пользовательский элемент управления, собирающий рукописные данные, если пользователь использует мышь или перо.

Imports System
Imports System.Collections.Generic
Imports System.Text
Imports System.Windows.Ink
Imports System.Windows.Input
Imports System.Windows.Input.StylusPlugIns
Imports System.Windows.Controls
Imports System.Windows


...


' A control for managing ink input
Class InkControl
    Inherits Label
    Private ip As InkPresenter
    Private dr As DynamicRenderer

    ' The StylusPointsCollection that gathers points 
    ' before Stroke from is created.
    Private stylusPoints As StylusPointCollection = Nothing


    Public Sub New()
        ' Add an InkPresenter for drawing.
        ip = New InkPresenter()
        Me.Content = ip

        ' Add a dynamic renderer that 
        ' draws ink as it "flows" from the stylus.
        dr = New DynamicRenderer()
        ip.AttachVisuals(dr.RootVisual, dr.DrawingAttributes)
        Me.StylusPlugIns.Add(dr)

        Dim cdr As New CustomDynamicRenderer()
        ip.AttachVisuals(cdr.RootVisual, cdr.DrawingAttributes)
        Me.StylusPlugIns.Add(cdr)

    End Sub 'New

    Shared Sub New()

        ' Allow ink to be drawn only within the bounds of the control.
        Dim owner As Type = GetType(InkControl)
        ClipToBoundsProperty.OverrideMetadata(owner, New FrameworkPropertyMetadata(True))

    End Sub 'New

    Protected Overrides Sub OnStylusDown(ByVal e As StylusDownEventArgs)
        ' Capture the stylus so all stylus input is routed to this control.
        Stylus.Capture(Me)

        ' Allocate memory for the StylusPointsCollection and
        ' add the StylusPoints that have come in so far.
        stylusPoints = New StylusPointCollection()
        Dim eventPoints As StylusPointCollection = e.GetStylusPoints(Me, stylusPoints.Description)

        stylusPoints.Add(eventPoints)

    End Sub 'OnStylusDown

    Protected Overrides Sub OnStylusMove(ByVal e As StylusEventArgs)

        If stylusPoints Is Nothing Then
            Return
        End If

        ' Add the StylusPoints that have come in since the 
        ' last call to OnStylusMove.
        Dim newStylusPoints As StylusPointCollection = e.GetStylusPoints(Me, stylusPoints.Description)
        stylusPoints.Add(newStylusPoints)

    End Sub 'OnStylusMove

    Protected Overrides Sub OnStylusUp(ByVal e As StylusEventArgs)
        ' Allocate memory for the StylusPointsCollection, if necessary.
        If stylusPoints Is Nothing Then
            Return
        End If

        ' Add the StylusPoints that have come in since the 
        ' last call to OnStylusMove.
        Dim newStylusPoints As StylusPointCollection = e.GetStylusPoints(Me, stylusPoints.Description)
        stylusPoints.Add(newStylusPoints)

        ' Create a new stroke from all the StylusPoints since OnStylusDown.
        Dim stroke As New Stroke(stylusPoints)

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

        ' Clear the StylusPointsCollection.
        stylusPoints = Nothing

        ' Release stylus capture.
        Stylus.Capture(Nothing)

    End Sub 'OnStylusUp

    Protected Overrides Sub OnMouseLeftButtonDown(ByVal e As MouseButtonEventArgs)

        MyBase.OnMouseLeftButtonDown(e)

        ' If a stylus generated this event, return.
        If Not (e.StylusDevice Is Nothing) Then
            Return
        End If

        ' Start collecting the points.
        stylusPoints = New StylusPointCollection()
        Dim pt As Point = e.GetPosition(Me)
        stylusPoints.Add(New StylusPoint(pt.X, pt.Y))

    End Sub 'OnMouseLeftButtonDown

    Protected Overrides Sub OnMouseMove(ByVal e As MouseEventArgs)

        MyBase.OnMouseMove(e)

        ' If a stylus generated this event, return.
        If Not (e.StylusDevice Is Nothing) Then
            Return
        End If

        ' Don't collect points unless the left mouse button
        ' is down.
        If e.LeftButton = MouseButtonState.Released Then
            Return
        End If

        If stylusPoints Is Nothing Then
            Return
        End If

        Dim pt As Point = e.GetPosition(Me)
        stylusPoints.Add(New StylusPoint(pt.X, pt.Y))

    End Sub 'OnMouseMove

    Protected Overrides Sub OnMouseLeftButtonUp(ByVal e As MouseButtonEventArgs)

        MyBase.OnMouseLeftButtonUp(e)

        ' If a stylus generated this event, return.
        If Not (e.StylusDevice Is Nothing) Then
            Return
        End If

        If stylusPoints Is Nothing Then
            stylusPoints = New StylusPointCollection()
        End If

        Dim pt As Point = e.GetPosition(Me)
        stylusPoints.Add(New StylusPoint(pt.X, pt.Y))

        ' Create a stroke and add it to the InkPresenter.
        Dim stroke As New Stroke(stylusPoints)
        stroke.DrawingAttributes = dr.DrawingAttributes
        ip.Strokes.Add(stroke)

        stylusPoints = Nothing

    End Sub 'OnMouseLeftButtonUp 
End Class 'StylusControl
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;

    }
}

Использование дополнительных подключаемых модулей и DynamicRenderer

Как и InkCanvas, пользовательский элемент управления может иметь пользовательские объекты StylusPlugIn и дополнительные объекты DynamicRenderer. Добавьте эти объекты в коллекцию StylusPlugIns. Порядок объектов StylusPlugIn в StylusPlugInCollection влияет на внешний вид рукописных данных при их отображении. Предположим, имеется DynamicRenderer с именем dynamicRenderer и пользовательский StylusPlugIn с именем translatePlugin, который смещает рукописный ввод, поступающий от планшетного пера. Если translatePlugin является первым StylusPlugIn в StylusPlugInCollection, а dynamicRenderer — вторым, рукописный ввод будет смещаться соответственно перемещениям мыши. Если dynamicRenderer является первым, а translatePlugin — вторым, рукописные данные не будут смещаться до тех пор, пока пользователь не поднимет перо.

Заключение

Можно создать элемент управления, который собирает и отображает рукописные данные, переопределяя методы событий пера. Создав собственный элемент управления, собственные производные классы StylusPlugIn и вставив их в StylusPlugInCollection, можно виртуально реализовать любую возможную модель поведения цифрового рукописного ввода. Имеется доступ к данным StylusPoint при их создании, благодаря чему приобретается возможность настроить ввод с помощью Stylus и отображать вводимые данные на экране так, как нужно в приложении. Поскольку есть такой доступ низкого уровня к данным StylusPoint, можно реализовать коллекцию рукописного ввода и отображать ее с оптимальной для приложения производительностью.

См. также

Другие ресурсы

Дополнительная обработка рукописных данных

Доступ и управление вводом с помощью пера, см. на веб-узле