├── .gitignore
├── AngleDrawTool.cs
├── App.config
├── App.xaml
├── App.xaml.cs
├── AreaDrawTool.cs
├── ArrowDrawTool.cs
├── Attach
└── IconElement.cs
├── ClosedCurveDrawTool.cs
├── CurveDrawTool.cs
├── DrawCursors.cs
├── DrawGeometryBase.cs
├── DrawToolType.cs
├── DrawTools.csproj
├── DrawTools.sln
├── DrawingCanvas.cs
├── DrawingCanvasViewer.cs
├── EllipseDrawTool.cs
├── EraserDrawTool.cs
├── IDrawTool.cs
├── Images
├── Cursor
│ └── hand.png
├── angle1.png
├── angle2.png
├── area1.png
├── area2.png
├── arrow1.png
├── arrow2.png
├── circle1.png
├── circle2.png
├── clear1.png
├── clear2.png
├── closedCurve1.png
├── closedCurve2.png
├── colors1.png
├── colors2.png
├── curve1.png
├── curve2.png
├── ellipse1.png
├── ellipse2.png
├── eraser1.png
├── eraser2.png
├── img1.jpg
├── line1.png
├── line2.png
├── pen1.png
├── pen2.png
├── pointer1.png
├── pointer2.png
├── polyline1.png
├── polyline2.png
├── ranging1.png
├── ranging2.png
├── rectangle1.png
├── rectangle2.png
├── text1.png
└── text2.png
├── LICENSE
├── LineDrawTool.cs
├── PenDrawTool.cs
├── PointerDrawTool.cs
├── PolygonDrawTool.cs
├── PolylineDrawTool.cs
├── Properties
├── AssemblyInfo.cs
├── Resources.Designer.cs
├── Resources.resx
├── Settings.Designer.cs
└── Settings.settings
├── RangingDrawTool.cs
├── RectangleDrawTool.cs
├── Serialize
├── DrawAngleSerializer.cs
├── DrawAreaSerializer.cs
├── DrawArrowSerializer.cs
├── DrawClosedCurveSerializer.cs
├── DrawCurveSerializer.cs
├── DrawEllipseSerializer.cs
├── DrawGeometrySerializer.cs
├── DrawGeometrySerializerBase.cs
├── DrawLineSerializer.cs
├── DrawPenSerializer.cs
├── DrawPolygonSerializer.cs
├── DrawPolylineSerializer.cs
├── DrawRangingSerializer.cs
├── DrawRectangleSerializer.cs
└── DrawTextSerializer.cs
├── TextDrawTool.cs
├── Themes
└── generic.xaml
├── Utils
├── DpiHelper.cs
├── ImageHelper.cs
├── NativeMethods.cs
└── TreeHelper.cs
├── Views
├── MainWindow.xaml
└── MainWindow.xaml.cs
└── packages.config
/.gitignore:
--------------------------------------------------------------------------------
1 | ## Ignore Visual Studio temporary files, build results, and
2 | ## files generated by popular Visual Studio add-ons.
3 | ##
4 | ## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore
5 |
6 | # User-specific files
7 | *.rsuser
8 | *.suo
9 | *.user
10 | *.userosscache
11 | *.sln.docstates
12 |
13 | # User-specific files (MonoDevelop/Xamarin Studio)
14 | *.userprefs
15 |
16 | # Mono auto generated files
17 | mono_crash.*
18 |
19 | # Build results
20 | [Dd]ebug/
21 | [Dd]ebugPublic/
22 | [Rr]elease/
23 | [Rr]eleases/
24 | x64/
25 | x86/
26 | [Aa][Rr][Mm]/
27 | [Aa][Rr][Mm]64/
28 | bld/
29 | [Bb]in/
30 | [Oo]bj/
31 | [Ll]og/
32 | [Ll]ogs/
33 |
34 | # Visual Studio 2015/2017 cache/options directory
35 | .vs/
36 | # Uncomment if you have tasks that create the project's static files in wwwroot
37 | #wwwroot/
38 |
39 | # Visual Studio 2017 auto generated files
40 | Generated\ Files/
41 |
42 | # MSTest test Results
43 | [Tt]est[Rr]esult*/
44 | [Bb]uild[Ll]og.*
45 |
46 | # NUnit
47 | *.VisualState.xml
48 | TestResult.xml
49 | nunit-*.xml
50 |
51 | # Build Results of an ATL Project
52 | [Dd]ebugPS/
53 | [Rr]eleasePS/
54 | dlldata.c
55 |
56 | # Benchmark Results
57 | BenchmarkDotNet.Artifacts/
58 |
59 | # .NET Core
60 | project.lock.json
61 | project.fragment.lock.json
62 | artifacts/
63 |
64 | # StyleCop
65 | StyleCopReport.xml
66 |
67 | # Files built by Visual Studio
68 | *_i.c
69 | *_p.c
70 | *_h.h
71 | *.ilk
72 | *.meta
73 | *.obj
74 | *.iobj
75 | *.pch
76 | *.pdb
77 | *.ipdb
78 | *.pgc
79 | *.pgd
80 | *.rsp
81 | *.sbr
82 | *.tlb
83 | *.tli
84 | *.tlh
85 | *.tmp
86 | *.tmp_proj
87 | *_wpftmp.csproj
88 | *.log
89 | *.vspscc
90 | *.vssscc
91 | .builds
92 | *.pidb
93 | *.svclog
94 | *.scc
95 |
96 | # Chutzpah Test files
97 | _Chutzpah*
98 |
99 | # Visual C++ cache files
100 | ipch/
101 | *.aps
102 | *.ncb
103 | *.opendb
104 | *.opensdf
105 | *.sdf
106 | *.cachefile
107 | *.VC.db
108 | *.VC.VC.opendb
109 |
110 | # Visual Studio profiler
111 | *.psess
112 | *.vsp
113 | *.vspx
114 | *.sap
115 |
116 | # Visual Studio Trace Files
117 | *.e2e
118 |
119 | # TFS 2012 Local Workspace
120 | $tf/
121 |
122 | # Guidance Automation Toolkit
123 | *.gpState
124 |
125 | # ReSharper is a .NET coding add-in
126 | _ReSharper*/
127 | *.[Rr]e[Ss]harper
128 | *.DotSettings.user
129 |
130 | # TeamCity is a build add-in
131 | _TeamCity*
132 |
133 | # DotCover is a Code Coverage Tool
134 | *.dotCover
135 |
136 | # AxoCover is a Code Coverage Tool
137 | .axoCover/*
138 | !.axoCover/settings.json
139 |
140 | # Visual Studio code coverage results
141 | *.coverage
142 | *.coveragexml
143 |
144 | # NCrunch
145 | _NCrunch_*
146 | .*crunch*.local.xml
147 | nCrunchTemp_*
148 |
149 | # MightyMoose
150 | *.mm.*
151 | AutoTest.Net/
152 |
153 | # Web workbench (sass)
154 | .sass-cache/
155 |
156 | # Installshield output folder
157 | [Ee]xpress/
158 |
159 | # DocProject is a documentation generator add-in
160 | DocProject/buildhelp/
161 | DocProject/Help/*.HxT
162 | DocProject/Help/*.HxC
163 | DocProject/Help/*.hhc
164 | DocProject/Help/*.hhk
165 | DocProject/Help/*.hhp
166 | DocProject/Help/Html2
167 | DocProject/Help/html
168 |
169 | # Click-Once directory
170 | publish/
171 |
172 | # Publish Web Output
173 | *.[Pp]ublish.xml
174 | *.azurePubxml
175 | # Note: Comment the next line if you want to checkin your web deploy settings,
176 | # but database connection strings (with potential passwords) will be unencrypted
177 | *.pubxml
178 | *.publishproj
179 |
180 | # Microsoft Azure Web App publish settings. Comment the next line if you want to
181 | # checkin your Azure Web App publish settings, but sensitive information contained
182 | # in these scripts will be unencrypted
183 | PublishScripts/
184 |
185 | # NuGet Packages
186 | *.nupkg
187 | # NuGet Symbol Packages
188 | *.snupkg
189 | # The packages folder can be ignored because of Package Restore
190 | **/[Pp]ackages/*
191 | # except build/, which is used as an MSBuild target.
192 | !**/[Pp]ackages/build/
193 | # Uncomment if necessary however generally it will be regenerated when needed
194 | #!**/[Pp]ackages/repositories.config
195 | # NuGet v3's project.json files produces more ignorable files
196 | *.nuget.props
197 | *.nuget.targets
198 |
199 | # Microsoft Azure Build Output
200 | csx/
201 | *.build.csdef
202 |
203 | # Microsoft Azure Emulator
204 | ecf/
205 | rcf/
206 |
207 | # Windows Store app package directories and files
208 | AppPackages/
209 | BundleArtifacts/
210 | Package.StoreAssociation.xml
211 | _pkginfo.txt
212 | *.appx
213 | *.appxbundle
214 | *.appxupload
215 |
216 | # Visual Studio cache files
217 | # files ending in .cache can be ignored
218 | *.[Cc]ache
219 | # but keep track of directories ending in .cache
220 | !?*.[Cc]ache/
221 |
222 | # Others
223 | ClientBin/
224 | ~$*
225 | *~
226 | *.dbmdl
227 | *.dbproj.schemaview
228 | *.jfm
229 | *.pfx
230 | *.publishsettings
231 | orleans.codegen.cs
232 |
233 | # Including strong name files can present a security risk
234 | # (https://github.com/github/gitignore/pull/2483#issue-259490424)
235 | #*.snk
236 |
237 | # Since there are multiple workflows, uncomment next line to ignore bower_components
238 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
239 | #bower_components/
240 |
241 | # RIA/Silverlight projects
242 | Generated_Code/
243 |
244 | # Backup & report files from converting an old project file
245 | # to a newer Visual Studio version. Backup files are not needed,
246 | # because we have git ;-)
247 | _UpgradeReport_Files/
248 | Backup*/
249 | UpgradeLog*.XML
250 | UpgradeLog*.htm
251 | ServiceFabricBackup/
252 | *.rptproj.bak
253 |
254 | # SQL Server files
255 | *.mdf
256 | *.ldf
257 | *.ndf
258 |
259 | # Business Intelligence projects
260 | *.rdl.data
261 | *.bim.layout
262 | *.bim_*.settings
263 | *.rptproj.rsuser
264 | *- [Bb]ackup.rdl
265 | *- [Bb]ackup ([0-9]).rdl
266 | *- [Bb]ackup ([0-9][0-9]).rdl
267 |
268 | # Microsoft Fakes
269 | FakesAssemblies/
270 |
271 | # GhostDoc plugin setting file
272 | *.GhostDoc.xml
273 |
274 | # Node.js Tools for Visual Studio
275 | .ntvs_analysis.dat
276 | node_modules/
277 |
278 | # Visual Studio 6 build log
279 | *.plg
280 |
281 | # Visual Studio 6 workspace options file
282 | *.opt
283 |
284 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
285 | *.vbw
286 |
287 | # Visual Studio LightSwitch build output
288 | **/*.HTMLClient/GeneratedArtifacts
289 | **/*.DesktopClient/GeneratedArtifacts
290 | **/*.DesktopClient/ModelManifest.xml
291 | **/*.Server/GeneratedArtifacts
292 | **/*.Server/ModelManifest.xml
293 | _Pvt_Extensions
294 |
295 | # Paket dependency manager
296 | .paket/paket.exe
297 | paket-files/
298 |
299 | # FAKE - F# Make
300 | .fake/
301 |
302 | # CodeRush personal settings
303 | .cr/personal
304 |
305 | # Python Tools for Visual Studio (PTVS)
306 | __pycache__/
307 | *.pyc
308 |
309 | # Cake - Uncomment if you are using it
310 | # tools/**
311 | # !tools/packages.config
312 |
313 | # Tabs Studio
314 | *.tss
315 |
316 | # Telerik's JustMock configuration file
317 | *.jmconfig
318 |
319 | # BizTalk build output
320 | *.btp.cs
321 | *.btm.cs
322 | *.odx.cs
323 | *.xsd.cs
324 |
325 | # OpenCover UI analysis results
326 | OpenCover/
327 |
328 | # Azure Stream Analytics local run output
329 | ASALocalRun/
330 |
331 | # MSBuild Binary and Structured Log
332 | *.binlog
333 |
334 | # NVidia Nsight GPU debugger configuration file
335 | *.nvuser
336 |
337 | # MFractors (Xamarin productivity tool) working folder
338 | .mfractor/
339 |
340 | # Local History for Visual Studio
341 | .localhistory/
342 |
343 | # BeatPulse healthcheck temp database
344 | healthchecksdb
345 |
346 | # Backup folder for Package Reference Convert tool in Visual Studio 2017
347 | MigrationBackup/
348 |
349 | # Ionide (cross platform F# VS Code tools) working folder
350 | .ionide/
351 |
--------------------------------------------------------------------------------
/AngleDrawTool.cs:
--------------------------------------------------------------------------------
1 | using DrawTools.Serialize;
2 | using System;
3 | using System.Windows;
4 | using System.Windows.Media;
5 |
6 | namespace DrawTools
7 | {
8 | ///
9 | /// 角度
10 | ///
11 | public sealed class AngleDrawTool : DrawGeometryBase
12 | {
13 | public AngleDrawTool(DrawingCanvas drawingCanvas) : base(drawingCanvas)
14 | {
15 | this.DrawingToolType = DrawToolType.Angle;
16 |
17 | // 准备要处理的事件
18 | this.CanTouchDown = true;
19 | }
20 |
21 | #region 鼠标键盘事件
22 |
23 | public override Boolean OnTouchLeave(Point point)
24 | {
25 | if (formattedText == null)
26 | this.drawingCanvas.DeleteVisual(this);
27 | else
28 | {
29 | var textGeometry = formattedText.BuildGeometry(textPoint);
30 | textGeometry.Transform = new RotateTransform(textAngle, textCenterPoint.X, textCenterPoint.Y);
31 | geometry = geometry.GetWidenedPathGeometry(pen);
32 | geometry = Geometry.Combine(geometry, textGeometry, GeometryCombineMode.Union, null);
33 |
34 | Draw();
35 | }
36 |
37 | this.drawingCanvas.DeleteWorkingDrawTool(this);
38 |
39 | this.IsFinish = true;
40 |
41 | this.CanTouchDown = false;
42 | this.CanTouchMove = false;
43 | this.CanTouchLeave = false;
44 |
45 | if (this.TouchId == 0 && this.drawingCanvas.IsMouseCaptured)
46 | this.drawingCanvas.ReleaseMouseCapture();
47 |
48 | return true;
49 | }
50 |
51 | public override Boolean OnTouchDown(Int32 touchId, Point point)
52 | {
53 | this.TouchId = touchId;
54 |
55 | if (!vertex.HasValue)
56 | {
57 | this.drawingCanvas.AddWorkingDrawTool(this);
58 |
59 | this.pen = this.drawingCanvas.Pen;
60 | this.minWidth = this.pen.Thickness * 20;
61 | this.arcLen = this.pen.Thickness * 15;
62 | this.arcTextLen = this.pen.Thickness * 25;
63 |
64 | this.fontSize = this.drawingCanvas.FontSize;
65 | this.typeface = new Typeface(new FontFamily("Microsoft YaHei UI,Tahoma"), FontStyles.Normal, FontWeights.Normal, FontStretches.Normal);
66 |
67 | vertex = point;
68 |
69 | geometry = new PathGeometry();
70 |
71 | var figure = new PathFigure { StartPoint = point };
72 | pathGeometry.Figures.Add(figure);
73 |
74 | this.CanTouchMove = true;
75 |
76 | if (this.TouchId != 0 || !this.drawingCanvas.CaptureMouse())
77 | this.CanTouchLeave = true;
78 |
79 | this.drawingCanvas.AddVisual(this);
80 | }
81 | else if ((point - vertex.Value).Length > minWidth && !startPoint.HasValue)
82 | {
83 | startPoint = point;
84 |
85 | var figure = pathGeometry.Figures[0];
86 | figure.StartPoint = point;
87 |
88 | points = new Point[2];
89 | points[0] = vertex.Value;
90 |
91 | var line = new LineSegment(vertex.Value, true) { IsSmoothJoin = true };
92 |
93 | if (mousePoint.HasValue)
94 | {
95 | figure.Segments[figure.Segments.Count - 1] = line;
96 | mousePoint = null;
97 | }
98 | else
99 | figure.Segments.Add(line);
100 |
101 | var dc = this.RenderOpen();
102 | dc.DrawGeometry(null, pen, geometry);
103 | dc.Close();
104 |
105 | if (point.X == vertex.Value.X)
106 | {
107 | arcPoint1.X = arcTextPoint1.X = point.X;
108 | arcPoint1.Y = vertex.Value.Y + (point.Y > vertex.Value.Y ? arcLen : -arcLen);
109 | arcTextPoint1.Y = vertex.Value.Y + (point.Y > vertex.Value.Y ? arcTextLen : -arcTextLen);
110 | }
111 | else
112 | {
113 | k1 = (point.Y - vertex.Value.Y) / (point.X - vertex.Value.X);
114 |
115 | var offsetX = Math.Sqrt(arcLen * arcLen / (k1.Value * k1.Value + 1)) * (point.X > vertex.Value.X ? 1 : -1);
116 |
117 | arcPoint1.X = vertex.Value.X + offsetX;
118 | arcPoint1.Y = vertex.Value.Y + offsetX * k1.Value;
119 |
120 | offsetX = Math.Sqrt(arcTextLen * arcTextLen / (k1.Value * k1.Value + 1)) * (point.X > vertex.Value.X ? 1 : -1);
121 |
122 | arcTextPoint1.X = vertex.Value.X + offsetX;
123 | arcTextPoint1.Y = vertex.Value.Y + offsetX * k1.Value;
124 | }
125 |
126 | figure = new PathFigure { StartPoint = arcPoint1 };
127 | pathGeometry.Figures.Add(figure);
128 |
129 | figure = new PathFigure { StartPoint = vertex.Value };
130 | pathGeometry.Figures.Add(figure);
131 | }
132 | else
133 | return OnTouchLeave(point);
134 |
135 | return true;
136 | }
137 |
138 | public override Boolean OnTouchMove(Point point)
139 | {
140 | if (!startPoint.HasValue)
141 | {
142 | // 画线
143 | var dc = this.RenderOpen();
144 |
145 | if ((point - vertex.Value).Length < minWidth)
146 | {
147 | dc.Close();
148 | return true;
149 | }
150 |
151 | var figure = pathGeometry.Figures[0];
152 | var line = new LineSegment(point, true) { IsSmoothJoin = true };
153 |
154 | if (mousePoint.HasValue)
155 | figure.Segments[figure.Segments.Count - 1] = line;
156 | else
157 | figure.Segments.Add(line);
158 |
159 | mousePoint = point;
160 |
161 | dc.DrawGeometry(null, pen, geometry);
162 | dc.Close();
163 | }
164 | else
165 | {
166 | // 画夹角
167 | if ((point - vertex.Value).Length < minWidth)
168 | return true;
169 |
170 | mousePoint = points[1] = point;
171 |
172 | var polyLine = new PolyLineSegment(points, true);
173 |
174 | var figure = pathGeometry.Figures[0];
175 | figure.Segments[figure.Segments.Count - 1] = polyLine;
176 |
177 | // 画圆弧
178 | if (point.X == vertex.Value.X)
179 | {
180 | arcPoint2.X = arcTextPoint2.X = point.X;
181 | arcPoint2.Y = vertex.Value.Y + (point.Y > vertex.Value.Y ? arcLen : -arcLen);
182 | arcTextPoint2.Y = vertex.Value.Y + (point.Y > vertex.Value.Y ? arcTextLen : -arcTextLen);
183 | }
184 | else
185 | {
186 | k2 = (point.Y - vertex.Value.Y) / (point.X - vertex.Value.X);
187 |
188 | var offsetX = Math.Sqrt(arcLen * arcLen / (k2.Value * k2.Value + 1)) * (point.X > vertex.Value.X ? 1 : -1);
189 |
190 | arcPoint2.X = vertex.Value.X + offsetX;
191 | arcPoint2.Y = vertex.Value.Y + offsetX * k2.Value;
192 |
193 | offsetX = Math.Sqrt(arcTextLen * arcTextLen / (k2.Value * k2.Value + 1)) * (point.X > vertex.Value.X ? 1 : -1);
194 |
195 | arcTextPoint2.X = vertex.Value.X + offsetX;
196 | arcTextPoint2.Y = vertex.Value.Y + offsetX * k2.Value;
197 | }
198 |
199 | if (k1.HasValue)
200 | {
201 | angle1 = Math.Atan(k1.Value) / Math.PI * 180;
202 |
203 | if (arcPoint1.X < vertex.Value.X)
204 | angle1 += 180;
205 | else if (k1.Value < 0)
206 | angle1 += 360;
207 | }
208 | else
209 | angle1 = arcPoint1.Y > vertex.Value.Y ? 90 : -90;
210 |
211 | if (k2.HasValue)
212 | {
213 | angle2 = Math.Atan(k2.Value) / Math.PI * 180;
214 |
215 | if (arcPoint2.X < vertex.Value.X)
216 | angle2 += 180;
217 | else if (k2.Value < 0)
218 | angle2 += 360;
219 | }
220 | else
221 | angle2 = arcPoint2.Y > vertex.Value.Y ? 90 : -90;
222 |
223 | angle = (angle2 + 360 - angle1) % 360;
224 | var clockwise = angle < 180;
225 | var arc = new ArcSegment(arcPoint2, new Size(arcLen, arcLen), 0, false, clockwise ? SweepDirection.Clockwise : SweepDirection.Counterclockwise, true);
226 |
227 | figure = pathGeometry.Figures[1];
228 |
229 | if (figure.Segments.Count == 0)
230 | figure.Segments.Add(arc);
231 | else
232 | figure.Segments[0] = arc;
233 |
234 | var dc = this.RenderOpen();
235 |
236 | // 画文字
237 | angle %= 180;
238 |
239 | if (!clockwise)
240 | angle = 180 - angle;
241 |
242 | var text = angle.ToString("0.00") + "°";
243 |
244 | formattedText = new FormattedText(
245 | text,
246 | System.Globalization.CultureInfo.InvariantCulture,
247 | FlowDirection.LeftToRight,
248 | typeface,
249 | this.fontSize,
250 | pen.Brush);
251 |
252 | textCenterPoint.X = (arcTextPoint1.X + arcTextPoint2.X) / 2;
253 | textCenterPoint.Y = (arcTextPoint1.Y + arcTextPoint2.Y) / 2;
254 |
255 | if (textCenterPoint.X == vertex.Value.X)
256 | {
257 | if (textCenterPoint.Y >= vertex.Value.Y)
258 | {
259 | textAngle = 90;
260 | textCenterPoint.Y = vertex.Value.Y + arcTextLen;
261 | }
262 | else
263 | {
264 | textAngle = -90;
265 | textCenterPoint.Y = vertex.Value.Y - arcTextLen;
266 | }
267 | }
268 | else
269 | {
270 | k3 = (textCenterPoint.Y - vertex.Value.Y) / (textCenterPoint.X - vertex.Value.X);
271 |
272 | textAngle = Math.Atan(k3.Value) / Math.PI * 180;
273 |
274 |
275 |
276 | var offsetX = Math.Sqrt(arcTextLen * arcTextLen / (k3.Value * k3.Value + 1)) * (textCenterPoint.X > vertex.Value.X ? 1 : -1);
277 |
278 | textCenterPoint.X = vertex.Value.X + offsetX;
279 | textCenterPoint.Y = vertex.Value.Y + offsetX * k3.Value;
280 | }
281 |
282 | figure = pathGeometry.Figures[2];
283 | var line = new LineSegment(textCenterPoint, true);
284 |
285 | if (figure.Segments.Count == 0)
286 | figure.Segments.Add(line);
287 | else
288 | figure.Segments[0] = line;
289 |
290 | dc.DrawGeometry(null, pen, geometry);
291 |
292 | dc.PushTransform(new RotateTransform(textAngle, textCenterPoint.X, textCenterPoint.Y));
293 |
294 | var _textAngle = textAngle;
295 |
296 | textPoint.X = textCenterPoint.X;
297 | textPoint.Y = textCenterPoint.Y - fontSize / 2;
298 |
299 | if (textPoint.X < vertex.Value.X)
300 | _textAngle += 180;
301 | else if (k3.Value < 0)
302 | _textAngle += 360;
303 |
304 | if (_textAngle >= 90 && _textAngle <= 270)
305 | {
306 | var width = text.Length * fontSize / 2 + fontSize;
307 | textPoint.X -= width;
308 | }
309 | else
310 | textPoint.X += fontSize / 2;
311 |
312 | dc.DrawText(formattedText, textPoint);
313 | dc.Pop();
314 |
315 | dc.Close();
316 | }
317 |
318 | return true;
319 | }
320 |
321 | #endregion
322 |
323 | #region 序列化
324 |
325 | public override DrawGeometrySerializerBase ToSerializer()
326 | {
327 | var serializer = new DrawAngleSerializer
328 | {
329 | Color = ((SolidColorBrush)pen.Brush).Color,
330 | StrokeThickness = pen.Thickness,
331 | Geometry = geometry.ToString()
332 | };
333 |
334 | if (geometry.Transform != null)
335 | serializer.Matrix = geometry.Transform.Value;
336 |
337 | return serializer;
338 | }
339 |
340 | public override void DeserializeFrom(DrawGeometrySerializerBase serializer)
341 | {
342 | this.pen = new Pen(new SolidColorBrush(serializer.Color), serializer.StrokeThickness);
343 |
344 | this.geometry = Geometry.Parse(serializer.Geometry).GetFlattenedPathGeometry();
345 | this.geometry.Transform = new TranslateTransform(serializer.Matrix.OffsetX, serializer.Matrix.OffsetY);
346 |
347 | this.IsFinish = true;
348 |
349 | this.Draw();
350 | }
351 |
352 | #endregion
353 |
354 | #region 字段
355 |
356 | private Point? vertex, startPoint, mousePoint; // 顶点、开始描点、鼠标移动点
357 | private Point[] points; // 角度连线
358 | private Point arcPoint1, arcPoint2; // 圆弧起始点
359 | private Point arcTextPoint1, arcTextPoint2, textCenterPoint, textPoint; // 文字圆弧起始点,及中点
360 | private Double? k1, k2, k3; // 两直线斜率,及文字斜率
361 | private Double angle, angle1, angle2, arcLen; // 两直线夹角, 及圆弧半径,
362 | private Double textAngle, arcTextLen; // 文字夹角,文字圆弧半径
363 | private Double minWidth;
364 | private Double fontSize;
365 | private Typeface typeface;
366 | private FormattedText formattedText;
367 |
368 | #endregion
369 | }
370 | }
371 |
--------------------------------------------------------------------------------
/App.config:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/App.xaml:
--------------------------------------------------------------------------------
1 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/App.xaml.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Configuration;
4 | using System.Data;
5 | using System.Linq;
6 | using System.Threading.Tasks;
7 | using System.Windows;
8 |
9 | namespace DrawTools
10 | {
11 | ///
12 | /// App.xaml 的交互逻辑
13 | ///
14 | public partial class App : Application
15 | {
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/AreaDrawTool.cs:
--------------------------------------------------------------------------------
1 | using DrawTools.Serialize;
2 | using DrawTools.Utils;
3 | using System;
4 | using System.Collections.Generic;
5 | using System.Linq;
6 | using System.Windows;
7 | using System.Windows.Media;
8 |
9 | namespace DrawTools
10 | {
11 | ///
12 | /// 面积
13 | ///
14 | public sealed class AreaDrawTool : DrawGeometryBase
15 | {
16 | public AreaDrawTool(DrawingCanvas drawingCanvas) : base(drawingCanvas)
17 | {
18 | this.DrawingToolType = DrawToolType.Area;
19 |
20 | // 准备要处理的事件
21 | this.CanTouchDown = true;
22 | }
23 |
24 | #region 鼠标键盘事件
25 |
26 | public override Boolean OnTouchLeave(Point point)
27 | {
28 | if (mousePoint.HasValue)
29 | {
30 | points.Add(mousePoint.Value);
31 | mousePoint = null;
32 | }
33 |
34 | if (area == 0)
35 | this.drawingCanvas.DeleteVisual(this);
36 | else
37 | {
38 | var textGeometry = formattedText.BuildGeometry(textPoint);
39 | geometry = geometry.GetWidenedPathGeometry(pen);
40 | geometry = Geometry.Combine(geometry, textGeometry, GeometryCombineMode.Union, null);
41 |
42 | Draw();
43 | }
44 |
45 | this.drawingCanvas.DeleteWorkingDrawTool(this);
46 |
47 | this.IsFinish = true;
48 |
49 | this.CanTouchDown = false;
50 | this.CanTouchMove = false;
51 | this.CanTouchLeave = false;
52 |
53 | if (this.TouchId == 0 && this.drawingCanvas.IsMouseCaptured)
54 | this.drawingCanvas.ReleaseMouseCapture();
55 |
56 | return true;
57 | }
58 |
59 | public override Boolean OnTouchDown(Int32 touchId, Point point)
60 | {
61 | this.TouchId = touchId;
62 |
63 | if (points.Count == 0)
64 | {
65 | this.drawingCanvas.AddWorkingDrawTool(this);
66 |
67 | this.pen = this.drawingCanvas.Pen;
68 | this.fontSize = this.drawingCanvas.FontSize;
69 | this.typeface = new Typeface(new FontFamily("Microsoft YaHei UI,Tahoma"), FontStyles.Normal, FontWeights.Normal, FontStretches.Normal);
70 |
71 | this.dpi = DpiHelper.GetDpiFromVisual(this.drawingCanvas);
72 |
73 | points.Add(point);
74 |
75 | geometry = new PathGeometry();
76 |
77 | var figure = new PathFigure { StartPoint = point };
78 | pathGeometry.Figures.Add(figure);
79 |
80 | this.CanTouchMove = true;
81 |
82 | if (this.TouchId != 0 || !this.drawingCanvas.CaptureMouse())
83 | this.CanTouchLeave = true;
84 |
85 | this.drawingCanvas.AddVisual(this);
86 | }
87 | else if ((point - points.Last()).Length <= pen.Thickness)
88 | return OnTouchLeave(point);
89 | else if (mousePoint.HasValue)
90 | {
91 | points.Add(mousePoint.Value);
92 | mousePoint = null;
93 | }
94 |
95 | return true;
96 | }
97 |
98 | public override Boolean OnTouchMove(Point point)
99 | {
100 | if ((point - points.Last()).Length <= pen.Thickness)
101 | return true;
102 |
103 | var figure = pathGeometry.Figures[0];
104 | var line = new LineSegment(point, true) { IsSmoothJoin = true };
105 |
106 | if (mousePoint.HasValue)
107 | figure.Segments[figure.Segments.Count - 1] = line;
108 | else
109 | figure.Segments.Add(line);
110 |
111 | mousePoint = point;
112 |
113 | figure.IsClosed = figure.Segments.Count >= 2;
114 |
115 | var dc = this.RenderOpen();
116 |
117 | dc.DrawGeometry(null, pen, geometry);
118 |
119 | if (figure.IsClosed)
120 | {
121 | area = geometry.GetArea();
122 |
123 | if (area != 0)
124 | {
125 | area = area / Dpi.Cm2Wpf / Dpi.Cm2Wpf * 100;
126 |
127 | var text = area.ToString("0.00") + "mm²";
128 |
129 | formattedText = new FormattedText(
130 | text,
131 | System.Globalization.CultureInfo.InvariantCulture,
132 | FlowDirection.LeftToRight,
133 | typeface,
134 | this.fontSize,
135 | pen.Brush);
136 |
137 | var width = text.Length * fontSize / 2;
138 |
139 | textPoint.X = (points.Sum(q => q.X) + point.X) / (points.Count + 1) - width / 2;
140 | textPoint.Y = (points.Sum(q => q.Y) + point.Y) / (points.Count + 1) - fontSize / 2;
141 |
142 | dc.DrawText(formattedText, textPoint);
143 | }
144 | }
145 |
146 | dc.Close();
147 |
148 | return true;
149 | }
150 |
151 | #endregion
152 |
153 | #region 序列化
154 |
155 | public override DrawGeometrySerializerBase ToSerializer()
156 | {
157 | var serializer = new DrawAreaSerializer
158 | {
159 | Color = ((SolidColorBrush)pen.Brush).Color,
160 | StrokeThickness = pen.Thickness,
161 | Geometry = geometry.ToString()
162 | };
163 |
164 | if (geometry.Transform != null)
165 | serializer.Matrix = geometry.Transform.Value;
166 |
167 | return serializer;
168 | }
169 |
170 | public override void DeserializeFrom(DrawGeometrySerializerBase serializer)
171 | {
172 | this.pen = new Pen(new SolidColorBrush(serializer.Color), serializer.StrokeThickness);
173 |
174 | this.geometry = Geometry.Parse(serializer.Geometry).GetFlattenedPathGeometry();
175 | this.geometry.Transform = new TranslateTransform(serializer.Matrix.OffsetX, serializer.Matrix.OffsetY);
176 |
177 | this.IsFinish = true;
178 |
179 | this.Draw();
180 | }
181 |
182 | #endregion
183 |
184 | #region 字段
185 |
186 | private List points = new List();
187 | private Point? mousePoint;
188 | private Point textPoint;
189 | private Double fontSize;
190 | private Typeface typeface;
191 | private Double area;
192 | private FormattedText formattedText;
193 | private Dpi dpi;
194 |
195 | #endregion
196 | }
197 | }
198 |
--------------------------------------------------------------------------------
/ArrowDrawTool.cs:
--------------------------------------------------------------------------------
1 | using DrawTools.Serialize;
2 | using System;
3 | using System.Windows;
4 | using System.Windows.Media;
5 |
6 | namespace DrawTools
7 | {
8 | ///
9 | /// 箭头
10 | ///
11 | public sealed class ArrowDrawTool : DrawGeometryBase
12 | {
13 | public ArrowDrawTool(DrawingCanvas drawingCanvas) : base(drawingCanvas)
14 | {
15 | this.DrawingToolType = DrawToolType.Arrow;
16 |
17 | // 准备要处理的事件
18 | this.CanTouchDown = true;
19 | }
20 |
21 | #region 鼠标键盘事件
22 |
23 | public override Boolean OnTouchLeave(Point point)
24 | {
25 | if (!endPoint.HasValue)
26 | this.drawingCanvas.DeleteVisual(this);
27 | else
28 | {
29 | geometry = geometry.GetWidenedPathGeometry(pen);
30 | Draw();
31 | }
32 |
33 | if (this.TouchId == 0 && this.drawingCanvas.IsMouseCaptured)
34 | this.drawingCanvas.ReleaseMouseCapture();
35 |
36 | this.drawingCanvas.DeleteWorkingDrawTool(this);
37 |
38 | this.IsFinish = true;
39 |
40 | this.CanKeyDown = false;
41 | this.CanTouchMove = false;
42 | this.CanTouchLeave = false;
43 |
44 | return true;
45 | }
46 |
47 | public override Boolean OnTouchDown(Int32 touchId, Point point)
48 | {
49 | this.TouchId = touchId;
50 |
51 | if (!startPoint.HasValue)
52 | {
53 | this.drawingCanvas.AddWorkingDrawTool(this);
54 |
55 | this.pen = this.drawingCanvas.Pen;
56 |
57 | startPoint = point;
58 |
59 | geometry = new PathGeometry();
60 |
61 | var figure = new PathFigure { StartPoint = point };
62 | pathGeometry.Figures.Add(figure);
63 | figure = new PathFigure();
64 | pathGeometry.Figures.Add(figure);
65 |
66 | arrowPoints = new Point[2];
67 |
68 | this.CanTouchMove = true;
69 |
70 | if (this.TouchId != 0 || !this.drawingCanvas.CaptureMouse())
71 | this.CanTouchLeave = true;
72 |
73 | this.drawingCanvas.AddVisual(this);
74 |
75 | return true;
76 | }
77 | else
78 | return OnTouchLeave(point);
79 | }
80 |
81 | public override Boolean OnTouchMove(Point point)
82 | {
83 | var dc = this.RenderOpen();
84 | var start = startPoint.Value;
85 |
86 | if ((point - start).Length <= pen.Thickness * 6)
87 | {
88 | dc.Close();
89 | return true;
90 | }
91 |
92 | var figure = pathGeometry.Figures[0];
93 |
94 | var line = new LineSegment(point, true);
95 |
96 | if (endPoint.HasValue)
97 | figure.Segments[0] = line;
98 | else
99 | figure.Segments.Add(line);
100 |
101 | figure = pathGeometry.Figures[1];
102 |
103 | arrowPoints[0] = point;
104 | Double? k = null; // 斜率
105 | var len = pen.Thickness * 6;
106 |
107 | if (start.X != point.X)
108 | k = (point.Y - start.Y) / (point.X - start.X);
109 |
110 | if (k.HasValue)
111 | {
112 | var angle = Math.Atan(k.Value) / Math.PI * 180;
113 | var center = new Point();
114 | var offsetX = Math.Sqrt(len * len / (k.Value * k.Value + 1)) * (point.X > start.X ? -1 : 1);
115 |
116 | center.X = point.X + offsetX;
117 | center.Y = point.Y + offsetX * k.Value;
118 |
119 | len /= 2;
120 | k = -1 / k;
121 |
122 | offsetX = Math.Sqrt(len * len / (k.Value * k.Value + 1)) * (angle > 0 ? 1 : -1);
123 |
124 | arrowStartPoint.X = center.X + offsetX;
125 | arrowStartPoint.Y = center.Y + offsetX * k.Value;
126 |
127 | arrowPoints[1].X = center.X - offsetX;
128 | arrowPoints[1].Y = center.Y - offsetX * k.Value;
129 | }
130 | else
131 | {
132 | if (start.Y > point.Y)
133 | {
134 | // 箭头向上
135 | arrowStartPoint.X = point.X - len / 2;
136 | arrowPoints[1].X = point.X + len / 2;
137 | arrowStartPoint.Y = arrowPoints[1].Y = point.Y + len;
138 | }
139 | else
140 | {
141 | arrowStartPoint.X = point.X + len / 2;
142 | arrowPoints[1].X = point.X - len / 2;
143 | arrowStartPoint.Y = arrowPoints[1].Y = point.Y - len;
144 | }
145 | }
146 |
147 | figure.StartPoint = arrowStartPoint;
148 | var polyLine = new PolyLineSegment(arrowPoints, true);
149 |
150 | if (endPoint.HasValue)
151 | figure.Segments[0] = polyLine;
152 | else
153 | figure.Segments.Add(polyLine);
154 |
155 | endPoint = point;
156 |
157 | dc.DrawGeometry(null, pen, geometry);
158 | dc.Close();
159 |
160 | return true;
161 | }
162 |
163 | #endregion
164 |
165 | #region 序列化
166 |
167 | public override DrawGeometrySerializerBase ToSerializer()
168 | {
169 | var serializer = new DrawArrowSerializer
170 | {
171 | Color = ((SolidColorBrush)pen.Brush).Color,
172 | StrokeThickness = pen.Thickness,
173 | Geometry = geometry.ToString()
174 | };
175 |
176 | if (geometry.Transform != null)
177 | serializer.Matrix = geometry.Transform.Value;
178 |
179 | return serializer;
180 | }
181 |
182 | public override void DeserializeFrom(DrawGeometrySerializerBase serializer)
183 | {
184 | this.pen = new Pen(new SolidColorBrush(serializer.Color), serializer.StrokeThickness);
185 |
186 | this.geometry = Geometry.Parse(serializer.Geometry).GetFlattenedPathGeometry();
187 | this.geometry.Transform = new TranslateTransform(serializer.Matrix.OffsetX, serializer.Matrix.OffsetY);
188 |
189 | this.IsFinish = true;
190 |
191 | this.Draw();
192 | }
193 |
194 | #endregion
195 |
196 | #region 字段
197 |
198 | private Point? startPoint, endPoint;
199 | private Point arrowStartPoint;
200 | private Point[] arrowPoints;
201 |
202 | #endregion
203 | }
204 | }
205 |
--------------------------------------------------------------------------------
/Attach/IconElement.cs:
--------------------------------------------------------------------------------
1 | using System.Windows;
2 | using System.Windows.Media;
3 |
4 | namespace DrawTools.Attach
5 | {
6 | public static class IconElement
7 | {
8 | public static readonly DependencyProperty ImageProperty = DependencyProperty.RegisterAttached(
9 | "Image", typeof(ImageSource), typeof(IconElement));
10 |
11 | public static void SetImage(DependencyObject element, ImageSource value) => element.SetValue(ImageProperty, value);
12 |
13 | public static ImageSource GetImage(DependencyObject element) => (ImageSource)element.GetValue(ImageProperty);
14 |
15 | public static readonly DependencyProperty ImageSelectedProperty = DependencyProperty.RegisterAttached(
16 | "ImageSelected", typeof(ImageSource), typeof(IconElement));
17 |
18 | public static void SetImageSelected(DependencyObject element, ImageSource value) => element.SetValue(ImageSelectedProperty, value);
19 |
20 | public static ImageSource GetImageSelected(DependencyObject element) => (ImageSource)element.GetValue(ImageSelectedProperty);
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/ClosedCurveDrawTool.cs:
--------------------------------------------------------------------------------
1 | using DrawTools.Serialize;
2 | using System;
3 | using System.Collections.Generic;
4 | using System.Linq;
5 | using System.Windows;
6 | using System.Windows.Media;
7 |
8 | namespace DrawTools
9 | {
10 | ///
11 | /// 曲线
12 | ///
13 | public sealed class ClosedCurveDrawTool : DrawGeometryBase
14 | {
15 | public ClosedCurveDrawTool(DrawingCanvas drawingCanvas) : base(drawingCanvas)
16 | {
17 | this.DrawingToolType = DrawToolType.ClosedCurve;
18 |
19 | // 准备要处理的事件
20 | this.CanTouchDown = true;
21 | }
22 |
23 | #region 鼠标键盘事件
24 |
25 | public override Boolean OnTouchLeave(Point point)
26 | {
27 | var figure = pathGeometry.Figures[0];
28 | figure.IsClosed = true;
29 |
30 | if (mousePoint.HasValue)
31 | {
32 | points.Add(mousePoint.Value);
33 | mousePoint = null;
34 | }
35 |
36 | point = points.Last();
37 | var first = points[0];
38 |
39 | if ((point - first).Length > pen.Thickness)
40 | {
41 | var centerX = (first.X + point.X) / 2;
42 | var bezier = new BezierSegment(new Point(centerX, point.Y), new Point(centerX, first.Y), first, true) { IsSmoothJoin = true };
43 |
44 | if (mousePoint.HasValue)
45 | figure.Segments[figure.Segments.Count - 1] = bezier;
46 | else
47 | {
48 | figure.Segments.Add(bezier);
49 | mousePoint = null;
50 | }
51 | }
52 |
53 | if (points.Count < 3)
54 | this.drawingCanvas.DeleteVisual(this);
55 | else
56 | {
57 | geometry = geometry.GetWidenedPathGeometry(pen);
58 | Draw();
59 | }
60 |
61 | this.drawingCanvas.DeleteWorkingDrawTool(this);
62 |
63 | this.IsFinish = true;
64 |
65 | this.CanTouchDown = false;
66 | this.CanTouchMove = false;
67 | this.CanTouchLeave = false;
68 |
69 | if (this.TouchId == 0 && this.drawingCanvas.IsMouseCaptured)
70 | this.drawingCanvas.ReleaseMouseCapture();
71 |
72 | return true;
73 | }
74 |
75 | public override Boolean OnTouchDown(Int32 touchId, Point point)
76 | {
77 | this.TouchId = touchId;
78 |
79 | if (points.Count == 0)
80 | {
81 | this.drawingCanvas.AddWorkingDrawTool(this);
82 |
83 | this.pen = this.drawingCanvas.Pen;
84 |
85 | points.Add(point);
86 |
87 | geometry = new PathGeometry();
88 |
89 | var figure = new PathFigure { StartPoint = point };
90 | pathGeometry.Figures.Add(figure);
91 |
92 | this.CanTouchMove = true;
93 |
94 | if (this.TouchId != 0 || !this.drawingCanvas.CaptureMouse())
95 | this.CanTouchLeave = true;
96 |
97 | this.drawingCanvas.AddVisual(this);
98 | }
99 | else if ((point - points.Last()).Length <= pen.Thickness)
100 | return OnTouchLeave(point);
101 | else if (mousePoint.HasValue)
102 | {
103 | points.Add(mousePoint.Value);
104 | mousePoint = null;
105 | }
106 |
107 | return true;
108 | }
109 |
110 | public override Boolean OnTouchMove(Point point)
111 | {
112 | var last = points.Last();
113 |
114 | if ((point - last).Length <= pen.Thickness)
115 | return true;
116 |
117 | var figure = pathGeometry.Figures[0];
118 | var centerX = (last.X + point.X) / 2;
119 | var bezier = new BezierSegment(new Point(centerX, last.Y), new Point(centerX, point.Y), point, true) { IsSmoothJoin = true };
120 |
121 | if (mousePoint.HasValue)
122 | figure.Segments[figure.Segments.Count - 1] = bezier;
123 | else
124 | figure.Segments.Add(bezier);
125 |
126 | mousePoint = point;
127 |
128 | var dc = this.RenderOpen();
129 | dc.DrawGeometry(null, pen, geometry);
130 | dc.Close();
131 |
132 | return true;
133 | }
134 |
135 | #endregion
136 |
137 | #region 序列化
138 |
139 | public override DrawGeometrySerializerBase ToSerializer()
140 | {
141 | var serializer = new DrawClosedCurveSerializer
142 | {
143 | Color = ((SolidColorBrush)pen.Brush).Color,
144 | StrokeThickness = pen.Thickness,
145 | Geometry = geometry.ToString()
146 | };
147 |
148 | if (geometry.Transform != null)
149 | serializer.Matrix = geometry.Transform.Value;
150 |
151 | return serializer;
152 | }
153 |
154 | public override void DeserializeFrom(DrawGeometrySerializerBase serializer)
155 | {
156 | this.pen = new Pen(new SolidColorBrush(serializer.Color), serializer.StrokeThickness);
157 |
158 | this.geometry = Geometry.Parse(serializer.Geometry).GetFlattenedPathGeometry();
159 | this.geometry.Transform = new TranslateTransform(serializer.Matrix.OffsetX, serializer.Matrix.OffsetY);
160 |
161 | this.IsFinish = true;
162 |
163 | this.Draw();
164 | }
165 |
166 | #endregion
167 |
168 | #region 字段
169 |
170 | private List points = new List();
171 | private Point? mousePoint;
172 |
173 | #endregion
174 | }
175 | }
176 |
--------------------------------------------------------------------------------
/CurveDrawTool.cs:
--------------------------------------------------------------------------------
1 | using DrawTools.Serialize;
2 | using System;
3 | using System.Collections.Generic;
4 | using System.Linq;
5 | using System.Windows;
6 | using System.Windows.Media;
7 |
8 | namespace DrawTools
9 | {
10 | ///
11 | /// 曲线
12 | ///
13 | public sealed class CurveDrawTool : DrawGeometryBase
14 | {
15 | public CurveDrawTool(DrawingCanvas drawingCanvas) : base(drawingCanvas)
16 | {
17 | this.DrawingToolType = DrawToolType.Curve;
18 |
19 | // 准备要处理的事件
20 | this.CanTouchDown = true;
21 | }
22 |
23 | #region 鼠标键盘事件
24 |
25 | public override Boolean OnTouchLeave(Point point)
26 | {
27 | if (mousePoint.HasValue)
28 | {
29 | points.Add(mousePoint.Value);
30 | mousePoint = null;
31 | }
32 |
33 | if (points.Count < 2)
34 | this.drawingCanvas.DeleteVisual(this);
35 | else
36 | {
37 | geometry = geometry.GetWidenedPathGeometry(pen);
38 | Draw();
39 | }
40 |
41 | this.drawingCanvas.DeleteWorkingDrawTool(this);
42 |
43 | this.IsFinish = true;
44 |
45 | this.CanTouchDown = false;
46 | this.CanTouchMove = false;
47 | this.CanTouchLeave = false;
48 |
49 | if (this.TouchId == 0 && this.drawingCanvas.IsMouseCaptured)
50 | this.drawingCanvas.ReleaseMouseCapture();
51 |
52 | return true;
53 | }
54 |
55 | public override Boolean OnTouchDown(Int32 touchId, Point point)
56 | {
57 | this.TouchId = touchId;
58 |
59 | if (points.Count == 0)
60 | {
61 | this.drawingCanvas.AddWorkingDrawTool(this);
62 |
63 | this.pen = this.drawingCanvas.Pen;
64 |
65 | points.Add(point);
66 |
67 | geometry = new PathGeometry();
68 |
69 | var figure = new PathFigure { StartPoint = point };
70 | pathGeometry.Figures.Add(figure);
71 |
72 | this.CanTouchMove = true;
73 |
74 | if (this.TouchId != 0 || !this.drawingCanvas.CaptureMouse())
75 | this.CanTouchLeave = true;
76 |
77 | this.drawingCanvas.AddVisual(this);
78 | }
79 | else if ((point - points.Last()).Length <= pen.Thickness)
80 | return OnTouchLeave(point);
81 | else if (mousePoint.HasValue)
82 | {
83 | points.Add(mousePoint.Value);
84 | mousePoint = null;
85 | }
86 |
87 | return true;
88 | }
89 |
90 | public override Boolean OnTouchMove(Point point)
91 | {
92 | var last = points.Last();
93 |
94 | if ((point - last).Length <= pen.Thickness)
95 | return true;
96 |
97 | var figure = pathGeometry.Figures[0];
98 | var centerX = (last.X + point.X) / 2;
99 | var bezier = new BezierSegment(new Point(centerX, last.Y), new Point(centerX, point.Y), point, true) { IsSmoothJoin = true };
100 |
101 | if (mousePoint.HasValue)
102 | figure.Segments[figure.Segments.Count - 1] = bezier;
103 | else
104 | figure.Segments.Add(bezier);
105 |
106 | mousePoint = point;
107 |
108 | var dc = this.RenderOpen();
109 | dc.DrawGeometry(null, pen, geometry);
110 | dc.Close();
111 |
112 | return true;
113 | }
114 |
115 | #endregion
116 |
117 | #region 序列化
118 |
119 | public override DrawGeometrySerializerBase ToSerializer()
120 | {
121 | var serializer = new DrawCurveSerializer
122 | {
123 | Color = ((SolidColorBrush)pen.Brush).Color,
124 | StrokeThickness = pen.Thickness,
125 | Geometry = geometry.ToString()
126 | };
127 |
128 | if (geometry.Transform != null)
129 | serializer.Matrix = geometry.Transform.Value;
130 |
131 | return serializer;
132 | }
133 |
134 | public override void DeserializeFrom(DrawGeometrySerializerBase serializer)
135 | {
136 | this.pen = new Pen(new SolidColorBrush(serializer.Color), serializer.StrokeThickness);
137 |
138 | this.geometry = Geometry.Parse(serializer.Geometry).GetFlattenedPathGeometry();
139 | this.geometry.Transform = new TranslateTransform(serializer.Matrix.OffsetX, serializer.Matrix.OffsetY);
140 |
141 | this.IsFinish = true;
142 |
143 | this.Draw();
144 | }
145 |
146 | #endregion
147 |
148 | #region 字段
149 |
150 | private List points = new List();
151 | private Point? mousePoint;
152 |
153 | #endregion
154 | }
155 | }
156 |
--------------------------------------------------------------------------------
/DrawCursors.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.Win32.SafeHandles;
2 | using System;
3 | using System.Drawing;
4 | using System.Drawing.Drawing2D;
5 | using System.Runtime.InteropServices;
6 | using System.Security.Permissions;
7 | using System.Windows;
8 | using System.Windows.Input;
9 | using System.Windows.Interop;
10 |
11 | namespace DrawTools
12 | {
13 | public static class DrawCursors
14 | {
15 | public static Cursor CreateBmpCursor(Bitmap bmp, UInt32 xHotSpot, UInt32 yHotSpot)
16 | {
17 | if (!NativeMethods.GetIconInfo(bmp.GetHicon(), out IconInfo iconInfo))
18 | return Cursors.Arrow;
19 |
20 | iconInfo.xHotspot = xHotSpot; // 焦点x轴坐标
21 | iconInfo.yHotspot = yHotSpot; // 焦点y轴坐标
22 | iconInfo.fIcon = false; // 设置鼠标
23 |
24 | var cursorHandle = NativeMethods.CreateIconIndirect(ref iconInfo);
25 | return CursorInteropHelper.Create(cursorHandle);
26 | }
27 |
28 | public static Cursor CreateBmpCursor(Uri uri, UInt32 xHotSpot, UInt32 yHotSpot)
29 | {
30 | var sri = Application.GetResourceStream(uri);
31 |
32 | using (var bmp = new Bitmap(sri.Stream))
33 | {
34 | return CreateBmpCursor(bmp, xHotSpot, yHotSpot);
35 | }
36 | }
37 |
38 | public static Cursor CreateBmpCursor(UInt32 width, UInt32 height, UInt32 border, System.Windows.Media.SolidColorBrush brush)
39 | {
40 | using (var bmp = new Bitmap((Int32)(width + border * 2), (Int32)(height + border * 2)))
41 | {
42 | using (var g = Graphics.FromImage(bmp))
43 | {
44 | g.SmoothingMode = SmoothingMode.HighQuality;
45 | g.InterpolationMode = InterpolationMode.HighQualityBicubic;
46 |
47 | var color = brush.Color;
48 | using (var pen = new Pen(Color.FromArgb(color.A, color.R, color.G, color.B), border))
49 | {
50 | g.DrawRectangle(pen, border, border, width, height);
51 | g.Flush();
52 | }
53 | }
54 |
55 | return CreateBmpCursor(bmp, width / 2 + border, height / 2 + border);
56 | }
57 | }
58 |
59 | private static Lazy hand = new Lazy(() => { return CreateBmpCursor(new Uri("Images/Cursor/hand.png", UriKind.Relative), 12, 12); });
60 | ///
61 | /// 拖动
62 | ///
63 | public static Cursor Hand => hand.Value;
64 | }
65 |
66 | public static class NativeMethods
67 | {
68 | [DllImport("user32.dll")]
69 | public static extern SafeIconHandle CreateIconIndirect(ref IconInfo icon);
70 |
71 | [DllImport("user32.dll")]
72 | public static extern Boolean DestroyIcon(IntPtr hIcon);
73 |
74 | [DllImport("user32.dll")]
75 | [return: MarshalAs(UnmanagedType.Bool)]
76 | public static extern Boolean GetIconInfo(IntPtr hIcon, out IconInfo pIconInfo);
77 | }
78 |
79 | public struct IconInfo
80 | {
81 | public Boolean fIcon;
82 | public UInt32 xHotspot;
83 | public UInt32 yHotspot;
84 | public IntPtr hbmMask;
85 | public IntPtr hbmColor;
86 | }
87 |
88 | [SecurityPermission(SecurityAction.LinkDemand, UnmanagedCode = true)]
89 | public class SafeIconHandle : SafeHandleZeroOrMinusOneIsInvalid
90 | {
91 | public SafeIconHandle() : base(true) { }
92 | protected override Boolean ReleaseHandle() => NativeMethods.DestroyIcon(handle);
93 | }
94 | }
95 |
--------------------------------------------------------------------------------
/DrawGeometryBase.cs:
--------------------------------------------------------------------------------
1 | using DrawTools.Serialize;
2 | using System;
3 | using System.Windows;
4 | using System.Windows.Input;
5 | using System.Windows.Media;
6 |
7 | namespace DrawTools
8 | {
9 | ///
10 | /// 画图几何图形基类
11 | ///
12 | public abstract class DrawGeometryBase : DrawingVisual, IDrawTool
13 | {
14 | public DrawGeometryBase(DrawingCanvas drawingCanvas)
15 | {
16 | this.drawingCanvas = drawingCanvas;
17 | }
18 |
19 | #region 鼠标键盘事件
20 |
21 | public virtual Boolean OnKeyDown(Key key) => false;
22 |
23 | public virtual Boolean OnKeyUp(Key key) => false;
24 |
25 | public virtual Boolean OnTouchDown(Int32 touchId, Point point) => false;
26 |
27 | public virtual Boolean OnTouchEnter(Point point) => false;
28 |
29 | public virtual Boolean OnTouchLeave(Point point) => false;
30 |
31 | public virtual Boolean OnTouchMove(Point point) => false;
32 |
33 | public virtual Boolean OnTouchUp(Point point) => false;
34 |
35 | #endregion
36 |
37 | #region 绘图事件
38 |
39 | public virtual void Draw()
40 | {
41 | var dc = this.RenderOpen();
42 | dc.DrawGeometry(pen.Brush, null, geometry);
43 | dc.Close();
44 | }
45 |
46 | public virtual Boolean Erase(Geometry erase)
47 | {
48 | geometry = Geometry.Combine(geometry, erase, GeometryCombineMode.Exclude, null);
49 |
50 | if (geometry.IsEmpty())
51 | return true;
52 |
53 | Draw();
54 |
55 | return false;
56 | }
57 |
58 | public virtual Boolean Select(Point point)
59 | {
60 | return geometry.FillContains(point);
61 | }
62 |
63 | public virtual Boolean Select(Geometry select)
64 | {
65 | return !Geometry.Combine(geometry, select, GeometryCombineMode.Intersect, null).IsEmpty();
66 | }
67 |
68 | public virtual Rect Selected()
69 | {
70 | if (Mode == 1)
71 | return selectRect;
72 |
73 | Mode = 1;
74 |
75 | var dc = this.RenderOpen();
76 |
77 | dc.DrawGeometry(pen.Brush, null, geometry);
78 |
79 | selectRect = GetRenderBounds();
80 |
81 | dc.DrawRectangle(Brushes.Transparent, this.drawingCanvas.SelectBackgroundPen, selectRect);
82 | dc.DrawRectangle(null, this.drawingCanvas.SelectPen, selectRect);
83 |
84 | dc.Close();
85 |
86 | return selectRect;
87 | }
88 |
89 | public virtual void Unselected()
90 | {
91 | if (Mode == 0)
92 | return;
93 |
94 | Mode = 0;
95 |
96 | Draw();
97 | }
98 |
99 | protected virtual Rect GetRenderBounds()
100 | {
101 | return geometry.GetRenderBounds(this.drawingCanvas.SelectPen);
102 | }
103 |
104 | public virtual void Move(Double dx, Double dy)
105 | {
106 | if (geometry.Transform == null)
107 | geometry.Transform = new TranslateTransform(dx, dy);
108 | else
109 | {
110 | var translate = (TranslateTransform)geometry.Transform;
111 | translate.X += dx;
112 | translate.Y += dy;
113 | }
114 |
115 | if (Mode == 1)
116 | {
117 | Mode = 0;
118 | Selected();
119 | }
120 | else
121 | Draw();
122 | }
123 |
124 | public virtual void Edit() { }
125 |
126 | #endregion
127 |
128 | #region 序列化
129 |
130 | public virtual DrawGeometrySerializerBase ToSerializer() => null;
131 |
132 | public virtual void DeserializeFrom(DrawGeometrySerializerBase serializer) { }
133 |
134 | #endregion
135 |
136 | #region 属性
137 |
138 | public Int32 TouchId { get; protected set; }
139 |
140 | public Boolean CanTouchEnter { get; protected set; }
141 |
142 | public Boolean CanTouchLeave { get; protected set; }
143 |
144 | public Boolean CanTouchDown { get; protected set; }
145 |
146 | public Boolean CanTouchMove { get; protected set; }
147 |
148 | public Boolean CanTouchUp { get; protected set; }
149 |
150 | public Boolean CanKeyDown { get; protected set; }
151 |
152 | public Boolean CanKeyUp { get; protected set; }
153 |
154 | public Boolean IsFinish { get; protected set; }
155 |
156 | public DrawToolType DrawingToolType { get; protected set; }
157 |
158 | public Boolean CanEdit { get; protected set; }
159 |
160 | public Int32 Mode { get; protected set; }
161 |
162 | #endregion
163 |
164 | #region 字段
165 |
166 | protected DrawingCanvas drawingCanvas;
167 | protected Geometry geometry;
168 | protected PathGeometry pathGeometry => (PathGeometry)geometry;
169 | protected Pen pen;
170 | protected Rect selectRect;
171 |
172 | #endregion
173 | }
174 | }
175 |
--------------------------------------------------------------------------------
/DrawToolType.cs:
--------------------------------------------------------------------------------
1 | namespace DrawTools
2 | {
3 | ///
4 | /// 画图工具类型
5 | ///
6 | public enum DrawToolType
7 | {
8 | ///
9 | /// 指针(拾取)
10 | ///
11 | Pointer,
12 | ///
13 | /// 画笔
14 | ///
15 | Pen,
16 | ///
17 | /// 橡皮擦
18 | ///
19 | Eraser,
20 | ///
21 | /// 测距
22 | ///
23 | Ranging,
24 | ///
25 | /// 直线
26 | ///
27 | Line,
28 | ///
29 | /// 箭头
30 | ///
31 | Arrow,
32 | ///
33 | /// 矩形
34 | ///
35 | Rectangle,
36 | ///
37 | /// 椭圆
38 | ///
39 | Ellipse,
40 | ///
41 | /// 角度
42 | ///
43 | Angle,
44 | ///
45 | /// 折线
46 | ///
47 | Polyline,
48 | ///
49 | /// 曲线
50 | ///
51 | Curve,
52 | ///
53 | /// 多边形
54 | ///
55 | Polygon,
56 | ///
57 | /// 闭合曲线
58 | ///
59 | ClosedCurve,
60 | ///
61 | /// 面积
62 | ///
63 | Area,
64 | ///
65 | /// 文字
66 | ///
67 | Text
68 | }
69 | }
70 |
--------------------------------------------------------------------------------
/DrawTools.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Debug
6 | AnyCPU
7 | {BE0F5277-A9A5-4235-AD1D-A68A72EDA03C}
8 | WinExe
9 | DrawTools
10 | DrawTools
11 | v4.5.2
12 | 512
13 | {60dc8134-eba5-43b8-bcc9-bb4bc16c2548};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}
14 | 4
15 | true
16 | true
17 |
18 |
19 | AnyCPU
20 | true
21 | full
22 | false
23 | bin\Debug\
24 | DEBUG;TRACE
25 | prompt
26 | 4
27 |
28 |
29 | AnyCPU
30 | pdbonly
31 | true
32 | bin\Release\
33 | TRACE
34 | prompt
35 | 4
36 |
37 |
38 |
39 | packages\HandyControl.3.0.0\lib\net45\HandyControl.dll
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 | 4.0
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 | MSBuild:Compile
63 | Designer
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 | Designer
98 | MSBuild:Compile
99 |
100 |
101 | MSBuild:Compile
102 | Designer
103 |
104 |
105 | App.xaml
106 | Code
107 |
108 |
109 |
110 |
111 |
112 |
113 |
114 |
115 |
116 | MainWindow.xaml
117 | Code
118 |
119 |
120 |
121 |
122 |
123 |
124 | Code
125 |
126 |
127 | True
128 | True
129 | Resources.resx
130 |
131 |
132 | True
133 | Settings.settings
134 | True
135 |
136 |
137 | ResXFileCodeGenerator
138 | Resources.Designer.cs
139 |
140 |
141 |
142 | SettingsSingleFileGenerator
143 | Settings.Designer.cs
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 |
--------------------------------------------------------------------------------
/DrawTools.sln:
--------------------------------------------------------------------------------
1 |
2 | Microsoft Visual Studio Solution File, Format Version 12.00
3 | # Visual Studio Version 16
4 | VisualStudioVersion = 16.0.30907.101
5 | MinimumVisualStudioVersion = 10.0.40219.1
6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DrawTools", "DrawTools.csproj", "{BE0F5277-A9A5-4235-AD1D-A68A72EDA03C}"
7 | EndProject
8 | Global
9 | GlobalSection(SolutionConfigurationPlatforms) = preSolution
10 | Debug|Any CPU = Debug|Any CPU
11 | Release|Any CPU = Release|Any CPU
12 | EndGlobalSection
13 | GlobalSection(ProjectConfigurationPlatforms) = postSolution
14 | {BE0F5277-A9A5-4235-AD1D-A68A72EDA03C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
15 | {BE0F5277-A9A5-4235-AD1D-A68A72EDA03C}.Debug|Any CPU.Build.0 = Debug|Any CPU
16 | {BE0F5277-A9A5-4235-AD1D-A68A72EDA03C}.Release|Any CPU.ActiveCfg = Release|Any CPU
17 | {BE0F5277-A9A5-4235-AD1D-A68A72EDA03C}.Release|Any CPU.Build.0 = Release|Any CPU
18 | EndGlobalSection
19 | GlobalSection(SolutionProperties) = preSolution
20 | HideSolutionNode = FALSE
21 | EndGlobalSection
22 | GlobalSection(ExtensibilityGlobals) = postSolution
23 | SolutionGuid = {F7D098F6-7E6C-4092-9225-34682866B227}
24 | EndGlobalSection
25 | EndGlobal
26 |
--------------------------------------------------------------------------------
/DrawingCanvas.cs:
--------------------------------------------------------------------------------
1 | using DrawTools.Serialize;
2 | using DrawTools.Utils;
3 | using System;
4 | using System.Collections.Generic;
5 | using System.IO;
6 | using System.Linq;
7 | using System.Windows;
8 | using System.Windows.Controls;
9 | using System.Windows.Documents;
10 | using System.Windows.Input;
11 | using System.Windows.Markup;
12 | using System.Windows.Media;
13 | using System.Windows.Media.Imaging;
14 | using System.Xml.Serialization;
15 |
16 | namespace DrawTools
17 | {
18 | [ContentProperty]
19 | public sealed class DrawingCanvas : Canvas
20 | {
21 | public DrawingCanvas()
22 | {
23 | this.ClipToBounds = true;
24 | this.Focusable = true;
25 | this.OriginalCursor = Cursors.Arrow;
26 | this.squareCursor = false;
27 |
28 | this.SelectBackgroundPen = new Pen(Brushes.White, 1);
29 | this.SelectPen = new Pen(Brushes.Black, 1) { DashStyle = new DashStyle(new Double[] { 4 }, 0) };
30 |
31 | this.Loaded += delegate
32 | {
33 | this.Focus();
34 | this.drawViewer = this.FindParent();
35 | };
36 | }
37 |
38 | #region 可视化
39 |
40 | private List visuals = new List();
41 |
42 | protected override Int32 VisualChildrenCount => visuals.Count + Children.Count;
43 |
44 | protected override Visual GetVisualChild(Int32 index)
45 | {
46 | if (index < visuals.Count)
47 | return visuals[index];
48 | else
49 | return Children[index - visuals.Count];
50 | }
51 |
52 | public void AddVisual(Visual visual)
53 | {
54 | visuals.Add(visual);
55 |
56 | base.AddVisualChild(visual);
57 | base.AddLogicalChild(visual);
58 | }
59 |
60 | public void Insert(Int32 index, Visual visual)
61 | {
62 | visuals.Insert(index, visual);
63 |
64 | base.AddVisualChild(visual);
65 | base.AddLogicalChild(visual);
66 | }
67 |
68 | public void MovoToHead(Visual visual)
69 | {
70 | var index = visuals.IndexOf(visual);
71 |
72 | if (index <= 0)
73 | return;
74 |
75 | visuals.RemoveAt(index);
76 | visuals.Insert(0, visual);
77 | }
78 |
79 | public void DeleteVisual(Visual visual)
80 | {
81 | visuals.Remove(visual);
82 |
83 | base.RemoveVisualChild(visual);
84 | base.RemoveLogicalChild(visual);
85 | }
86 |
87 | public DrawingVisual GetVisual(Point point)
88 | {
89 | var hitResult = VisualTreeHelper.HitTest(this, point);
90 | return hitResult.VisualHit as DrawingVisual;
91 | }
92 |
93 | #endregion
94 |
95 | #region 依赖属性
96 |
97 | public static readonly DependencyProperty BrushProperty = DependencyProperty.Register("Brush", typeof(SolidColorBrush), typeof(DrawingCanvas), new PropertyMetadata(Brushes.Black, OnBrushPropertyChanged));
98 |
99 | private static void OnBrushPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
100 | {
101 | ((DrawingCanvas)d).UpdateCursor();
102 | }
103 |
104 | ///
105 | /// 画刷颜色
106 | ///
107 | public SolidColorBrush Brush { get => (SolidColorBrush)this.GetValue(BrushProperty); set => this.SetValue(BrushProperty, value); }
108 |
109 | public static readonly DependencyProperty StrokeThicknessProperty = DependencyProperty.Register("StrokeThickness", typeof(UInt32), typeof(DrawingCanvas), new PropertyMetadata(1u, OnStrokeThicknessPropertyChanged));
110 |
111 | private static void OnStrokeThicknessPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
112 | {
113 | ((DrawingCanvas)d).UpdateCursor();
114 | }
115 |
116 | ///
117 | /// 画刷宽度
118 | ///
119 | public UInt32 StrokeThickness { get => (UInt32)this.GetValue(StrokeThicknessProperty); set => this.SetValue(StrokeThicknessProperty, value); }
120 |
121 | public static readonly DependencyProperty DrawingToolTypeProperty = DependencyProperty.Register("DrawingToolType", typeof(DrawToolType), typeof(DrawingCanvas), new PropertyMetadata(DrawToolType.Pointer, OnDrawingToolTypePropertyChanged));
122 |
123 | private static void OnDrawingToolTypePropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
124 | {
125 | var drawing = (DrawingCanvas)d;
126 |
127 | if (!drawing.IsKeyboardFocused)
128 | drawing.Focus();
129 |
130 | drawing.UpdateCursor();
131 | }
132 |
133 | ///
134 | /// 当前的画图工具
135 | ///
136 | public DrawToolType DrawingToolType { get => (DrawToolType)this.GetValue(DrawingToolTypeProperty); set => this.SetValue(DrawingToolTypeProperty, value); }
137 |
138 | public static readonly DependencyProperty FontSizeProperty = TextElement.FontSizeProperty.AddOwner(typeof(DrawingCanvas), new FrameworkPropertyMetadata(12d, FrameworkPropertyMetadataOptions.Inherits));
139 | ///
140 | /// 文本大小
141 | ///
142 | public Double FontSize { get => (Double)this.GetValue(FontSizeProperty); set => this.SetValue(FontSizeProperty, value); }
143 |
144 | public static readonly DependencyProperty ZoomProperty = DrawingCanvasViewer.ZoomProperty.AddOwner(typeof(DrawingCanvas), new FrameworkPropertyMetadata(1d, FrameworkPropertyMetadataOptions.Inherits, new PropertyChangedCallback(OnZoomPropertyChanged)));
145 |
146 | private static void OnZoomPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
147 | {
148 | if (!Double.IsNaN((Double)e.NewValue))
149 | ((DrawingCanvas)d).UpdateCursor();
150 | }
151 |
152 | ///
153 | /// X轴缩放
154 | ///
155 | public Double Zoom { get => (Double)this.GetValue(ZoomProperty); set => this.SetValue(ZoomProperty, value); }
156 |
157 | #endregion
158 |
159 | #region 鼠标键盘事件
160 |
161 | protected override void OnMouseEnter(MouseEventArgs e)
162 | {
163 | IDrawTool tool;
164 |
165 | var point = e.GetPosition(this);
166 | for (var i = workingDrawTools.Count - 1; i >= 0; i--)
167 | {
168 | tool = workingDrawTools[i];
169 |
170 | if (tool.TouchId == 0 && tool.CanTouchEnter && (e.Handled = tool.OnTouchEnter(point)))
171 | return;
172 | }
173 | }
174 |
175 | protected override void OnMouseLeave(MouseEventArgs e)
176 | {
177 | lastPoint = null;
178 |
179 | IDrawTool tool;
180 |
181 | var point = e.GetPosition(this);
182 | for (var i = workingDrawTools.Count - 1; i >= 0; i--)
183 | {
184 | tool = workingDrawTools[i];
185 |
186 | if (tool.TouchId == 0 && tool.CanTouchLeave && (e.Handled = tool.OnTouchLeave(point)))
187 | return;
188 | }
189 | }
190 |
191 | protected override void OnMouseLeftButtonDown(MouseButtonEventArgs e)
192 | {
193 | if (!this.IsKeyboardFocused)
194 | this.Focus();
195 |
196 | if (this.canDragStart)
197 | {
198 | lastPoint = e.GetPosition(this.drawViewer);
199 | e.Handled = true;
200 | return;
201 | }
202 |
203 | IDrawTool tool;
204 |
205 | var point = e.GetPosition(this);
206 | for (var i = workingDrawTools.Count - 1; i >= 0; i--)
207 | {
208 | tool = workingDrawTools[i];
209 |
210 | if (tool.CanTouchDown && (e.Handled = tool.OnTouchDown(0, point)))
211 | return;
212 | }
213 |
214 | tool = CreateDrawingTool();
215 |
216 | if (tool.CanTouchDown && (e.Handled = tool.OnTouchDown(0, point)))
217 | return;
218 |
219 | if (this.canDragStart)
220 | {
221 | lastPoint = e.GetPosition(this);
222 | e.Handled = true;
223 | return;
224 | }
225 | }
226 |
227 | protected override void OnMouseMove(MouseEventArgs e)
228 | {
229 | if (this.canDragMove)
230 | {
231 | var drawPoint = e.GetPosition(this.drawViewer);
232 | var dx = lastPoint.Value.X - drawPoint.X;
233 | var dy = lastPoint.Value.Y - drawPoint.Y;
234 | this.drawViewer.ScrollBy(dx, dy);
235 | lastPoint = drawPoint;
236 | e.Handled = true;
237 | return;
238 | }
239 |
240 | var point = e.GetPosition(this);
241 |
242 | if (point.X < 0 || point.Y < 0 || point.X > this.ActualWidth || point.Y > this.ActualHeight)
243 | return;
244 |
245 | IDrawTool tool;
246 |
247 | for (var i = workingDrawTools.Count - 1; i >= 0; i--)
248 | {
249 | tool = workingDrawTools[i];
250 |
251 | if (tool.TouchId == 0 && tool.CanTouchMove && (e.Handled = tool.OnTouchMove(e.GetPosition(this))))
252 | return;
253 | }
254 | }
255 |
256 | protected override void OnMouseLeftButtonUp(MouseButtonEventArgs e)
257 | {
258 | lastPoint = null;
259 |
260 | IDrawTool tool;
261 |
262 | var point = e.GetPosition(this);
263 | for (var i = workingDrawTools.Count - 1; i >= 0; i--)
264 | {
265 | tool = workingDrawTools[i];
266 |
267 | if (tool.TouchId == 0 && tool.CanTouchUp && (e.Handled = tool.OnTouchUp(point)))
268 | return;
269 | }
270 | }
271 |
272 | protected override void OnKeyDown(KeyEventArgs e)
273 | {
274 | IDrawTool tool;
275 |
276 | for (var i = workingDrawTools.Count - 1; i >= 0; i--)
277 | {
278 | tool = workingDrawTools[i];
279 |
280 | if (tool.CanKeyDown && (e.Handled = tool.OnKeyDown(e.Key)))
281 | return;
282 | }
283 |
284 | if (e.Key == Key.Space)
285 | {
286 | this.OriginalCursor = DrawCursors.Hand;
287 | this.squareCursor = false;
288 | }
289 | }
290 |
291 | protected override void OnKeyUp(KeyEventArgs e)
292 | {
293 | IDrawTool tool;
294 |
295 | for (var i = workingDrawTools.Count - 1; i >= 0; i--)
296 | {
297 | tool = workingDrawTools[i];
298 |
299 | if (tool.CanKeyUp && (e.Handled = tool.OnKeyUp(e.Key)))
300 | return;
301 | }
302 |
303 | if (e.Key == Key.Space)
304 | this.UpdateCursor();
305 | }
306 |
307 | #endregion
308 |
309 | #region 触摸事件
310 |
311 | protected override void OnTouchEnter(TouchEventArgs e)
312 | {
313 | IDrawTool tool;
314 |
315 | var point = e.GetTouchPoint(this).Position;
316 | for (var i = workingDrawTools.Count - 1; i >= 0; i--)
317 | {
318 | tool = workingDrawTools[i];
319 |
320 | if (tool.TouchId == e.TouchDevice.Id && tool.CanTouchEnter && (e.Handled = tool.OnTouchEnter(point)))
321 | return;
322 | }
323 | }
324 |
325 | protected override void OnTouchLeave(TouchEventArgs e)
326 | {
327 | lastPoint = null;
328 |
329 | IDrawTool tool;
330 |
331 | var point = e.GetTouchPoint(this).Position;
332 | for (var i = workingDrawTools.Count - 1; i >= 0; i--)
333 | {
334 | tool = workingDrawTools[i];
335 |
336 | if (tool.TouchId == e.TouchDevice.Id && tool.CanTouchLeave && (e.Handled = tool.OnTouchLeave(point)))
337 | return;
338 | }
339 | }
340 |
341 | protected override void OnTouchDown(TouchEventArgs e)
342 | {
343 | if (!this.IsKeyboardFocused)
344 | this.Focus();
345 |
346 | if (this.canDragStart)
347 | {
348 | lastPoint = e.GetTouchPoint(this.drawViewer).Position;
349 | e.Handled = true;
350 | return;
351 | }
352 |
353 | IDrawTool tool;
354 |
355 | var point = e.GetTouchPoint(this).Position;
356 | for (var i = workingDrawTools.Count - 1; i >= 0; i--)
357 | {
358 | tool = workingDrawTools[i];
359 |
360 | if (tool.CanTouchDown && (e.Handled = tool.OnTouchDown(0, point)))
361 | return;
362 | }
363 |
364 | tool = CreateDrawingTool();
365 |
366 | if (tool.CanTouchDown && (e.Handled = tool.OnTouchDown(0, point)))
367 | return;
368 |
369 | if (this.canDragStart)
370 | {
371 | lastPoint = point;
372 | e.Handled = true;
373 | return;
374 | }
375 | }
376 |
377 | protected override void OnTouchMove(TouchEventArgs e)
378 | {
379 | if (this.canDragMove)
380 | {
381 | var drawPoint = e.GetTouchPoint(this.drawViewer).Position;
382 | var dx = lastPoint.Value.X - drawPoint.X;
383 | var dy = lastPoint.Value.Y - drawPoint.Y;
384 | this.drawViewer.ScrollBy(dx, dy);
385 | lastPoint = drawPoint;
386 | e.Handled = true;
387 | return;
388 | }
389 |
390 | var point = e.GetTouchPoint(this).Position;
391 |
392 | if (point.X < 0 || point.Y < 0 || point.X > this.ActualWidth || point.Y > this.ActualHeight)
393 | return;
394 |
395 | IDrawTool tool;
396 |
397 | for (var i = workingDrawTools.Count - 1; i >= 0; i--)
398 | {
399 | tool = workingDrawTools[i];
400 |
401 | if (tool.TouchId == 0 && tool.CanTouchMove && (e.Handled = tool.OnTouchMove(point)))
402 | return;
403 | }
404 | }
405 |
406 | protected override void OnTouchUp(TouchEventArgs e)
407 | {
408 | lastPoint = null;
409 |
410 | IDrawTool tool;
411 |
412 | var point = e.GetTouchPoint(this).Position;
413 | for (var i = workingDrawTools.Count - 1; i >= 0; i--)
414 | {
415 | tool = workingDrawTools[i];
416 |
417 | if (tool.TouchId == 0 && tool.CanTouchUp && (e.Handled = tool.OnTouchUp(point)))
418 | return;
419 | }
420 | }
421 |
422 | #endregion
423 |
424 | #region 公开方法
425 |
426 | public void Clear()
427 | {
428 | foreach (var visual in visuals)
429 | {
430 | base.RemoveLogicalChild(visual);
431 | base.RemoveVisualChild(visual);
432 | }
433 |
434 | this.visuals.Clear();
435 | this.Children.Clear();
436 | this.workingDrawTools.Clear();
437 | }
438 |
439 | public IEnumerable GetDrawGeometries()
440 | {
441 | return visuals.OfType().Where(q => q.IsFinish);
442 | }
443 |
444 | public void AddWorkingDrawTool(IDrawTool drawTool)
445 | {
446 | this.workingDrawTools.Add(drawTool);
447 | }
448 |
449 | public void DeleteWorkingDrawTool(IDrawTool drawTool)
450 | {
451 | this.workingDrawTools.Remove(drawTool);
452 | }
453 |
454 | ///
455 | /// 转为图片
456 | ///
457 | ///
458 | ///
459 | ///
460 | ///
461 | ///
462 | public BitmapFrame ToBitmapFrame(Int32 pixelWidth, Int32 pixelHeight, Dpi dpi, ImageSource image = null)
463 | {
464 | var visual = GetDrawingVisual(pixelWidth, pixelHeight, dpi, image);
465 |
466 | if (visual == null)
467 | return null;
468 |
469 | var renderBitmap = new RenderTargetBitmap(pixelWidth, pixelHeight, dpi.DpiX, dpi.DpiY, PixelFormats.Pbgra32);
470 | renderBitmap.Render(visual);
471 |
472 | return BitmapFrame.Create(renderBitmap);
473 | }
474 |
475 | ///
476 | /// 打印
477 | ///
478 | ///
479 | ///
480 | ///
481 | ///
482 | public void Print(Int32 pixelWidth, Int32 pixelHeight, Dpi dpi, ImageSource image = null)
483 | {
484 | var visual = GetDrawingVisual(pixelWidth, pixelHeight, dpi, image);
485 |
486 | if (visual == null)
487 | return;
488 |
489 | var printDlg = new PrintDialog();
490 |
491 | if ((Boolean)printDlg.ShowDialog())
492 | {
493 | var l = Math.Max(pixelWidth, pixelHeight);
494 | var s = Math.Min(pixelWidth, pixelHeight);
495 |
496 | var sizeL = Math.Max(printDlg.PrintableAreaWidth, printDlg.PrintableAreaHeight);
497 | var sizeS = Math.Min(printDlg.PrintableAreaWidth, printDlg.PrintableAreaHeight);
498 |
499 | var zoom = Math.Min(sizeL / l, sizeS / s);
500 | visual.Transform = new ScaleTransform(zoom, zoom);
501 |
502 | if (pixelWidth != pixelHeight && (pixelWidth > pixelHeight ^ printDlg.PrintableAreaWidth > printDlg.PrintableAreaHeight))
503 | printDlg.PrintTicket.PageOrientation = System.Printing.PageOrientation.Landscape;
504 |
505 | printDlg.PrintVisual(visual, nameof(DrawingCanvas));
506 | }
507 | }
508 |
509 | public void Save(String filepath)
510 | {
511 | var serializer = new DrawGeometrySerializer
512 | {
513 | Geometries = this.GetDrawGeometries().Select(q => q.ToSerializer()).ToArray()
514 | };
515 |
516 | var xml = new XmlSerializer(typeof(DrawGeometrySerializer));
517 |
518 | using (var fs = new FileStream(filepath, FileMode.Create, FileAccess.Write, FileShare.None))
519 | {
520 | xml.Serialize(fs, serializer);
521 | }
522 | }
523 |
524 | public void Load(String filepath)
525 | {
526 | var xml = new XmlSerializer(typeof(DrawGeometrySerializer));
527 |
528 | using (var fs = new FileStream(filepath, FileMode.Open, FileAccess.Read, FileShare.Read))
529 | {
530 | var serializer = (DrawGeometrySerializer)xml.Deserialize(fs);
531 |
532 | this.Clear();
533 |
534 | foreach (var draw in serializer.Geometries)
535 | {
536 | this.AddVisual(draw.Deserialize(this));
537 | }
538 | }
539 | }
540 |
541 | #endregion
542 |
543 | #region 私有方法
544 |
545 | private IDrawTool CreateDrawingTool()
546 | {
547 | switch (DrawingToolType)
548 | {
549 | case DrawToolType.Pointer:
550 | return new PointerDrawTool(this);
551 | case DrawToolType.Pen:
552 | return new PenDrawTool(this);
553 | case DrawToolType.Eraser:
554 | return new EraserDrawTool(this);
555 | case DrawToolType.Line:
556 | return new LineDrawTool(this);
557 | case DrawToolType.Arrow:
558 | return new ArrowDrawTool(this);
559 | case DrawToolType.Ranging:
560 | return new RangingDrawTool(this);
561 | case DrawToolType.Rectangle:
562 | return new RectangleDrawTool(this);
563 | case DrawToolType.Ellipse:
564 | return new EllipseDrawTool(this);
565 | case DrawToolType.Angle:
566 | return new AngleDrawTool(this);
567 | case DrawToolType.Polyline:
568 | return new PolylineDrawTool(this);
569 | case DrawToolType.Curve:
570 | return new CurveDrawTool(this);
571 | case DrawToolType.Polygon:
572 | return new PolygonDrawTool(this);
573 | case DrawToolType.ClosedCurve:
574 | return new ClosedCurveDrawTool(this);
575 | case DrawToolType.Area:
576 | return new AreaDrawTool(this);
577 | case DrawToolType.Text:
578 | return new TextDrawTool(this);
579 | default:
580 | throw new ArgumentOutOfRangeException("不支持画图工具" + DrawingToolType);
581 | }
582 | }
583 |
584 | private void UpdateCursor()
585 | {
586 | switch (this.DrawingToolType)
587 | {
588 | case DrawToolType.Pointer:
589 | this.OriginalCursor = Cursors.Arrow;
590 | this.squareCursor = false;
591 | break;
592 | case DrawToolType.Text:
593 | this.OriginalCursor = Cursors.IBeam;
594 | this.squareCursor = false;
595 | break;
596 | default:
597 | if ((squareCursor && cursorLength == StrokeThickness && cursorZoom == Zoom) || Double.IsNaN(StrokeThickness) || Double.IsNaN(Zoom))
598 | break;
599 | var w = (UInt32)Math.Max(1, StrokeThickness * Zoom);
600 | var h = (UInt32)Math.Max(1, StrokeThickness * Zoom);
601 | var border = (UInt32)Math.Max(1, 2 * Math.Min(1, Zoom));
602 | this.OriginalCursor = DrawCursors.CreateBmpCursor(w, h, border, Brush);
603 | this.squareCursor = true;
604 | this.cursorLength = StrokeThickness;
605 | this.cursorZoom = Zoom;
606 | break;
607 | }
608 | }
609 |
610 | private DrawingVisual GetDrawingVisual(Int32 pixelWidth, Int32 pixelHeight, Dpi dpi, ImageSource image = null)
611 | {
612 | var drawGeometries = this.GetDrawGeometries();
613 |
614 | if (drawGeometries.Count() == 0)
615 | return null;
616 |
617 | var root = new DrawingVisual();
618 |
619 | var dc = root.RenderOpen();
620 |
621 | if (image != null)
622 | dc.DrawImage(image, new Rect(new Size(pixelWidth * dpi.Px2WpfX, pixelHeight * dpi.Px2WpfY)));
623 |
624 | foreach (var draw in drawGeometries)
625 | {
626 | dc.DrawDrawing(draw.Drawing);
627 | }
628 |
629 | dc.Close();
630 |
631 | return root;
632 | }
633 |
634 | #endregion
635 |
636 | #region 属性
637 |
638 | ///
639 | /// 画笔
640 | ///
641 | public Pen Pen => new Pen(this.Brush, this.StrokeThickness);
642 | ///
643 | /// 选择画笔
644 | ///
645 | public Pen SelectPen { get; }
646 | ///
647 | /// 选择底色画笔
648 | ///
649 | public Pen SelectBackgroundPen { get; }
650 |
651 | #endregion
652 |
653 | #region 字段
654 |
655 | ///
656 | /// 正在进行的画图工具
657 | ///
658 | private List workingDrawTools = new List();
659 |
660 | private Cursor originalCursor;
661 | ///
662 | /// 原始指针
663 | ///
664 | public Cursor OriginalCursor
665 | {
666 | get => originalCursor;
667 | set
668 | {
669 | if (originalCursor == value)
670 | return;
671 |
672 | originalCursor = value;
673 |
674 | if (!handleCursor)
675 | this.Cursor = value;
676 | }
677 | }
678 |
679 | ///
680 | /// 鼠标指针是否正在被控制
681 | ///
682 | public Boolean handleCursor;
683 |
684 | public Boolean squareCursor;
685 | public UInt32 cursorLength;
686 | public Double cursorZoom;
687 |
688 | private Point? lastPoint;
689 | private DrawingCanvasViewer drawViewer;
690 | private Boolean canDragStart => this.Cursor == DrawCursors.Hand && drawViewer != null;
691 | private Boolean canDragMove => canDragStart && lastPoint.HasValue;
692 |
693 | #endregion
694 | }
695 | }
696 |
--------------------------------------------------------------------------------
/DrawingCanvasViewer.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Windows;
3 | using System.Windows.Controls;
4 | using System.Windows.Input;
5 | using System.Windows.Markup;
6 | using System.Windows.Media.Imaging;
7 |
8 | namespace DrawTools
9 | {
10 | [ContentProperty("DrawingCanvas")]
11 | [TemplatePart(Name = "Part_ScrollViewer", Type = typeof(ScrollViewer))]
12 | public sealed class DrawingCanvasViewer : Control
13 | {
14 | #region 依赖属性
15 |
16 | public static readonly DependencyProperty DrawingCanvasProperty = DependencyProperty.Register("DrawingCanvas", typeof(DrawingCanvas), typeof(DrawingCanvasViewer));
17 | ///
18 | /// 画板
19 | ///
20 | public DrawingCanvas DrawingCanvas { get => (DrawingCanvas)this.GetValue(DrawingCanvasProperty); set => this.SetValue(DrawingCanvasProperty, value); }
21 |
22 | public static readonly DependencyProperty BackgroundImageProperty = DependencyProperty.Register("BackgroundImage", typeof(BitmapSource), typeof(DrawingCanvasViewer), new PropertyMetadata(OnBackgroundImagePropertyChanged));
23 |
24 | private static void OnBackgroundImagePropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
25 | {
26 | var viewer = (DrawingCanvasViewer)d;
27 | viewer.EnsureZoom();
28 | viewer.EnsureInnerSize();
29 | }
30 |
31 | ///
32 | /// 背景图
33 | ///
34 | public BitmapSource BackgroundImage { get => (BitmapSource)this.GetValue(BackgroundImageProperty); set => this.SetValue(BackgroundImageProperty, value); }
35 |
36 | public static readonly DependencyProperty InnerWidthProperty = DependencyProperty.Register("InnerWidth", typeof(Double), typeof(DrawingCanvasViewer), new PropertyMetadata(Double.NaN));
37 |
38 | public Double InnerWidth { get => (Double)this.GetValue(InnerWidthProperty); set => this.SetValue(InnerWidthProperty, value); }
39 |
40 | public static readonly DependencyProperty InnerHeightProperty = DependencyProperty.Register("InnerHeight", typeof(Double), typeof(DrawingCanvasViewer), new PropertyMetadata(Double.NaN));
41 |
42 | public Double InnerHeight { get => (Double)this.GetValue(InnerHeightProperty); set => this.SetValue(InnerHeightProperty, value); }
43 |
44 | public static readonly DependencyProperty ImageWidthProperty = DependencyProperty.Register("ImageWidth", typeof(Double), typeof(DrawingCanvasViewer), new PropertyMetadata(Double.NaN));
45 |
46 | public Double ImageWidth { get => (Double)this.GetValue(ImageWidthProperty); set => this.SetValue(ImageWidthProperty, value); }
47 |
48 | public static readonly DependencyProperty ImageHeightProperty = DependencyProperty.Register("ImageHeight", typeof(Double), typeof(DrawingCanvasViewer), new PropertyMetadata(Double.NaN));
49 |
50 | public Double ImageHeight { get => (Double)this.GetValue(ImageHeightProperty); set => this.SetValue(ImageHeightProperty, value); }
51 |
52 | public static readonly DependencyProperty ZoomProperty = DependencyProperty.Register("Zoom", typeof(Double), typeof(DrawingCanvasViewer), new PropertyMetadata(Double.NaN, OnZoomPropertyChanged));
53 |
54 | private static void OnZoomPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
55 | {
56 | var viewer = (DrawingCanvasViewer)d;
57 | viewer.EnsureInnerSize();
58 | viewer.EnsureImageSize();
59 | }
60 |
61 | ///
62 | /// 缩放百分比
63 | ///
64 | public Double Zoom { get => (Double)this.GetValue(ZoomProperty); set => this.SetValue(ZoomProperty, value); }
65 |
66 | #endregion
67 |
68 | #region 构造器
69 |
70 | static DrawingCanvasViewer()
71 | {
72 | DefaultStyleKeyProperty.OverrideMetadata(typeof(DrawingCanvasViewer), new FrameworkPropertyMetadata(typeof(DrawingCanvasViewer)));
73 | }
74 |
75 | public DrawingCanvasViewer()
76 | {
77 | this.Loaded += OnFirstLoaded;
78 | }
79 |
80 | #endregion
81 |
82 | #region 公开方法
83 |
84 | public void ScrollBy(Double dx, Double dy)
85 | {
86 | if (!CanDrag)
87 | return;
88 |
89 | if (dx != 0)
90 | scrollViewer.ScrollToHorizontalOffset(scrollViewer.HorizontalOffset + dx);
91 | if (dy != 0)
92 | scrollViewer.ScrollToVerticalOffset(scrollViewer.VerticalOffset + dy);
93 | }
94 |
95 | #endregion
96 |
97 | #region 私有方法
98 |
99 | public override void OnApplyTemplate()
100 | {
101 | base.OnApplyTemplate();
102 |
103 | if (scrollViewer != null)
104 | scrollViewer.ScrollChanged -= OnScrollChanged;
105 |
106 | scrollViewer = this.Template.FindName("Part_ScrollViewer", this) as ScrollViewer;
107 |
108 | if (scrollViewer == null)
109 | throw new NullReferenceException("模板找不到Part_ScrollViewer");
110 | }
111 |
112 | private void OnFirstLoaded(Object sender, RoutedEventArgs e)
113 | {
114 | this.Loaded -= OnFirstLoaded;
115 |
116 | if (BackgroundImage == null || scrollViewer == null)
117 | return;
118 |
119 | if (Double.IsNaN(this.Zoom))
120 | EnsureZoom();
121 |
122 | EnsureInnerSize();
123 |
124 | this.SizeChanged += OnSizeChanged;
125 | OnSizeChanged(sender, e);
126 |
127 | scrollViewer.ScrollChanged += OnScrollChanged;
128 | }
129 |
130 | private void EnsureZoom()
131 | {
132 | if (BackgroundImage == null || scrollViewer == null)
133 | {
134 | this.ClearValue(ZoomProperty);
135 | return;
136 | }
137 |
138 | var zoom = Math.Min(scrollViewer.ViewportWidth / BackgroundImage.Width, scrollViewer.ViewportHeight / BackgroundImage.Height);
139 | this.SetCurrentValue(ZoomProperty, Math.Min(1, zoom));
140 | }
141 |
142 | private void EnsureInnerSize()
143 | {
144 | if (BackgroundImage == null || scrollViewer == null || Double.IsNaN(this.Zoom))
145 | {
146 | this.ClearValue(InnerWidthProperty);
147 | this.ClearValue(InnerHeightProperty);
148 | return;
149 | }
150 |
151 | var width = BackgroundImage.Width * this.Zoom;
152 | var height = BackgroundImage.Height * this.Zoom;
153 |
154 | if (width < scrollViewer.ViewportWidth)
155 | width = scrollViewer.ViewportWidth;
156 |
157 | if (height < scrollViewer.ViewportHeight)
158 | height = scrollViewer.ViewportHeight;
159 |
160 | scrollViewer.ScrollChanged -= OnScrollChanged;
161 |
162 | this.SetValue(InnerWidthProperty, width);
163 | this.SetValue(InnerHeightProperty, height);
164 |
165 | scrollViewer.ScrollChanged += OnScrollChanged;
166 |
167 | this.EnsureScrollOffset();
168 | }
169 |
170 | private void EnsureImageSize()
171 | {
172 | if (BackgroundImage == null || Double.IsNaN(this.Zoom))
173 | {
174 | this.ClearValue(ImageWidthProperty);
175 | this.ClearValue(ImageHeightProperty);
176 | return;
177 | }
178 |
179 | this.SetValue(ImageWidthProperty, BackgroundImage.Width * this.Zoom);
180 | this.SetValue(ImageHeightProperty, BackgroundImage.Height * this.Zoom);
181 | }
182 |
183 | private void EnsureScrollOffset()
184 | {
185 | var offsetX = (this.InnerWidth - scrollViewer.ActualWidth) * center.X;
186 | var offsetY = (this.InnerHeight - scrollViewer.ActualHeight) * center.Y;
187 |
188 | scrollViewer.ScrollToHorizontalOffset(offsetX);
189 | scrollViewer.ScrollToVerticalOffset(offsetY);
190 | }
191 |
192 | private void OnSizeChanged(Object sender, RoutedEventArgs e)
193 | {
194 | this.EnsureScrollOffset();
195 | }
196 |
197 | private void OnScrollChanged(Object sender, ScrollChangedEventArgs e)
198 | {
199 | if (this.InnerWidth == scrollViewer.ActualWidth)
200 | {
201 | center.X = 0.5;
202 | center.Y = 0.5;
203 | }
204 | else
205 | {
206 | center.X = (this.InnerWidth == scrollViewer.ActualWidth) ? 0 : scrollViewer.HorizontalOffset / (this.InnerWidth - scrollViewer.ActualWidth);
207 | center.Y = (this.InnerHeight == scrollViewer.ActualHeight) ? 0 : scrollViewer.VerticalOffset / (this.InnerHeight - scrollViewer.ActualHeight);
208 | }
209 | }
210 |
211 | protected override void OnPreviewMouseWheel(MouseWheelEventArgs e)
212 | {
213 | if (Keyboard.Modifiers.HasFlag(ModifierKeys.Control))
214 | {
215 | Zoom *= 1 + (e.Delta > 0 ? scale : -scale);
216 | e.Handled = true;
217 | }
218 | }
219 |
220 | #endregion
221 |
222 | #region 属性
223 |
224 | public Boolean CanDrag => this.scrollViewer != null && (InnerHeight > scrollViewer.ViewportHeight || InnerWidth > scrollViewer.ViewportWidth);
225 |
226 | #endregion
227 |
228 | #region 字段
229 |
230 | private ScrollViewer scrollViewer;
231 | private Point center = new Point(0.5, 0.5);
232 | private Double scale = 0.05;
233 |
234 | #endregion
235 | }
236 | }
237 |
--------------------------------------------------------------------------------
/EllipseDrawTool.cs:
--------------------------------------------------------------------------------
1 | using DrawTools.Serialize;
2 | using System;
3 | using System.Windows;
4 | using System.Windows.Input;
5 | using System.Windows.Media;
6 |
7 | namespace DrawTools
8 | {
9 | ///
10 | /// 椭圆
11 | ///
12 | public sealed class EllipseDrawTool : DrawGeometryBase
13 | {
14 | public EllipseDrawTool(DrawingCanvas drawingCanvas) : base(drawingCanvas)
15 | {
16 | this.DrawingToolType = DrawToolType.Ellipse;
17 |
18 | // 准备要处理的事件
19 | this.CanTouchDown = true;
20 | }
21 |
22 | #region 鼠标键盘事件
23 |
24 | public override Boolean OnKeyDown(Key key)
25 | {
26 | if (key != Key.LeftShift || !bottomRight.HasValue)
27 | return false;
28 |
29 | return OnTouchMove(bottomRight.Value);
30 | }
31 |
32 | public override Boolean OnKeyUp(Key key)
33 | {
34 | if (key != Key.LeftShift || !bottomRight.HasValue)
35 | return false;
36 |
37 | return OnTouchMove(bottomRight.Value);
38 | }
39 |
40 | public override Boolean OnTouchLeave(Point point)
41 | {
42 | if (!bottomRight.HasValue)
43 | this.drawingCanvas.DeleteVisual(this);
44 | else
45 | {
46 | var figure = pathGeometry.Figures[0];
47 | figure.StartPoint = new Point(topLeft.Value.X, center.Y);
48 |
49 | var clockwise = bottomRight.Value.X > topLeft.Value.X ? SweepDirection.Clockwise : SweepDirection.Counterclockwise;
50 | var arc = new ArcSegment(new Point(bottomRight.Value.X, center.Y), new Size(radiusX, radiusY), 0, false, clockwise, true);
51 | figure.Segments.Add(arc);
52 |
53 | arc = new ArcSegment(figure.StartPoint, new Size(radiusX, radiusY), 0, false, clockwise, true);
54 | figure.Segments.Add(arc);
55 |
56 | geometry = geometry.GetWidenedPathGeometry(pen);
57 | Draw();
58 | }
59 |
60 | this.drawingCanvas.DeleteWorkingDrawTool(this);
61 |
62 | this.IsFinish = true;
63 |
64 | this.CanTouchDown = false;
65 | this.CanTouchMove = false;
66 | this.CanTouchLeave = false;
67 | this.CanKeyDown = false;
68 | this.CanKeyUp = false;
69 |
70 | if (this.TouchId == 0 && this.drawingCanvas.IsMouseCaptured)
71 | this.drawingCanvas.ReleaseMouseCapture();
72 |
73 | return true;
74 | }
75 |
76 | public override Boolean OnTouchDown(Int32 touchId, Point point)
77 | {
78 | this.TouchId = touchId;
79 |
80 | if (!topLeft.HasValue)
81 | {
82 | this.drawingCanvas.AddWorkingDrawTool(this);
83 |
84 | this.pen = this.drawingCanvas.Pen;
85 |
86 | topLeft = point;
87 |
88 | this.geometry = new PathGeometry();
89 |
90 | var figure = new PathFigure { IsClosed = true };
91 | pathGeometry.Figures.Add(figure);
92 |
93 | this.CanTouchMove = true;
94 | this.CanKeyDown = true;
95 | this.CanKeyUp = true;
96 |
97 | if (this.TouchId != 0 || !this.drawingCanvas.CaptureMouse())
98 | this.CanTouchLeave = true;
99 |
100 | this.drawingCanvas.AddVisual(this);
101 | }
102 | else
103 | return OnTouchLeave(point);
104 |
105 | return true;
106 | }
107 |
108 | public override Boolean OnTouchMove(Point point)
109 | {
110 | var dc = this.RenderOpen();
111 |
112 | var startPoint = topLeft.Value;
113 |
114 | if (Keyboard.IsKeyDown(Key.LeftShift))
115 | {
116 | var len = Math.Min(Math.Abs(point.X - startPoint.X), Math.Abs(point.Y - startPoint.Y));
117 | point = new Point(startPoint.X + (point.X > startPoint.X ? len : -len), startPoint.Y + (point.Y > startPoint.Y ? len : -len));
118 | }
119 |
120 | if ((startPoint - point).Length <= pen.Thickness)
121 | {
122 | dc.Close();
123 | return true;
124 | }
125 |
126 | bottomRight = point;
127 |
128 | radiusX = (point.X - startPoint.X) / 2;
129 | radiusY = (point.Y - startPoint.Y) / 2;
130 | center.X = startPoint.X + radiusX;
131 | center.Y = startPoint.Y + radiusY;
132 |
133 | radiusX = Math.Abs(radiusX);
134 | radiusY = Math.Abs(radiusY);
135 |
136 | dc.DrawEllipse(null, pen, center, radiusX, radiusY);
137 | dc.Close();
138 |
139 | return true;
140 | }
141 |
142 | #endregion
143 |
144 | #region 序列化
145 |
146 | public override DrawGeometrySerializerBase ToSerializer()
147 | {
148 | var serializer = new DrawEllipseSerializer
149 | {
150 | Color = ((SolidColorBrush)pen.Brush).Color,
151 | StrokeThickness = pen.Thickness,
152 | Geometry = geometry.ToString()
153 | };
154 |
155 | if (geometry.Transform != null)
156 | serializer.Matrix = geometry.Transform.Value;
157 |
158 | return serializer;
159 | }
160 |
161 | public override void DeserializeFrom(DrawGeometrySerializerBase serializer)
162 | {
163 | this.pen = new Pen(new SolidColorBrush(serializer.Color), serializer.StrokeThickness);
164 |
165 | this.geometry = Geometry.Parse(serializer.Geometry).GetFlattenedPathGeometry();
166 | this.geometry.Transform = new TranslateTransform(serializer.Matrix.OffsetX, serializer.Matrix.OffsetY);
167 |
168 | this.IsFinish = true;
169 |
170 | this.Draw();
171 | }
172 |
173 | #endregion
174 |
175 | #region 字段
176 |
177 | private Point? topLeft, bottomRight;
178 | private Double radiusX, radiusY;
179 | private Point center;
180 |
181 | #endregion
182 | }
183 | }
184 |
--------------------------------------------------------------------------------
/EraserDrawTool.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Windows;
4 | using System.Windows.Input;
5 | using System.Windows.Media;
6 |
7 | namespace DrawTools
8 | {
9 | ///
10 | /// 橡皮擦
11 | ///
12 | public sealed class EraserDrawTool : IDrawTool
13 | {
14 | public EraserDrawTool(DrawingCanvas drawingCanvas)
15 | {
16 | this.drawingCanvas = drawingCanvas;
17 |
18 | // 准备要处理的事件
19 | this.CanTouchDown = true;
20 | }
21 |
22 | #region 公开方法
23 |
24 | public Boolean OnKeyDown(Key key)
25 | {
26 | throw new NotImplementedException();
27 | }
28 |
29 | public Boolean OnKeyUp(Key key)
30 | {
31 | throw new NotImplementedException();
32 | }
33 |
34 | public Boolean OnTouchDown(Int32 touchId, Point point)
35 | {
36 | this.TouchId = touchId;
37 |
38 | this.CanTouchDown = false;
39 |
40 | this.pen = this.drawingCanvas.Pen;
41 | this.radius = pen.Thickness / 2;
42 |
43 | this.lastPoint = point;
44 |
45 | this.deleteDrawGeometries = new List();
46 |
47 | var geometry = new PathGeometry();
48 | var figure = new PathFigure { StartPoint = new Point(point.X - radius, point.Y - radius), IsClosed = true, IsFilled = true };
49 | geometry.Figures.Add(figure);
50 |
51 | var line = new LineSegment(new Point(point.X + radius, point.Y - radius), false);
52 | figure.Segments.Add(line);
53 |
54 | line = new LineSegment(new Point(point.X + radius, point.Y + radius), false);
55 | figure.Segments.Add(line);
56 |
57 | line = new LineSegment(new Point(point.X - radius, point.Y + radius), false);
58 | figure.Segments.Add(line);
59 |
60 | if (Erase(geometry))
61 | {
62 | IsFinish = true;
63 | return true;
64 | }
65 |
66 | this.drawingCanvas.AddWorkingDrawTool(this);
67 |
68 | this.CanTouchMove = true;
69 | this.CanTouchUp = true;
70 |
71 | if (this.TouchId != 0 || !this.drawingCanvas.CaptureMouse())
72 | this.CanTouchLeave = true;
73 |
74 | this.drawingCanvas.handleCursor = true;
75 |
76 | return true;
77 | }
78 |
79 | public Boolean OnTouchEnter(Point point)
80 | {
81 | throw new NotImplementedException();
82 | }
83 |
84 | public Boolean OnTouchLeave(Point point)
85 | {
86 | return this.OnTouchUp(point);
87 | }
88 |
89 | public Boolean OnTouchMove(Point point)
90 | {
91 | var geometry = new PathGeometry();
92 | var figure = new PathFigure { IsClosed = true, IsFilled = true };
93 | geometry.Figures.Add(figure);
94 |
95 | var positiveX = radius * (point.X > lastPoint.X ? 1 : -1);
96 | var positiveY = radius * (point.Y > lastPoint.Y ? 1 : -1);
97 |
98 | figure.StartPoint = new Point(lastPoint.X - positiveX, lastPoint.Y - positiveY);
99 |
100 | var line = new LineSegment(new Point(lastPoint.X + positiveX, lastPoint.Y - positiveY), false);
101 | figure.Segments.Add(line);
102 |
103 | line = new LineSegment(new Point(point.X + positiveX, point.Y - positiveY), false);
104 | figure.Segments.Add(line);
105 |
106 | line = new LineSegment(new Point(point.X + positiveX, point.Y + positiveY), false);
107 | figure.Segments.Add(line);
108 |
109 | line = new LineSegment(new Point(point.X - positiveX, point.Y + positiveY), false);
110 | figure.Segments.Add(line);
111 |
112 | line = new LineSegment(new Point(lastPoint.X - positiveX, lastPoint.Y + positiveY), false);
113 | figure.Segments.Add(line);
114 |
115 | lastPoint = point;
116 |
117 | if (Erase(geometry))
118 | OnTouchUp(point);
119 |
120 | return true;
121 | }
122 |
123 | public Boolean OnTouchUp(Point point)
124 | {
125 | this.drawingCanvas.DeleteWorkingDrawTool(this);
126 |
127 | this.IsFinish = true;
128 |
129 | this.CanTouchMove = false;
130 | this.CanTouchUp = false;
131 | this.CanTouchLeave = false;
132 |
133 | if (this.TouchId == 0 && this.drawingCanvas.IsMouseCaptured)
134 | this.drawingCanvas.ReleaseMouseCapture();
135 |
136 | this.drawingCanvas.handleCursor = false;
137 |
138 | return true;
139 | }
140 |
141 | #endregion
142 |
143 | #region 私有方法
144 |
145 | private Boolean Erase(Geometry geometry)
146 | {
147 | var empty = true;
148 |
149 | foreach (var g in this.drawingCanvas.GetDrawGeometries())
150 | {
151 | if (g.Erase(geometry))
152 | deleteDrawGeometries.Add(g);
153 | else
154 | empty = false;
155 | }
156 |
157 | foreach (var g in deleteDrawGeometries)
158 | {
159 | this.drawingCanvas.DeleteVisual(g);
160 | }
161 |
162 | deleteDrawGeometries.Clear();
163 |
164 | return empty;
165 | }
166 |
167 | #endregion
168 |
169 | #region 属性
170 |
171 | public Int32 TouchId { get; private set; }
172 |
173 | public Boolean CanTouchEnter { get; private set; }
174 |
175 | public Boolean CanTouchLeave { get; private set; }
176 |
177 | public Boolean CanTouchDown { get; private set; }
178 |
179 | public Boolean CanTouchMove { get; private set; }
180 |
181 | public Boolean CanTouchUp { get; private set; }
182 |
183 | public Boolean CanKeyDown { get; private set; }
184 |
185 | public Boolean CanKeyUp { get; private set; }
186 |
187 | public Boolean IsFinish { get; private set; }
188 |
189 | public DrawToolType DrawingToolType => DrawToolType.Eraser;
190 |
191 | #endregion
192 |
193 | #region 字段
194 |
195 | private DrawingCanvas drawingCanvas;
196 | private Point lastPoint;
197 | private Pen pen;
198 | private Double radius;
199 | private List deleteDrawGeometries;
200 |
201 | #endregion
202 | }
203 | }
204 |
--------------------------------------------------------------------------------
/IDrawTool.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Windows;
3 | using System.Windows.Input;
4 |
5 | namespace DrawTools
6 | {
7 | ///
8 | /// 绘制工具接口
9 | ///
10 | public interface IDrawTool
11 | {
12 | ///
13 | /// 触摸Id,用于分辨多点触摸,0表示鼠标
14 | ///
15 | Int32 TouchId { get; }
16 |
17 | ///
18 | /// 是否可以处理鼠标进入事件
19 | ///
20 | Boolean CanTouchEnter { get; }
21 |
22 | ///
23 | /// 处理鼠标进入事件
24 | ///
25 | /// 相对画布的点
26 | /// 事件是否已处理
27 | Boolean OnTouchEnter(Point point);
28 |
29 | ///
30 | /// 是否可以处理鼠标离开事件
31 | ///
32 | Boolean CanTouchLeave { get; }
33 |
34 | ///
35 | /// 处理鼠标离开事件
36 | ///
37 | /// 相对画布的点
38 | /// 事件是否已处理
39 | Boolean OnTouchLeave(Point point);
40 |
41 | ///
42 | /// 是否可以处理鼠标按下事件
43 | ///
44 | Boolean CanTouchDown { get; }
45 |
46 | ///
47 | /// 执行鼠标按下事件
48 | ///
49 | /// 触摸Id,用于分辨多点触摸,0表示鼠标
50 | /// 相对画布的点
51 | /// 事件是否已处理
52 | Boolean OnTouchDown(Int32 touchId, Point point);
53 |
54 | ///
55 | /// 是否可以处理鼠标移动事件
56 | ///
57 | Boolean CanTouchMove { get; }
58 |
59 | ///
60 | /// 执行鼠标移动事件
61 | ///
62 | /// 相对画布的点
63 | /// 事件是否已处理
64 | Boolean OnTouchMove(Point point);
65 |
66 | ///
67 | /// 是否可以处理鼠标弹起事件
68 | ///
69 | Boolean CanTouchUp { get; }
70 |
71 | ///
72 | /// 执行鼠标弹起事件
73 | ///
74 | /// 相对画布的点
75 | /// 事件是否已处理
76 | Boolean OnTouchUp(Point point);
77 |
78 | ///
79 | /// 是否可以处理键盘按下事件
80 | ///
81 | Boolean CanKeyDown { get; }
82 |
83 | ///
84 | /// 处理键盘按下事件
85 | ///
86 | ///
87 | /// 事件是否已处理
88 | Boolean OnKeyDown(Key key);
89 |
90 | ///
91 | /// 是否可以处理键盘弹起事件
92 | ///
93 | Boolean CanKeyUp { get; }
94 |
95 | ///
96 | /// 处理键盘弹起事件
97 | ///
98 | ///
99 | /// 事件是否已处理
100 | Boolean OnKeyUp(Key key);
101 |
102 | ///
103 | /// 是否结束
104 | ///
105 | Boolean IsFinish { get; }
106 |
107 | ///
108 | /// 画图工具类型
109 | ///
110 | DrawToolType DrawingToolType { get; }
111 | }
112 | }
113 |
--------------------------------------------------------------------------------
/Images/Cursor/hand.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/LowPlayer/DrawTools/3c707f30624613df400e4b36ec81e53ef26d5865/Images/Cursor/hand.png
--------------------------------------------------------------------------------
/Images/angle1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/LowPlayer/DrawTools/3c707f30624613df400e4b36ec81e53ef26d5865/Images/angle1.png
--------------------------------------------------------------------------------
/Images/angle2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/LowPlayer/DrawTools/3c707f30624613df400e4b36ec81e53ef26d5865/Images/angle2.png
--------------------------------------------------------------------------------
/Images/area1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/LowPlayer/DrawTools/3c707f30624613df400e4b36ec81e53ef26d5865/Images/area1.png
--------------------------------------------------------------------------------
/Images/area2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/LowPlayer/DrawTools/3c707f30624613df400e4b36ec81e53ef26d5865/Images/area2.png
--------------------------------------------------------------------------------
/Images/arrow1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/LowPlayer/DrawTools/3c707f30624613df400e4b36ec81e53ef26d5865/Images/arrow1.png
--------------------------------------------------------------------------------
/Images/arrow2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/LowPlayer/DrawTools/3c707f30624613df400e4b36ec81e53ef26d5865/Images/arrow2.png
--------------------------------------------------------------------------------
/Images/circle1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/LowPlayer/DrawTools/3c707f30624613df400e4b36ec81e53ef26d5865/Images/circle1.png
--------------------------------------------------------------------------------
/Images/circle2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/LowPlayer/DrawTools/3c707f30624613df400e4b36ec81e53ef26d5865/Images/circle2.png
--------------------------------------------------------------------------------
/Images/clear1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/LowPlayer/DrawTools/3c707f30624613df400e4b36ec81e53ef26d5865/Images/clear1.png
--------------------------------------------------------------------------------
/Images/clear2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/LowPlayer/DrawTools/3c707f30624613df400e4b36ec81e53ef26d5865/Images/clear2.png
--------------------------------------------------------------------------------
/Images/closedCurve1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/LowPlayer/DrawTools/3c707f30624613df400e4b36ec81e53ef26d5865/Images/closedCurve1.png
--------------------------------------------------------------------------------
/Images/closedCurve2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/LowPlayer/DrawTools/3c707f30624613df400e4b36ec81e53ef26d5865/Images/closedCurve2.png
--------------------------------------------------------------------------------
/Images/colors1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/LowPlayer/DrawTools/3c707f30624613df400e4b36ec81e53ef26d5865/Images/colors1.png
--------------------------------------------------------------------------------
/Images/colors2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/LowPlayer/DrawTools/3c707f30624613df400e4b36ec81e53ef26d5865/Images/colors2.png
--------------------------------------------------------------------------------
/Images/curve1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/LowPlayer/DrawTools/3c707f30624613df400e4b36ec81e53ef26d5865/Images/curve1.png
--------------------------------------------------------------------------------
/Images/curve2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/LowPlayer/DrawTools/3c707f30624613df400e4b36ec81e53ef26d5865/Images/curve2.png
--------------------------------------------------------------------------------
/Images/ellipse1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/LowPlayer/DrawTools/3c707f30624613df400e4b36ec81e53ef26d5865/Images/ellipse1.png
--------------------------------------------------------------------------------
/Images/ellipse2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/LowPlayer/DrawTools/3c707f30624613df400e4b36ec81e53ef26d5865/Images/ellipse2.png
--------------------------------------------------------------------------------
/Images/eraser1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/LowPlayer/DrawTools/3c707f30624613df400e4b36ec81e53ef26d5865/Images/eraser1.png
--------------------------------------------------------------------------------
/Images/eraser2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/LowPlayer/DrawTools/3c707f30624613df400e4b36ec81e53ef26d5865/Images/eraser2.png
--------------------------------------------------------------------------------
/Images/img1.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/LowPlayer/DrawTools/3c707f30624613df400e4b36ec81e53ef26d5865/Images/img1.jpg
--------------------------------------------------------------------------------
/Images/line1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/LowPlayer/DrawTools/3c707f30624613df400e4b36ec81e53ef26d5865/Images/line1.png
--------------------------------------------------------------------------------
/Images/line2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/LowPlayer/DrawTools/3c707f30624613df400e4b36ec81e53ef26d5865/Images/line2.png
--------------------------------------------------------------------------------
/Images/pen1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/LowPlayer/DrawTools/3c707f30624613df400e4b36ec81e53ef26d5865/Images/pen1.png
--------------------------------------------------------------------------------
/Images/pen2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/LowPlayer/DrawTools/3c707f30624613df400e4b36ec81e53ef26d5865/Images/pen2.png
--------------------------------------------------------------------------------
/Images/pointer1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/LowPlayer/DrawTools/3c707f30624613df400e4b36ec81e53ef26d5865/Images/pointer1.png
--------------------------------------------------------------------------------
/Images/pointer2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/LowPlayer/DrawTools/3c707f30624613df400e4b36ec81e53ef26d5865/Images/pointer2.png
--------------------------------------------------------------------------------
/Images/polyline1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/LowPlayer/DrawTools/3c707f30624613df400e4b36ec81e53ef26d5865/Images/polyline1.png
--------------------------------------------------------------------------------
/Images/polyline2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/LowPlayer/DrawTools/3c707f30624613df400e4b36ec81e53ef26d5865/Images/polyline2.png
--------------------------------------------------------------------------------
/Images/ranging1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/LowPlayer/DrawTools/3c707f30624613df400e4b36ec81e53ef26d5865/Images/ranging1.png
--------------------------------------------------------------------------------
/Images/ranging2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/LowPlayer/DrawTools/3c707f30624613df400e4b36ec81e53ef26d5865/Images/ranging2.png
--------------------------------------------------------------------------------
/Images/rectangle1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/LowPlayer/DrawTools/3c707f30624613df400e4b36ec81e53ef26d5865/Images/rectangle1.png
--------------------------------------------------------------------------------
/Images/rectangle2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/LowPlayer/DrawTools/3c707f30624613df400e4b36ec81e53ef26d5865/Images/rectangle2.png
--------------------------------------------------------------------------------
/Images/text1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/LowPlayer/DrawTools/3c707f30624613df400e4b36ec81e53ef26d5865/Images/text1.png
--------------------------------------------------------------------------------
/Images/text2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/LowPlayer/DrawTools/3c707f30624613df400e4b36ec81e53ef26d5865/Images/text2.png
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2021 并不是真的快乐
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/LineDrawTool.cs:
--------------------------------------------------------------------------------
1 | using DrawTools.Serialize;
2 | using System;
3 | using System.Windows;
4 | using System.Windows.Media;
5 |
6 | namespace DrawTools
7 | {
8 | ///
9 | /// 直线
10 | ///
11 | public sealed class LineDrawTool : DrawGeometryBase
12 | {
13 | public LineDrawTool(DrawingCanvas drawingCanvas) : base(drawingCanvas)
14 | {
15 | this.DrawingToolType = DrawToolType.Line;
16 |
17 | // 准备要处理的事件
18 | this.CanTouchDown = true;
19 | }
20 |
21 | #region 鼠标键盘事件
22 |
23 | public override Boolean OnTouchLeave(Point point)
24 | {
25 | if (!endPoint.HasValue)
26 | this.drawingCanvas.DeleteVisual(this);
27 | else
28 | {
29 | geometry = geometry.GetWidenedPathGeometry(pen);
30 | Draw();
31 | }
32 |
33 | if (this.TouchId == 0 && this.drawingCanvas.IsMouseCaptured)
34 | this.drawingCanvas.ReleaseMouseCapture();
35 |
36 | this.drawingCanvas.DeleteWorkingDrawTool(this);
37 |
38 | this.IsFinish = true;
39 |
40 | this.CanKeyDown = false;
41 | this.CanTouchMove = false;
42 | this.CanTouchLeave = false;
43 |
44 | return true;
45 | }
46 |
47 | public override Boolean OnTouchDown(Int32 touchId, Point point)
48 | {
49 | this.TouchId = touchId;
50 |
51 | if (!startPoint.HasValue)
52 | {
53 | this.drawingCanvas.AddWorkingDrawTool(this);
54 |
55 | this.pen = this.drawingCanvas.Pen;
56 |
57 | startPoint = point;
58 |
59 | this.geometry = new PathGeometry();
60 |
61 | var figure = new PathFigure { StartPoint = point };
62 | pathGeometry.Figures.Add(figure);
63 |
64 | this.CanTouchMove = true;
65 |
66 | if (this.TouchId != 0 || !this.drawingCanvas.CaptureMouse())
67 | this.CanTouchLeave = true;
68 |
69 | this.drawingCanvas.AddVisual(this);
70 |
71 | return true;
72 | }
73 | else
74 | return OnTouchLeave(point);
75 | }
76 |
77 | public override Boolean OnTouchMove(Point point)
78 | {
79 | var start = startPoint.Value;
80 |
81 | if ((start - point).Length < pen.Thickness)
82 | return true;
83 |
84 | var dc = this.RenderOpen();
85 |
86 | var figure = pathGeometry.Figures[0];
87 | var line = new LineSegment(point, true);
88 |
89 | if (endPoint.HasValue)
90 | figure.Segments[0] = line;
91 | else
92 | figure.Segments.Add(line);
93 |
94 | endPoint = point;
95 |
96 | dc.DrawGeometry(null, pen, geometry);
97 | dc.Close();
98 |
99 | return true;
100 | }
101 |
102 | #endregion
103 |
104 | #region 序列化
105 |
106 | public override DrawGeometrySerializerBase ToSerializer()
107 | {
108 | var serializer = new DrawLineSerializer
109 | {
110 | Color = ((SolidColorBrush)pen.Brush).Color,
111 | StrokeThickness = pen.Thickness,
112 | Geometry = geometry.ToString(),
113 | StartPoint = startPoint.Value,
114 | EndPoint = endPoint.Value
115 | };
116 |
117 | if (geometry.Transform != null)
118 | serializer.Matrix = geometry.Transform.Value;
119 |
120 | return serializer;
121 | }
122 |
123 | public override void DeserializeFrom(DrawGeometrySerializerBase serializer)
124 | {
125 | var lineSerializer = (DrawLineSerializer)serializer;
126 |
127 | this.pen = new Pen(new SolidColorBrush(serializer.Color), serializer.StrokeThickness);
128 |
129 | this.geometry = Geometry.Parse(serializer.Geometry).GetFlattenedPathGeometry();
130 | this.geometry.Transform = new TranslateTransform(serializer.Matrix.OffsetX, serializer.Matrix.OffsetY);
131 |
132 | this.startPoint = lineSerializer.StartPoint;
133 | this.endPoint = lineSerializer.EndPoint;
134 |
135 | this.IsFinish = true;
136 |
137 | this.Draw();
138 | }
139 |
140 | #endregion
141 |
142 | #region 字段
143 |
144 | private Point? startPoint, endPoint;
145 |
146 | #endregion
147 | }
148 | }
149 |
--------------------------------------------------------------------------------
/PenDrawTool.cs:
--------------------------------------------------------------------------------
1 | using DrawTools.Serialize;
2 | using System;
3 | using System.Collections.Generic;
4 | using System.Linq;
5 | using System.Windows;
6 | using System.Windows.Media;
7 |
8 | namespace DrawTools
9 | {
10 | ///
11 | /// 画笔
12 | ///
13 | public sealed class PenDrawTool : DrawGeometryBase
14 | {
15 | public PenDrawTool(DrawingCanvas drawingCanvas) : base(drawingCanvas)
16 | {
17 | this.DrawingToolType = DrawToolType.Pen;
18 |
19 | // 准备要处理的事件
20 | this.CanTouchDown = true;
21 | }
22 |
23 | #region 鼠标键盘事件
24 |
25 | public override Boolean OnTouchDown(Int32 touchId, Point point)
26 | {
27 | this.TouchId = touchId;
28 |
29 | this.drawingCanvas.AddWorkingDrawTool(this);
30 | this.drawingCanvas.AddVisual(this);
31 | this.drawingCanvas.handleCursor = true;
32 |
33 | this.CanTouchDown = false;
34 |
35 | this.pen = this.drawingCanvas.Pen;
36 |
37 | points.Add(point);
38 |
39 | geometry = new PathGeometry();
40 |
41 | var figure = new PathFigure { StartPoint = point };
42 | pathGeometry.Figures.Add(figure);
43 |
44 | this.CanTouchMove = true;
45 | this.CanTouchUp = true;
46 |
47 | if (this.TouchId != 0 || !this.drawingCanvas.CaptureMouse())
48 | this.CanTouchLeave = true;
49 |
50 | return true;
51 | }
52 |
53 | public override Boolean OnTouchMove(Point point)
54 | {
55 | if ((point - points.Last()).Length < pen.Thickness)
56 | return true;
57 |
58 | points.Add(point);
59 |
60 | var figure = pathGeometry.Figures[0];
61 | var line = new LineSegment(point, true) { IsSmoothJoin = true };
62 | figure.Segments.Add(line);
63 |
64 | var dc = this.RenderOpen();
65 | dc.DrawGeometry(null, pen, geometry);
66 | dc.Close();
67 |
68 | return true;
69 | }
70 |
71 | public override Boolean OnTouchUp(Point point)
72 | {
73 | if (points.Count < 2)
74 | this.drawingCanvas.DeleteVisual(this);
75 | else
76 | {
77 | geometry = geometry.GetWidenedPathGeometry(pen);
78 | Draw();
79 | }
80 |
81 | this.drawingCanvas.DeleteWorkingDrawTool(this);
82 |
83 | this.IsFinish = true;
84 |
85 | this.CanTouchMove = false;
86 | this.CanTouchUp = false;
87 | this.CanTouchLeave = false;
88 |
89 | if (this.TouchId == 0 && this.drawingCanvas.IsMouseCaptured)
90 | this.drawingCanvas.ReleaseMouseCapture();
91 |
92 | this.drawingCanvas.handleCursor = false;
93 |
94 | return true;
95 | }
96 |
97 | #endregion
98 |
99 | #region 序列化
100 |
101 | public override DrawGeometrySerializerBase ToSerializer()
102 | {
103 | var serializer = new DrawPenSerializer
104 | {
105 | Color = ((SolidColorBrush)pen.Brush).Color,
106 | StrokeThickness = pen.Thickness,
107 | Geometry = geometry.ToString()
108 | };
109 |
110 | if (geometry.Transform != null)
111 | serializer.Matrix = geometry.Transform.Value;
112 |
113 | return serializer;
114 | }
115 |
116 | public override void DeserializeFrom(DrawGeometrySerializerBase serializer)
117 | {
118 | this.pen = new Pen(new SolidColorBrush(serializer.Color), serializer.StrokeThickness);
119 |
120 | this.geometry = Geometry.Parse(serializer.Geometry).GetFlattenedPathGeometry();
121 | this.geometry.Transform = new TranslateTransform(serializer.Matrix.OffsetX, serializer.Matrix.OffsetY);
122 |
123 | this.IsFinish = true;
124 |
125 | this.Draw();
126 | }
127 |
128 | #endregion
129 |
130 | #region 字段
131 |
132 | private List points = new List();
133 |
134 | #endregion
135 | }
136 | }
137 |
--------------------------------------------------------------------------------
/PointerDrawTool.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Windows;
4 | using System.Windows.Input;
5 | using System.Windows.Media;
6 |
7 | namespace DrawTools
8 | {
9 | ///
10 | /// 指针(拾取)
11 | ///
12 | public sealed class PointerDrawTool : DrawingVisual, IDrawTool
13 | {
14 | public PointerDrawTool(DrawingCanvas drawingCanvas)
15 | {
16 | this.drawingCanvas = drawingCanvas;
17 |
18 | // 准备要处理的事件
19 | this.CanTouchDown = true;
20 |
21 | this.selectedDrawGeometries = new List();
22 | }
23 |
24 | #region 公开方法
25 |
26 | public Boolean OnKeyDown(Key key)
27 | {
28 | throw new NotImplementedException();
29 | }
30 |
31 | public Boolean OnKeyUp(Key key)
32 | {
33 | throw new NotImplementedException();
34 | }
35 |
36 | public Boolean OnTouchDown(Int32 touchId, Point point)
37 | {
38 | if (this.drawingCanvas.DrawingToolType != this.DrawingToolType)
39 | {
40 | Delete();
41 | return false;
42 | }
43 |
44 | this.TouchId = touchId;
45 |
46 | this.mode = 0;
47 |
48 | if (this.VisualParent == null)
49 | {
50 | this.drawingCanvas.AddWorkingDrawTool(this);
51 | this.drawingCanvas.Insert(0, this);
52 | }
53 | else
54 | {
55 | this.drawingCanvas.MovoToHead(this);
56 |
57 | var visual = this.drawingCanvas.GetVisual(point);
58 |
59 | if (visual == this || (visual is DrawGeometryBase draw && selectedDrawGeometries.Contains(draw)))
60 | {
61 | this.mode = 1;
62 | this.lastPoint = point;
63 | }
64 | }
65 |
66 | this.startPoint = point;
67 |
68 | if (this.mode == 0)
69 | {
70 | selectRect = drawRect = null;
71 |
72 | this.selectedDrawGeometries.Clear();
73 |
74 | foreach (var draw in this.drawingCanvas.GetDrawGeometries())
75 | {
76 | if (draw.Select(point))
77 | {
78 | if (selectRect.HasValue)
79 | selectRect = Rect.Union(selectRect.Value, draw.Selected());
80 | else
81 | selectRect = draw.Selected();
82 |
83 | selectedDrawGeometries.Add(draw);
84 | }
85 | else
86 | draw.Unselected();
87 | }
88 |
89 | if (this.geometry == null)
90 | {
91 | this.geometry = new PathGeometry();
92 | var figure = new PathFigure { StartPoint = point, IsClosed = true, IsFilled = true };
93 | geometry.Figures.Add(figure);
94 | }
95 | else
96 | geometry.Figures[0].StartPoint = point;
97 | }
98 |
99 | this.CanTouchMove = true;
100 | this.CanTouchUp = true;
101 |
102 | if (this.TouchId != 0 || !this.drawingCanvas.CaptureMouse())
103 | this.CanTouchLeave = true;
104 |
105 | return true;
106 | }
107 |
108 | public Boolean OnTouchEnter(Point point)
109 | {
110 | throw new NotImplementedException();
111 | }
112 |
113 | public Boolean OnTouchLeave(Point point)
114 | {
115 | return OnTouchUp(point);
116 | }
117 |
118 | public Boolean OnTouchMove(Point point)
119 | {
120 | if (mode == 0)
121 | {
122 | var figure = geometry.Figures[0];
123 |
124 | var topRight = new Point(point.X, startPoint.Y);
125 | var bottomLeft = new Point(startPoint.X, point.Y);
126 |
127 | if (figure.Segments.Count == 0)
128 | {
129 | var line = new LineSegment(topRight, false);
130 | figure.Segments.Add(line);
131 |
132 | line = new LineSegment(point, false);
133 | figure.Segments.Add(line);
134 |
135 | line = new LineSegment(bottomLeft, false);
136 | figure.Segments.Add(line);
137 | }
138 | else
139 | {
140 | var line = (LineSegment)figure.Segments[0];
141 | line.Point = topRight;
142 |
143 | line = (LineSegment)figure.Segments[1];
144 | line.Point = point;
145 |
146 | line = (LineSegment)figure.Segments[2];
147 | line.Point = bottomLeft;
148 | }
149 |
150 | selectedDrawGeometries.Clear();
151 |
152 | foreach (var draw in this.drawingCanvas.GetDrawGeometries())
153 | {
154 | if (draw.Select(geometry))
155 | {
156 | if (selectRect.HasValue)
157 | selectRect = Rect.Union(selectRect.Value, draw.Selected());
158 | else
159 | selectRect = draw.Selected();
160 |
161 | selectedDrawGeometries.Add(draw);
162 | }
163 | else
164 | draw.Unselected();
165 | }
166 |
167 | drawRect = new Rect(startPoint, point);
168 |
169 | var dc = this.RenderOpen();
170 | dc.DrawRectangle(null, this.drawingCanvas.SelectBackgroundPen, drawRect.Value);
171 | dc.DrawRectangle(null, this.drawingCanvas.SelectPen, drawRect.Value);
172 | dc.Close();
173 | }
174 | else if (mode == 1)
175 | {
176 | // 移动
177 | var dx = point.X - lastPoint.X;
178 | var dy = point.Y - lastPoint.Y;
179 |
180 | lastPoint = point;
181 |
182 | foreach (var draw in selectedDrawGeometries)
183 | {
184 | draw.Move(dx, dy);
185 | }
186 |
187 | var rect = selectRect.Value;
188 | rect.X += dx;
189 | rect.Y += dy;
190 | selectRect = rect;
191 |
192 | var dc = this.RenderOpen();
193 | dc.DrawRectangle(Brushes.Transparent, this.drawingCanvas.SelectBackgroundPen, selectRect.Value);
194 | dc.DrawRectangle(null, this.drawingCanvas.SelectPen, selectRect.Value);
195 | dc.Close();
196 | }
197 |
198 | return true;
199 | }
200 |
201 | public Boolean OnTouchUp(Point point)
202 | {
203 | if (mode == 0)
204 | {
205 | if (selectedDrawGeometries.Count == 0)
206 | {
207 | Delete();
208 | return true;
209 | }
210 |
211 | if (drawRect.HasValue || selectedDrawGeometries.Count > 1)
212 | {
213 | if (drawRect.HasValue)
214 | selectRect = Rect.Union(selectRect.Value, drawRect.Value);
215 |
216 | var dc = this.RenderOpen();
217 | dc.DrawRectangle(Brushes.Transparent, this.drawingCanvas.SelectBackgroundPen, selectRect.Value);
218 | dc.DrawRectangle(null, this.drawingCanvas.SelectPen, selectRect.Value);
219 | dc.Close();
220 | }
221 | }
222 | else if (mode == 1 && startPoint == lastPoint)
223 | {
224 | var visual = this.drawingCanvas.GetVisual(point);
225 |
226 | if (visual is DrawGeometryBase selectDraw && selectDraw.CanEdit)
227 | {
228 | selectDraw.Edit();
229 |
230 | foreach (var draw in selectedDrawGeometries)
231 | {
232 | if (draw != selectDraw)
233 | draw.Unselected();
234 | }
235 |
236 | selectedDrawGeometries.Clear();
237 |
238 | Delete();
239 | return true;
240 | }
241 | }
242 |
243 | this.CanTouchMove = false;
244 | this.CanTouchUp = false;
245 | this.CanTouchLeave = false;
246 |
247 | if (this.TouchId == 0 && this.drawingCanvas.IsMouseCaptured)
248 | this.drawingCanvas.ReleaseMouseCapture();
249 |
250 | return true;
251 | }
252 |
253 | #endregion
254 |
255 | #region 私有方法
256 |
257 | private void Delete()
258 | {
259 | foreach (var draw in selectedDrawGeometries)
260 | {
261 | draw.Unselected();
262 | }
263 |
264 | selectedDrawGeometries.Clear();
265 |
266 | this.drawingCanvas.DeleteVisual(this);
267 | this.drawingCanvas.DeleteWorkingDrawTool(this);
268 |
269 | IsFinish = true;
270 |
271 | this.CanTouchDown = false;
272 | this.CanTouchMove = false;
273 | this.CanTouchUp = false;
274 | this.CanTouchLeave = false;
275 |
276 | if (this.TouchId == 0 && this.drawingCanvas.IsMouseCaptured)
277 | this.drawingCanvas.ReleaseMouseCapture();
278 | }
279 |
280 | #endregion
281 |
282 | #region 属性
283 |
284 | public Int32 TouchId { get; private set; }
285 |
286 | public Boolean CanTouchEnter { get; private set; }
287 |
288 | public Boolean CanTouchLeave { get; private set; }
289 |
290 | public Boolean CanTouchDown { get; private set; }
291 |
292 | public Boolean CanTouchMove { get; private set; }
293 |
294 | public Boolean CanTouchUp { get; private set; }
295 |
296 | public Boolean CanKeyDown { get; private set; }
297 |
298 | public Boolean CanKeyUp { get; private set; }
299 |
300 | public Boolean IsFinish { get; private set; }
301 |
302 | public DrawToolType DrawingToolType => DrawToolType.Pointer;
303 |
304 | #endregion
305 |
306 | #region 字段
307 |
308 | private DrawingCanvas drawingCanvas;
309 | private Point startPoint, lastPoint;
310 | private List selectedDrawGeometries;
311 | private PathGeometry geometry;
312 | private Rect? selectRect, drawRect;
313 | ///
314 | /// 0:拾取|1:拖动
315 | ///
316 | private Int32 mode;
317 |
318 | #endregion
319 | }
320 | }
321 |
--------------------------------------------------------------------------------
/PolygonDrawTool.cs:
--------------------------------------------------------------------------------
1 | using DrawTools.Serialize;
2 | using System;
3 | using System.Collections.Generic;
4 | using System.Linq;
5 | using System.Windows;
6 | using System.Windows.Media;
7 |
8 | namespace DrawTools
9 | {
10 | ///
11 | /// 多边形
12 | ///
13 | public sealed class PolygonDrawTool : DrawGeometryBase
14 | {
15 | public PolygonDrawTool(DrawingCanvas drawingCanvas) : base(drawingCanvas)
16 | {
17 | this.DrawingToolType = DrawToolType.Polygon;
18 |
19 | // 准备要处理的事件
20 | this.CanTouchDown = true;
21 | }
22 |
23 | #region 鼠标键盘事件
24 |
25 | public override Boolean OnTouchLeave(Point point)
26 | {
27 | if (mousePoint.HasValue)
28 | {
29 | points.Add(mousePoint.Value);
30 | mousePoint = null;
31 | }
32 |
33 | if (points.Count < 3)
34 | this.drawingCanvas.DeleteVisual(this);
35 | else
36 | {
37 | var figure = pathGeometry.Figures[0];
38 | figure.IsClosed = true;
39 |
40 | geometry = geometry.GetWidenedPathGeometry(pen);
41 | Draw();
42 | }
43 |
44 | this.drawingCanvas.DeleteWorkingDrawTool(this);
45 |
46 | this.IsFinish = true;
47 |
48 | this.CanTouchDown = false;
49 | this.CanTouchMove = false;
50 | this.CanTouchLeave = false;
51 |
52 | if (this.TouchId == 0 && this.drawingCanvas.IsMouseCaptured)
53 | this.drawingCanvas.ReleaseMouseCapture();
54 |
55 | return true;
56 | }
57 |
58 | public override Boolean OnTouchDown(Int32 touchId, Point point)
59 | {
60 | this.TouchId = touchId;
61 |
62 | if (points.Count == 0)
63 | {
64 | this.drawingCanvas.AddWorkingDrawTool(this);
65 |
66 | this.pen = this.drawingCanvas.Pen;
67 |
68 | points.Add(point);
69 |
70 | geometry = new PathGeometry();
71 |
72 | var figure = new PathFigure { StartPoint = point };
73 | pathGeometry.Figures.Add(figure);
74 |
75 | this.CanTouchMove = true;
76 |
77 | if (this.TouchId != 0 || !this.drawingCanvas.CaptureMouse())
78 | this.CanTouchLeave = true;
79 |
80 | this.drawingCanvas.AddVisual(this);
81 | }
82 | else if ((point - points.Last()).Length <= pen.Thickness)
83 | return OnTouchLeave(point);
84 | else if (mousePoint.HasValue)
85 | {
86 | points.Add(mousePoint.Value);
87 | mousePoint = null;
88 | }
89 |
90 | return true;
91 | }
92 |
93 | public override Boolean OnTouchMove(Point point)
94 | {
95 | if ((point - points.Last()).Length <= pen.Thickness)
96 | return true;
97 |
98 | var figure = pathGeometry.Figures[0];
99 | var line = new LineSegment(point, true) { IsSmoothJoin = true };
100 |
101 | if (mousePoint.HasValue)
102 | figure.Segments[figure.Segments.Count - 1] = line;
103 | else
104 | figure.Segments.Add(line);
105 |
106 | mousePoint = point;
107 |
108 | var dc = this.RenderOpen();
109 | dc.DrawGeometry(null, pen, geometry);
110 | dc.Close();
111 |
112 | return true;
113 | }
114 |
115 | #endregion
116 |
117 | #region 序列化
118 |
119 | public override DrawGeometrySerializerBase ToSerializer()
120 | {
121 | var serializer = new DrawPolygonSerializer
122 | {
123 | Color = ((SolidColorBrush)pen.Brush).Color,
124 | StrokeThickness = pen.Thickness,
125 | Geometry = geometry.ToString()
126 | };
127 |
128 | if (geometry.Transform != null)
129 | serializer.Matrix = geometry.Transform.Value;
130 |
131 | return serializer;
132 | }
133 |
134 | public override void DeserializeFrom(DrawGeometrySerializerBase serializer)
135 | {
136 | this.pen = new Pen(new SolidColorBrush(serializer.Color), serializer.StrokeThickness);
137 |
138 | this.geometry = Geometry.Parse(serializer.Geometry).GetFlattenedPathGeometry();
139 | this.geometry.Transform = new TranslateTransform(serializer.Matrix.OffsetX, serializer.Matrix.OffsetY);
140 |
141 | this.IsFinish = true;
142 |
143 | this.Draw();
144 | }
145 |
146 | #endregion
147 |
148 | #region 字段
149 |
150 | private List points = new List();
151 | private Point? mousePoint;
152 |
153 | #endregion
154 | }
155 | }
156 |
--------------------------------------------------------------------------------
/PolylineDrawTool.cs:
--------------------------------------------------------------------------------
1 | using DrawTools.Serialize;
2 | using System;
3 | using System.Collections.Generic;
4 | using System.Linq;
5 | using System.Windows;
6 | using System.Windows.Media;
7 |
8 | namespace DrawTools
9 | {
10 | ///
11 | /// 折线
12 | ///
13 | public sealed class PolylineDrawTool : DrawGeometryBase
14 | {
15 | public PolylineDrawTool(DrawingCanvas drawingCanvas) : base(drawingCanvas)
16 | {
17 | this.DrawingToolType = DrawToolType.Polyline;
18 |
19 | // 准备要处理的事件
20 | this.CanTouchDown = true;
21 | }
22 |
23 | #region 鼠标键盘事件
24 |
25 | public override Boolean OnTouchLeave(Point point)
26 | {
27 | if (mousePoint.HasValue)
28 | {
29 | points.Add(mousePoint.Value);
30 | mousePoint = null;
31 | }
32 |
33 | if (points.Count < 2)
34 | this.drawingCanvas.DeleteVisual(this);
35 | else
36 | {
37 | geometry = geometry.GetWidenedPathGeometry(pen);
38 | Draw();
39 | }
40 |
41 | this.drawingCanvas.DeleteWorkingDrawTool(this);
42 |
43 | this.IsFinish = true;
44 |
45 | this.CanTouchDown = false;
46 | this.CanTouchMove = false;
47 | this.CanTouchLeave = false;
48 |
49 | if (this.TouchId == 0 && this.drawingCanvas.IsMouseCaptured)
50 | this.drawingCanvas.ReleaseMouseCapture();
51 |
52 | return true;
53 | }
54 |
55 | public override Boolean OnTouchDown(Int32 touchId, Point point)
56 | {
57 | this.TouchId = touchId;
58 |
59 | if (points.Count == 0)
60 | {
61 | this.drawingCanvas.AddWorkingDrawTool(this);
62 |
63 | this.pen = this.drawingCanvas.Pen;
64 |
65 | points.Add(point);
66 |
67 | geometry = new PathGeometry();
68 |
69 | var figure = new PathFigure { StartPoint = point };
70 | pathGeometry.Figures.Add(figure);
71 |
72 | this.CanTouchMove = true;
73 |
74 | if (this.TouchId != 0 || !this.drawingCanvas.CaptureMouse())
75 | this.CanTouchLeave = true;
76 |
77 | this.drawingCanvas.AddVisual(this);
78 | }
79 | else if ((point - points.Last()).Length <= pen.Thickness)
80 | OnTouchLeave(point);
81 | else if (mousePoint.HasValue)
82 | {
83 | points.Add(mousePoint.Value);
84 | mousePoint = null;
85 | }
86 |
87 | return true;
88 | }
89 |
90 | public override Boolean OnTouchMove(Point point)
91 | {
92 | if ((point - points.Last()).Length <= pen.Thickness)
93 | return true;
94 |
95 | var figure = pathGeometry.Figures[0];
96 | var line = new LineSegment(point, true) { IsSmoothJoin = true };
97 |
98 | if (mousePoint.HasValue)
99 | figure.Segments[figure.Segments.Count - 1] = line;
100 | else
101 | figure.Segments.Add(line);
102 |
103 | mousePoint = point;
104 |
105 | var dc = this.RenderOpen();
106 | dc.DrawGeometry(null, pen, geometry);
107 | dc.Close();
108 |
109 | return true;
110 | }
111 |
112 | #endregion
113 |
114 | #region 序列化
115 |
116 | public override DrawGeometrySerializerBase ToSerializer()
117 | {
118 | var serializer = new DrawPolylineSerializer
119 | {
120 | Color = ((SolidColorBrush)pen.Brush).Color,
121 | StrokeThickness = pen.Thickness,
122 | Geometry = geometry.ToString()
123 | };
124 |
125 | if (geometry.Transform != null)
126 | serializer.Matrix = geometry.Transform.Value;
127 |
128 | return serializer;
129 | }
130 |
131 | public override void DeserializeFrom(DrawGeometrySerializerBase serializer)
132 | {
133 | this.pen = new Pen(new SolidColorBrush(serializer.Color), serializer.StrokeThickness);
134 |
135 | this.geometry = Geometry.Parse(serializer.Geometry).GetFlattenedPathGeometry();
136 | this.geometry.Transform = new TranslateTransform(serializer.Matrix.OffsetX, serializer.Matrix.OffsetY);
137 |
138 | this.IsFinish = true;
139 |
140 | this.Draw();
141 | }
142 |
143 | #endregion
144 |
145 | #region 字段
146 |
147 | private List points = new List();
148 | private Point? mousePoint;
149 |
150 | #endregion
151 | }
152 | }
153 |
--------------------------------------------------------------------------------
/Properties/AssemblyInfo.cs:
--------------------------------------------------------------------------------
1 | using System.Reflection;
2 | using System.Resources;
3 | using System.Runtime.CompilerServices;
4 | using System.Runtime.InteropServices;
5 | using System.Windows;
6 |
7 | // 有关程序集的一般信息由以下
8 | // 控制。更改这些特性值可修改
9 | // 与程序集关联的信息。
10 | [assembly: AssemblyTitle("DrawTools")]
11 | [assembly: AssemblyDescription("")]
12 | [assembly: AssemblyConfiguration("")]
13 | [assembly: AssemblyCompany("")]
14 | [assembly: AssemblyProduct("DrawTools")]
15 | [assembly: AssemblyCopyright("Copyright © 2020")]
16 | [assembly: AssemblyTrademark("")]
17 | [assembly: AssemblyCulture("")]
18 |
19 | // 将 ComVisible 设置为 false 会使此程序集中的类型
20 | //对 COM 组件不可见。如果需要从 COM 访问此程序集中的类型
21 | //请将此类型的 ComVisible 特性设置为 true。
22 | [assembly: ComVisible(false)]
23 |
24 | //若要开始生成可本地化的应用程序,请设置
25 | //.csproj 文件中的 CultureYouAreCodingWith
26 | //例如,如果您在源文件中使用的是美国英语,
27 | //使用的是美国英语,请将 设置为 en-US。 然后取消
28 | //对以下 NeutralResourceLanguage 特性的注释。 更新
29 | //以下行中的“en-US”以匹配项目文件中的 UICulture 设置。
30 |
31 | //[assembly: NeutralResourcesLanguage("en-US", UltimateResourceFallbackLocation.Satellite)]
32 |
33 |
34 | [assembly: ThemeInfo(
35 | ResourceDictionaryLocation.None, //主题特定资源词典所处位置
36 | //(未在页面中找到资源时使用,
37 | //或应用程序资源字典中找到时使用)
38 | ResourceDictionaryLocation.SourceAssembly //常规资源词典所处位置
39 | //(未在页面中找到资源时使用,
40 | //、应用程序或任何主题专用资源字典中找到时使用)
41 | )]
42 |
43 |
44 | // 程序集的版本信息由下列四个值组成:
45 | //
46 | // 主版本
47 | // 次版本
48 | // 生成号
49 | // 修订号
50 | //
51 | //可以指定所有这些值,也可以使用“生成号”和“修订号”的默认值
52 | //通过使用 "*",如下所示:
53 | // [assembly: AssemblyVersion("1.0.*")]
54 | [assembly: AssemblyVersion("1.0.0.0")]
55 | [assembly: AssemblyFileVersion("1.0.0.0")]
56 |
--------------------------------------------------------------------------------
/Properties/Resources.Designer.cs:
--------------------------------------------------------------------------------
1 | //------------------------------------------------------------------------------
2 | //
3 | // 此代码由工具生成。
4 | // 运行时版本: 4.0.30319.42000
5 | //
6 | // 对此文件的更改可能导致不正确的行为,如果
7 | // 重新生成代码,则所做更改将丢失。
8 | //
9 | //------------------------------------------------------------------------------
10 |
11 | namespace DrawTools.Properties
12 | {
13 |
14 |
15 | ///
16 | /// 强类型资源类,用于查找本地化字符串等。
17 | ///
18 | // 此类是由 StronglyTypedResourceBuilder
19 | // 类通过类似于 ResGen 或 Visual Studio 的工具自动生成的。
20 | // 若要添加或删除成员,请编辑 .ResX 文件,然后重新运行 ResGen
21 | // (以 /str 作为命令选项),或重新生成 VS 项目。
22 | [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")]
23 | [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
24 | [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
25 | internal class Resources
26 | {
27 |
28 | private static global::System.Resources.ResourceManager resourceMan;
29 |
30 | private static global::System.Globalization.CultureInfo resourceCulture;
31 |
32 | [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
33 | internal Resources()
34 | {
35 | }
36 |
37 | ///
38 | /// 返回此类使用的缓存 ResourceManager 实例。
39 | ///
40 | [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
41 | internal static global::System.Resources.ResourceManager ResourceManager
42 | {
43 | get
44 | {
45 | if ((resourceMan == null))
46 | {
47 | global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("DrawTools.Properties.Resources", typeof(Resources).Assembly);
48 | resourceMan = temp;
49 | }
50 | return resourceMan;
51 | }
52 | }
53 |
54 | ///
55 | /// 覆盖当前线程的 CurrentUICulture 属性
56 | /// 使用此强类型的资源类的资源查找。
57 | ///
58 | [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
59 | internal static global::System.Globalization.CultureInfo Culture
60 | {
61 | get
62 | {
63 | return resourceCulture;
64 | }
65 | set
66 | {
67 | resourceCulture = value;
68 | }
69 | }
70 | }
71 | }
72 |
--------------------------------------------------------------------------------
/Properties/Resources.resx:
--------------------------------------------------------------------------------
1 |
2 |
3 |
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 | text/microsoft-resx
107 |
108 |
109 | 2.0
110 |
111 |
112 | System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
113 |
114 |
115 | System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
116 |
117 |
--------------------------------------------------------------------------------
/Properties/Settings.Designer.cs:
--------------------------------------------------------------------------------
1 | //------------------------------------------------------------------------------
2 | //
3 | // This code was generated by a tool.
4 | // Runtime Version:4.0.30319.42000
5 | //
6 | // Changes to this file may cause incorrect behavior and will be lost if
7 | // the code is regenerated.
8 | //
9 | //------------------------------------------------------------------------------
10 |
11 | namespace DrawTools.Properties
12 | {
13 |
14 |
15 | [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
16 | [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "11.0.0.0")]
17 | internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase
18 | {
19 |
20 | private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings())));
21 |
22 | public static Settings Default
23 | {
24 | get
25 | {
26 | return defaultInstance;
27 | }
28 | }
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/Properties/Settings.settings:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/RangingDrawTool.cs:
--------------------------------------------------------------------------------
1 | using DrawTools.Serialize;
2 | using DrawTools.Utils;
3 | using System;
4 | using System.Windows;
5 | using System.Windows.Media;
6 |
7 | namespace DrawTools
8 | {
9 | public sealed class RangingDrawTool : DrawGeometryBase
10 | {
11 | public RangingDrawTool(DrawingCanvas drawingCanvas) : base(drawingCanvas)
12 | {
13 | this.DrawingToolType = DrawToolType.Ranging;
14 |
15 | // 准备要处理的事件
16 | this.CanTouchDown = true;
17 | }
18 |
19 | #region 鼠标键盘事件
20 |
21 | public override Boolean OnTouchLeave(Point point)
22 | {
23 | if (!endPoint.HasValue || (startPoint.Value - endPoint.Value).Length < pen.Thickness)
24 | this.drawingCanvas.DeleteVisual(this);
25 | else
26 | {
27 | var textGeometry = formattedText.BuildGeometry(textPoint);
28 | textGeometry.Transform = new RotateTransform(angle, center.X, center.Y);
29 | geometry = geometry.GetWidenedPathGeometry(pen);
30 | geometry = Geometry.Combine(geometry, textGeometry, GeometryCombineMode.Union, null);
31 |
32 | Draw();
33 | }
34 |
35 | this.drawingCanvas.DeleteWorkingDrawTool(this);
36 |
37 | this.IsFinish = true;
38 |
39 | this.CanKeyDown = false;
40 | this.CanTouchMove = false;
41 | this.CanTouchLeave = false;
42 |
43 | if (this.TouchId == 0 && this.drawingCanvas.IsMouseCaptured)
44 | this.drawingCanvas.ReleaseMouseCapture();
45 |
46 | return true;
47 | }
48 |
49 | public override Boolean OnTouchDown(Int32 touchId, Point point)
50 | {
51 | this.TouchId = touchId;
52 |
53 | if (!startPoint.HasValue)
54 | {
55 | this.drawingCanvas.AddWorkingDrawTool(this);
56 |
57 | this.pen = this.drawingCanvas.Pen;
58 |
59 | this.fontSize = this.drawingCanvas.FontSize;
60 | this.typeface = new Typeface(new FontFamily("Microsoft YaHei UI,Tahoma"), FontStyles.Normal, FontWeights.Normal, FontStretches.Normal);
61 |
62 | this.dpi = DpiHelper.GetDpiFromVisual(this.drawingCanvas);
63 |
64 | startPoint = point;
65 |
66 | geometry = new PathGeometry();
67 |
68 | var figure = new PathFigure();
69 | pathGeometry.Figures.Add(figure);
70 |
71 | this.CanTouchMove = true;
72 |
73 | if (this.TouchId != 0 || !this.drawingCanvas.CaptureMouse())
74 | this.CanTouchLeave = true;
75 |
76 | this.drawingCanvas.AddVisual(this);
77 |
78 | return true;
79 | }
80 | else
81 | return OnTouchLeave(point);
82 | }
83 |
84 | public override Boolean OnTouchMove(Point point)
85 | {
86 | var start = startPoint.Value;
87 |
88 | if ((start - point).Length < this.drawingCanvas.StrokeThickness)
89 | return true;
90 |
91 | var dc = this.RenderOpen();
92 |
93 | endPoint = point;
94 |
95 | var x = Math.Abs(point.X - start.X) / Dpi.Cm2Wpf;
96 | var y = Math.Abs(point.Y - start.Y) / Dpi.Cm2Wpf;
97 | var len = Math.Sqrt(x * x + y * y);
98 | var text = (len * 10).ToString("0.00") + "mm";
99 |
100 | formattedText = new FormattedText(
101 | text,
102 | System.Globalization.CultureInfo.InvariantCulture,
103 | FlowDirection.LeftToRight,
104 | typeface,
105 | this.fontSize,
106 | pen.Brush);
107 |
108 | center = new Point((start.X + point.X) / 2, (start.Y + point.Y) / 2);
109 | var width = text.Length * fontSize / 2; // 文字宽度
110 | textPoint = new Point(center.X - width / 2, center.Y - fontSize * 1.2 - pen.Thickness); // 文字左上角,1.2倍行高
111 |
112 | Double? k = null; // 斜率
113 |
114 | if (start.X == point.X)
115 | angle = start.Y > point.Y ? 90 : -90;
116 | else
117 | {
118 | k = (point.Y - start.Y) / (point.X - start.X);
119 | angle = Math.Atan(k.Value) / Math.PI * 180;
120 | }
121 |
122 | dc.PushTransform(new RotateTransform(angle, center.X, center.Y));
123 | dc.DrawText(formattedText, textPoint);
124 | dc.Pop();
125 |
126 | var tangentK = k.HasValue ? (-1 / k) : null;
127 | var tangentLen = pen.Thickness + fontSize * 1.2;
128 |
129 | if (tangentK.HasValue)
130 | {
131 | var offsetX1 = Math.Sqrt(tangentLen * tangentLen / (1 + tangentK.Value * tangentK.Value)) * (angle > 0 ? 1 : -1);
132 |
133 | tangent1.X = offsetX1 + start.X;
134 | tangent1.Y = start.Y + offsetX1 * tangentK.Value;
135 |
136 | tangent2.X = offsetX1 + point.X;
137 | tangent2.Y = point.Y + offsetX1 * tangentK.Value;
138 | }
139 | else
140 | {
141 | tangent1.X = start.X + (angle == 90 ? tangentLen : -tangentLen);
142 | tangent1.Y = start.Y;
143 |
144 | tangent2.X = point.X + (angle == 90 ? tangentLen : -tangentLen);
145 | tangent2.Y = point.Y;
146 | }
147 |
148 | var figure = pathGeometry.Figures[0];
149 | figure.StartPoint = tangent1;
150 | figure.Segments.Clear();
151 |
152 | var line = new LineSegment(start, true) { IsSmoothJoin = true };
153 | figure.Segments.Add(line);
154 | line = new LineSegment(point, true) { IsSmoothJoin = true };
155 | figure.Segments.Add(line);
156 | line = new LineSegment(tangent2, true) { IsSmoothJoin = true };
157 | figure.Segments.Add(line);
158 |
159 | dc.DrawGeometry(null, pen, geometry);
160 | dc.Close();
161 |
162 | return true;
163 | }
164 |
165 | #endregion
166 |
167 | #region 序列化
168 |
169 | public override DrawGeometrySerializerBase ToSerializer()
170 | {
171 | var serializer = new DrawRangingSerializer
172 | {
173 | Color = ((SolidColorBrush)pen.Brush).Color,
174 | StrokeThickness = pen.Thickness,
175 | Geometry = geometry.ToString()
176 | };
177 |
178 | if (geometry.Transform != null)
179 | serializer.Matrix = geometry.Transform.Value;
180 |
181 | return serializer;
182 | }
183 |
184 | public override void DeserializeFrom(DrawGeometrySerializerBase serializer)
185 | {
186 | this.pen = new Pen(new SolidColorBrush(serializer.Color), serializer.StrokeThickness);
187 |
188 | this.geometry = Geometry.Parse(serializer.Geometry).GetFlattenedPathGeometry();
189 | this.geometry.Transform = new TranslateTransform(serializer.Matrix.OffsetX, serializer.Matrix.OffsetY);
190 |
191 | this.IsFinish = true;
192 |
193 | this.Draw();
194 | }
195 |
196 | #endregion
197 |
198 | #region 字段
199 |
200 | private Point? startPoint, endPoint;
201 | private Point center, textPoint, tangent1, tangent2;
202 | private Double fontSize;
203 | private Typeface typeface;
204 | private FormattedText formattedText;
205 | private Double angle;
206 | private Dpi dpi;
207 |
208 | #endregion
209 | }
210 | }
211 |
--------------------------------------------------------------------------------
/RectangleDrawTool.cs:
--------------------------------------------------------------------------------
1 | using DrawTools.Serialize;
2 | using System;
3 | using System.Windows;
4 | using System.Windows.Input;
5 | using System.Windows.Media;
6 |
7 | namespace DrawTools
8 | {
9 | ///
10 | /// 矩形
11 | ///
12 | public sealed class RectangleDrawTool : DrawGeometryBase
13 | {
14 | public RectangleDrawTool(DrawingCanvas drawingCanvas) : base(drawingCanvas)
15 | {
16 | this.DrawingToolType = DrawToolType.Rectangle;
17 |
18 | // 准备要处理的事件
19 | this.CanTouchDown = true;
20 | }
21 |
22 | #region 鼠标键盘事件
23 |
24 | public override Boolean OnKeyDown(Key key)
25 | {
26 | if (key != Key.LeftShift || !bottomRight.HasValue)
27 | return false;
28 |
29 | return OnTouchMove(bottomRight.Value);
30 | }
31 |
32 | public override Boolean OnKeyUp(Key key)
33 | {
34 | if (key != Key.LeftShift || !bottomRight.HasValue)
35 | return false;
36 |
37 | return OnTouchMove(bottomRight.Value);
38 | }
39 |
40 | public override Boolean OnTouchLeave(Point point)
41 | {
42 | if (!bottomRight.HasValue)
43 | this.drawingCanvas.DeleteVisual(this);
44 | else
45 | {
46 | var figure = pathGeometry.Figures[0];
47 |
48 | var line = new LineSegment(new Point(bottomRight.Value.X, topLeft.Value.Y), true);
49 | figure.Segments.Add(line);
50 |
51 | line = new LineSegment(bottomRight.Value, true);
52 | figure.Segments.Add(line);
53 |
54 | line = new LineSegment(new Point(topLeft.Value.X, bottomRight.Value.Y), true);
55 | figure.Segments.Add(line);
56 |
57 | geometry = geometry.GetWidenedPathGeometry(pen);
58 |
59 | Draw();
60 | }
61 |
62 | this.drawingCanvas.DeleteWorkingDrawTool(this);
63 |
64 | this.IsFinish = true;
65 |
66 | this.CanTouchDown = false;
67 | this.CanTouchMove = false;
68 | this.CanTouchLeave = false;
69 | this.CanKeyDown = false;
70 | this.CanKeyUp = false;
71 |
72 | if (this.TouchId == 0 && this.drawingCanvas.IsMouseCaptured)
73 | this.drawingCanvas.ReleaseMouseCapture();
74 |
75 | return true;
76 | }
77 |
78 | public override Boolean OnTouchDown(Int32 touchId, Point point)
79 | {
80 | this.TouchId = touchId;
81 |
82 | if (!topLeft.HasValue)
83 | {
84 | this.drawingCanvas.AddWorkingDrawTool(this);
85 |
86 | this.pen = this.drawingCanvas.Pen;
87 |
88 | topLeft = point;
89 |
90 | this.geometry = new PathGeometry();
91 |
92 | var figure = new PathFigure { StartPoint = point, IsClosed = true };
93 | pathGeometry.Figures.Add(figure);
94 |
95 | this.CanTouchMove = true;
96 | this.CanKeyDown = true;
97 | this.CanKeyUp = true;
98 |
99 | if (this.TouchId != 0 || !this.drawingCanvas.CaptureMouse())
100 | this.CanTouchLeave = true;
101 |
102 | this.drawingCanvas.AddVisual(this);
103 | }
104 | else
105 | return OnTouchLeave(point);
106 |
107 | return true;
108 | }
109 |
110 | public override Boolean OnTouchMove(Point point)
111 | {
112 | var dc = this.RenderOpen();
113 |
114 | var startPoint = topLeft.Value;
115 |
116 | if (Keyboard.IsKeyDown(Key.LeftShift))
117 | {
118 | var len = Math.Min(Math.Abs(point.X - startPoint.X), Math.Abs(point.Y - startPoint.Y));
119 | point = new Point(startPoint.X + (point.X > startPoint.X ? len : -len), startPoint.Y + (point.Y > startPoint.Y ? len : -len));
120 | }
121 |
122 | if ((startPoint - point).Length > pen.Thickness)
123 | {
124 | bottomRight = point;
125 | dc.DrawRectangle(null, pen, new Rect(startPoint, point));
126 | }
127 |
128 | dc.Close();
129 |
130 | return true;
131 | }
132 |
133 | #endregion
134 |
135 | #region 序列化
136 |
137 | public override DrawGeometrySerializerBase ToSerializer()
138 | {
139 | var serializer = new DrawRectangleSerializer
140 | {
141 | Color = ((SolidColorBrush)pen.Brush).Color,
142 | StrokeThickness = pen.Thickness,
143 | Geometry = geometry.ToString()
144 | };
145 |
146 | if (geometry.Transform != null)
147 | serializer.Matrix = geometry.Transform.Value;
148 |
149 | return serializer;
150 | }
151 |
152 | public override void DeserializeFrom(DrawGeometrySerializerBase serializer)
153 | {
154 | this.pen = new Pen(new SolidColorBrush(serializer.Color), serializer.StrokeThickness);
155 |
156 | this.geometry = Geometry.Parse(serializer.Geometry).GetFlattenedPathGeometry();
157 | this.geometry.Transform = new TranslateTransform(serializer.Matrix.OffsetX, serializer.Matrix.OffsetY);
158 |
159 | this.IsFinish = true;
160 |
161 | this.Draw();
162 | }
163 |
164 | #endregion
165 |
166 | #region 字段
167 |
168 | private Point? topLeft, bottomRight;
169 |
170 | #endregion
171 | }
172 | }
173 |
--------------------------------------------------------------------------------
/Serialize/DrawAngleSerializer.cs:
--------------------------------------------------------------------------------
1 | namespace DrawTools.Serialize
2 | {
3 | public sealed class DrawAngleSerializer : DrawGeometrySerializerBase
4 | {
5 | public override DrawGeometryBase Deserialize(DrawingCanvas drawingCanvas)
6 | {
7 | var draw = new AngleDrawTool(drawingCanvas);
8 | draw.DeserializeFrom(this);
9 | return draw;
10 | }
11 |
12 | #region 属性
13 |
14 |
15 |
16 | #endregion
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/Serialize/DrawAreaSerializer.cs:
--------------------------------------------------------------------------------
1 | namespace DrawTools.Serialize
2 | {
3 | public sealed class DrawAreaSerializer : DrawGeometrySerializerBase
4 | {
5 | public override DrawGeometryBase Deserialize(DrawingCanvas drawingCanvas)
6 | {
7 | var draw = new AreaDrawTool(drawingCanvas);
8 | draw.DeserializeFrom(this);
9 | return draw;
10 | }
11 |
12 | #region 属性
13 |
14 |
15 |
16 | #endregion
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/Serialize/DrawArrowSerializer.cs:
--------------------------------------------------------------------------------
1 | namespace DrawTools.Serialize
2 | {
3 | public sealed class DrawArrowSerializer : DrawGeometrySerializerBase
4 | {
5 | public override DrawGeometryBase Deserialize(DrawingCanvas drawingCanvas)
6 | {
7 | var draw = new ArrowDrawTool(drawingCanvas);
8 | draw.DeserializeFrom(this);
9 | return draw;
10 | }
11 |
12 | #region 属性
13 |
14 |
15 |
16 | #endregion
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/Serialize/DrawClosedCurveSerializer.cs:
--------------------------------------------------------------------------------
1 | namespace DrawTools.Serialize
2 | {
3 | public sealed class DrawClosedCurveSerializer : DrawGeometrySerializerBase
4 | {
5 | public override DrawGeometryBase Deserialize(DrawingCanvas drawingCanvas)
6 | {
7 | var draw = new ClosedCurveDrawTool(drawingCanvas);
8 | draw.DeserializeFrom(this);
9 | return draw;
10 | }
11 |
12 | #region 属性
13 |
14 |
15 |
16 | #endregion
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/Serialize/DrawCurveSerializer.cs:
--------------------------------------------------------------------------------
1 | namespace DrawTools.Serialize
2 | {
3 | public sealed class DrawCurveSerializer : DrawGeometrySerializerBase
4 | {
5 | public override DrawGeometryBase Deserialize(DrawingCanvas drawingCanvas)
6 | {
7 | var draw = new CurveDrawTool(drawingCanvas);
8 | draw.DeserializeFrom(this);
9 | return draw;
10 | }
11 |
12 | #region 属性
13 |
14 |
15 |
16 | #endregion
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/Serialize/DrawEllipseSerializer.cs:
--------------------------------------------------------------------------------
1 | namespace DrawTools.Serialize
2 | {
3 | public sealed class DrawEllipseSerializer : DrawGeometrySerializerBase
4 | {
5 | public override DrawGeometryBase Deserialize(DrawingCanvas drawingCanvas)
6 | {
7 | var draw = new EllipseDrawTool(drawingCanvas);
8 | draw.DeserializeFrom(this);
9 | return draw;
10 | }
11 |
12 | #region 属性
13 |
14 |
15 |
16 | #endregion
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/Serialize/DrawGeometrySerializer.cs:
--------------------------------------------------------------------------------
1 | using System.Xml.Serialization;
2 |
3 | namespace DrawTools.Serialize
4 | {
5 | [XmlRoot("Geometries")]
6 | public sealed class DrawGeometrySerializer
7 | {
8 | #region 属性
9 |
10 | [XmlArrayItem(typeof(DrawPenSerializer)),
11 | XmlArrayItem(typeof(DrawRangingSerializer)),
12 | XmlArrayItem(typeof(DrawLineSerializer)),
13 | XmlArrayItem(typeof(DrawArrowSerializer)),
14 | XmlArrayItem(typeof(DrawRectangleSerializer)),
15 | XmlArrayItem(typeof(DrawEllipseSerializer)),
16 | XmlArrayItem(typeof(DrawAngleSerializer)),
17 | XmlArrayItem(typeof(DrawPolylineSerializer)),
18 | XmlArrayItem(typeof(DrawCurveSerializer)),
19 | XmlArrayItem(typeof(DrawPolygonSerializer)),
20 | XmlArrayItem(typeof(DrawClosedCurveSerializer)),
21 | XmlArrayItem(typeof(DrawAreaSerializer)),
22 | XmlArrayItem(typeof(DrawTextSerializer))]
23 | public DrawGeometrySerializerBase[] Geometries { get; set; }
24 |
25 | #endregion
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/Serialize/DrawGeometrySerializerBase.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Windows.Media;
3 |
4 | namespace DrawTools.Serialize
5 | {
6 | ///
7 | /// 画图几何图形序列化基类
8 | ///
9 | public abstract class DrawGeometrySerializerBase
10 | {
11 | ///
12 | /// 反序列化
13 | ///
14 | ///
15 | public abstract DrawGeometryBase Deserialize(DrawingCanvas drawingCanvas);
16 |
17 | #region 属性
18 |
19 | public Double StrokeThickness { get; set; }
20 |
21 | public Color Color { get; set; }
22 |
23 | public String Geometry { get; set; }
24 |
25 | public Matrix Matrix { get; set; }
26 |
27 | #endregion
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/Serialize/DrawLineSerializer.cs:
--------------------------------------------------------------------------------
1 | using System.Windows;
2 |
3 | namespace DrawTools.Serialize
4 | {
5 | public sealed class DrawLineSerializer : DrawGeometrySerializerBase
6 | {
7 | public override DrawGeometryBase Deserialize(DrawingCanvas drawingCanvas)
8 | {
9 | var draw = new LineDrawTool(drawingCanvas);
10 | draw.DeserializeFrom(this);
11 | return draw;
12 | }
13 |
14 | #region 属性
15 |
16 | public Point StartPoint { get; set; }
17 | public Point EndPoint { get; set; }
18 |
19 | #endregion
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/Serialize/DrawPenSerializer.cs:
--------------------------------------------------------------------------------
1 | namespace DrawTools.Serialize
2 | {
3 | public sealed class DrawPenSerializer : DrawGeometrySerializerBase
4 | {
5 | public override DrawGeometryBase Deserialize(DrawingCanvas drawingCanvas)
6 | {
7 | var draw = new PenDrawTool(drawingCanvas);
8 | draw.DeserializeFrom(this);
9 | return draw;
10 | }
11 |
12 | #region 属性
13 |
14 |
15 |
16 | #endregion
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/Serialize/DrawPolygonSerializer.cs:
--------------------------------------------------------------------------------
1 | namespace DrawTools.Serialize
2 | {
3 | public sealed class DrawPolygonSerializer : DrawGeometrySerializerBase
4 | {
5 | public override DrawGeometryBase Deserialize(DrawingCanvas drawingCanvas)
6 | {
7 | var draw = new PolygonDrawTool(drawingCanvas);
8 | draw.DeserializeFrom(this);
9 | return draw;
10 | }
11 |
12 | #region 属性
13 |
14 |
15 |
16 | #endregion
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/Serialize/DrawPolylineSerializer.cs:
--------------------------------------------------------------------------------
1 | namespace DrawTools.Serialize
2 | {
3 | public sealed class DrawPolylineSerializer : DrawGeometrySerializerBase
4 | {
5 | public override DrawGeometryBase Deserialize(DrawingCanvas drawingCanvas)
6 | {
7 | var draw = new PolylineDrawTool(drawingCanvas);
8 | draw.DeserializeFrom(this);
9 | return draw;
10 | }
11 |
12 | #region 属性
13 |
14 |
15 |
16 | #endregion
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/Serialize/DrawRangingSerializer.cs:
--------------------------------------------------------------------------------
1 | namespace DrawTools.Serialize
2 | {
3 | public sealed class DrawRangingSerializer : DrawGeometrySerializerBase
4 | {
5 | public override DrawGeometryBase Deserialize(DrawingCanvas drawingCanvas)
6 | {
7 | var draw = new RangingDrawTool(drawingCanvas);
8 | draw.DeserializeFrom(this);
9 | return draw;
10 | }
11 |
12 | #region 属性
13 |
14 |
15 |
16 | #endregion
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/Serialize/DrawRectangleSerializer.cs:
--------------------------------------------------------------------------------
1 | namespace DrawTools.Serialize
2 | {
3 | public sealed class DrawRectangleSerializer : DrawGeometrySerializerBase
4 | {
5 | public override DrawGeometryBase Deserialize(DrawingCanvas drawingCanvas)
6 | {
7 | var draw = new RectangleDrawTool(drawingCanvas);
8 | draw.DeserializeFrom(this);
9 | return draw;
10 | }
11 |
12 | #region 属性
13 |
14 |
15 |
16 | #endregion
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/Serialize/DrawTextSerializer.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Windows;
3 |
4 | namespace DrawTools.Serialize
5 | {
6 | public sealed class DrawTextSerializer : DrawGeometrySerializerBase
7 | {
8 | public override DrawGeometryBase Deserialize(DrawingCanvas drawingCanvas)
9 | {
10 | var draw = new TextDrawTool(drawingCanvas);
11 | draw.DeserializeFrom(this);
12 | return draw;
13 | }
14 |
15 | #region 属性
16 |
17 | public Point StartPoint { get; set; }
18 |
19 | public String Text { get; set; }
20 |
21 | public Double Width { get; set; }
22 |
23 | public Double Height { get; set; }
24 |
25 | #endregion
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/TextDrawTool.cs:
--------------------------------------------------------------------------------
1 | using DrawTools.Serialize;
2 | using System;
3 | using System.Windows;
4 | using System.Windows.Controls;
5 | using System.Windows.Media;
6 |
7 | namespace DrawTools
8 | {
9 | ///
10 | /// 文本
11 | ///
12 | public sealed class TextDrawTool : DrawGeometryBase
13 | {
14 | public TextDrawTool(DrawingCanvas drawingCanvas) : base(drawingCanvas)
15 | {
16 | this.DrawingToolType = DrawToolType.Text;
17 |
18 | // 准备要处理的事件
19 | this.CanTouchDown = true;
20 | }
21 |
22 | #region 鼠标键盘事件
23 |
24 | public override Boolean OnTouchDown(Int32 touchId, Point point)
25 | {
26 | this.TouchId = touchId;
27 |
28 | if (!IsFinish && this.drawingCanvas.GetVisual(point) is TextDrawTool textDrawTool)
29 | {
30 | textDrawTool.TouchId = TouchId;
31 | textDrawTool.Edit();
32 |
33 | this.IsFinish = true;
34 | this.CanTouchDown = false;
35 | }
36 | else if (textBox == null)
37 | {
38 | this.drawingCanvas.AddWorkingDrawTool(this);
39 | this.drawingCanvas.AddVisual(this);
40 |
41 | this.startPoint = point;
42 | this.pen = new Pen(this.drawingCanvas.Brush, 1);
43 |
44 | this.fontSize = this.drawingCanvas.FontSize;
45 | this.typeface = new Typeface(new FontFamily("Microsoft YaHei UI,Tahoma"), FontStyles.Normal, FontWeights.Normal, FontStretches.Normal);
46 |
47 | this.padding = new Thickness(pen.Thickness * 3);
48 | this.minWidth = this.minHeight = fontSize * 2 + pen.Thickness * 8;
49 |
50 | AddTextBox();
51 | }
52 | else
53 | OnTextBoxLostFocus(null, null);
54 |
55 | return true;
56 | }
57 |
58 | #endregion
59 |
60 | #region 绘图事件
61 |
62 | public override void Draw()
63 | {
64 | var dc = this.RenderOpen();
65 | dc.DrawRectangle(Brushes.Transparent, null, selectRect);
66 | dc.DrawGeometry(pen.Brush, null, geometry);
67 | dc.Close();
68 | }
69 |
70 | public override Boolean Select(Point point)
71 | {
72 | return selectRect.Contains(point);
73 | }
74 |
75 | public override Boolean Select(Geometry select)
76 | {
77 | var rect = select.GetRenderBounds(this.drawingCanvas.SelectPen);
78 | return selectRect.IntersectsWith(rect);
79 | }
80 |
81 | protected override Rect GetRenderBounds()
82 | {
83 | return selectRect;
84 | }
85 |
86 | public override void Move(Double dx, Double dy)
87 | {
88 | startPoint.X += dx;
89 | startPoint.Y += dy;
90 |
91 | selectRect.Location = startPoint;
92 |
93 | base.Move(dx, dy);
94 | }
95 |
96 | public override void Edit()
97 | {
98 | if (Mode == 2)
99 | return;
100 |
101 | Mode = 2;
102 |
103 | this.drawingCanvas.AddWorkingDrawTool(this);
104 |
105 | this.IsFinish = false;
106 | this.CanTouchDown = true;
107 | this.CanEdit = false;
108 |
109 | AddTextBox();
110 | }
111 |
112 | #endregion
113 |
114 | #region 序列化
115 |
116 | public override DrawGeometrySerializerBase ToSerializer()
117 | {
118 | var serializer = new DrawTextSerializer
119 | {
120 | Color = ((SolidColorBrush)pen.Brush).Color,
121 | StrokeThickness = pen.Thickness,
122 | Geometry = geometry.ToString(),
123 | StartPoint = startPoint,
124 | Text = text,
125 | Width = selectRect.Width,
126 | Height = selectRect.Height
127 | };
128 |
129 | if (geometry.Transform != null)
130 | serializer.Matrix = geometry.Transform.Value;
131 |
132 | return serializer;
133 | }
134 |
135 | public override void DeserializeFrom(DrawGeometrySerializerBase serializer)
136 | {
137 | this.pen = new Pen(new SolidColorBrush(serializer.Color), serializer.StrokeThickness);
138 |
139 | this.geometry = Geometry.Parse(serializer.Geometry).GetFlattenedPathGeometry();
140 | this.geometry.Transform = new TranslateTransform(serializer.Matrix.OffsetX, serializer.Matrix.OffsetY);
141 |
142 | var textSerializer = (DrawTextSerializer)serializer;
143 |
144 | this.selectRect.Location = this.startPoint = textSerializer.StartPoint;
145 | this.text = textSerializer.Text;
146 | this.selectRect.Width = textSerializer.Width;
147 | this.selectRect.Height = textSerializer.Height;
148 |
149 | this.IsFinish = true;
150 |
151 | this.Draw();
152 | }
153 |
154 | #endregion
155 |
156 | #region 私有方法
157 |
158 | private void AddTextBox()
159 | {
160 | this.textBox = new TextBox
161 | {
162 | Background = Brushes.Transparent,
163 | Foreground = pen.Brush,
164 | BorderThickness = new Thickness(),
165 | Padding = padding,
166 | FontFamily = new FontFamily("Microsoft YaHei UI,Tahoma"),
167 | FontSize = fontSize,
168 | Style = null,
169 | FocusVisualStyle = null,
170 | AcceptsReturn = true,
171 | AcceptsTab = true,
172 | MinWidth = minWidth,
173 | MinHeight = minHeight,
174 | Text = text
175 | };
176 |
177 | textBox.SelectionStart = textBox.Text.Length;
178 |
179 | Canvas.SetLeft(textBox, startPoint.X + pen.Thickness);
180 | Canvas.SetTop(textBox, startPoint.Y + pen.Thickness);
181 |
182 | textBox.Loaded += OnTextBoxLoaded;
183 | textBox.SizeChanged += OnTextBoxSizeChanged;
184 | textBox.LostFocus += OnTextBoxLostFocus;
185 |
186 | this.drawingCanvas.Children.Add(textBox);
187 | }
188 |
189 | private void OnTextBoxLoaded(Object sender, RoutedEventArgs e)
190 | {
191 | textBox.Focus();
192 |
193 | var ch = (ContentControl)textBox.Template.FindName("PART_ContentHost", textBox);
194 |
195 | var tf = ((Visual)ch.Content).TransformToAncestor(textBox);
196 | var offset = tf.Transform(new Point());
197 |
198 | textPoint.X = startPoint.X + offset.X + pen.Thickness;
199 | textPoint.Y = startPoint.Y + offset.Y + pen.Thickness;
200 | }
201 |
202 | private void OnTextBoxSizeChanged(Object sender, SizeChangedEventArgs e)
203 | {
204 | actualWidth = e.NewSize.Width;
205 | actualHeight = e.NewSize.Height;
206 |
207 | selectRect = new Rect(startPoint, new Size(actualWidth + pen.Thickness * 2, actualHeight + pen.Thickness * 2));
208 |
209 | var dc = this.RenderOpen();
210 | dc.DrawRectangle(Brushes.Transparent, pen, selectRect);
211 | dc.Close();
212 | }
213 |
214 | private void OnTextBoxLostFocus(Object sender, RoutedEventArgs e)
215 | {
216 | textBox.Loaded -= OnTextBoxLoaded;
217 | textBox.SizeChanged -= OnTextBoxSizeChanged;
218 | textBox.LostFocus -= OnTextBoxLostFocus;
219 |
220 | this.drawingCanvas.Focus();
221 | this.drawingCanvas.Children.Remove(textBox);
222 |
223 | if (String.IsNullOrWhiteSpace(textBox.Text))
224 | this.drawingCanvas.DeleteVisual(this);
225 | else
226 | {
227 | text = textBox.Text;
228 |
229 | var formattedText = new FormattedText(text,
230 | System.Globalization.CultureInfo.InvariantCulture,
231 | FlowDirection.LeftToRight,
232 | typeface,
233 | this.fontSize,
234 | pen.Brush);
235 |
236 | geometry = formattedText.BuildGeometry(textPoint).GetFlattenedPathGeometry();
237 | Draw();
238 | }
239 |
240 | this.drawingCanvas.DeleteWorkingDrawTool(this);
241 |
242 | this.IsFinish = true;
243 | this.CanTouchDown = false;
244 | this.CanEdit = true;
245 |
246 | textBox = null;
247 | }
248 |
249 | #endregion
250 |
251 | #region 字段
252 |
253 | private Double fontSize;
254 | private Typeface typeface;
255 | private Thickness padding;
256 | private TextBox textBox;
257 | private String text;
258 | private Point startPoint, textPoint;
259 | private Double minWidth, minHeight, actualWidth, actualHeight;
260 |
261 | #endregion
262 | }
263 | }
264 |
--------------------------------------------------------------------------------
/Themes/generic.xaml:
--------------------------------------------------------------------------------
1 |
4 |
5 |
33 |
34 |
--------------------------------------------------------------------------------
/Utils/DpiHelper.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Drawing;
3 | using System.Runtime.InteropServices;
4 | using System.Windows;
5 | using System.Windows.Media;
6 |
7 | namespace DrawTools.Utils
8 | {
9 | public struct Dpi
10 | {
11 | public Dpi(Double x, Double y)
12 | {
13 | DpiX = x;
14 | DpiY = y;
15 |
16 | Px2WpfX = 96 / DpiX;
17 | Px2WpfY = 96 / DpiY;
18 | }
19 |
20 | public Double DpiX { get; }
21 |
22 | public Double DpiY { get; }
23 |
24 | public Double Px2WpfX { get; }
25 |
26 | public Double Px2WpfY { get; }
27 |
28 | ///
29 | /// 英寸-厘米
30 | ///
31 | public static readonly Double In2Cm = 2.54;
32 | ///
33 | /// 英寸-磅
34 | ///
35 | public static readonly Double In2Pt = 72;
36 | ///
37 | /// 厘米-wpf
38 | ///
39 | public static readonly Double Cm2Wpf = 96 / 2.54;
40 | }
41 |
42 | public sealed class DpiHelper
43 | {
44 | #region Graphics
45 |
46 | public static Dpi GetDpiByGraphics(IntPtr hWnd)
47 | {
48 | using (var graphics = Graphics.FromHwnd(hWnd))
49 | {
50 | return new Dpi(graphics.DpiX, graphics.DpiY);
51 | }
52 | }
53 |
54 | #endregion
55 |
56 | #region CompositionTarget
57 |
58 | public static Dpi GetDpiFromVisual(Visual visual)
59 | {
60 | var source = PresentationSource.FromVisual(visual);
61 | return (source == null || source.CompositionTarget == null) ? GetDpiByWin32(IntPtr.Zero) : new Dpi(96.0 * source.CompositionTarget.TransformToDevice.M11, 96.0 * source.CompositionTarget.TransformToDevice.M22);
62 | }
63 |
64 | #endregion
65 |
66 | #region Win32 API
67 |
68 | private const Int32 LOGPIXELSX = 88;
69 | private const Int32 LOGPIXELSY = 90;
70 |
71 | [DllImport("gdi32.dll")]
72 | private static extern Int32 GetDeviceCaps(IntPtr hdc, Int32 index);
73 |
74 | [DllImport("user32.dll")]
75 | private static extern IntPtr GetDC(IntPtr hWnd);
76 |
77 | [DllImport("user32.dll")]
78 | private static extern Int32 ReleaseDC(IntPtr hWnd, IntPtr hDc);
79 |
80 | public static Dpi GetDpiByWin32(IntPtr hwnd)
81 | {
82 | var hDc = GetDC(hwnd);
83 |
84 | var dpiX = GetDeviceCaps(hDc, LOGPIXELSX);
85 | var dpiY = GetDeviceCaps(hDc, LOGPIXELSY);
86 |
87 | ReleaseDC(hwnd, hDc);
88 | return new Dpi(dpiX, dpiY);
89 | }
90 |
91 | #endregion
92 | }
93 | }
94 |
--------------------------------------------------------------------------------
/Utils/ImageHelper.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.IO;
3 | using System.Windows.Media.Imaging;
4 |
5 | namespace DrawTools.Utils
6 | {
7 | public static class ImageHelper
8 | {
9 | public static void Save(String filepath, params BitmapFrame[] frames)
10 | {
11 | BitmapEncoder encoder = null;
12 |
13 |
14 | switch (Path.GetExtension(filepath))
15 | {
16 | case ".jpg":
17 | case ".jpeg":
18 | encoder = new JpegBitmapEncoder();
19 | break;
20 | case ".png":
21 | encoder = new PngBitmapEncoder();
22 | break;
23 | default:
24 | encoder = new BmpBitmapEncoder();
25 | break;
26 | }
27 |
28 |
29 | foreach (var frame in frames)
30 | {
31 | encoder.Frames.Add(frame);
32 | }
33 |
34 | using (var fs = new FileStream(filepath, FileMode.Create))
35 | {
36 | encoder.Save(fs);
37 | }
38 | }
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/Utils/NativeMethods.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Runtime.InteropServices;
3 |
4 | namespace DrawTools.Utils
5 | {
6 | public static class NativeMethods
7 | {
8 | ///
9 | /// 调用win api将指定名称的打印机设置为默认打印机
10 | ///
11 | /// 打印机名称
12 | ///
13 | [DllImport("winspool.drv")]
14 | public static extern Boolean SetDefaultPrinter(String Name);
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/Utils/TreeHelper.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Windows;
3 | using System.Windows.Media;
4 |
5 | namespace DrawTools.Utils
6 | {
7 | public static class TreeHelper
8 | {
9 | ///
10 | /// 根据类型,查找父节点
11 | ///
12 | /// 查找对象类型
13 | /// 查找源点
14 | /// 筛选条件
15 | /// 查找对象
16 | public static T FindParent(this DependencyObject d, Func filter = null) where T : class
17 | {
18 | if (d == null)
19 | return null;
20 |
21 | var parent = LogicalTreeHelper.GetParent(d) ?? VisualTreeHelper.GetParent(d);
22 |
23 | while (parent != null)
24 | {
25 | if (parent is T t)
26 | {
27 | if (filter == null)
28 | return t;
29 | else if (filter.Invoke(t))
30 | return t;
31 | }
32 |
33 | parent = LogicalTreeHelper.GetParent(parent) ?? VisualTreeHelper.GetParent(parent);
34 | }
35 |
36 | return null;
37 | }
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/Views/MainWindow.xaml:
--------------------------------------------------------------------------------
1 |
11 |
12 |
23 |
46 |
47 |
70 |
71 |
92 |
93 |
94 |
95 |
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 |
--------------------------------------------------------------------------------
/Views/MainWindow.xaml.cs:
--------------------------------------------------------------------------------
1 | using DrawTools.Utils;
2 | using Microsoft.Win32;
3 | using System;
4 | using System.IO;
5 | using System.Linq;
6 | using System.Windows;
7 | using System.Windows.Controls;
8 |
9 | namespace DrawTools.Views
10 | {
11 | ///
12 | /// MainWindow.xaml 的交互逻辑
13 | ///
14 | public partial class MainWindow
15 | {
16 | public MainWindow()
17 | {
18 | InitializeComponent();
19 |
20 | color_picker.SelectedColorChanged += delegate { this.drawCanvas.Brush = color_picker.SelectedBrush; btn_color.IsChecked = false; };
21 | color_picker.Canceled += delegate { btn_color.IsChecked = false; };
22 |
23 | this.toolbar.AddHandler(RadioButton.CheckedEvent, new RoutedEventHandler(OnDrawToolChecked));
24 | }
25 |
26 | private void OnDrawToolChecked(Object sender, RoutedEventArgs e)
27 | {
28 | if (e.Source is RadioButton btn && btn.Tag is String typeStr)
29 | drawCanvas.DrawingToolType = (DrawToolType)Enum.Parse(typeof(DrawToolType), typeStr);
30 | }
31 |
32 | private void btn_clear_Click(object sender, RoutedEventArgs e)
33 | {
34 | drawCanvas.Clear();
35 | }
36 |
37 | private void OnSaveClick(Object sender, RoutedEventArgs e)
38 | {
39 | if (this.drawCanvas.GetDrawGeometries().Count() == 0)
40 | return;
41 |
42 | var folder = Path.Combine(Environment.CurrentDirectory, "Draws");
43 |
44 | if (!Directory.Exists(folder))
45 | Directory.CreateDirectory("Draws");
46 |
47 | var dlg = new SaveFileDialog
48 | {
49 | Filter = "XML files (*.xml)|*.xml",
50 | OverwritePrompt = true,
51 | DefaultExt = "xml",
52 | InitialDirectory = folder,
53 | RestoreDirectory = true
54 | };
55 |
56 | if ((Boolean)dlg.ShowDialog())
57 | this.drawCanvas.Save(dlg.FileName);
58 | }
59 |
60 | private void OnOpenClick_1(Object sender, RoutedEventArgs e)
61 | {
62 | var folder = Path.Combine(Environment.CurrentDirectory, "Draws");
63 |
64 | if (!Directory.Exists(folder))
65 | Directory.CreateDirectory("Draws");
66 |
67 | var dlg = new OpenFileDialog
68 | {
69 | Filter = "XML files (*.xml)|*.xml",
70 | DefaultExt = "xml",
71 | InitialDirectory = folder,
72 | RestoreDirectory = true
73 | };
74 |
75 | if ((Boolean)dlg.ShowDialog())
76 | this.drawCanvas.Load(dlg.FileName);
77 | }
78 |
79 | private void OnPrintClick(Object sender, RoutedEventArgs e)
80 | {
81 | var backgroundImage = this.drawViewer.BackgroundImage;
82 |
83 | this.drawCanvas.Print(backgroundImage.PixelWidth, backgroundImage.PixelHeight, DpiHelper.GetDpiFromVisual(this.drawCanvas), backgroundImage);
84 | }
85 |
86 | private void OnSaveImageClick(Object sender, RoutedEventArgs e)
87 | {
88 | var backgroundImage = this.drawViewer.BackgroundImage;
89 |
90 | var frame = this.drawCanvas.ToBitmapFrame(backgroundImage.PixelWidth, backgroundImage.PixelHeight, DpiHelper.GetDpiFromVisual(this.drawCanvas), backgroundImage);
91 |
92 | if (frame == null)
93 | return;
94 |
95 | var folder = Path.Combine(Environment.CurrentDirectory, "Images");
96 |
97 | if (!Directory.Exists(folder))
98 | Directory.CreateDirectory("Images");
99 |
100 | var dlg = new SaveFileDialog
101 | {
102 | Filter = "Images files (*.jpg;*.jpeg;*.png;*.bmp)|*.jpg;*.jpeg;*.png;*.bmp",
103 | OverwritePrompt = true,
104 | DefaultExt = "jpg",
105 | InitialDirectory = folder,
106 | RestoreDirectory = true
107 | };
108 |
109 | if ((Boolean)dlg.ShowDialog())
110 | ImageHelper.Save(dlg.FileName, frame);
111 | }
112 | }
113 | }
114 |
--------------------------------------------------------------------------------
/packages.config:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------