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

Можно создать пользовательский элемент управления, который динамически и статически преобразовывает для просмотра рукописный ввод. То есть преобразовывает для просмотра рукописный ввод, когда пользователь рисует штрих, что приводит к появлению рукописного ввода из планшетного пера, и отображает рукописный ввод после добавления в элемент управления с помощью планшетного пера, вставки из буфера обмена или загрузки из файла. Для динамического преобразования рукописного ввода для просмотра элемент управления должен использовать DynamicRenderer. Для статического преобразования рукописного ввода для просмотра необходимо переопределить методы событий пера (OnStylusDown, OnStylusMove и OnStylusUp) так, чтобы они собирали данные 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. Подключите RootVisualDynamicRenderer к InkPresenter путем вызова метода AttachVisuals и добавьте 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. Захватив перо, элемент управления продолжит получать события StylusMove и StylusUp, даже если перо покинет границы элемента управления. Это не является строго обязательным, но почти всегда требуется для обеспечения качественного взаимодействия с пользователем. Создайте новый объект 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 и создайте новый Stroke с данными StylusPointCollection. Добавьте новый метод Stroke в коллекцию StrokesInkPresenter и отмените захват пера.

    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. Создайте новый метод Stroke с данными StylusPointCollection и добавьте созданный метод Stroke в коллекцию StrokesInkPresenter.

    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;
    }
}

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

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

Заключение

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

См. также