WPF中Canvas和InkCanvas

前言

WPF中绘图有两种方式CanvasInkCanvas

  • Canvas需要完全由自己实现。
  • InkCanvas已经默认为我们实现了基本的绘制,同时效果也比较好。

InkCanvas

推荐使用InkCanvas,使用它绘制线的时候会自动优化转折的地方,会变得平滑。

InkCanvas本身已经支持使用鼠标或者触屏来画线,下面的示例是使用代码进行画线。

画线

如下在BlackboardCanvas中绘制一条直线

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
private void DrawTest()
{
// 创建一条直线
List<Point> pointList = new List<Point>();
pointList.Add(new Point(50, 50));
pointList.Add(new Point(200, 200));
StylusPointCollection points = new StylusPointCollection(pointList);
Stroke stroke = new Stroke(points);

// 设置笔刷属性
DrawingAttributes drawingAttributes = new DrawingAttributes
{
Color = Colors.Red,
Width = 3
};
stroke.DrawingAttributes = drawingAttributes;

// 添加到InkCanvas的Strokes集合中
BlackboardCanvas.Strokes.Add(stroke);
}

我们也可以在Stroke中添加新的点

1
stroke.StylusPoints.Add(new StylusPoint(300, 200));

鼠标事件绘制

在实际绘制中我们可以在鼠标按下时添加对象

1
2
3
4
5
6
7
8
9
10
11
12
Stroke stroke = new Stroke(new StylusPointCollection(new[] { new Point(100, 100) }));

// 设置笔刷属性
DrawingAttributes drawingAttributes = new DrawingAttributes
{
Color = Colors.Red,
Width = 3
};
stroke.DrawingAttributes = drawingAttributes;

// 添加到InkCanvas的Strokes集合中
BlackboardCanvas.Strokes.Add(stroke);

鼠标移动时添加点

1
BlackboardCanvas.Strokes.Last().StylusPoints.Add(new StylusPoint(300, 200));

示例代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public void MouseDown(double x, double y)
{
Stroke stroke = new Stroke(new StylusPointCollection(new[] { new Point(x, y) }));

// 设置笔刷属性
DrawingAttributes drawingAttributes = new DrawingAttributes
{
Color = _pencolor,
Width = _pensize
};
stroke.DrawingAttributes = drawingAttributes;

// 添加到InkCanvas的Strokes集合中
_mCanvas.Strokes.Add(stroke);
}

public void MouseMove(double x, double y)
{
_mCanvas.Strokes.Last().StylusPoints.Add(new StylusPoint(x, y));
}

鼠标事件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
_mCanvas.MouseMove += Canvas_MouseMove;
_mCanvas.AddHandler
(
UIElement.MouseLeftButtonDownEvent,
new MouseButtonEventHandler(Canvas_MouseDown),
true
);
_mCanvas.AddHandler
(
UIElement.MouseLeftButtonUpEvent,
new MouseButtonEventHandler(Canvas_MouseUp),
true
);

private bool _isMouseDown;
private void Canvas_MouseDown(object sender, MouseButtonEventArgs e)
{
_isMouseDown = true;
var position = e.GetPosition(_mCanvas);
Console.WriteLine($@"X:{position.X} Y:{position.Y}");
}

private void Canvas_MouseMove(object sender, MouseEventArgs e)
{
if (_isMouseDown)
{
var position = e.GetPosition(_mCanvas);
Console.WriteLine($@"X:{position.X} Y:{position.Y}");
}
}

private void Canvas_MouseUp(object sender, MouseButtonEventArgs e)
{
_isMouseDown = false;
var position = e.GetPosition(_mCanvas);
Console.WriteLine($@"X:{position.X} Y:{position.Y}");
}

注意

MouseDownMouseUp事件无法正常工作,因为它是由InkCanvas处理的,并且没有被冒泡。

解决方法有两种

  1. 我们可以使用PreviewMouseDown/PreviewMouseLeftButtonDownPreviewMouseUp/PreviewMouseLeftButtonUp来代替,因为他们是隧道事件,并且在冒泡事件之前首先运行。
  2. 使用AddHandler

推荐

建议使用使用AddHandler,因为PreviewMouseUp实际是在事件执行之前触发,本来我们要在这个事件中要保存已绘制的笔迹,但是实际上会少了最后的一笔,因为最后一笔的绘制还没执行。

方式1

1
2
3
_mCanvas.PreviewMouseLeftButtonDown += Canvas_MouseDown;
_mCanvas.MouseMove += Canvas_MouseMove;
_mCanvas.PreviewMouseLeftButtonUp += Canvas_MouseUp;

方式2

使用AddHandler.aspx):

1
2
3
4
5
6
7
8
9
10
11
12
13
_mCanvas.MouseMove += Canvas_MouseMove;
_mCanvas.AddHandler
(
UIElement.MouseLeftButtonDownEvent,
new MouseButtonEventHandler(Canvas_MouseDown),
true
);
_mCanvas.AddHandler
(
UIElement.MouseLeftButtonUpEvent,
new MouseButtonEventHandler(Canvas_MouseUp),
true
);

工具类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
using System.Collections.Generic;
using System.Linq;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Ink;
using System.Windows.Input;
using System.Windows.Media;
using Z.Utils.Common;

namespace SchoolClient.Utils
{
using System;

public interface IMouseEvent
{
void CanvasMouseDown(double x, double y);
void CanvasMouseMove(double x, double y);
void CanvasMouseUp(double x, double y);
}

internal enum ZPenType : byte
{
Pen = 1,
Erase = 2
};

internal class ZbbPage
{
public List<ZbbPageStep> Lines { get; set; }
public List<ZbbPageStep> LinesHistoty { get; set; }

public ZbbPage()
{
Lines = new List<ZbbPageStep>();
LinesHistoty = new List<ZbbPageStep>();
}
}

internal class ZbbPageStep
{
public StrokeCollection LinesCurr { get; set; }
public StrokeCollection LinesAdd { get; set; }
public StrokeCollection LinesRemove { get; set; }

public ZbbPageStep()
{
LinesCurr = new StrokeCollection();
LinesAdd = new StrokeCollection();
LinesRemove = new StrokeCollection();
}
}

public class ZjBlackboardNew
{
private InkCanvas _mCanvas;

private IMouseEvent _mouseEvent;

//private ZPenType type = ZPenType.Pen;
private int _pagenum;

private const int ERASESIZE = 64;
private int _pensize = 3;
private int _undoOrRedo; //是否在进行撤销恢复操作
private Color _pencolor;

private readonly List<ZbbPage> _strokesPageAll = new List<ZbbPage>();

// 添加这个变量是因为在用橡皮擦时 一次操作会触发多次StrokesChanged回掉 这里是把多次回掉合并在一起
private ZbbPageStep _step;

public ZjBlackboardNew(InkCanvas canvas)
{
Init(canvas, Colors.White);
}

public void SetMouseEvent(IMouseEvent mEvent)
{
_mouseEvent = mEvent;
}

public ZjBlackboardNew(InkCanvas canvas, Color pencolor)
{
Init(canvas, pencolor);
}

private void Init(InkCanvas canvas, Color pencolor)
{
_mCanvas = canvas;
_pencolor = pencolor;
ZbbPage page = new ZbbPage();
page.Lines.Add(new ZbbPageStep());
_strokesPageAll.Add(page);
if (_mCanvas != null)
{
_mCanvas.EditingMode = InkCanvasEditingMode.Ink;
_mCanvas.UseCustomCursor = true;
_mCanvas.Cursor = Cursors.Pen;
DrawingAttributes drawingAttributes = new DrawingAttributes();
_mCanvas.DefaultDrawingAttributes = drawingAttributes;
drawingAttributes.Width = _pensize;
drawingAttributes.Height = _pensize;
drawingAttributes.Color = _pencolor;
drawingAttributes.FitToCurve = true;
drawingAttributes.IgnorePressure = false;
_mCanvas.Strokes.StrokesChanged += Strokes_StrokesChanged;
_mCanvas.StrokeCollected += Canvas_StrokeCollected;
_mCanvas.StrokeErasing += Canvas_StrokeErasing;
_mCanvas.StrokeErased += Canvas_StrokeErased;
_mCanvas.MouseMove += Canvas_MouseMove;
_mCanvas.AddHandler
(
UIElement.MouseLeftButtonDownEvent,
new MouseButtonEventHandler(Canvas_MouseDown),
true
);
_mCanvas.AddHandler
(
UIElement.MouseLeftButtonUpEvent,
new MouseButtonEventHandler(Canvas_MouseUp),
true
);
}
}

private void Canvas_StrokeErasing(object sender, InkCanvasStrokeErasingEventArgs e)
{
_undoOrRedo = 0;
}

private void Canvas_StrokeErased(object sender, RoutedEventArgs e)
{
}

private void Canvas_StrokeCollected(object sender, InkCanvasStrokeCollectedEventArgs e)
{
}

private bool _isMouseDown;

private void Canvas_MouseDown(object sender, MouseButtonEventArgs e)
{
_isMouseDown = true;
var position = e.GetPosition(_mCanvas);
if (_mouseEvent != null)
{
_mouseEvent.CanvasMouseDown(position.X, position.Y);
}
}

private void Canvas_MouseMove(object sender, MouseEventArgs e)
{
if (_isMouseDown)
{
var position = e.GetPosition(_mCanvas);
if (_mouseEvent != null)
{
_mouseEvent.CanvasMouseMove(position.X, position.Y);
}
}
}

private void Canvas_MouseUp(object sender, MouseButtonEventArgs e)
{
if (_isMouseDown)
{
_isMouseDown = false;
var position = e.GetPosition(_mCanvas);
if (_mouseEvent != null)
{
_mouseEvent.CanvasMouseUp(position.X, position.Y);
}
}
if (_step != null)
{
ZbbPage page = _strokesPageAll[_pagenum];
if (page != null)
{
_step.LinesCurr.Add(_mCanvas.Strokes);
page.Lines.Add(_step);
_step = null;
}
}
}

private void Strokes_StrokesChanged(object sender, StrokeCollectionChangedEventArgs e)
{
if (_undoOrRedo > 0)
{
_undoOrRedo -= 1;
return;
}

if (_step == null)
{
_step = new ZbbPageStep();
}

// 笔模式
if (e.Added.Count > 0 && e.Removed.Count == 0)
{
_step.LinesAdd.Add(e.Added);
}
// 橡皮模式 会多次进入回掉
else if (e.Removed.Count > 0)
{
_step.LinesAdd.Add(e.Added);
for (int i = 0; i < e.Removed.Count; i++)
{
Stroke removeItem = e.Removed[i];
try
{
if (_step.LinesAdd.Contains(removeItem))
{
_step.LinesAdd.Remove(removeItem);
}
else
{
_step.LinesRemove.Add(removeItem);
}
}
catch (Exception ex)
{
ZLogHelper.WriteErrLog("【笔迹添加移除事件】(Strokes_StrokesChanged)" + ex.Message, ex);
}
}
}
}

// public方法 笔
public void change_pen(Color color, int size)
{
_pencolor = color;
_pensize = size;
//this.type = ZPenType.Pen;
DrawingAttributes drawingAttributes = new DrawingAttributes();
_mCanvas.DefaultDrawingAttributes = drawingAttributes;
drawingAttributes.Color = color;
drawingAttributes.Width = size;
drawingAttributes.Height = size;
drawingAttributes.FitToCurve = true;
drawingAttributes.IgnorePressure = false;
_mCanvas.EditingMode = InkCanvasEditingMode.Ink;
_mCanvas.UseCustomCursor = true;
_mCanvas.Cursor = Cursors.Pen;
}

// 橡皮
public void change_erase()
{
//this.type = ZPenType.Erase;
_mCanvas.EditingMode = InkCanvasEditingMode.EraseByPoint;
_mCanvas.UseCustomCursor = false;
_mCanvas.EraserShape = new EllipseStylusShape
(
ERASESIZE,
ERASESIZE,
0
);
}

// 撤销
public void Undo()
{
ZbbPage page = _strokesPageAll[_pagenum];

if (page != null && _mCanvas.Strokes.Count > 0 && page.Lines.Count > 1)
{
ZbbPageStep last = page.Lines.Last();
page.Lines.Remove(last);
page.LinesHistoty.Add(last);
if (page.Lines.Last().LinesCurr.Count > 0)
{
_undoOrRedo = 2;
}
else
{
_undoOrRedo = 1;
}

_mCanvas.Strokes.Clear();
_mCanvas.Strokes.Add(page.Lines.Last().LinesCurr);
}
}

// 恢复
public void Redo()
{
ZbbPage page = _strokesPageAll[_pagenum];
if (page != null && page.LinesHistoty.Count > 0)
{
ZbbPageStep line = page.LinesHistoty[page.LinesHistoty.Count - 1];

page.Lines.Add(line);
page.LinesHistoty.Remove(line);
_undoOrRedo = page.Lines.Last().LinesCurr.Count > 0 ? 2 : 1;
_mCanvas.Strokes.Clear();
_mCanvas.Strokes.Add(page.Lines.Last().LinesCurr);
}
}

// 清空
public void Clear()
{
ZbbPage page = _strokesPageAll[_pagenum];
int num = page.Lines.Count;
for (int i = 0; i < num; i++)
{
page = _strokesPageAll[_pagenum];
if (page != null && _mCanvas.Strokes.Count > 0 && page.Lines.Count > 1)
{
ZbbPageStep last = page.Lines.Last();
page.Lines.Remove(last);
page.LinesHistoty.Add(last);
if (page.Lines.Last().LinesCurr.Count > 0)
{
_undoOrRedo = 2;
}
else
{
_undoOrRedo = 1;
}

_mCanvas.Strokes.Clear();
_mCanvas.Strokes.Add(page.Lines.Last().LinesCurr);
}
}
}

public void Changepage(int mpagenum)
{
if (_pagenum != mpagenum)
{
_pagenum = mpagenum;
if (_pagenum >= _strokesPageAll.Count)
{
int numadd = _pagenum - _strokesPageAll.Count + 1;
for (int i = 0; i < numadd; i++)
{
ZbbPage pagetemp = new ZbbPage();
pagetemp.Lines.Add(new ZbbPageStep());
_strokesPageAll.Add(pagetemp);
}
}

ZbbPage page = _strokesPageAll[_pagenum];
if (page != null)
{
if (page.Lines.Last().LinesCurr.Count > 0)
{
_undoOrRedo += 1;
}

if (_mCanvas.Strokes.Count > 0)
{
_undoOrRedo += 1;
_mCanvas.Strokes.Clear();
}

_mCanvas.Strokes.Add(page.Lines.Last().LinesCurr);
}
}
}

public void MouseDown(double x, double y)
{
Stroke stroke = new Stroke(new StylusPointCollection(new[] { new Point(x, y) }));

// 设置笔刷属性
DrawingAttributes drawingAttributes = new DrawingAttributes
{
Color = _pencolor,
Width = _pensize
};
stroke.DrawingAttributes = drawingAttributes;

// 添加到InkCanvas的Strokes集合中
_mCanvas.Strokes.Add(stroke);
}

public void MouseMove(double x, double y)
{
_mCanvas.Strokes.Last().StylusPoints.Add(new StylusPoint(x, y));
}

public void MouseUp(double x, double y)
{
if (_step != null)
{
ZbbPage page = _strokesPageAll[_pagenum];
if (page != null)
{
_step.LinesCurr.Add(_mCanvas.Strokes);
page.Lines.Add(_step);
_step = null;
}
}
}
}
}

Canvas

工具类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
using System;
using System.Collections.Generic;
using System.Linq;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Resources;
using System.Windows.Shapes;

namespace SchoolClient.Utils
{
internal enum PenType : byte
{
Pen = 1,
Erase = 2
};

internal class ZjbbPage
{
public List<Polyline> polylines { get; set; }
public List<Polyline> undoHistory { get; set; }

public ZjbbPage()
{
polylines = new List<Polyline>();
undoHistory = new List<Polyline>();
}
}

public class ZjBlackboard
{
private readonly Canvas _mCanvas;
private readonly Image _mEraseImg;
private PenType _type = PenType.Pen;
private int _page = 0;
private int _erasesize = 32;

private readonly TextBox _mTextbox;

// 只在mouse事件时生效
private bool _sketching;

// 定义下面的变量来解决在部分电脑上touch同时也会触发mouse事件
private bool _istouch = false;

private bool _ismouse;

private readonly List<ZjbbPage> _strokesPageAll = new List<ZjbbPage>();

public ZjBlackboard
(
Canvas canvas,
Image image,
TextBox textbox
)
{
_mCanvas = canvas;
_mEraseImg = image;
_mTextbox = textbox;
_strokesPageAll.Add(new ZjbbPage());
if (canvas != null)
{
Panel.SetZIndex(_mEraseImg, int.MaxValue);
canvas.MouseLeftButtonDown += m_mousedown;
canvas.MouseMove += Mousemove;
canvas.MouseLeftButtonUp += m_mouseup;
}
}

private void SetCursor()
{
if (_type == PenType.Pen)
{
_mCanvas.Cursor = Cursors.Arrow;
}
else
{
StreamResourceInfo sri = Application.GetResourceStream(new Uri(@"cur\erase.cur", UriKind.Relative));
if (sri != null)
{
_mCanvas.Cursor = new Cursor(sri.Stream);
}
}
}

private void m_mousedown(object sender, MouseEventArgs e)
{
if (!_istouch)
{
_ismouse = true;
if (_ismouse)
{
if (_mTextbox != null)
{
_mTextbox.Text += "mousedown" + (e.GetPosition(_mCanvas).ToString()) + "\n";
}
if (!_sketching)
{
Polyline curvePolyline = new Polyline();
if (_type == PenType.Pen)
{
curvePolyline.Stroke = Brushes.White;
curvePolyline.StrokeThickness = 2;
}
else
{
curvePolyline.Stroke = _mCanvas.Background;
curvePolyline.StrokeThickness = _erasesize;
}
curvePolyline.StrokeLineJoin = PenLineJoin.Round;
PointCollection points = new PointCollection { e.GetPosition(_mCanvas) };
curvePolyline.Points = points;
_strokesPageAll[_page].polylines.Add(curvePolyline);
_mCanvas.Children.Add(curvePolyline);
_sketching = true;
}
}
}
}

private void Mousemove(object sender, MouseEventArgs e)
{
Point point = e.GetPosition(_mCanvas);
_mEraseImg.SetValue(Canvas.LeftProperty, point.X);
_mEraseImg.SetValue(Canvas.TopProperty, point.Y);
if (_mEraseImg != null)
{
}
if (!_istouch)
{
if (_ismouse)
{
if (_mTextbox != null)
{
_mTextbox.Text += "mousemove" + (e.GetPosition(_mCanvas).ToString()) + "\n";
}
if (_sketching)
{
if (_strokesPageAll[_page].polylines.Count > 0)
{
Polyline curvePolyline = _strokesPageAll[_page].polylines.Last();
curvePolyline.Points.Add(point);
}
}
}
}
}

private void m_mouseup(object sender, MouseEventArgs e)
{
if (!_istouch)
{
if (_ismouse)
{
if (_mTextbox != null)
{
_mTextbox.Text += "mouseup" + (e.GetPosition(_mCanvas).ToString()) + "\n";
}
_sketching = false;
_ismouse = false;
}
}
}

// public方法

// 笔
public void change_pen()
{
_type = PenType.Pen;
SetCursor();
}

// 橡皮
public void change_erase()
{
_type = PenType.Erase;
SetCursor();
}

// 撤销
public void Undo()
{
ZjbbPage zjbbPage = _strokesPageAll[_page];
if (zjbbPage != null && zjbbPage.polylines.Count > 0)
{
Polyline lastline = zjbbPage.polylines.Last();
zjbbPage.polylines.Remove(lastline);
zjbbPage.undoHistory.Add(lastline);
_mCanvas.Children.Remove(lastline);
}
}

// 恢复
public void Redo()
{
ZjbbPage page = _strokesPageAll[_page];
if (page != null && page.undoHistory.Count > 0)
{
Polyline lastline = page.undoHistory.Last();
page.polylines.Add(lastline);
page.undoHistory.Remove(lastline);
_mCanvas.Children.Add(lastline);
}
}

// 清空
public void Clear()
{
ZjbbPage page = _strokesPageAll[_page];
if (page != null)
{
page.polylines.Clear();
page.undoHistory.Clear();
_mCanvas.Children.Clear();
}
}
}
}