잉크 입력 컨트롤 만들기
동적으로 또는 정적으로 잉크를 렌더링하는 사용자 지정 컨트롤을 만들 수 있습니다. 즉, 태블릿 펜을 사용하거나, 클립보드에서 붙여 넣거나, 파일에서 로드하는 방법을 통해 사용자가 스트로크를 그릴 때 잉크를 렌더링하고, 잉크가 태블릿 펜에서 "흐르는 것처럼" 나타나도록 하고, 잉크가 컨트롤에 추가된 후 잉크를 표시합니다. 잉크를 동적으로 렌더링하려면 컨트롤은 DynamicRenderer를 사용해야 합니다. 잉크를 정적으로 렌더링하려면 스타일러스 이벤트 메서드(OnStylusDown, OnStylusMove 및 OnStylusUp)를 재정의하여 StylusPoint 데이터를 수집하고, 스트로크를 만들고, 컨트롤에 잉크를 렌더링하는 InkPresenter에 이를 추가해야 합니다.
이 항목에는 다음과 같은 하위 단원이 포함되어 있습니다.
방법: 스타일러스 포인트 데이터 수집 및 잉크 스트로크 만들기
방법: 컨트롤이 마우스 입력을 받도록 설정
종합
추가 플러그 인 및 DynamicRenderer 사용
결론
방법: 스타일러스 포인트 데이터 수집 및 잉크 스트로크 만들기
잉크 스트로크를 수집하고 관리하는 컨트롤을 만들려면 다음을 수행합니다.
Control 또는 Label과 같은 Control에서 파생된 클래스 중 하나에서 클래스를 파생시킵니다.
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 { ... }
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; }
AttachVisuals 메서드를 호출하여 DynamicRenderer의 RootVisual을 InkPresenter에 연결하고 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); }
OnStylusDown 메서드를 재정의합니다. 이 메서드에서 Capture에 대한 호출을 사용하여 스타일러스를 캡처합니다. 컨트롤은 스타일러스를 캡처함으로써 스타일러스가 컨트롤의 경계를 벗어나더라도 StylusMove 및 StylusUp 이벤트를 계속 수신합니다. 반드시 이렇게 해야 하는 것은 아니지만 사용자의 편리함을 위해 가급적이면 이 방법을 사용하는 것이 좋습니다. StylusPoint 데이터를 수집하기 위해 새 StylusPointCollection을 만듭니다. 마지막으로, 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); }
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); }
OnStylusUp 메서드를 재정의하고 StylusPointCollection 데이터로 새 Stroke를 만듭니다. 이렇게 만든 새 Stroke를 InkPresenter의 Strokes 컬렉션에 추가하고 스타일러스 캡처를 해제합니다.
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); }
방법: 컨트롤이 마우스 입력을 받도록 설정
이전 컨트롤을 응용 프로그램에 추가하고 실행한 다음 마우스를 입력 장치로 사용하면 스트로크가 지속되지 않음을 알 수 있습니다. 마우스를 입력 장치로 사용할 때 스트로크가 지속되게 하려면 다음을 수행합니다.
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)); }
OnMouseMove 메서드를 재정의합니다. 이벤트가 발생했을 때 마우스의 위치를 가져오고 포인트 데이터를 사용하여 StylusPoint를 만듭니다. 앞서 만든 StylusPointCollection 개체에 StylusPoint를 추가합니다.
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)); }
OnMouseLeftButtonUp 메서드를 재정의합니다. StylusPointCollection 데이터로 새 Stroke를 만들고, 앞서 만든 새 Stroke를 InkPresenter의 Strokes 컬렉션에 추가합니다.
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 컬렉션에 추가합니다. StylusPlugInCollection에서 StylusPlugIn 개체의 순서는 렌더링되는 잉크 모양에 영향을 줍니다. dynamicRenderer라는 DynamicRenderer와 태블릿 펜에서 잉크를 오프셋하는 translatePlugin이라는 사용자 지정 StylusPlugIn이 있다고 가정해 보겠습니다. translatePlugin이 StylusPlugInCollection에서 첫 번째 StylusPlugIn이고 dynamicRenderer가 두 번째인 경우 사용자가 펜을 움직이면 "흐르는" 잉크가 오프셋됩니다. dynamicRenderer가 첫 번째이고 translatePlugin이 두 번째이면 사용자가 펜을 들어 올리기 전까지는 잉크가 오프셋되지 않습니다.
결론
스타일러스 이벤트 메서드를 재정의하여 잉크를 수집하고 렌더링하는 컨트롤을 만들 수 있습니다. 컨트롤을 직접 만들고, StylusPlugIn 클래스를 파생시키고, 이를 StylusPlugInCollection에 삽입하면 상상할 수 있는 거의 모든 동작을 디지털 잉크로 구현할 수 있습니다. 생성되는 StylusPoint 데이터에 액세스할 수 있으므로 Stylus 입력을 사용자 지정하여 응용 프로그램에 적합한 형태로 화면에 렌더링할 수 있습니다. StylusPoint 데이터에 대한 하위 수준 액세스가 가능하므로 잉크 컬렉션을 구현하고 응용 프로그램에 최적의 성능으로 렌더링할 수 있습니다.