├── .gitattributes ├── .github └── workflows │ └── dotnet-build.yml ├── .gitignore ├── CHANGELOG.md ├── Directory.Build.props ├── GeometryToolDemo.sln ├── LICENSE ├── README.md ├── build └── Version.props ├── docs └── image │ └── README │ ├── README0.png │ ├── README1.png │ ├── README2.png │ ├── README3.png │ ├── README4.png │ └── README5.png └── src └── GeometryTool └── GeometryTool ├── Action ├── BorderLock.cs ├── GeomortyStringConverter.cs ├── GraphAdd.cs └── XMLHelper.cs ├── App.config ├── App.xaml ├── App.xaml.cs ├── Converter ├── BorderWidthHeightConverter.cs ├── CanvasScaleTransformConverter.cs ├── ImageVisibilityConverter.cs ├── IntBoolConverter.cs ├── ScaleCenterConverter.cs └── SliderConverterToDouble.cs ├── GeometryTool.csproj ├── Image ├── Bezier.png ├── Circle.png ├── Color.png ├── Curve.png ├── Down.png ├── DowntoUp.png ├── Ellipse.png ├── Left.png ├── Line.png ├── Pan.png ├── QBezier.png ├── Rectangular.png ├── Right.png ├── RighttoLeft.png ├── Triangle.png ├── TurnOver.png ├── Up.png ├── UptoDown.png ├── XuanZhuan.png ├── cuxi.png ├── icon.png ├── lock.png ├── myCanvas.png ├── point.png └── unlock.png ├── MainWindow.xaml ├── MainWindow.xaml.cs ├── Model ├── AutoPoints.cs ├── BorderWithAdorner.cs ├── BorderWithDrag.cs ├── GeometryAdorner.cs ├── GeometryChrome.cs ├── GraphAppearance.cs ├── LockAdorner.cs ├── LockChrome.cs ├── MoveThumb.cs ├── ResizeRotateAdorner.cs ├── ResizeRotateChrome.cs ├── ResizeThumb.cs ├── RotateThumb.cs ├── RoutedCommand.cs ├── SizeAdorner.cs └── SizeChrome.cs ├── Properties ├── AssemblyInfo.cs ├── Resources.Designer.cs ├── Resources.resx ├── Settings.Designer.cs └── Settings.settings ├── Resources ├── ButtonImageStyle.xaml ├── ColorsStyle.xaml ├── GeometryChromeStyle.xaml ├── LableStyle.xaml ├── PulseButtonStyle.xaml ├── RadioButtonStyle.xaml ├── ResizeRotateChromeStyle.xaml ├── RightTabControlStyle.xaml ├── SizeChrome.xaml └── ToolBarImage.xaml └── app.manifest /.gitattributes: -------------------------------------------------------------------------------- 1 | ############################################################################### 2 | # Set default behavior to automatically normalize line endings. 3 | ############################################################################### 4 | * text=auto 5 | 6 | ############################################################################### 7 | # Set default behavior for command prompt diff. 8 | # 9 | # This is need for earlier builds of msysgit that does not have it on by 10 | # default for csharp files. 11 | # Note: This is only used by command line 12 | ############################################################################### 13 | #*.cs diff=csharp 14 | 15 | ############################################################################### 16 | # Set the merge driver for project and solution files 17 | # 18 | # Merging from the command prompt will add diff markers to the files if there 19 | # are conflicts (Merging from VS is not affected by the settings below, in VS 20 | # the diff markers are never inserted). Diff markers may cause the following 21 | # file extensions to fail to load in VS. An alternative would be to treat 22 | # these files as binary and thus will always conflict and require user 23 | # intervention with every merge. To do so, just uncomment the entries below 24 | ############################################################################### 25 | #*.sln merge=binary 26 | #*.csproj merge=binary 27 | #*.vbproj merge=binary 28 | #*.vcxproj merge=binary 29 | #*.vcproj merge=binary 30 | #*.dbproj merge=binary 31 | #*.fsproj merge=binary 32 | #*.lsproj merge=binary 33 | #*.wixproj merge=binary 34 | #*.modelproj merge=binary 35 | #*.sqlproj merge=binary 36 | #*.wwaproj merge=binary 37 | 38 | ############################################################################### 39 | # behavior for image files 40 | # 41 | # image files are treated as binary by default. 42 | ############################################################################### 43 | #*.jpg binary 44 | #*.png binary 45 | #*.gif binary 46 | 47 | ############################################################################### 48 | # diff behavior for common document formats 49 | # 50 | # Convert binary document formats to text before diffing them. This feature 51 | # is only available from the command line. Turn it on by uncommenting the 52 | # entries below. 53 | ############################################################################### 54 | #*.doc diff=astextplain 55 | #*.DOC diff=astextplain 56 | #*.docx diff=astextplain 57 | #*.DOCX diff=astextplain 58 | #*.dot diff=astextplain 59 | #*.DOT diff=astextplain 60 | #*.pdf diff=astextplain 61 | #*.PDF diff=astextplain 62 | #*.rtf diff=astextplain 63 | #*.RTF diff=astextplain 64 | -------------------------------------------------------------------------------- /.github/workflows/dotnet-build.yml: -------------------------------------------------------------------------------- 1 | name: .NET Build 2 | 3 | on: [push, pull_request] 4 | 5 | jobs: 6 | build: 7 | 8 | runs-on: windows-latest 9 | 10 | steps: 11 | - uses: actions/checkout@v1 12 | 13 | - name: Setup .NET 14 | uses: actions/setup-dotnet@v1 15 | with: 16 | dotnet-version: | 17 | 3.1.x 18 | 5.0.x 19 | 6.0.x 20 | 21 | - name: Build with dotnet 22 | run: dotnet build --configuration Release 23 | 24 | - name: Test 25 | run: dotnet test --configuration Release -------------------------------------------------------------------------------- /.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 | [Ww][Ii][Nn]32/ 27 | [Aa][Rr][Mm]/ 28 | [Aa][Rr][Mm]64/ 29 | bld/ 30 | [Bb]in/ 31 | [Oo]bj/ 32 | [Oo]ut/ 33 | [Ll]og/ 34 | [Ll]ogs/ 35 | 36 | # Visual Studio 2015/2017 cache/options directory 37 | .vs/ 38 | # Uncomment if you have tasks that create the project's static files in wwwroot 39 | #wwwroot/ 40 | 41 | # Visual Studio 2017 auto generated files 42 | Generated\ Files/ 43 | 44 | # MSTest test Results 45 | [Tt]est[Rr]esult*/ 46 | [Bb]uild[Ll]og.* 47 | 48 | # NUnit 49 | *.VisualState.xml 50 | TestResult.xml 51 | nunit-*.xml 52 | 53 | # Build Results of an ATL Project 54 | [Dd]ebugPS/ 55 | [Rr]eleasePS/ 56 | dlldata.c 57 | 58 | # Benchmark Results 59 | BenchmarkDotNet.Artifacts/ 60 | 61 | # .NET Core 62 | project.lock.json 63 | project.fragment.lock.json 64 | artifacts/ 65 | 66 | # ASP.NET Scaffolding 67 | ScaffoldingReadMe.txt 68 | 69 | # StyleCop 70 | StyleCopReport.xml 71 | 72 | # Files built by Visual Studio 73 | *_i.c 74 | *_p.c 75 | *_h.h 76 | *.ilk 77 | *.meta 78 | *.obj 79 | *.iobj 80 | *.pch 81 | *.pdb 82 | *.ipdb 83 | *.pgc 84 | *.pgd 85 | *.rsp 86 | *.sbr 87 | *.tlb 88 | *.tli 89 | *.tlh 90 | *.tmp 91 | *.tmp_proj 92 | *_wpftmp.csproj 93 | *.log 94 | *.vspscc 95 | *.vssscc 96 | .builds 97 | *.pidb 98 | *.svclog 99 | *.scc 100 | 101 | # Chutzpah Test files 102 | _Chutzpah* 103 | 104 | # Visual C++ cache files 105 | ipch/ 106 | *.aps 107 | *.ncb 108 | *.opendb 109 | *.opensdf 110 | *.sdf 111 | *.cachefile 112 | *.VC.db 113 | *.VC.VC.opendb 114 | 115 | # Visual Studio profiler 116 | *.psess 117 | *.vsp 118 | *.vspx 119 | *.sap 120 | 121 | # Visual Studio Trace Files 122 | *.e2e 123 | 124 | # TFS 2012 Local Workspace 125 | $tf/ 126 | 127 | # Guidance Automation Toolkit 128 | *.gpState 129 | 130 | # ReSharper is a .NET coding add-in 131 | _ReSharper*/ 132 | *.[Rr]e[Ss]harper 133 | *.DotSettings.user 134 | 135 | # TeamCity is a build add-in 136 | _TeamCity* 137 | 138 | # DotCover is a Code Coverage Tool 139 | *.dotCover 140 | 141 | # AxoCover is a Code Coverage Tool 142 | .axoCover/* 143 | !.axoCover/settings.json 144 | 145 | # Coverlet is a free, cross platform Code Coverage Tool 146 | coverage*.json 147 | coverage*.xml 148 | coverage*.info 149 | 150 | # Visual Studio code coverage results 151 | *.coverage 152 | *.coveragexml 153 | 154 | # NCrunch 155 | _NCrunch_* 156 | .*crunch*.local.xml 157 | nCrunchTemp_* 158 | 159 | # MightyMoose 160 | *.mm.* 161 | AutoTest.Net/ 162 | 163 | # Web workbench (sass) 164 | .sass-cache/ 165 | 166 | # Installshield output folder 167 | [Ee]xpress/ 168 | 169 | # DocProject is a documentation generator add-in 170 | DocProject/buildhelp/ 171 | DocProject/Help/*.HxT 172 | DocProject/Help/*.HxC 173 | DocProject/Help/*.hhc 174 | DocProject/Help/*.hhk 175 | DocProject/Help/*.hhp 176 | DocProject/Help/Html2 177 | DocProject/Help/html 178 | 179 | # Click-Once directory 180 | publish/ 181 | 182 | # Publish Web Output 183 | *.[Pp]ublish.xml 184 | *.azurePubxml 185 | # Note: Comment the next line if you want to checkin your web deploy settings, 186 | # but database connection strings (with potential passwords) will be unencrypted 187 | *.pubxml 188 | *.publishproj 189 | 190 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 191 | # checkin your Azure Web App publish settings, but sensitive information contained 192 | # in these scripts will be unencrypted 193 | PublishScripts/ 194 | 195 | # NuGet Packages 196 | *.nupkg 197 | # NuGet Symbol Packages 198 | *.snupkg 199 | # The packages folder can be ignored because of Package Restore 200 | **/[Pp]ackages/* 201 | # except build/, which is used as an MSBuild target. 202 | !**/[Pp]ackages/build/ 203 | # Uncomment if necessary however generally it will be regenerated when needed 204 | #!**/[Pp]ackages/repositories.config 205 | # NuGet v3's project.json files produces more ignorable files 206 | *.nuget.props 207 | *.nuget.targets 208 | 209 | # Microsoft Azure Build Output 210 | csx/ 211 | *.build.csdef 212 | 213 | # Microsoft Azure Emulator 214 | ecf/ 215 | rcf/ 216 | 217 | # Windows Store app package directories and files 218 | AppPackages/ 219 | BundleArtifacts/ 220 | Package.StoreAssociation.xml 221 | _pkginfo.txt 222 | *.appx 223 | *.appxbundle 224 | *.appxupload 225 | 226 | # Visual Studio cache files 227 | # files ending in .cache can be ignored 228 | *.[Cc]ache 229 | # but keep track of directories ending in .cache 230 | !?*.[Cc]ache/ 231 | 232 | # Others 233 | ClientBin/ 234 | ~$* 235 | *~ 236 | *.dbmdl 237 | *.dbproj.schemaview 238 | *.jfm 239 | *.pfx 240 | *.publishsettings 241 | orleans.codegen.cs 242 | 243 | # Including strong name files can present a security risk 244 | # (https://github.com/github/gitignore/pull/2483#issue-259490424) 245 | #*.snk 246 | 247 | # Since there are multiple workflows, uncomment next line to ignore bower_components 248 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 249 | #bower_components/ 250 | 251 | # RIA/Silverlight projects 252 | Generated_Code/ 253 | 254 | # Backup & report files from converting an old project file 255 | # to a newer Visual Studio version. Backup files are not needed, 256 | # because we have git ;-) 257 | _UpgradeReport_Files/ 258 | Backup*/ 259 | UpgradeLog*.XML 260 | UpgradeLog*.htm 261 | ServiceFabricBackup/ 262 | *.rptproj.bak 263 | 264 | # SQL Server files 265 | *.mdf 266 | *.ldf 267 | *.ndf 268 | 269 | # Business Intelligence projects 270 | *.rdl.data 271 | *.bim.layout 272 | *.bim_*.settings 273 | *.rptproj.rsuser 274 | *- [Bb]ackup.rdl 275 | *- [Bb]ackup ([0-9]).rdl 276 | *- [Bb]ackup ([0-9][0-9]).rdl 277 | 278 | # Microsoft Fakes 279 | FakesAssemblies/ 280 | 281 | # GhostDoc plugin setting file 282 | *.GhostDoc.xml 283 | 284 | # Node.js Tools for Visual Studio 285 | .ntvs_analysis.dat 286 | node_modules/ 287 | 288 | # Visual Studio 6 build log 289 | *.plg 290 | 291 | # Visual Studio 6 workspace options file 292 | *.opt 293 | 294 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.) 295 | *.vbw 296 | 297 | # Visual Studio LightSwitch build output 298 | **/*.HTMLClient/GeneratedArtifacts 299 | **/*.DesktopClient/GeneratedArtifacts 300 | **/*.DesktopClient/ModelManifest.xml 301 | **/*.Server/GeneratedArtifacts 302 | **/*.Server/ModelManifest.xml 303 | _Pvt_Extensions 304 | 305 | # Paket dependency manager 306 | .paket/paket.exe 307 | paket-files/ 308 | 309 | # FAKE - F# Make 310 | .fake/ 311 | 312 | # CodeRush personal settings 313 | .cr/personal 314 | 315 | # Python Tools for Visual Studio (PTVS) 316 | __pycache__/ 317 | *.pyc 318 | 319 | # Cake - Uncomment if you are using it 320 | # tools/** 321 | # !tools/packages.config 322 | 323 | # Tabs Studio 324 | *.tss 325 | 326 | # Telerik's JustMock configuration file 327 | *.jmconfig 328 | 329 | # BizTalk build output 330 | *.btp.cs 331 | *.btm.cs 332 | *.odx.cs 333 | *.xsd.cs 334 | 335 | # OpenCover UI analysis results 336 | OpenCover/ 337 | 338 | # Azure Stream Analytics local run output 339 | ASALocalRun/ 340 | 341 | # MSBuild Binary and Structured Log 342 | *.binlog 343 | 344 | # NVidia Nsight GPU debugger configuration file 345 | *.nvuser 346 | 347 | # MFractors (Xamarin productivity tool) working folder 348 | .mfractor/ 349 | 350 | # Local History for Visual Studio 351 | .localhistory/ 352 | 353 | # BeatPulse healthcheck temp database 354 | healthchecksdb 355 | 356 | # Backup folder for Package Reference Convert tool in Visual Studio 2017 357 | MigrationBackup/ 358 | 359 | # Ionide (cross platform F# VS Code tools) working folder 360 | .ionide/ 361 | 362 | # Fody - auto-generated XML schema 363 | FodyWeavers.xsd -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # GeometryToolDemo -------------------------------------------------------------------------------- /Directory.Build.props: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | latest 5 | $(MSBuildThisFileDirectory)bin\$(Configuration) 6 | dotnet campus(.NET 职业技术学院) 7 | 8 | 9 | dotnet-campus 10 | https://github.com/dotnet-campus/GeometryToolDemo 11 | https://github.com/dotnet-campus/GeometryToolDemo 12 | TODO: 请在此处添加包描述信息。 13 | 14 | git 15 | Copyright © 2021 dotnet campus, All Rights Reserved. 16 | 17 | -------------------------------------------------------------------------------- /GeometryToolDemo.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 17 4 | VisualStudioVersion = 17.0.32014.148 5 | MinimumVisualStudioVersion = 15.0.26124.0 6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GeometryTool", "src\GeometryTool\GeometryTool\GeometryTool.csproj", "{06B492A3-8225-4497-91A2-3D5FB4DEF45E}" 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 | {06B492A3-8225-4497-91A2-3D5FB4DEF45E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 15 | {06B492A3-8225-4497-91A2-3D5FB4DEF45E}.Debug|Any CPU.Build.0 = Debug|Any CPU 16 | {06B492A3-8225-4497-91A2-3D5FB4DEF45E}.Release|Any CPU.ActiveCfg = Release|Any CPU 17 | {06B492A3-8225-4497-91A2-3D5FB4DEF45E}.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 = {B4BBE725-CB3C-4EA5-8754-B5A230B27F29} 24 | EndGlobalSection 25 | EndGlobal 26 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2015-2022 [YuWeiCong](https://github.com/YuWeiCong) and dotnet campus 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 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # GeometryToolDemo 2 | 3 | ![](https://github.com/dotnet-campus/GeometryToolDemo/workflows/.NET%20Build/badge.svg) 4 | 5 | 二维绘画小工具 6 | 7 | ![](docs/image/README/README1.png) 8 | 9 | ![](docs/image/README/README0.png) 10 | 11 | ![](docs/image/README/README2.png) 12 | 13 | ![](docs/image/README/README3.png) 14 | 15 | ![](docs/image/README/README4.png) 16 | 17 | ![](docs/image/README/README5.png) 18 | 19 | -------------------------------------------------------------------------------- /build/Version.props: -------------------------------------------------------------------------------- 1 | 2 | 3 | 1.0.0 4 | 5 | -------------------------------------------------------------------------------- /docs/image/README/README0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dotnet-campus/GeometryToolDemo/b488b66099e0de4c96ca4a184e56d51084ffd2f8/docs/image/README/README0.png -------------------------------------------------------------------------------- /docs/image/README/README1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dotnet-campus/GeometryToolDemo/b488b66099e0de4c96ca4a184e56d51084ffd2f8/docs/image/README/README1.png -------------------------------------------------------------------------------- /docs/image/README/README2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dotnet-campus/GeometryToolDemo/b488b66099e0de4c96ca4a184e56d51084ffd2f8/docs/image/README/README2.png -------------------------------------------------------------------------------- /docs/image/README/README3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dotnet-campus/GeometryToolDemo/b488b66099e0de4c96ca4a184e56d51084ffd2f8/docs/image/README/README3.png -------------------------------------------------------------------------------- /docs/image/README/README4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dotnet-campus/GeometryToolDemo/b488b66099e0de4c96ca4a184e56d51084ffd2f8/docs/image/README/README4.png -------------------------------------------------------------------------------- /docs/image/README/README5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dotnet-campus/GeometryToolDemo/b488b66099e0de4c96ca4a184e56d51084ffd2f8/docs/image/README/README5.png -------------------------------------------------------------------------------- /src/GeometryTool/GeometryTool/Action/BorderLock.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Windows; 3 | using System.Windows.Controls; 4 | using System.Windows.Data; 5 | using System.Windows.Documents; 6 | using System.Windows.Media; 7 | using System.Windows.Media.Imaging; 8 | using System.Windows.Shapes; 9 | 10 | namespace GeometryTool; 11 | 12 | /// 13 | /// 点的融合和拆分 14 | /// 15 | public class BorderLock 16 | { 17 | /// 18 | /// 用于记录要进行命中测试的图形 19 | /// 20 | private readonly BorderWithDrag _border; 21 | 22 | /// 23 | /// 用于计算命中的BorderWithDrag的数量 24 | /// 25 | private int _count; 26 | 27 | public BorderLock(BorderWithDrag border) 28 | { 29 | _border = border; 30 | } 31 | 32 | public BorderLock() 33 | { 34 | } 35 | 36 | /// 37 | /// 点的合并 38 | /// 39 | /// 40 | public void Lock(Point point) 41 | { 42 | var currentPoint = point; 43 | 44 | _border.PointList.Clear(); 45 | VisualTreeHelper.HitTest(_border.Parent as Canvas, null, //进行命中测试 46 | MyHitTestResult, 47 | new PointHitTestParameters(currentPoint)); 48 | 49 | if (_count > 1) //如果该点有多于两个BorderWithDrag,说明有点融合 50 | { 51 | if (_border.BrotherBorder == null) 52 | { 53 | _border.BrotherBorder = _border.PointList[1]; 54 | } 55 | 56 | if (_border.BrotherBorder.LockAdornor == null) //只选择一个图层来显示Adorner 57 | { 58 | _border.BrotherBorder.LockAdornor = new LockAdorner(_border.BrotherBorder); 59 | var layer = AdornerLayer.GetAdornerLayer(_border.BrotherBorder.Parent as Canvas); 60 | if (layer != null) 61 | { 62 | layer.Add(_border.BrotherBorder.LockAdornor); 63 | } 64 | } 65 | 66 | _border.BrotherBorder.HasOtherPoint = true; //显示锁 67 | _border.BrotherBorder.LockAdornor.LockChrome.Source = 68 | new BitmapImage(new Uri("Image/lock.png", UriKind.Relative)); 69 | 70 | if (_border != _border.BrotherBorder) 71 | { 72 | _border.BrotherBorder.PointList = _border.PointList; 73 | } 74 | 75 | foreach (var border in _border.BrotherBorder.PointList) 76 | { 77 | if (border != _border.BrotherBorder) 78 | { 79 | BindingOperations.ClearBinding((border.Child as Path).Data as EllipseGeometry, 80 | EllipseGeometry.CenterProperty); 81 | var binding = new Binding("Center") 82 | { Source = (_border.BrotherBorder.Child as Path).Data as EllipseGeometry }; 83 | binding.Mode = BindingMode.TwoWay; 84 | BindingOperations.SetBinding((border.Child as Path).Data as EllipseGeometry, 85 | EllipseGeometry.CenterProperty, binding); 86 | } 87 | } 88 | } 89 | else 90 | { 91 | _border.HasOtherPoint = false; 92 | } 93 | } 94 | 95 | /// 96 | /// 命中测试 97 | /// 98 | /// 99 | /// 100 | public HitTestResultBehavior MyHitTestResult(HitTestResult result) 101 | { 102 | var path = result.VisualHit as Path; 103 | if (path != null) 104 | { 105 | var border = path.Parent as BorderWithDrag; 106 | if (border != null) //如果命中的图形是BorderWithDrag,就Count++ 107 | { 108 | _border.PointList.Add(border); 109 | _count++; 110 | if (border.LockAdornor != null) //选择一个已经带有Adorner的Border来显示锁 111 | { 112 | _border.BrotherBorder = border; 113 | } 114 | } 115 | } 116 | 117 | return HitTestResultBehavior.Continue; 118 | } 119 | 120 | /// 121 | /// 解开点的融合 122 | /// 123 | /// 124 | public void UnLock(BorderWithDrag border) 125 | { 126 | var brotherBorder = border.BrotherBorder; 127 | var p = ((border.Child as Path).Data as EllipseGeometry).Center; 128 | foreach (var item in border.PointList) //解开所用的Binding 129 | { 130 | BindingOperations.ClearBinding((item.Child as Path).Data as EllipseGeometry, 131 | EllipseGeometry.CenterProperty); 132 | item.BrotherBorder = null; 133 | ((item.Child as Path).Data as EllipseGeometry).Center = p; //重定位到原来的位置 134 | } 135 | 136 | border.HasOtherPoint = false; 137 | } 138 | } -------------------------------------------------------------------------------- /src/GeometryTool/GeometryTool/Action/GeomortyStringConverter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | using System.Text.RegularExpressions; 5 | using System.Windows; 6 | using System.Windows.Controls; 7 | using System.Windows.Data; 8 | using System.Windows.Media; 9 | using System.Windows.Shapes; 10 | 11 | namespace GeometryTool; 12 | 13 | /// 14 | /// 字符串转图形,图形转字符串 15 | /// 16 | public class GeometryStringConverter 17 | { 18 | private readonly GraphAppearance _graphAppearance; 19 | private readonly Canvas _rootCanvas; 20 | 21 | /// 22 | /// 构造函数 23 | /// 24 | /// 25 | /// 26 | public GeometryStringConverter(Canvas rootCanvas, GraphAppearance graphAppearance) 27 | { 28 | _rootCanvas = rootCanvas; 29 | _graphAppearance = graphAppearance; 30 | } 31 | 32 | /// 33 | /// 从字符串中读取图形,并绘制出来 34 | /// 35 | /// 36 | public BorderWithAdorner GeometryFromString(string vMiniLanguage) 37 | { 38 | var graphAdd = new GraphAdd(); 39 | var path = new Path 40 | { 41 | Stroke = _graphAppearance.Stroke, 42 | StrokeThickness = _graphAppearance.StrokeThickness, 43 | StrokeDashArray = _graphAppearance.StrokeDashArray 44 | }; 45 | 46 | if (_graphAppearance.Fill != null) 47 | { 48 | path.Fill = _graphAppearance.Fill; 49 | } 50 | 51 | var regex = new Regex(@"([a-zA-Z])\s*([^a-zA-Z]*)"); 52 | var matchList = regex.Matches(vMiniLanguage); 53 | 54 | //定义直线 55 | var pathGeometry = new PathGeometry(); 56 | var borderWithAdorner = new BorderWithAdorner(); 57 | borderWithAdorner.Child = path; 58 | path.Data = pathGeometry; 59 | pathGeometry.Figures = new PathFigureCollection(); 60 | var mPathFigure = new PathFigure(); 61 | var ellipsePoint = new Point(); 62 | var ellipseStartPath = new Path(); 63 | var ellipseList = new List(); 64 | var number = 0; 65 | BorderWithDrag border; 66 | for (var i = 0; i < matchList.Count; ++i) 67 | { 68 | //产生点 69 | number++; 70 | var match = matchList[i]; 71 | if (i == 0) //处理起点的问题 72 | { 73 | if (match.Groups[1].ToString() != "M") //起点包含M 74 | { 75 | mPathFigure.StartPoint = new Point { X = 0, Y = 0 }; 76 | graphAdd.AddPointWithNoBorder(ellipsePoint, _graphAppearance, _rootCanvas, out ellipseStartPath); 77 | borderWithAdorner.EllipseList.Add(ellipseStartPath); 78 | pathGeometry.Figures.Add(mPathFigure); 79 | } 80 | else //起点不包含M 81 | { 82 | //创建PathFigure,并绑定StartPoint 83 | GetEllipsePointWithNoBorder(match, out ellipseStartPath, out ellipsePoint, 84 | @"([\+\-\d\.]*),([\+\-\d\.]*)"); //构造EllipsePoint 85 | borderWithAdorner.EllipseList.Add(ellipseStartPath); 86 | if (Regex.IsMatch(vMiniLanguage, "[HLV]")) 87 | { 88 | ellipseList.Add(ellipseStartPath); 89 | border = new BorderWithDrag(path, 1, ellipseList); 90 | border.Child = ellipseStartPath; 91 | } 92 | else 93 | { 94 | border = new BorderWithDrag(); 95 | border.Child = ellipseStartPath; 96 | } 97 | } 98 | 99 | var startEllipseGeometry = ellipseStartPath.Data as EllipseGeometry; 100 | pathGeometry.Figures.Clear(); 101 | mPathFigure = new PathFigure(); 102 | if (Regex.IsMatch(vMiniLanguage, @"\sZ")) 103 | { 104 | mPathFigure.IsClosed = true; 105 | } 106 | 107 | var binding = new Binding("Center") { Source = startEllipseGeometry }; //绑定起点 108 | binding.Mode = BindingMode.TwoWay; 109 | BindingOperations.SetBinding(mPathFigure, PathFigure.StartPointProperty, binding); 110 | pathGeometry.Figures.Add(mPathFigure); 111 | var segmentCollection = new PathSegmentCollection(); 112 | mPathFigure.Segments = segmentCollection; 113 | } 114 | 115 | Path vPath; 116 | 117 | switch (match.Groups[1].ToString()) 118 | { 119 | case "L": 120 | { 121 | //创建PathFigure,并绑定StartPoint 122 | 123 | GetEllipsePointWithNoBorder(match, out vPath, out ellipsePoint, 124 | @"([\+\-\d\.]*),([\+\-\d\.]*)"); //构造EllipsePoint 125 | borderWithAdorner.EllipseList.Add(vPath); 126 | //创建LineSegment,并绑定Point 127 | graphAdd.AddLineSegment(mPathFigure, vPath); 128 | ellipseList.Add(vPath); 129 | border = new BorderWithDrag(path, number, ellipseList); 130 | border.Child = vPath; 131 | 132 | break; 133 | } 134 | 135 | case "H": 136 | { 137 | ellipsePoint.X = Convert.ToDouble(match.Groups[2].ToString()); 138 | 139 | graphAdd.AddPointWithNoBorder(ellipsePoint, _graphAppearance, _rootCanvas, out vPath); 140 | borderWithAdorner.EllipseList.Add(vPath); 141 | graphAdd.AddLineSegment(mPathFigure, vPath); 142 | ellipseList.Add(vPath); 143 | border = new BorderWithDrag(path, number, ellipseList); 144 | border.Child = vPath; 145 | 146 | break; 147 | } 148 | 149 | case "V": 150 | { 151 | ellipsePoint.Y = Convert.ToDouble(match.Groups[2].ToString()); 152 | 153 | graphAdd.AddPointWithNoBorder(ellipsePoint, _graphAppearance, _rootCanvas, out vPath); 154 | borderWithAdorner.EllipseList.Add(vPath); 155 | graphAdd.AddLineSegment(mPathFigure, vPath); 156 | ellipseList.Add(vPath); 157 | border = new BorderWithDrag(path, number, ellipseList); 158 | border.Child = vPath; 159 | 160 | break; 161 | } 162 | 163 | case "A": 164 | { 165 | //创建LineSegment,并绑定Point 166 | var size = new Size 167 | { 168 | Width = Convert.ToDouble(Regex 169 | .Match(match.Groups[0].ToString(), @"A ([\+\-\d\.]*),([\+\-\d\.]*)").Groups[1].ToString()), 170 | Height = Convert.ToDouble(Regex 171 | .Match(match.Groups[0].ToString(), @"A ([\+\-\d\.]*),([\+\-\d\.]*)").Groups[2].ToString()) 172 | }; 173 | 174 | SweepDirection vSweepDirection; 175 | var isLargeArc = false; 176 | var matches = Regex.Matches(match.Groups[0].ToString(), 177 | @"A\s*[\+\-\d\.]+,[\+\-\d\.]+\s*([\+\-\.\d]+)\s*([\+\-\.\d]+)\s*([\+\-\.\d]+)"); 178 | if (Convert.ToInt32(matches[0].Groups[2].ToString()) == 1) //设置SweepDirection 179 | { 180 | vSweepDirection = SweepDirection.Clockwise; 181 | } 182 | else 183 | { 184 | vSweepDirection = SweepDirection.Counterclockwise; 185 | } 186 | 187 | if (Convert.ToInt32(matches[0].Groups[3].ToString()) == 1) //设置IsLargeArc 188 | { 189 | isLargeArc = true; 190 | } 191 | 192 | //创建PathFigure,并绑定StartPoint 193 | GetEllipsePointWithNoBorder(match, out vPath, out ellipsePoint, 194 | @"\d ([\+\-\d\.]*),([\+\-\d\.]*)"); //构造EllipsePoint 195 | graphAdd.AddArcSegment(mPathFigure, vPath, size, 196 | Convert.ToDouble(matches[0].Groups[1].ToString()), vSweepDirection, isLargeArc); 197 | borderWithAdorner.EllipseList.Add(vPath); 198 | border = new BorderWithDrag(); 199 | border.Child = vPath; 200 | break; 201 | } 202 | 203 | case "C": 204 | { 205 | //创建PathFigure,并绑定StartPoint 206 | Path ellipsePath1; 207 | GetEllipsePointWithNoBorder(match, out ellipsePath1, out ellipsePoint, 208 | @"([\+\-\d\.]*),([\+\-\d\.]*)"); //构造EllipsePoint 209 | borderWithAdorner.EllipseList.Add(ellipsePath1); 210 | border = new BorderWithDrag(); 211 | border.Child = ellipsePath1; 212 | 213 | Path ellipsePath2; 214 | GetEllipsePointWithNoBorder(match, out ellipsePath2, out ellipsePoint, 215 | @"C\s+[\+\-\d\.]+,[\+\-\d\.]+\s*([\+\-\d\.]+),([\+\-\d\.]+)"); //构造EllipsePoint 216 | borderWithAdorner.EllipseList.Add(ellipsePath2); 217 | border = new BorderWithDrag(); 218 | border.Child = ellipsePath2; 219 | 220 | GetEllipsePointWithNoBorder(match, out vPath, out ellipsePoint, 221 | @"([\+\-\d\.]+),([\+\-\d\.]+)\s*$"); //构造EllipsePoint 222 | borderWithAdorner.EllipseList.Add(vPath); 223 | graphAdd.AddBezierSegment(mPathFigure, ellipsePath1, ellipsePath2, vPath); 224 | border = new BorderWithDrag(); 225 | border.Child = vPath; 226 | 227 | break; 228 | } 229 | 230 | case "Q": 231 | { 232 | //创建PathFigure,并绑定StartPoint 233 | Path ellipsePath1; 234 | GetEllipsePointWithNoBorder(match, out ellipsePath1, out ellipsePoint, 235 | @"([\+\-\d\.]*),([\+\-\d\.]*)"); //构造EllipsePoint 236 | borderWithAdorner.EllipseList.Add(ellipsePath1); 237 | border = new BorderWithDrag(); 238 | border.Child = ellipsePath1; 239 | 240 | GetEllipsePointWithNoBorder(match, out vPath, out ellipsePoint, 241 | @"\d\s+([\+\-\d\.]*),([\+\-\d\.]*)"); //构造EllipsePoint 242 | borderWithAdorner.EllipseList.Add(vPath); 243 | graphAdd.AddQuadraticSegment(mPathFigure, ellipsePath1, vPath); 244 | border = new BorderWithDrag(); 245 | border.Child = vPath; 246 | 247 | break; 248 | } 249 | } 250 | } 251 | 252 | return borderWithAdorner; 253 | } 254 | 255 | /// 256 | /// 把图形转换成为字符串 257 | /// 258 | /// 259 | /// 260 | public string StringFromGeometry(Path vPath) 261 | { 262 | var miniLanguage = new StringBuilder(); 263 | var pathGeometry = vPath.Data as PathGeometry; 264 | var pathFigure = pathGeometry.Figures[0]; 265 | var segmentCol = pathFigure.Segments; 266 | miniLanguage.Append("M " + pathFigure.StartPoint.X + "," + pathFigure.StartPoint.Y + " "); 267 | 268 | foreach (var item in segmentCol) 269 | { 270 | if (item.GetType() == typeof(LineSegment)) 271 | { 272 | miniLanguage.Append("L " + (item as LineSegment).Point.X + "," + (item as LineSegment).Point.Y + " "); 273 | } 274 | else if (item.GetType() == typeof(ArcSegment)) 275 | { 276 | var arcSegment = item as ArcSegment; 277 | miniLanguage.Append("A " + arcSegment.Size.Width + "," + arcSegment.Size.Height + " " + 278 | arcSegment.RotationAngle + " "); 279 | miniLanguage.Append((arcSegment.IsLargeArc ? 1 : 0) + " " + 280 | (arcSegment.SweepDirection == SweepDirection.Clockwise ? 1 : 0) + " "); 281 | miniLanguage.Append(arcSegment.Point.X + "," + arcSegment.Point.Y + " "); 282 | } 283 | else if (item.GetType() == typeof(BezierSegment)) 284 | { 285 | var bezierSegment = item as BezierSegment; 286 | miniLanguage.Append("C " + bezierSegment.Point1.X + "," + bezierSegment.Point1.Y + " "); 287 | miniLanguage.Append(bezierSegment.Point2.X + "," + bezierSegment.Point2.Y + " "); 288 | miniLanguage.Append(bezierSegment.Point3.X + "," + bezierSegment.Point3.Y + " "); 289 | } 290 | else if (item.GetType() == typeof(QuadraticBezierSegment)) 291 | { 292 | var QBezierSegment = item as QuadraticBezierSegment; 293 | miniLanguage.Append("Q " + QBezierSegment.Point1.X + "," + QBezierSegment.Point1.Y + " "); 294 | miniLanguage.Append(QBezierSegment.Point2.X + "," + QBezierSegment.Point2.Y + " "); 295 | } 296 | } 297 | 298 | if (pathFigure.IsClosed) 299 | { 300 | miniLanguage.Append("Z "); 301 | } 302 | 303 | return miniLanguage.ToString(); 304 | } 305 | 306 | /// 307 | /// 产生一个EllipsePoint 308 | /// 309 | private void GetEllipsePoint(Match match, out Path ellipsePath, out Point ellipsePoint, string vPattern) 310 | { 311 | var graphAdd = new GraphAdd(); 312 | ellipsePoint = new Point(); 313 | ellipsePoint.X = Convert.ToDouble(Regex.Match(match.Groups[0].ToString(), vPattern).Groups[1].ToString()); 314 | ellipsePoint.Y = Convert.ToDouble(Regex.Match(match.Groups[0].ToString(), vPattern).Groups[2].ToString()); 315 | ellipsePath = new Path(); 316 | graphAdd.AddPoint(ellipsePoint, _graphAppearance, _rootCanvas, out ellipsePath); 317 | } 318 | 319 | /// 320 | /// 产生一个带BorderWithAdorner的Ellipse 321 | /// 322 | /// 323 | /// 324 | /// 325 | /// 326 | private void GetEllipsePointWithNoBorder(Match match, out Path ellipsePath, out Point ellipsePoint, string vPattern) 327 | { 328 | var graphAdd = new GraphAdd(); 329 | ellipsePoint = new Point(); 330 | ellipsePoint.X = Convert.ToDouble(Regex.Match(match.Groups[0].ToString(), vPattern).Groups[1].ToString()); 331 | ellipsePoint.Y = Convert.ToDouble(Regex.Match(match.Groups[0].ToString(), vPattern).Groups[2].ToString()); 332 | ellipsePath = new Path(); 333 | graphAdd.AddPointWithNoBorder(ellipsePoint, _graphAppearance, _rootCanvas, out ellipsePath); 334 | } 335 | 336 | /// 337 | /// 构造PathGeometry 338 | /// 339 | /// 340 | /// 341 | public string StringFromPathGeometry(Path vPath) 342 | { 343 | var stringBuilder = new StringBuilder(); 344 | stringBuilder.AppendLine(" "); 345 | stringBuilder.AppendLine(" "); 346 | stringBuilder.AppendLine(" "); 348 | stringBuilder.AppendLine(" "); 349 | stringBuilder.AppendLine(" "); 350 | stringBuilder.AppendLine(" "); 352 | stringBuilder.AppendLine(" "); 353 | stringBuilder.Append(" "); 354 | return stringBuilder.ToString(); 355 | } 356 | 357 | /// 358 | /// 从XML中读取图形 359 | /// 360 | /// 361 | /// 362 | public List PathGeometryFromString(string vXMLString) 363 | { 364 | var pattern = 365 | @"\s*\s*\s*\s*\s*"; 366 | var matchList = Regex.Matches(vXMLString, pattern); 367 | var borderWithAdornerList = new List(); 368 | foreach (Match item in matchList) 369 | { 370 | var backgroundColor = item.Groups[1].ToString(); 371 | var strokeColor = item.Groups[4].ToString(); 372 | var miniLanguage = item.Groups[2].ToString(); 373 | var strokeThickness = item.Groups[3].ToString(); 374 | var graphAppearance = new GraphAppearance(); 375 | graphAppearance.Fill = new SolidColorBrush((Color)ColorConverter.ConvertFromString(backgroundColor)); 376 | graphAppearance.Stroke = new SolidColorBrush((Color)ColorConverter.ConvertFromString(strokeColor)); 377 | graphAppearance.StrokeThickness = Convert.ToDouble(strokeThickness); 378 | var geometryStringConverter = new GeometryStringConverter(MainWindow.MyRootCanvas, graphAppearance); 379 | var newBorderWithAdorner = 380 | geometryStringConverter.GeometryFromString(miniLanguage); //把Mini-Language转化成为图形,实现图形的深度复制 381 | var newPath = newBorderWithAdorner.Child as Path; 382 | newPath.Stroke = graphAppearance.Stroke; 383 | newPath.StrokeThickness = graphAppearance.StrokeThickness; 384 | newPath.Fill = graphAppearance.Fill; 385 | borderWithAdornerList.Add(newBorderWithAdorner); 386 | } 387 | 388 | return borderWithAdornerList; 389 | } 390 | } -------------------------------------------------------------------------------- /src/GeometryTool/GeometryTool/Action/GraphAdd.cs: -------------------------------------------------------------------------------- 1 | using System.Windows; 2 | using System.Windows.Controls; 3 | using System.Windows.Data; 4 | using System.Windows.Media; 5 | using System.Windows.Shapes; 6 | 7 | namespace GeometryTool; 8 | 9 | /// 10 | /// 用于绘制图形,如点,直线,三角形,矩形,圆,椭圆 11 | /// 12 | public class GraphAdd 13 | { 14 | /// 15 | /// 包装Path的border 16 | /// 17 | private BorderWithDrag _border; 18 | 19 | private BorderWithAdorner _borderWithAdorner; 20 | 21 | /// 22 | /// 新建一个PathGeometry 23 | /// 24 | /// 25 | /// 26 | /// 27 | /// 28 | /// 29 | public void NewPathGeometry(GraphAppearance vGraphAppearance, Canvas vRootCanvas, out Path vPath, Path vEllipsePath, 30 | bool vIsClose) 31 | { 32 | //设置直线的外观 33 | vPath = new Path 34 | { 35 | Stroke = vGraphAppearance.Stroke, 36 | StrokeThickness = vGraphAppearance.StrokeThickness 37 | }; 38 | 39 | if (vGraphAppearance.Fill != Brushes.Transparent) 40 | { 41 | vPath.Fill = vGraphAppearance.Fill; 42 | } 43 | 44 | var pathGeometry = new PathGeometry(); 45 | vPath.Data = pathGeometry; 46 | pathGeometry.FillRule = vGraphAppearance.FillRule; 47 | pathGeometry.Figures = new PathFigureCollection(); 48 | var pathFigure = new PathFigure { IsClosed = vIsClose }; 49 | pathGeometry.Figures.Add(pathFigure); 50 | var segmentCollection = new PathSegmentCollection(); 51 | pathFigure.Segments = segmentCollection; 52 | 53 | //设置起点绑定该Point 54 | var e = vEllipsePath.Data as EllipseGeometry; 55 | var binding = new Binding("Center") { Source = e }; 56 | binding.Mode = BindingMode.TwoWay; 57 | BindingOperations.SetBinding(pathFigure, PathFigure.StartPointProperty, binding); 58 | } 59 | 60 | /// 61 | /// 添加一个LineSegment 62 | /// 63 | /// 64 | /// 65 | public void AddLineSegment(PathFigure vPathFigure, Path vEllipsePath) 66 | { 67 | var e = (EllipseGeometry)vEllipsePath.Data; //获取要绑定的点 68 | var newLineSegment = new LineSegment(); 69 | var binding = new Binding("Center") { Source = e }; //绑定点 70 | binding.Mode = BindingMode.TwoWay; 71 | BindingOperations.SetBinding(newLineSegment, LineSegment.PointProperty, binding); 72 | vPathFigure.Segments.Add(newLineSegment); 73 | } 74 | 75 | /// 76 | /// 添加一条ArgSegment 77 | /// 78 | public void AddArcSegment(PathFigure vPathFigure, Path vEllipsePath, Size vSize, double vRotationAngle, 79 | SweepDirection vSweepDirection, bool vIsLargeArc) 80 | { 81 | var e = (EllipseGeometry)vEllipsePath.Data; //获取要绑定的点 82 | var newArcSegment = new ArcSegment(); 83 | newArcSegment.Size = vSize; 84 | newArcSegment.SweepDirection = vSweepDirection; 85 | newArcSegment.IsLargeArc = vIsLargeArc; 86 | newArcSegment.RotationAngle = vRotationAngle; 87 | var binding = new Binding("Center") { Source = e }; //绑定点 88 | binding.Mode = BindingMode.TwoWay; 89 | BindingOperations.SetBinding(newArcSegment, ArcSegment.PointProperty, binding); 90 | vPathFigure.Segments.Add(newArcSegment); 91 | } 92 | 93 | /// 94 | /// 三次方贝塞尔曲线 95 | /// 96 | /// 97 | /// 98 | /// 99 | /// 100 | public void AddBezierSegment(PathFigure vPathFigure, Path vEllipsePath1, Path vEllipsePath2, Path vEllipsePath3) 101 | { 102 | var e1 = (EllipseGeometry)vEllipsePath1.Data; //获取要绑定的点1 103 | var e2 = (EllipseGeometry)vEllipsePath2.Data; //获取要绑定的点2 104 | var e3 = (EllipseGeometry)vEllipsePath3.Data; //获取要绑定的点3 105 | var newLineSegment = new BezierSegment(); 106 | 107 | var binding1 = new Binding("Center") { Source = e1 }; //绑定点1 108 | binding1.Mode = BindingMode.TwoWay; 109 | BindingOperations.SetBinding(newLineSegment, BezierSegment.Point1Property, binding1); 110 | 111 | var binding2 = new Binding("Center") { Source = e2 }; //绑定点2 112 | binding2.Mode = BindingMode.TwoWay; 113 | BindingOperations.SetBinding(newLineSegment, BezierSegment.Point2Property, binding2); 114 | 115 | var binding3 = new Binding("Center") { Source = e3 }; //绑定点3 116 | binding3.Mode = BindingMode.TwoWay; 117 | BindingOperations.SetBinding(newLineSegment, BezierSegment.Point3Property, binding3); 118 | 119 | vPathFigure.Segments.Add(newLineSegment); 120 | } 121 | 122 | /// 123 | /// 两次方贝塞尔曲线 124 | /// 125 | /// 126 | /// 127 | /// 128 | public void AddQuadraticSegment(PathFigure vPathFigure, Path vEllipsePath1, Path vEllipsePath2) 129 | { 130 | var e1 = (EllipseGeometry)vEllipsePath1.Data; //获取要绑定的点1 131 | var e2 = (EllipseGeometry)vEllipsePath2.Data; //获取要绑定的点2 132 | var newLineSegment = new QuadraticBezierSegment(); 133 | 134 | var binding1 = new Binding("Center") { Source = e1 }; //绑定点1 135 | binding1.Mode = BindingMode.TwoWay; 136 | BindingOperations.SetBinding(newLineSegment, QuadraticBezierSegment.Point1Property, binding1); 137 | 138 | var binding2 = new Binding("Center") { Source = e2 }; //绑定点2 139 | binding2.Mode = BindingMode.TwoWay; 140 | BindingOperations.SetBinding(newLineSegment, QuadraticBezierSegment.Point2Property, binding2); 141 | 142 | vPathFigure.Segments.Add(newLineSegment); 143 | } 144 | 145 | /// 146 | /// 绘制一个随鼠标移动的线段 147 | /// 148 | /// 149 | /// 150 | public void AddHorvorLine(PathFigure vPathFigure, Point vPoint) 151 | { 152 | var lastLine = vPathFigure.Segments[vPathFigure.Segments.Count - 1] as LineSegment; 153 | lastLine.Point = vPoint; 154 | } 155 | 156 | /// 157 | /// 画一条直线 158 | /// 159 | /// 直线的外观 160 | /// 直线的容器 161 | /// 为了修改终点而是用的变量 162 | /// 鼠标点击的图形 163 | /// 表示是起点还是终点 164 | public void AddLine(GraphAppearance vGraphAppearance, Canvas vRootCanvas, ref Path vPath, Path vHitPath, 165 | ref int vIsStartPoint, ref PathFigure vPathFigure, bool isClose) 166 | { 167 | if (vIsStartPoint == 0) //表示起点 168 | { 169 | //设置直线的外观 170 | vPath = new Path 171 | { 172 | Stroke = vGraphAppearance.Stroke, 173 | StrokeThickness = vGraphAppearance.StrokeThickness, 174 | StrokeDashArray = vGraphAppearance.StrokeDashArray 175 | }; 176 | 177 | if (vGraphAppearance.Fill != Brushes.Transparent) 178 | { 179 | vPath.Fill = vGraphAppearance.Fill; 180 | } 181 | 182 | //定义直线 183 | var pathGeometry = new PathGeometry(); 184 | pathGeometry.Figures = new PathFigureCollection(); 185 | vPathFigure = new PathFigure { IsClosed = isClose }; 186 | pathGeometry.Figures.Add(vPathFigure); 187 | var segmentCollection = new PathSegmentCollection(); 188 | vPathFigure.Segments = segmentCollection; 189 | var lineSegment = new LineSegment(); 190 | 191 | vPathFigure.Segments.Add(lineSegment); 192 | vPath.Data = pathGeometry; 193 | 194 | //将直线的起点和某个点绑定在一起 195 | var e = vHitPath.Data as EllipseGeometry; 196 | var binding = new Binding("Center") { Source = e }; 197 | binding.Mode = BindingMode.TwoWay; 198 | BindingOperations.SetBinding(vPathFigure, PathFigure.StartPointProperty, binding); 199 | lineSegment.Point = e.Center; 200 | _borderWithAdorner = new BorderWithAdorner(); 201 | _borderWithAdorner.EllipseList.Add(vHitPath); 202 | _borderWithAdorner.Child = vPath; 203 | vRootCanvas.Children.Add(_borderWithAdorner); 204 | vIsStartPoint = 1; 205 | } 206 | else //表示终点 207 | { 208 | //将直线的终点和某个点绑定在一起 209 | var lineSegment = vPathFigure.Segments[vPathFigure.Segments.Count - 1] as LineSegment; 210 | var e = vHitPath.Data as EllipseGeometry; 211 | var binding1 = new Binding("Center") { Source = e }; 212 | BindingOperations.SetBinding(lineSegment, LineSegment.PointProperty, binding1); 213 | 214 | var newlineSegment = new LineSegment(); 215 | newlineSegment.Point = new Point { X = e.Center.X, Y = e.Center.Y }; 216 | vPathFigure.Segments.Add(newlineSegment); 217 | 218 | _borderWithAdorner.EllipseList.Add(vHitPath); 219 | } 220 | } 221 | 222 | /// 223 | /// 在面板上面画一个圆 224 | /// 225 | /// 226 | /// 227 | /// 228 | public void AddPoint(Point vPoint, GraphAppearance vGraphAppearance, Canvas vRootCanvas, out Path vPath) 229 | { 230 | //1.设置圆的外观 231 | vPath = new Path(); 232 | vPath.Stroke = Brushes.Black; 233 | vPath.Fill = Brushes.White; 234 | vPath.StrokeThickness = 0.1; 235 | var ellipse = new EllipseGeometry(); 236 | ellipse.RadiusX = 0.2; 237 | ellipse.RadiusY = 0.2; 238 | ellipse.Center = vPoint; 239 | vPath.Data = ellipse; 240 | 241 | //把圆放进border里面,因为Border中有装饰器,同时可以使圆可以被拖动 242 | _border = new BorderWithDrag(); 243 | 244 | _border.Child = vPath; 245 | vRootCanvas.Children.Add(_border); 246 | } 247 | 248 | /// 249 | /// 在面板上面画一个圆 250 | /// 251 | /// 252 | /// 253 | /// 254 | public void AddPointWithNoBorder(Point vPoint, GraphAppearance vGraphAppearance, Canvas vRootCanvas, out Path vPath) 255 | { 256 | //1.设置圆的外观 257 | vPath = new Path(); 258 | vPath.Stroke = Brushes.Black; 259 | vPath.Fill = Brushes.White; 260 | vPath.StrokeThickness = 0.1; 261 | var ellipse = new EllipseGeometry(); 262 | ellipse.RadiusX = 0.2; 263 | ellipse.RadiusY = 0.2; 264 | ellipse.Center = vPoint; 265 | vPath.Data = ellipse; 266 | } 267 | 268 | /// 269 | /// 绘制一个圆 270 | /// 271 | /// 272 | /// 273 | /// 274 | /// 275 | public BorderWithAdorner AddGeometryOfCircle(Point vPoint, GraphAppearance vGraphAppearance, Canvas vRootCanvas, 276 | out Path vPath) 277 | { 278 | //设置直线的外观 279 | vPath = new Path 280 | { 281 | Stroke = vGraphAppearance.Stroke, 282 | StrokeThickness = vGraphAppearance.StrokeThickness, 283 | StrokeDashArray = vGraphAppearance.StrokeDashArray 284 | }; 285 | 286 | if (vGraphAppearance.Fill != Brushes.Transparent) 287 | { 288 | vPath.Fill = vGraphAppearance.Fill; 289 | } 290 | 291 | //定义第一个PathFigure 292 | var pathGeometry = new PathGeometry(); 293 | vPath.Data = pathGeometry; 294 | var borderWithAdorner = new BorderWithAdorner(); 295 | borderWithAdorner.Child = vPath; 296 | vRootCanvas.Children.Add(borderWithAdorner); 297 | pathGeometry.Figures = new PathFigureCollection(); 298 | var pathFigure = new PathFigure(); 299 | pathGeometry.Figures.Add(pathFigure); 300 | var segmentCollection = new PathSegmentCollection(); 301 | pathFigure.Segments = segmentCollection; 302 | 303 | //绘制第一个点,并绑定起点 304 | Path ellipsePath; 305 | AddPoint(vPoint, vGraphAppearance, vRootCanvas, out ellipsePath); 306 | borderWithAdorner.EllipseList.Add(ellipsePath); 307 | var ellipseGeometry = ellipsePath.Data as EllipseGeometry; 308 | var startGeometry = ellipseGeometry; 309 | var firstPointBinding = new Binding("Center") { Source = ellipseGeometry }; 310 | firstPointBinding.Mode = BindingMode.TwoWay; 311 | BindingOperations.SetBinding(pathFigure, PathFigure.StartPointProperty, firstPointBinding); 312 | 313 | //绘制第二个点和第一条曲线,并绑定终点 314 | AddPoint(vPoint, vGraphAppearance, vRootCanvas, out ellipsePath); //添加点 315 | borderWithAdorner.EllipseList.Add(ellipsePath); 316 | ellipseGeometry = ellipsePath.Data as EllipseGeometry; 317 | var firstLineSe = new ArcSegment(); 318 | var secondPointBinding = new Binding("Center") { Source = ellipseGeometry }; 319 | secondPointBinding.Mode = BindingMode.TwoWay; 320 | BindingOperations.SetBinding(firstLineSe, ArcSegment.PointProperty, secondPointBinding); //绑定Point 321 | pathFigure.Segments.Add(firstLineSe); 322 | 323 | //绘制第二条曲线,并绑定终点 324 | var secondLineSe = new ArcSegment(); 325 | AddPoint(vPoint, vGraphAppearance, vRootCanvas, out ellipsePath); //添加点 326 | borderWithAdorner.EllipseList.Add(ellipsePath); 327 | ellipseGeometry = ellipsePath.Data as EllipseGeometry; 328 | var forthPointBinding = new Binding("Center") { Source = ellipseGeometry }; 329 | forthPointBinding.Mode = BindingMode.TwoWay; 330 | BindingOperations.SetBinding(secondLineSe, ArcSegment.PointProperty, forthPointBinding); //绑定Point 331 | pathFigure.Segments.Add(secondLineSe); 332 | var fiveBinding = new Binding("Center") { Source = startGeometry, Mode = BindingMode.TwoWay }; 333 | BindingOperations.SetBinding(ellipseGeometry, EllipseGeometry.CenterProperty, fiveBinding); 334 | 335 | secondLineSe.Size = new Size { Height = 0.1, Width = 0.1 }; 336 | firstLineSe.Size = new Size { Height = 0.1, Width = 0.1 }; 337 | return borderWithAdorner; 338 | } 339 | } -------------------------------------------------------------------------------- /src/GeometryTool/GeometryTool/Action/XMLHelper.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | using System.Text.RegularExpressions; 3 | 4 | namespace GeometryTool; 5 | 6 | public class XMLHelper 7 | { 8 | /// 9 | /// 读取xml文件,返回图形所在Match 10 | /// 11 | /// 12 | public Match ReadXml(string vPath) 13 | { 14 | var streamReader = new StreamReader(vPath); 15 | var regex = new Regex(@"\s*([^F]*)"); 16 | var match = regex.Match(streamReader.ReadToEnd()); 17 | streamReader.Close(); 18 | return match; 19 | } 20 | 21 | /// 22 | /// 把图形写入XML文件中写入XML 23 | /// 24 | /// 25 | /// 26 | public void WriteXml(string vPath, string vGeometryString) 27 | { 28 | var streamWriter = new StreamWriter(vPath); 29 | streamWriter.Write(vGeometryString); 30 | streamWriter.Close(); 31 | } 32 | } -------------------------------------------------------------------------------- /src/GeometryTool/GeometryTool/App.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /src/GeometryTool/GeometryTool/App.xaml: -------------------------------------------------------------------------------- 1 |  6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /src/GeometryTool/GeometryTool/App.xaml.cs: -------------------------------------------------------------------------------- 1 | using System.Windows; 2 | 3 | namespace GeometryTool; 4 | 5 | /// 6 | /// App.xaml 的交互逻辑 7 | /// 8 | public partial class App : Application 9 | { 10 | } -------------------------------------------------------------------------------- /src/GeometryTool/GeometryTool/Converter/BorderWidthHeightConverter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Globalization; 3 | using System.Windows.Data; 4 | 5 | namespace GeometryTool; 6 | 7 | /// 8 | /// 用于使画板自动适应屏幕的大小 9 | /// 10 | public class BorderWidthHeightConverter : IValueConverter 11 | { 12 | public object Convert(object value, Type targetType, object parameter, CultureInfo culture) 13 | { 14 | if ((double)value > 0) 15 | { 16 | return (double)value - System.Convert.ToDouble(parameter); 17 | } 18 | 19 | return 0; 20 | } 21 | 22 | public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) 23 | { 24 | return null; 25 | } 26 | } -------------------------------------------------------------------------------- /src/GeometryTool/GeometryTool/Converter/CanvasScaleTransformConverter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Globalization; 3 | using System.Windows.Data; 4 | 5 | namespace GeometryTool; 6 | 7 | /// 8 | /// 画板的缩放的Converter 9 | /// 10 | public class CanvasScaleTransformConverter : IValueConverter 11 | { 12 | public object Convert(object value, Type targetType, object parameter, CultureInfo culture) 13 | { 14 | if ((double)value > 0) 15 | { 16 | return System.Convert.ToDouble(string.Format("{0:F}", (double)value)); //等于Slider的Value的十分之一 17 | } 18 | 19 | return 0; 20 | } 21 | 22 | public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) 23 | { 24 | return null; 25 | } 26 | } -------------------------------------------------------------------------------- /src/GeometryTool/GeometryTool/Converter/ImageVisibilityConverter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Globalization; 3 | using System.Windows; 4 | using System.Windows.Data; 5 | 6 | namespace GeometryTool; 7 | 8 | /// 9 | /// 用于控制所的显示或者隐藏 10 | /// 11 | internal class ImageVisibilityConverter : IValueConverter 12 | { 13 | public object Convert(object value, Type targetType, object parameter, CultureInfo culture) 14 | { 15 | if ((bool)value) 16 | { 17 | return Visibility.Visible; 18 | } 19 | 20 | return Visibility.Hidden; 21 | } 22 | 23 | public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) 24 | { 25 | return null; 26 | } 27 | } -------------------------------------------------------------------------------- /src/GeometryTool/GeometryTool/Converter/IntBoolConverter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Globalization; 3 | using System.Windows.Data; 4 | 5 | namespace GeometryTool; 6 | 7 | /// 8 | /// 用于Int和Bool之间的转换,其中0代表false,1代表true 9 | /// 10 | public class IntBoolConverter : IValueConverter 11 | { 12 | public object Convert(object value, Type targetType, object parameter, CultureInfo culture) 13 | { 14 | try 15 | { 16 | if (value != null) //判断是否为空 17 | { 18 | if ((int)value == 0) 19 | { 20 | return false; 21 | } 22 | 23 | return true; 24 | } 25 | 26 | return false; 27 | } 28 | catch 29 | { 30 | if (value != null) //判断是否为空 31 | { 32 | if ((bool)value) 33 | { 34 | return 1; 35 | } 36 | 37 | return 0; 38 | } 39 | 40 | return 0; 41 | } 42 | } 43 | 44 | public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) 45 | { 46 | try 47 | { 48 | if (value != null) //判断是否为空 49 | { 50 | if ((int)value == 0) 51 | { 52 | return false; 53 | } 54 | 55 | return true; 56 | } 57 | 58 | return false; 59 | } 60 | catch 61 | { 62 | if (value != null) //判断是否为空 63 | { 64 | if ((bool)value) 65 | { 66 | return 1; 67 | } 68 | 69 | return 0; 70 | } 71 | 72 | return 0; 73 | } 74 | } 75 | } -------------------------------------------------------------------------------- /src/GeometryTool/GeometryTool/Converter/ScaleCenterConverter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Globalization; 3 | using System.Windows.Data; 4 | 5 | namespace GeometryTool; 6 | 7 | /// 8 | /// 进行画板缩放之后,调整画板的位置 9 | /// 10 | internal class ScaleCenterConverter : IValueConverter 11 | { 12 | public object Convert(object value, Type targetType, object parameter, CultureInfo culture) 13 | { 14 | if ((double)value > 0) 15 | { 16 | return (double)value / 2.0; 17 | } 18 | 19 | return 0; 20 | } 21 | 22 | public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) 23 | { 24 | return null; 25 | } 26 | } -------------------------------------------------------------------------------- /src/GeometryTool/GeometryTool/Converter/SliderConverterToDouble.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Globalization; 3 | using System.Windows.Data; 4 | 5 | namespace GeometryTool; 6 | 7 | /// 8 | /// 线条粗细的转换 9 | /// 10 | internal class SliderConverterToDouble : IValueConverter 11 | { 12 | public object Convert(object value, Type targetType, object parameter, CultureInfo culture) 13 | { 14 | if (value != null) //判断是否为空 15 | { 16 | var sliderValue = (double)value; 17 | if (sliderValue <= 1) 18 | { 19 | return sliderValue * 10; 20 | } 21 | 22 | return sliderValue; 23 | } 24 | 25 | return null; 26 | } 27 | 28 | public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) 29 | { 30 | if (value != null) //判断是否为空 31 | { 32 | var sliderValue = (double)value; 33 | if (sliderValue < 1) 34 | { 35 | return sliderValue; 36 | } 37 | 38 | return System.Convert.ToDouble(sliderValue / 10); 39 | } 40 | 41 | return null; 42 | } 43 | } -------------------------------------------------------------------------------- /src/GeometryTool/GeometryTool/GeometryTool.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | net6.0-windows 4 | WinExe 5 | false 6 | true 7 | true 8 | true 9 | 10 | 11 | app.manifest 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | -------------------------------------------------------------------------------- /src/GeometryTool/GeometryTool/Image/Bezier.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dotnet-campus/GeometryToolDemo/b488b66099e0de4c96ca4a184e56d51084ffd2f8/src/GeometryTool/GeometryTool/Image/Bezier.png -------------------------------------------------------------------------------- /src/GeometryTool/GeometryTool/Image/Circle.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dotnet-campus/GeometryToolDemo/b488b66099e0de4c96ca4a184e56d51084ffd2f8/src/GeometryTool/GeometryTool/Image/Circle.png -------------------------------------------------------------------------------- /src/GeometryTool/GeometryTool/Image/Color.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dotnet-campus/GeometryToolDemo/b488b66099e0de4c96ca4a184e56d51084ffd2f8/src/GeometryTool/GeometryTool/Image/Color.png -------------------------------------------------------------------------------- /src/GeometryTool/GeometryTool/Image/Curve.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dotnet-campus/GeometryToolDemo/b488b66099e0de4c96ca4a184e56d51084ffd2f8/src/GeometryTool/GeometryTool/Image/Curve.png -------------------------------------------------------------------------------- /src/GeometryTool/GeometryTool/Image/Down.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dotnet-campus/GeometryToolDemo/b488b66099e0de4c96ca4a184e56d51084ffd2f8/src/GeometryTool/GeometryTool/Image/Down.png -------------------------------------------------------------------------------- /src/GeometryTool/GeometryTool/Image/DowntoUp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dotnet-campus/GeometryToolDemo/b488b66099e0de4c96ca4a184e56d51084ffd2f8/src/GeometryTool/GeometryTool/Image/DowntoUp.png -------------------------------------------------------------------------------- /src/GeometryTool/GeometryTool/Image/Ellipse.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dotnet-campus/GeometryToolDemo/b488b66099e0de4c96ca4a184e56d51084ffd2f8/src/GeometryTool/GeometryTool/Image/Ellipse.png -------------------------------------------------------------------------------- /src/GeometryTool/GeometryTool/Image/Left.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dotnet-campus/GeometryToolDemo/b488b66099e0de4c96ca4a184e56d51084ffd2f8/src/GeometryTool/GeometryTool/Image/Left.png -------------------------------------------------------------------------------- /src/GeometryTool/GeometryTool/Image/Line.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dotnet-campus/GeometryToolDemo/b488b66099e0de4c96ca4a184e56d51084ffd2f8/src/GeometryTool/GeometryTool/Image/Line.png -------------------------------------------------------------------------------- /src/GeometryTool/GeometryTool/Image/Pan.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dotnet-campus/GeometryToolDemo/b488b66099e0de4c96ca4a184e56d51084ffd2f8/src/GeometryTool/GeometryTool/Image/Pan.png -------------------------------------------------------------------------------- /src/GeometryTool/GeometryTool/Image/QBezier.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dotnet-campus/GeometryToolDemo/b488b66099e0de4c96ca4a184e56d51084ffd2f8/src/GeometryTool/GeometryTool/Image/QBezier.png -------------------------------------------------------------------------------- /src/GeometryTool/GeometryTool/Image/Rectangular.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dotnet-campus/GeometryToolDemo/b488b66099e0de4c96ca4a184e56d51084ffd2f8/src/GeometryTool/GeometryTool/Image/Rectangular.png -------------------------------------------------------------------------------- /src/GeometryTool/GeometryTool/Image/Right.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dotnet-campus/GeometryToolDemo/b488b66099e0de4c96ca4a184e56d51084ffd2f8/src/GeometryTool/GeometryTool/Image/Right.png -------------------------------------------------------------------------------- /src/GeometryTool/GeometryTool/Image/RighttoLeft.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dotnet-campus/GeometryToolDemo/b488b66099e0de4c96ca4a184e56d51084ffd2f8/src/GeometryTool/GeometryTool/Image/RighttoLeft.png -------------------------------------------------------------------------------- /src/GeometryTool/GeometryTool/Image/Triangle.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dotnet-campus/GeometryToolDemo/b488b66099e0de4c96ca4a184e56d51084ffd2f8/src/GeometryTool/GeometryTool/Image/Triangle.png -------------------------------------------------------------------------------- /src/GeometryTool/GeometryTool/Image/TurnOver.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dotnet-campus/GeometryToolDemo/b488b66099e0de4c96ca4a184e56d51084ffd2f8/src/GeometryTool/GeometryTool/Image/TurnOver.png -------------------------------------------------------------------------------- /src/GeometryTool/GeometryTool/Image/Up.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dotnet-campus/GeometryToolDemo/b488b66099e0de4c96ca4a184e56d51084ffd2f8/src/GeometryTool/GeometryTool/Image/Up.png -------------------------------------------------------------------------------- /src/GeometryTool/GeometryTool/Image/UptoDown.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dotnet-campus/GeometryToolDemo/b488b66099e0de4c96ca4a184e56d51084ffd2f8/src/GeometryTool/GeometryTool/Image/UptoDown.png -------------------------------------------------------------------------------- /src/GeometryTool/GeometryTool/Image/XuanZhuan.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dotnet-campus/GeometryToolDemo/b488b66099e0de4c96ca4a184e56d51084ffd2f8/src/GeometryTool/GeometryTool/Image/XuanZhuan.png -------------------------------------------------------------------------------- /src/GeometryTool/GeometryTool/Image/cuxi.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dotnet-campus/GeometryToolDemo/b488b66099e0de4c96ca4a184e56d51084ffd2f8/src/GeometryTool/GeometryTool/Image/cuxi.png -------------------------------------------------------------------------------- /src/GeometryTool/GeometryTool/Image/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dotnet-campus/GeometryToolDemo/b488b66099e0de4c96ca4a184e56d51084ffd2f8/src/GeometryTool/GeometryTool/Image/icon.png -------------------------------------------------------------------------------- /src/GeometryTool/GeometryTool/Image/lock.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dotnet-campus/GeometryToolDemo/b488b66099e0de4c96ca4a184e56d51084ffd2f8/src/GeometryTool/GeometryTool/Image/lock.png -------------------------------------------------------------------------------- /src/GeometryTool/GeometryTool/Image/myCanvas.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dotnet-campus/GeometryToolDemo/b488b66099e0de4c96ca4a184e56d51084ffd2f8/src/GeometryTool/GeometryTool/Image/myCanvas.png -------------------------------------------------------------------------------- /src/GeometryTool/GeometryTool/Image/point.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dotnet-campus/GeometryToolDemo/b488b66099e0de4c96ca4a184e56d51084ffd2f8/src/GeometryTool/GeometryTool/Image/point.png -------------------------------------------------------------------------------- /src/GeometryTool/GeometryTool/Image/unlock.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dotnet-campus/GeometryToolDemo/b488b66099e0de4c96ca4a184e56d51084ffd2f8/src/GeometryTool/GeometryTool/Image/unlock.png -------------------------------------------------------------------------------- /src/GeometryTool/GeometryTool/Model/AutoPoints.cs: -------------------------------------------------------------------------------- 1 | using System.Windows; 2 | 3 | namespace GeometryTool; 4 | 5 | /// 6 | /// 坐标点的换算,实现自动吸附功能 7 | /// 8 | public class AutoPoints 9 | { 10 | /// 11 | /// 坐标点的换算,实现自动吸附功能 12 | /// 13 | public Point GetAutoAdsorbPoint(Point oldPoint) 14 | { 15 | var p = new Point(); 16 | if (oldPoint.X * 10 % 10 >= 5) 17 | { 18 | var b = (int)oldPoint.X; //计算离其最近的一个X坐标 19 | p.X = (int)oldPoint.X + 1; 20 | } 21 | else 22 | { 23 | p.X = (int)oldPoint.X; 24 | } 25 | 26 | if (oldPoint.Y * 10 % 10 >= 5) //计算离其最近的一个Y坐标 27 | { 28 | p.Y = (int)oldPoint.Y + 1; 29 | } 30 | else 31 | { 32 | p.Y = (int)oldPoint.Y; 33 | } 34 | 35 | return p; 36 | } 37 | } -------------------------------------------------------------------------------- /src/GeometryTool/GeometryTool/Model/BorderWithAdorner.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Windows.Controls; 3 | using System.Windows.Documents; 4 | using System.Windows.Input; 5 | using System.Windows.Media; 6 | using System.Windows.Shapes; 7 | 8 | namespace GeometryTool; 9 | 10 | /// 11 | /// 包含着图形的border,主要是显示选择框 12 | /// 13 | public class BorderWithAdorner : Border 14 | { 15 | public List EllipseList { get; } 16 | 17 | /// 18 | /// 图形的选择框 19 | /// 20 | public GeometryAdorner GeometryAdorner { set; get; } 21 | 22 | /// 23 | /// 最大的X位置 24 | /// 25 | public double MaxX { set; get; } 26 | 27 | /// 28 | /// 最大的Y位置 29 | /// 30 | public double MaxY { set; get; } 31 | 32 | /// 33 | /// 最小的X位置 34 | /// 35 | public double MinX { set; get; } 36 | 37 | /// 38 | /// 最小的Y位置 39 | /// 40 | public double MinY { set; get; } 41 | 42 | public BorderWithAdorner() 43 | { 44 | MouseDown += Element_MouseDown; 45 | EllipseList = new List(); 46 | } 47 | 48 | /// 49 | /// 当鼠标点击图形的的时候,产生边线 50 | /// 51 | /// 52 | /// 53 | public void Element_MouseDown(object sender, MouseEventArgs e) 54 | { 55 | if (MainWindow.ActionMode == "Select") 56 | { 57 | ShowAdorner(); 58 | } 59 | } 60 | 61 | /// 62 | /// 展示Adorner 63 | /// 64 | public void ShowAdorner() 65 | { 66 | var layer = AdornerLayer.GetAdornerLayer(Parent as Canvas); 67 | if (layer != null) 68 | { 69 | GetFourPoint(this); 70 | if (GeometryAdorner == null) 71 | { 72 | var path = Child as Path; 73 | GeometryAdorner = new GeometryAdorner(this); 74 | layer.Add(GeometryAdorner); 75 | } 76 | } 77 | } 78 | 79 | /// 80 | /// 获取图形四个边角的位置 81 | /// 82 | public void GetFourPoint(BorderWithAdorner borderWithAdorner) 83 | { 84 | if (borderWithAdorner.EllipseList.Count > 0) 85 | { 86 | borderWithAdorner.MinX = (EllipseList[0].Data as EllipseGeometry).Center.X; 87 | borderWithAdorner.MinY = (EllipseList[0].Data as EllipseGeometry).Center.Y; 88 | borderWithAdorner.MaxX = 0; 89 | borderWithAdorner.MaxY = 0; 90 | } 91 | 92 | foreach (var path in borderWithAdorner.EllipseList) 93 | { 94 | var item = path.Data as EllipseGeometry; 95 | var point = item.Center; 96 | if (borderWithAdorner.MaxX < point.X) 97 | { 98 | borderWithAdorner.MaxX = point.X; 99 | } 100 | 101 | if (borderWithAdorner.MaxY < point.Y) 102 | { 103 | borderWithAdorner.MaxY = point.Y; 104 | } 105 | 106 | if (borderWithAdorner.MinX > point.X) 107 | { 108 | borderWithAdorner.MinX = point.X; 109 | } 110 | 111 | if (borderWithAdorner.MinY > point.Y) 112 | { 113 | borderWithAdorner.MinY = point.Y; 114 | } 115 | } 116 | } 117 | 118 | /// 119 | /// 用于复制Border,模仿深度复制 120 | /// 121 | /// 122 | /// 123 | public BorderWithAdorner CopyBorder(BorderWithAdorner borderWithAdorner) 124 | { 125 | var path = borderWithAdorner.Child as Path; 126 | var graphAppearance = new GraphAppearance 127 | { 128 | Stroke = path.Stroke, 129 | StrokeThickness = path.StrokeThickness, 130 | StrokeDashArray = path.StrokeDashArray, 131 | Fill = path.Fill 132 | }; 133 | var geometryStringConverter = new GeometryStringConverter(MainWindow.MyRootCanvas, graphAppearance); 134 | var miniLanguage = 135 | geometryStringConverter.StringFromGeometry(borderWithAdorner.Child as Path); //把该图形转化成为Mini-Language 136 | var newBorderWithAdorner = 137 | geometryStringConverter.GeometryFromString(miniLanguage); //把Mini-Language转化成为图形,实现图形的深度复制 138 | var newPath = newBorderWithAdorner.Child as Path; 139 | newPath.Stroke = path.Stroke; 140 | newPath.StrokeThickness = path.StrokeThickness; 141 | newPath.StrokeDashArray = path.StrokeDashArray; 142 | newPath.Fill = path.Fill; 143 | return newBorderWithAdorner; 144 | } 145 | } -------------------------------------------------------------------------------- /src/GeometryTool/GeometryTool/Model/BorderWithDrag.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Windows; 3 | using System.Windows.Controls; 4 | using System.Windows.Data; 5 | using System.Windows.Input; 6 | using System.Windows.Media; 7 | using System.Windows.Shapes; 8 | 9 | namespace GeometryTool 10 | { 11 | /// 12 | /// 主要让border添加了拖动响应,用于点 13 | /// 14 | public class BorderWithDrag : Border 15 | { 16 | /// 17 | /// 依赖属性,用于表示该位置是否含有多个点,来控制Adorner的显示 18 | /// 19 | public static readonly DependencyProperty HasOtherPointProperty = 20 | DependencyProperty.Register("HasOtherPoint", typeof(bool), typeof(BorderWithDrag), 21 | new FrameworkPropertyMetadata(false, null)); 22 | 23 | /// 24 | /// 表示同一图层中 25 | /// 26 | public BorderWithDrag BrotherBorder; 27 | 28 | private readonly List _borderWithDragList = new(); 29 | 30 | /// 31 | /// 表示是否可以拖动该点 32 | /// 33 | private bool _canLock; 34 | 35 | /// 36 | /// 用于表示所在的图形中所拥有的点集 37 | /// 38 | private readonly List _ellipseList; 39 | 40 | /// 41 | /// 表示点所在的图形 42 | /// 43 | private readonly Path _geometryPath; 44 | 45 | /// 46 | /// 表示是否可以拖动 47 | /// 48 | private bool _isDragDropInEffect; 49 | 50 | /// 51 | /// 表示控件的装饰器 52 | /// 53 | public LockAdorner LockAdornor { set; get; } 54 | 55 | /// 56 | /// 表示这是图形中第几个点 57 | /// 58 | private int _number; 59 | 60 | public List PointList = new(); 61 | 62 | /// 63 | /// 表示当前点的位置 64 | /// 65 | private Point _currentPoint; 66 | 67 | /// 68 | /// 无参数的构造函数,主要是为了给Border响应鼠标的事件 69 | /// 70 | public BorderWithDrag() 71 | { 72 | MouseLeftButtonDown += Element_MouseLeftButtonDown; 73 | MouseMove += Element_MouseMove; 74 | MouseLeftButtonUp += Element_MouseLeftButtonUp; 75 | } 76 | 77 | /// 78 | /// 有参数的构造函数,主要添加了点的删除功能 79 | /// 80 | /// 81 | /// 82 | /// 83 | public BorderWithDrag(Path path, int number, List ellipseList) 84 | { 85 | MouseLeftButtonDown += Element_MouseLeftButtonDown; 86 | MouseMove += Element_MouseMove; 87 | MouseLeftButtonUp += Element_MouseLeftButtonUp; 88 | _geometryPath = path; 89 | _number = number; 90 | _ellipseList = ellipseList; 91 | 92 | ContextMenu = new ContextMenu(); 93 | var deleteItem = new MenuItem(); 94 | deleteItem.Header = "删除"; 95 | deleteItem.Click += DeletedItem_Click; 96 | ContextMenu.Items.Add(deleteItem); 97 | } 98 | 99 | public bool HasOtherPoint 100 | { 101 | get => (bool)GetValue(HasOtherPointProperty); 102 | set => SetValue(HasOtherPointProperty, value); 103 | } 104 | 105 | /// 106 | /// 拖动鼠标,移动控件 107 | /// 108 | /// 109 | /// 110 | public void Element_MouseMove(object sender, MouseEventArgs e) 111 | { 112 | //判断是否在选择模式下的操作以及是否可以拖动 113 | if (_isDragDropInEffect && MainWindow.ActionMode == "Select") 114 | { 115 | var oldPoint = e.GetPosition(MainWindow.MyRootCanvas); //获取目前鼠标的相对位置 116 | var point = new AutoPoints().GetAutoAdsorbPoint(oldPoint); //计算最近的网格的位置 117 | _currentPoint = point; 118 | var currentFrameworkElement = sender as FrameworkElement; 119 | var borderWithDrag = currentFrameworkElement as BorderWithDrag; 120 | var path = borderWithDrag.Child as Path; 121 | var ellipse = path.Data as EllipseGeometry; 122 | 123 | ellipse.Center = new Point { X = point.X, Y = point.Y }; 124 | } 125 | } 126 | 127 | /// 128 | /// 鼠标左击控件,使控件可以被拖动 129 | /// 130 | /// 131 | /// 132 | public void Element_MouseLeftButtonDown(object sender, MouseButtonEventArgs e) 133 | { 134 | //判断是否在选择模式下的操作 135 | if (MainWindow.ActionMode == "Select") 136 | { 137 | var currentFrameworkElement = sender as FrameworkElement; 138 | 139 | //判断是否命中的是点 140 | if (((currentFrameworkElement as BorderWithDrag).Child as Path).Data as EllipseGeometry != null) 141 | { 142 | currentFrameworkElement.CaptureMouse(); 143 | currentFrameworkElement.Cursor = Cursors.Hand; 144 | _isDragDropInEffect = true; //设置可以拖动 145 | _canLock = true; 146 | } 147 | 148 | e.Handled = true; 149 | } 150 | } 151 | 152 | /// 153 | /// 鼠标释放,使控件不能被拖动 154 | /// 155 | /// 156 | /// 157 | public void Element_MouseLeftButtonUp(object sender, MouseButtonEventArgs e) 158 | { 159 | //判断是否在选择模式下的操作以及是否可以拖动 160 | var currentFrameworkElement = sender as FrameworkElement; 161 | if (_isDragDropInEffect && MainWindow.ActionMode == "Select" && _canLock) 162 | { 163 | _currentPoint = e.GetPosition((UIElement)sender); //获取当前鼠标的位置 164 | _currentPoint = new AutoPoints().GetAutoAdsorbPoint(_currentPoint); //修正该位置 165 | var borderLock = new BorderLock(this); //进行点融合的判读 166 | borderLock.Lock(_currentPoint); 167 | } 168 | 169 | _isDragDropInEffect = false; 170 | currentFrameworkElement.ReleaseMouseCapture(); 171 | _canLock = false; 172 | } 173 | 174 | /// 175 | /// 添加Border的删除功能 176 | /// 177 | /// 178 | /// 179 | public void DeletedItem_Click(object sender, RoutedEventArgs e) 180 | { 181 | _borderWithDragList.Clear(); 182 | VisualTreeHelper.HitTest(Parent as Canvas, null, //进行命中测试 183 | MyHitTestResult, 184 | new PointHitTestParameters(_currentPoint)); 185 | var deleteAll = false; 186 | 187 | foreach (var item in _borderWithDragList) 188 | { 189 | if (item.HasOtherPoint) 190 | { 191 | deleteAll = true; 192 | break; 193 | } 194 | } 195 | 196 | if (deleteAll) 197 | { 198 | foreach (var item in _borderWithDragList) 199 | { 200 | if (item._number != 0) 201 | { 202 | DeletePoint(item); 203 | } 204 | } 205 | } 206 | else 207 | { 208 | DeletePoint(this); 209 | } 210 | } 211 | 212 | /// 213 | /// 命中测试 214 | /// 215 | /// 216 | /// 217 | public HitTestResultBehavior MyHitTestResult(HitTestResult result) 218 | { 219 | var path = result.VisualHit as Path; 220 | if (path != null) 221 | { 222 | var border = path.Parent as BorderWithDrag; 223 | if (border != null) //如果命中的图形是BorderWithDrag,就Count++ 224 | { 225 | _borderWithDragList.Add(border); 226 | } 227 | } 228 | 229 | return HitTestResultBehavior.Continue; 230 | } 231 | 232 | /// 233 | /// 融合点 234 | /// 235 | /// 236 | /// 237 | public void LockItem_Click(object sender, RoutedEventArgs e) 238 | { 239 | var borderLock = new BorderLock(this); 240 | borderLock.Lock(_currentPoint); 241 | HasOtherPoint = true; 242 | } 243 | 244 | /// 245 | /// 拆分点 246 | /// 247 | /// 248 | /// 249 | public void UnLockItem_Click(object sender, RoutedEventArgs e) 250 | { 251 | var borderLock = new BorderLock(); 252 | borderLock.UnLock(this); 253 | HasOtherPoint = false; 254 | } 255 | 256 | /// 257 | /// 删除不必要的点 258 | /// 259 | /// 260 | public void DeletePoint(BorderWithDrag item) 261 | { 262 | if (item._number != 0) 263 | { 264 | var path = item._geometryPath.Data as PathGeometry; //获取该Border所在PathGeometry 265 | var pathFigure = path.Figures[0]; 266 | if (item._number != 1) //如果要删除的不是起点 267 | { 268 | pathFigure.Segments.RemoveAt(item._number - 2); //直接移除Segment 269 | } 270 | else //如果要删除的是起点 271 | { 272 | var pathFigure2 = new PathFigure(); 273 | for (var i = 0; i < pathFigure.Segments.Count; ++i) 274 | { 275 | if (i == 0) //复制出另外一个PathFigure,令其起点为原来的PathFigure中的LineSegment的Point 276 | { 277 | var binding = new Binding("Center") 278 | { Source = item._ellipseList[1].Data as EllipseGeometry }; 279 | BindingOperations.SetBinding(pathFigure2, PathFigure.StartPointProperty, binding); 280 | } 281 | else 282 | { 283 | pathFigure2.Segments.Add(pathFigure.Segments[i]); 284 | } 285 | } 286 | 287 | path.Figures.Add(pathFigure2); 288 | path.Figures.RemoveAt(0); 289 | } 290 | 291 | MainWindow.MyRootCanvas.Children.Remove(item); //在窗体上移除该点 292 | 293 | item._ellipseList.RemoveAt(item._number - 1); 294 | for (var j = 0; j < item._ellipseList.Count; ++j) //重新定位点集的位置 295 | { 296 | var border = item._ellipseList[j].Parent as BorderWithDrag; 297 | border._number = j + 1; 298 | } 299 | } 300 | } 301 | } 302 | } -------------------------------------------------------------------------------- /src/GeometryTool/GeometryTool/Model/GeometryAdorner.cs: -------------------------------------------------------------------------------- 1 | using System.Windows; 2 | using System.Windows.Documents; 3 | using System.Windows.Media; 4 | 5 | namespace GeometryTool; 6 | 7 | /// 8 | /// 选择框的Adorner 9 | /// 10 | public class GeometryAdorner : Adorner 11 | { 12 | public GeometryChrome GeometryChrome { set; get; } //选择框的真正样式 13 | private readonly VisualCollection _visuals; 14 | 15 | /// 16 | /// 构造函数,主要是使chrome是DataContext为BorderWithAdorner 17 | /// 18 | /// 19 | public GeometryAdorner(BorderWithAdorner borderWithAdorner) 20 | : base(borderWithAdorner) 21 | { 22 | SnapsToDevicePixels = true; 23 | GeometryChrome = new GeometryChrome(); 24 | GeometryChrome.DataContext = borderWithAdorner; 25 | _visuals = new VisualCollection(this); 26 | _visuals.Add(GeometryChrome); 27 | } 28 | 29 | /// 30 | /// 重写的VisualChildrenCount函数 31 | /// 32 | protected override int VisualChildrenCount => _visuals.Count; 33 | 34 | /// 35 | /// 重写的ArrangeOverride 36 | /// 37 | /// 38 | /// 39 | protected override Size ArrangeOverride(Size arrangeBounds) 40 | { 41 | arrangeBounds.Height += 0.5; 42 | arrangeBounds.Width += 0.5; 43 | GeometryChrome.Arrange(new Rect(arrangeBounds)); 44 | return arrangeBounds; 45 | } 46 | 47 | /// 48 | /// 重写GetVisualChild函数 49 | /// 50 | /// 51 | /// 52 | protected override Visual GetVisualChild(int index) 53 | { 54 | return _visuals[index]; 55 | } 56 | } -------------------------------------------------------------------------------- /src/GeometryTool/GeometryTool/Model/GeometryChrome.cs: -------------------------------------------------------------------------------- 1 | using System.Windows; 2 | using System.Windows.Controls; 3 | using System.Windows.Media; 4 | using System.Windows.Shapes; 5 | 6 | namespace GeometryTool; 7 | 8 | /// 9 | /// 图形的外层拖动的Adorner 10 | /// 11 | public class GeometryChrome : ContentControl 12 | { 13 | public GeometryChrome() 14 | { 15 | ContextMenu = new ContextMenu(); 16 | var copyItem = new MenuItem(); 17 | copyItem.Header = "复制"; 18 | copyItem.Command = RoutedCommands.CopyCommand; 19 | ContextMenu.Items.Add(copyItem); 20 | 21 | var cutItem = new MenuItem(); 22 | cutItem.Header = "剪切"; 23 | cutItem.Command = RoutedCommands.CutCommand; 24 | ContextMenu.Items.Add(cutItem); 25 | 26 | var deleteItem = new MenuItem(); 27 | deleteItem.Header = "删除"; 28 | deleteItem.Command = RoutedCommands.DeleteCommand; 29 | ContextMenu.Items.Add(deleteItem); 30 | 31 | var topItem = new MenuItem(); 32 | topItem.Header = "置于顶层"; 33 | topItem.Click += TopItem_Click; 34 | ContextMenu.Items.Add(topItem); 35 | var bottomItem = new MenuItem(); 36 | 37 | bottomItem.Header = "置于底层"; 38 | bottomItem.Click += BottomItem_Click; 39 | ContextMenu.Items.Add(bottomItem); 40 | } 41 | 42 | public static void Arrange(GeometryChrome geometryChrome) 43 | { 44 | var border = geometryChrome.DataContext as BorderWithAdorner; 45 | geometryChrome.ArrangeOverride(new Size { Width = border.MaxX, Height = border.MaxY }); 46 | } 47 | 48 | /// 49 | /// 绘制这个锁 50 | /// 51 | /// 52 | /// 53 | protected override Size ArrangeOverride(Size arrangeBounds) 54 | { 55 | var border = DataContext as BorderWithAdorner; 56 | border.MaxX = 0; 57 | border.MinX = (border.EllipseList[0].Data as EllipseGeometry).Center.X; 58 | border.MaxY = 0; 59 | border.MinY = (border.EllipseList[0].Data as EllipseGeometry).Center.Y; 60 | foreach (var path in border.EllipseList) 61 | { 62 | var item = path.Data as EllipseGeometry; 63 | var p = item.Center; 64 | if (border.MaxX < p.X) 65 | { 66 | border.MaxX = p.X; 67 | } 68 | 69 | if (border.MaxY < p.Y) 70 | { 71 | border.MaxY = p.Y; 72 | } 73 | 74 | if (border.MinX > p.X) 75 | { 76 | border.MinX = p.X; 77 | } 78 | 79 | if (border.MinY > p.Y) 80 | { 81 | border.MinY = p.Y; 82 | } 83 | } 84 | 85 | var pathGeometry = (border.Child as Path).Data as PathGeometry; 86 | if (pathGeometry.Figures[0].Segments.Count > 0) 87 | { 88 | var geometry = pathGeometry.GetFlattenedPathGeometry(); 89 | var bound = geometry.GetRenderBounds(new Pen(null, (border.Child as Path).StrokeThickness)); 90 | Width = bound.Width + 1; 91 | Height = bound.Height + 1; 92 | Margin = new Thickness(border.ActualWidth - bound.Width - 0.65, border.ActualHeight - bound.Height - 0.65, 93 | 0, 0); 94 | } 95 | else 96 | { 97 | Width = border.MaxX - border.MinX + 1; 98 | Height = border.MaxY - border.MinY + 1; 99 | 100 | Margin = new Thickness(border.MinX - 0.5, border.MinY - 0.5, 0, 0); 101 | } 102 | 103 | return base.ArrangeOverride(arrangeBounds); 104 | } 105 | 106 | /// 107 | /// 复制图形 108 | /// 109 | /// 110 | /// 111 | public void CopyItem_Click(object sender, RoutedEventArgs e) 112 | { 113 | var borderWithAdorner = DataContext as BorderWithAdorner; 114 | MainWindow.CopyBorderWithAdorner = borderWithAdorner; //记录要粘贴的图形 115 | MainWindow.PasteCount = 0; //把粘贴次数重置为0 116 | 117 | var rootCanvas = MainWindow.MyRootCanvas; 118 | var rootCanvasParent = rootCanvas.Parent as Canvas; 119 | var canvasBorder = rootCanvasParent.Parent as Border; 120 | var scrollViewer = canvasBorder.Parent as ScrollViewer; 121 | var rootGrid = scrollViewer.Parent as Grid; 122 | var mainWindow = rootGrid.Parent as MainWindow; //获取MainWindow实例,为了修改CanPaste属性 123 | mainWindow.CanPaste = true; 124 | } 125 | 126 | /// 127 | /// 剪切图形 128 | /// 129 | /// 130 | /// 131 | public void CutItem_Click(object sender, RoutedEventArgs e) 132 | { 133 | var borderWithAdorner = DataContext as BorderWithAdorner; 134 | MainWindow.CopyBorderWithAdorner = borderWithAdorner; //记录要粘贴的图形 135 | MainWindow.PasteCount = 0; //把粘贴次数重置为0 136 | 137 | var rootCanvas = MainWindow.MyRootCanvas; 138 | var rootCanvasParent = rootCanvas.Parent as Canvas; 139 | var canvasBorder = rootCanvasParent.Parent as Border; 140 | var scrollViewer = canvasBorder.Parent as ScrollViewer; 141 | var rootGrid = scrollViewer.Parent as Grid; 142 | var mainWindow = rootGrid.Parent as MainWindow; //获取MainWindow实例,为了修改CanPaste属性 143 | mainWindow.CanPaste = true; 144 | rootCanvas.Children.Remove(borderWithAdorner); 145 | foreach (var item in borderWithAdorner.EllipseList) 146 | { 147 | var borderWithDrag = item.Parent as BorderWithDrag; 148 | rootCanvas.Children.Remove(borderWithDrag); 149 | } 150 | } 151 | 152 | /// 153 | /// 删除图形 154 | /// 155 | /// 156 | /// 157 | public void DeleteItem_Click(object sender, RoutedEventArgs e) 158 | { 159 | var borderWithAdorner = DataContext as BorderWithAdorner; 160 | var rootCanvas = MainWindow.MyRootCanvas; 161 | rootCanvas.Children.Remove(borderWithAdorner); //移除图形 162 | foreach (var item in borderWithAdorner.EllipseList) //移除图形上面的点 163 | { 164 | var borderWithDrag = item.Parent as BorderWithDrag; 165 | rootCanvas.Children.Remove(borderWithDrag); 166 | } 167 | } 168 | 169 | /// 170 | /// 把图形至于顶层 171 | /// 172 | /// 173 | /// 174 | public void TopItem_Click(object sender, RoutedEventArgs e) 175 | { 176 | var borderWithAdorner = DataContext as BorderWithAdorner; 177 | Panel.SetZIndex(borderWithAdorner, MainWindow.HightestLevel++); 178 | for (var i = 0; i < borderWithAdorner.EllipseList.Count; ++i) 179 | { 180 | Panel.SetZIndex(borderWithAdorner.EllipseList[i].Parent as BorderWithDrag, MainWindow.HightestLevel); 181 | } 182 | } 183 | 184 | /// 185 | /// 把图形至于底层 186 | /// 187 | /// 188 | /// 189 | public void BottomItem_Click(object sender, RoutedEventArgs e) 190 | { 191 | var borderWithAdorner = DataContext as BorderWithAdorner; 192 | Panel.SetZIndex(borderWithAdorner, MainWindow.LowestLevel--); 193 | for (var i = 0; i < borderWithAdorner.EllipseList.Count; ++i) 194 | { 195 | Panel.SetZIndex(borderWithAdorner.EllipseList[i].Parent as BorderWithDrag, MainWindow.LowestLevel); 196 | } 197 | } 198 | } -------------------------------------------------------------------------------- /src/GeometryTool/GeometryTool/Model/GraphAppearance.cs: -------------------------------------------------------------------------------- 1 | using System.ComponentModel; 2 | using System.Windows.Media; 3 | 4 | namespace GeometryTool; 5 | 6 | /// 7 | /// 图形的外观 8 | /// 9 | public class GraphAppearance 10 | { 11 | /// 12 | /// 填充规则 13 | /// 14 | private FillRule _fillRule; 15 | 16 | /// 17 | /// 设置虚实 18 | /// 19 | private DoubleCollection _strokeDashArray; 20 | 21 | /// 22 | /// 设置图形的线条的大小 23 | /// 24 | private double _strokeThickness; 25 | 26 | public GraphAppearance() 27 | { 28 | StrokeThickness = 0.1; 29 | 30 | Stroke = Brushes.Black; 31 | Fill = Brushes.Transparent; 32 | FillRule = FillRule.EvenOdd; 33 | StrokeDashArray = new DoubleCollection(); 34 | StrokeDashArray.Add(1); 35 | StrokeDashArray.Add(0); 36 | } 37 | 38 | public double StrokeThickness 39 | { 40 | get => _strokeThickness; 41 | set 42 | { 43 | _strokeThickness = value; 44 | if (PropertyChanged != null) 45 | { 46 | PropertyChanged.Invoke(this, new PropertyChangedEventArgs("StrokeThickness")); 47 | } 48 | } 49 | } 50 | 51 | public DoubleCollection StrokeDashArray 52 | { 53 | get => _strokeDashArray; 54 | set 55 | { 56 | _strokeDashArray = value; 57 | if (PropertyChanged != null) 58 | { 59 | PropertyChanged.Invoke(this, new PropertyChangedEventArgs("StrokeDashArray")); 60 | } 61 | } 62 | } 63 | 64 | /// 65 | /// 设置图形的颜色 66 | /// 67 | private Brush _stroke; 68 | 69 | /// 70 | /// 设置图形的颜色 71 | /// 72 | public Brush Stroke 73 | { 74 | get => _stroke; 75 | set 76 | { 77 | _stroke = value; 78 | if (PropertyChanged != null) 79 | { 80 | PropertyChanged.Invoke(this, new PropertyChangedEventArgs("Stroke")); 81 | } 82 | } 83 | } 84 | 85 | /// 86 | /// 设置图形填充的颜色 87 | /// 88 | private Brush _fill; 89 | 90 | /// 91 | /// 设置图形填充的颜色 92 | /// 93 | public Brush Fill 94 | { 95 | get => _fill; 96 | set 97 | { 98 | _fill = value; 99 | if (PropertyChanged != null) 100 | { 101 | PropertyChanged.Invoke(this, new PropertyChangedEventArgs("Fill")); 102 | } 103 | } 104 | } 105 | 106 | public FillRule FillRule 107 | { 108 | get => _fillRule; 109 | set 110 | { 111 | _fillRule = value; 112 | if (PropertyChanged != null) 113 | { 114 | PropertyChanged.Invoke(this, new PropertyChangedEventArgs("FillRule")); 115 | } 116 | } 117 | } 118 | 119 | public event PropertyChangedEventHandler PropertyChanged; 120 | } -------------------------------------------------------------------------------- /src/GeometryTool/GeometryTool/Model/LockAdorner.cs: -------------------------------------------------------------------------------- 1 | using System.Windows; 2 | using System.Windows.Documents; 3 | using System.Windows.Media; 4 | 5 | namespace GeometryTool; 6 | 7 | /// 8 | /// 融合点时锁的Adorner 9 | /// 10 | public class LockAdorner : Adorner 11 | { 12 | /// 13 | /// 锁的样式 14 | /// 15 | public LockChrome LockChrome { get; } 16 | private readonly VisualCollection _visuals; 17 | 18 | /// 19 | /// 重写的构造函数 20 | /// 21 | /// 22 | public LockAdorner(UIElement borderWithAdorner) 23 | : base(borderWithAdorner) 24 | { 25 | SnapsToDevicePixels = true; 26 | LockChrome = new LockChrome(); 27 | LockChrome.DataContext = borderWithAdorner; 28 | _visuals = new VisualCollection(this); 29 | _visuals.Add(LockChrome); 30 | } 31 | 32 | /// 33 | /// 重写的VisualChildrenCount 34 | /// 35 | protected override int VisualChildrenCount => _visuals.Count; 36 | 37 | /// 38 | /// 重写的ArrangeOverride函数 39 | /// 40 | /// 41 | /// 42 | protected override Size ArrangeOverride(Size arrangeBounds) 43 | { 44 | LockChrome.Arrange(new Rect(arrangeBounds)); 45 | return arrangeBounds; 46 | } 47 | 48 | /// 49 | /// 重写的GetVisualChild的函数 50 | /// 51 | /// 52 | /// 53 | protected override Visual GetVisualChild(int index) 54 | { 55 | return _visuals[index]; 56 | } 57 | } -------------------------------------------------------------------------------- /src/GeometryTool/GeometryTool/Model/LockChrome.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Windows; 3 | using System.Windows.Controls; 4 | using System.Windows.Data; 5 | using System.Windows.Input; 6 | using System.Windows.Media; 7 | using System.Windows.Media.Imaging; 8 | 9 | namespace GeometryTool; 10 | 11 | /// 12 | /// 锁真正的外观 13 | /// 14 | public class LockChrome : Image 15 | { 16 | private readonly bool _isLock; 17 | 18 | public LockChrome() 19 | { 20 | MouseDown += Element_MouseLeftButtonDown; //给这个Image添加点击事件,用于解开融合 21 | Source = new BitmapImage(new Uri("Image/lock.png", UriKind.Relative)); 22 | _isLock = true; 23 | } 24 | 25 | /// 26 | /// 绘制这个锁 27 | /// 28 | /// 29 | /// 30 | protected override Size ArrangeOverride(Size arrangeBounds) 31 | { 32 | Width = 1.2; 33 | Height = 1.2; 34 | 35 | HorizontalAlignment = HorizontalAlignment.Right; //放在点的右下角 36 | VerticalAlignment = VerticalAlignment.Bottom; 37 | 38 | var translateTransform = new TranslateTransform(); //做一个旋转的变换 39 | translateTransform.X = 1.2; 40 | translateTransform.Y = -0.3; 41 | RenderTransform = translateTransform; 42 | 43 | var binding = new Binding("HasOtherPoint") { Converter = new ImageVisibilityConverter() }; 44 | SetBinding(VisibilityProperty, binding); //当没有重合点的时候,隐藏锁 45 | 46 | return base.ArrangeOverride(arrangeBounds); 47 | } 48 | 49 | /// 50 | /// 用于解开融合 51 | /// 52 | /// 53 | /// 54 | public void Element_MouseLeftButtonDown(object sender, MouseButtonEventArgs e) 55 | { 56 | var chrome = sender as LockChrome; 57 | if (chrome != null) 58 | { 59 | if (_isLock) //融合变不融合 60 | { 61 | var border = DataContext as BorderWithDrag; 62 | var borderLock = new BorderLock(); 63 | borderLock.UnLock(border); 64 | } 65 | 66 | e.Handled = true; 67 | } 68 | } 69 | } -------------------------------------------------------------------------------- /src/GeometryTool/GeometryTool/Model/MoveThumb.cs: -------------------------------------------------------------------------------- 1 | using System.Windows; 2 | using System.Windows.Controls.Primitives; 3 | using System.Windows.Media; 4 | 5 | namespace GeometryTool; 6 | 7 | /// 8 | /// 实现图形拖动的Thumb 9 | /// 10 | public class MoveThumb : Thumb 11 | { 12 | /// 13 | /// 包含该图形的BorderWithAdorner 14 | /// 15 | private BorderWithAdorner _borderWithAdorner; 16 | 17 | /// 18 | /// 构造函数,注册拖动事件 19 | /// 20 | public MoveThumb() 21 | { 22 | DragStarted += MoveThumb_DragStarted; 23 | DragDelta += MoveThumb_DragDelta; 24 | DragCompleted += MoveThumb_DragCompleted; 25 | } 26 | 27 | /// 28 | /// 开始拖动,主要是获取包含着被拖动的图形的BorderWithAdorner 29 | /// 30 | /// 31 | /// 32 | private void MoveThumb_DragStarted(object sender, DragStartedEventArgs e) 33 | { 34 | _borderWithAdorner = DataContext as BorderWithAdorner; 35 | } 36 | 37 | /// 38 | /// 图形被拖动过程 39 | /// 40 | /// 41 | /// 42 | private void MoveThumb_DragDelta(object sender, DragDeltaEventArgs e) 43 | { 44 | if (_borderWithAdorner != null) 45 | { 46 | var dragDelta = new Point(e.HorizontalChange, e.VerticalChange); //获取移动的距离 47 | var border = _borderWithAdorner; 48 | for (var i = 0; i < border.EllipseList.Count; ++i) 49 | { 50 | var borderWithDrag = border.EllipseList[i].Parent as BorderWithDrag; 51 | if (borderWithDrag.HasOtherPoint) //如果该点有锁,便跳过移动 52 | { 53 | continue; 54 | } 55 | 56 | var item = border.EllipseList[i].Data as EllipseGeometry; //更新点的坐标 57 | item.Center = new Point 58 | { X = item.Center.X + e.HorizontalChange, Y = item.Center.Y + e.VerticalChange }; 59 | } 60 | } 61 | } 62 | 63 | /// 64 | /// 图形结束拖动,实现自动吸附 65 | /// 66 | /// 67 | /// 68 | private void MoveThumb_DragCompleted(object sender, DragCompletedEventArgs e) 69 | { 70 | for (var i = 0; i < _borderWithAdorner.EllipseList.Count; ++i) 71 | { 72 | var item = _borderWithAdorner.EllipseList[i].Data as EllipseGeometry; 73 | var p1 = item.Center; 74 | item.Center = new AutoPoints().GetAutoAdsorbPoint(p1); 75 | } 76 | } 77 | } -------------------------------------------------------------------------------- /src/GeometryTool/GeometryTool/Model/ResizeRotateAdorner.cs: -------------------------------------------------------------------------------- 1 | using System.Windows; 2 | using System.Windows.Controls; 3 | using System.Windows.Documents; 4 | using System.Windows.Media; 5 | 6 | namespace GeometryTool; 7 | 8 | /// 9 | /// 拖动边的Adorner 10 | /// 11 | public class ResizeRotateAdorner : Adorner 12 | { 13 | private readonly ResizeRotateChrome _chrome; 14 | private readonly VisualCollection _visuals; 15 | 16 | public ResizeRotateAdorner(ContentControl borderWithAdorner) 17 | : base(borderWithAdorner) 18 | { 19 | SnapsToDevicePixels = true; 20 | _chrome = new ResizeRotateChrome(); 21 | _chrome.DataContext = borderWithAdorner; 22 | _visuals = new VisualCollection(this); 23 | _visuals.Add(_chrome); 24 | } 25 | 26 | protected override int VisualChildrenCount => _visuals.Count; 27 | 28 | protected override Size ArrangeOverride(Size arrangeBounds) 29 | { 30 | _chrome.Arrange(new Rect(arrangeBounds)); 31 | return arrangeBounds; 32 | } 33 | 34 | protected override Visual GetVisualChild(int index) 35 | { 36 | return _visuals[index]; 37 | } 38 | } -------------------------------------------------------------------------------- /src/GeometryTool/GeometryTool/Model/ResizeRotateChrome.cs: -------------------------------------------------------------------------------- 1 | using System.Windows; 2 | using System.Windows.Controls; 3 | 4 | namespace GeometryTool; 5 | 6 | /// 7 | /// 可拖动的选择框的边的外观 8 | /// 9 | public class ResizeRotateChrome : Control 10 | { 11 | static ResizeRotateChrome() 12 | { 13 | DefaultStyleKeyProperty.OverrideMetadata(typeof(ResizeRotateChrome), 14 | new FrameworkPropertyMetadata(typeof(ResizeRotateChrome))); 15 | } 16 | } -------------------------------------------------------------------------------- /src/GeometryTool/GeometryTool/Model/ResizeThumb.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Windows; 3 | using System.Windows.Controls.Primitives; 4 | using System.Windows.Media; 5 | 6 | namespace GeometryTool; 7 | 8 | /// 9 | /// 可拖动的选择框的边的外观 10 | /// 11 | public class ResizeThumb : Thumb 12 | { 13 | private BorderWithAdorner _borderWithAdorner; 14 | 15 | /// 16 | /// 构造函数,用于注册事件 17 | /// 18 | public ResizeThumb() 19 | { 20 | DragStarted += ResizeThumb_DragStarted; 21 | DragDelta += ResizeThumb_DragDelta; 22 | DragCompleted += ResizeThumb_DragCompleted; 23 | } 24 | 25 | /// 26 | /// 开始缩放 27 | /// 28 | /// 29 | /// 30 | private void ResizeThumb_DragStarted(object sender, DragStartedEventArgs e) 31 | { 32 | _borderWithAdorner = DataContext as BorderWithAdorner; 33 | } 34 | 35 | /// 36 | /// 图形的缩放 37 | /// 38 | /// 39 | /// 40 | private void ResizeThumb_DragDelta(object sender, DragDeltaEventArgs e) 41 | { 42 | double maxX = _borderWithAdorner.MaxX, maxY = _borderWithAdorner.MaxY; //记录图形四个角落的值 43 | double minX = _borderWithAdorner.MinX, minY = _borderWithAdorner.MinY; 44 | var oldHeight = _borderWithAdorner.ActualHeight; //记录图形未缩放之前的高度 45 | var oldWidth = _borderWithAdorner.ActualWidth; //记录图形未缩放之前的宽度 46 | double newWidth = 0; //缩放后图形新的宽度 47 | double newHeight = 0; 48 | double deltaVertical, deltaHorizontal = 0; //缩放后图形新的高度 49 | if (_borderWithAdorner != null) 50 | { 51 | switch (VerticalAlignment) 52 | { 53 | case VerticalAlignment.Bottom: //如果是拖动下面的边 54 | deltaVertical = -e.VerticalChange; 55 | newHeight = oldHeight - deltaVertical; 56 | break; 57 | case VerticalAlignment.Top: //如果是拖动上面的边 58 | deltaVertical = e.VerticalChange; 59 | newHeight = oldHeight - deltaVertical; 60 | break; 61 | } 62 | 63 | switch (HorizontalAlignment) 64 | { 65 | case HorizontalAlignment.Left: //如果是拖动左边的边 66 | deltaHorizontal = e.HorizontalChange; 67 | newWidth = oldWidth - deltaHorizontal; 68 | 69 | break; 70 | case HorizontalAlignment.Right: //如果是拖动右边的边 71 | deltaHorizontal = -e.HorizontalChange; 72 | newWidth = oldWidth - deltaHorizontal; 73 | break; 74 | } 75 | } 76 | 77 | var multipleX = newWidth / oldWidth; 78 | var multipleY = newHeight / oldHeight; 79 | switch (VerticalAlignment) //改变图形的高度 80 | { 81 | case VerticalAlignment.Bottom: 82 | for (var i = 0; i < _borderWithAdorner.EllipseList.Count; ++i) 83 | { 84 | var item = _borderWithAdorner.EllipseList[i].Data as EllipseGeometry; 85 | var borderWithDrag = _borderWithAdorner.EllipseList[i].Parent as BorderWithDrag; 86 | if (borderWithDrag.HasOtherPoint) 87 | { 88 | continue; 89 | } 90 | 91 | var p1 = item.Center; 92 | var newY = multipleY * (p1.Y - minY) + minY; 93 | item.Center = new Point { X = p1.X, Y = newY }; 94 | } 95 | 96 | break; 97 | case VerticalAlignment.Top: 98 | for (var i = 0; i < _borderWithAdorner.EllipseList.Count; ++i) 99 | { 100 | var item = _borderWithAdorner.EllipseList[i].Data as EllipseGeometry; 101 | var borderWithDrag = _borderWithAdorner.EllipseList[i].Parent as BorderWithDrag; 102 | if (borderWithDrag.HasOtherPoint) 103 | { 104 | continue; 105 | } 106 | 107 | var p1 = item.Center; 108 | var newY = multipleY * (p1.Y - maxY) + maxY; 109 | item.Center = new Point { X = p1.X, Y = newY }; 110 | } 111 | 112 | break; 113 | } 114 | 115 | switch (HorizontalAlignment) //改变图形的宽度 116 | { 117 | case HorizontalAlignment.Left: 118 | for (var i = 0; i < _borderWithAdorner.EllipseList.Count; ++i) 119 | { 120 | var item = _borderWithAdorner.EllipseList[i].Data as EllipseGeometry; 121 | var borderWithDrag = _borderWithAdorner.EllipseList[i].Parent as BorderWithDrag; 122 | if (borderWithDrag.HasOtherPoint) 123 | { 124 | continue; 125 | } 126 | 127 | var p1 = item.Center; 128 | var newX = multipleX * (p1.X - maxX) + maxX; 129 | item.Center = new Point { X = newX, Y = p1.Y }; 130 | if (i < 2) 131 | { 132 | Console.WriteLine("mouse: " + e.HorizontalChange + " p" + i + ": " + newX + " "); 133 | } 134 | } 135 | 136 | break; 137 | case HorizontalAlignment.Right: 138 | for (var i = 0; i < _borderWithAdorner.EllipseList.Count; ++i) 139 | { 140 | var item = _borderWithAdorner.EllipseList[i].Data as EllipseGeometry; 141 | var borderWithDrag = _borderWithAdorner.EllipseList[i].Parent as BorderWithDrag; 142 | if (borderWithDrag.HasOtherPoint) 143 | { 144 | continue; 145 | } 146 | 147 | var p1 = item.Center; 148 | var newX = multipleX * (p1.X - minX) + minX; 149 | 150 | item.Center = new Point { X = newX, Y = p1.Y }; 151 | } 152 | 153 | break; 154 | } 155 | 156 | _borderWithAdorner.MinX = _borderWithAdorner.MaxX; 157 | _borderWithAdorner.MaxX = 0; 158 | _borderWithAdorner.MinY = _borderWithAdorner.MaxY; 159 | 160 | _borderWithAdorner.MaxY = 0; 161 | 162 | foreach (var path in _borderWithAdorner.EllipseList) //重新计算图形的四个边角值 163 | { 164 | var item = path.Data as EllipseGeometry; 165 | var p = item.Center; 166 | if (_borderWithAdorner.MaxX < p.X) 167 | { 168 | _borderWithAdorner.MaxX = p.X; 169 | } 170 | 171 | if (_borderWithAdorner.MaxY < p.Y) 172 | { 173 | _borderWithAdorner.MaxY = p.Y; 174 | } 175 | 176 | if (_borderWithAdorner.MinX > p.X) 177 | { 178 | _borderWithAdorner.MinX = p.X; 179 | } 180 | 181 | if (_borderWithAdorner.MinY > p.Y) 182 | { 183 | _borderWithAdorner.MinY = p.Y; 184 | } 185 | } 186 | 187 | GeometryChrome.Arrange(_borderWithAdorner.GeometryAdorner.GeometryChrome); 188 | e.Handled = true; 189 | } 190 | 191 | /// 192 | /// 缩放后自动吸附 193 | /// 194 | /// 195 | /// 196 | private void ResizeThumb_DragCompleted(object sender, DragCompletedEventArgs e) 197 | { 198 | for (var i = 0; i < _borderWithAdorner.EllipseList.Count; ++i) 199 | { 200 | var item = _borderWithAdorner.EllipseList[i].Data as EllipseGeometry; 201 | var p1 = item.Center; 202 | item.Center = new AutoPoints().GetAutoAdsorbPoint(p1); 203 | } 204 | 205 | e.Handled = true; 206 | } 207 | } -------------------------------------------------------------------------------- /src/GeometryTool/GeometryTool/Model/RotateThumb.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Windows; 3 | using System.Windows.Controls; 4 | using System.Windows.Controls.Primitives; 5 | using System.Windows.Input; 6 | using System.Windows.Media; 7 | 8 | namespace GeometryTool; 9 | 10 | /// 11 | /// 图形旋转的Thumb 12 | /// 13 | public class RotateThumb : Thumb 14 | { 15 | /// 16 | /// 图形的Border 17 | /// 18 | private BorderWithAdorner _borderWithAdorner; 19 | 20 | /// 21 | /// 图形所在的画布 22 | /// 23 | private Canvas _canvas; 24 | 25 | /// 26 | /// 图形的重点 27 | /// 28 | private Point _centerPoint; 29 | 30 | /// 31 | /// 开始的坐标向量 32 | /// 33 | private Vector _startVector; 34 | 35 | public RotateThumb() 36 | { 37 | DragDelta += RotateThumb_DragDelta; 38 | DragStarted += RotateThumb_DragStarted; 39 | DragCompleted += RotateThumb_DragCompleted; 40 | } 41 | 42 | /// 43 | /// 开始旋转 44 | /// 45 | /// 46 | /// 47 | private void RotateThumb_DragStarted(object sender, DragStartedEventArgs e) 48 | { 49 | _borderWithAdorner = DataContext as BorderWithAdorner; 50 | 51 | if (_borderWithAdorner != null) 52 | { 53 | _canvas = VisualTreeHelper.GetParent(_borderWithAdorner) as Canvas; 54 | if (_canvas != null) 55 | { 56 | _centerPoint = _borderWithAdorner.TranslatePoint( 57 | new Point 58 | { 59 | X = (_borderWithAdorner.MaxX + _borderWithAdorner.MinX) / 2.0, 60 | Y = (_borderWithAdorner.MaxY + _borderWithAdorner.MinY) / 2.0 61 | }, 62 | _canvas); 63 | 64 | var startPoint = Mouse.GetPosition(_canvas); 65 | _startVector = Point.Subtract(startPoint, _centerPoint); //计算开始的向量 66 | } 67 | } 68 | } 69 | 70 | /// 71 | /// 旋转的过程 72 | /// 73 | /// 74 | /// 75 | private void RotateThumb_DragDelta(object sender, DragDeltaEventArgs e) 76 | { 77 | if (_borderWithAdorner != null && _canvas != null) 78 | { 79 | var currentPoint = Mouse.GetPosition(_canvas); 80 | var deltaVector = Point.Subtract(currentPoint, _centerPoint); 81 | var angle = Vector.AngleBetween(_startVector, deltaVector) / 360 * 2 * Math.PI; 82 | var centerX = (_borderWithAdorner.MaxX + _borderWithAdorner.MinX) / 2.0; 83 | var centerY = (_borderWithAdorner.MaxY + _borderWithAdorner.MinY) / 2.0; //计算旋转的角度,中点坐标 84 | 85 | foreach (var item in _borderWithAdorner.EllipseList) //根据公式来计算算旋转后的位置 86 | { 87 | if ((item.Parent as BorderWithDrag).HasOtherPoint) 88 | { 89 | continue; 90 | } 91 | 92 | var ellipse = item.Data as EllipseGeometry; 93 | var oldPoint = ellipse.Center; 94 | var newX = (oldPoint.X - centerX) * Math.Cos(angle) - (oldPoint.Y - centerY) * Math.Sin(angle) + 95 | centerX; 96 | var newY = (oldPoint.X - centerX) * Math.Sin(angle) + (oldPoint.Y - centerY) * Math.Cos(angle) + 97 | centerY; 98 | ellipse.Center = new Point { X = newX, Y = newY }; 99 | } 100 | 101 | var startPoint = currentPoint; 102 | _startVector = Point.Subtract(startPoint, _centerPoint); //重新赋值开始的向量 103 | } 104 | } 105 | 106 | /// 107 | /// 结束旋转,并自动吸附到最近的点 108 | /// 109 | /// 110 | /// 111 | private void RotateThumb_DragCompleted(object sender, DragCompletedEventArgs e) 112 | { 113 | foreach (var item in _borderWithAdorner.EllipseList) 114 | { 115 | var ellipse = item.Data as EllipseGeometry; 116 | var oldPoint = ellipse.Center; 117 | var p = new AutoPoints().GetAutoAdsorbPoint(oldPoint); 118 | ellipse.Center = new Point { X = p.X, Y = p.Y }; 119 | } 120 | } 121 | } -------------------------------------------------------------------------------- /src/GeometryTool/GeometryTool/Model/RoutedCommand.cs: -------------------------------------------------------------------------------- 1 | using System.Windows.Input; 2 | 3 | namespace GeometryTool; 4 | 5 | /// 6 | /// 定义快捷键的路由事件 7 | /// 8 | public static class RoutedCommands 9 | { 10 | /// 11 | /// 粘贴的路由事件 12 | /// 13 | private static RoutedUICommand _pasteCommand; 14 | 15 | /// 16 | /// 复制的路由事件 17 | /// 18 | private static RoutedUICommand _copyCommand; 19 | 20 | /// 21 | /// 剪切的路由事件 22 | /// 23 | private static RoutedUICommand _cutCommand; 24 | 25 | /// 26 | /// 删除的路由事件 27 | /// 28 | private static RoutedUICommand _deleteCommand; 29 | 30 | /// 31 | /// 保存的路由事件 32 | /// 33 | private static RoutedUICommand _saveCommand; 34 | 35 | /// 36 | /// 新建的路由事件 37 | /// 38 | private static RoutedUICommand _newCommand; 39 | 40 | /// 41 | /// 打开的路由事件 42 | /// 43 | private static RoutedUICommand _openCommand; 44 | 45 | public static RoutedUICommand PasteCommand 46 | { 47 | get 48 | { 49 | if (_pasteCommand == null) 50 | { 51 | _pasteCommand = new RoutedUICommand 52 | ( 53 | "Paste", 54 | "Paste", 55 | typeof(MainWindow), 56 | new InputGestureCollection { new KeyGesture(Key.V, ModifierKeys.Control) } 57 | ); 58 | } 59 | 60 | return _pasteCommand; 61 | } 62 | } 63 | 64 | public static RoutedUICommand CopyCommand 65 | { 66 | get 67 | { 68 | if (_copyCommand == null) 69 | { 70 | _copyCommand = new RoutedUICommand 71 | ( 72 | "Copy", 73 | "Copy", 74 | typeof(MainWindow), 75 | new InputGestureCollection { new KeyGesture(Key.C, ModifierKeys.Control) } 76 | ); 77 | } 78 | 79 | return _copyCommand; 80 | } 81 | } 82 | 83 | public static RoutedUICommand CutCommand 84 | { 85 | get 86 | { 87 | if (_cutCommand == null) 88 | { 89 | _cutCommand = new RoutedUICommand 90 | ( 91 | "Cut", 92 | "Cut", 93 | typeof(MainWindow), 94 | new InputGestureCollection { new KeyGesture(Key.X, ModifierKeys.Control) } 95 | ); 96 | } 97 | 98 | return _cutCommand; 99 | } 100 | } 101 | 102 | public static RoutedUICommand DeleteCommand 103 | { 104 | get 105 | { 106 | if (_deleteCommand == null) 107 | { 108 | _deleteCommand = new RoutedUICommand 109 | ( 110 | "Delete", 111 | "Delete", 112 | typeof(MainWindow), 113 | new InputGestureCollection { new KeyGesture(Key.Delete) } 114 | ); 115 | } 116 | 117 | return _deleteCommand; 118 | } 119 | } 120 | 121 | public static RoutedUICommand SaveCommand 122 | { 123 | get 124 | { 125 | if (_saveCommand == null) 126 | { 127 | _saveCommand = new RoutedUICommand 128 | ( 129 | "Save", 130 | "Save", 131 | typeof(MainWindow), 132 | new InputGestureCollection { new KeyGesture(Key.S, ModifierKeys.Control) } 133 | ); 134 | } 135 | 136 | return _saveCommand; 137 | } 138 | } 139 | 140 | public static RoutedUICommand NewCommand 141 | { 142 | get 143 | { 144 | if (_newCommand == null) 145 | { 146 | _newCommand = new RoutedUICommand 147 | ( 148 | "New", 149 | "New", 150 | typeof(MainWindow), 151 | new InputGestureCollection { new KeyGesture(Key.N, ModifierKeys.Control) } 152 | ); 153 | } 154 | 155 | return _newCommand; 156 | } 157 | } 158 | 159 | public static RoutedUICommand OpenCommand 160 | { 161 | get 162 | { 163 | if (_openCommand == null) 164 | { 165 | _openCommand = new RoutedUICommand 166 | ( 167 | "Open", 168 | "Open", 169 | typeof(MainWindow), 170 | new InputGestureCollection { new KeyGesture(Key.O, ModifierKeys.Control) } 171 | ); 172 | } 173 | 174 | return _openCommand; 175 | } 176 | } 177 | } -------------------------------------------------------------------------------- /src/GeometryTool/GeometryTool/Model/SizeAdorner.cs: -------------------------------------------------------------------------------- 1 | using System.Windows; 2 | using System.Windows.Documents; 3 | using System.Windows.Media; 4 | 5 | namespace GeometryTool; 6 | 7 | public class SizeAdorner : Adorner 8 | { 9 | private readonly SizeChrome _chrome; 10 | private readonly VisualCollection _visuals; 11 | 12 | public SizeAdorner(BorderWithAdorner borderWithAdorner) 13 | : base(borderWithAdorner) 14 | { 15 | SnapsToDevicePixels = true; 16 | _chrome = new SizeChrome(); 17 | _chrome.DataContext = borderWithAdorner; 18 | _visuals = new VisualCollection(this); 19 | _visuals.Add(_chrome); 20 | } 21 | 22 | protected override int VisualChildrenCount => _visuals.Count; 23 | 24 | protected override Visual GetVisualChild(int index) 25 | { 26 | return _visuals[index]; 27 | } 28 | 29 | protected override Size ArrangeOverride(Size arrangeBounds) 30 | { 31 | _chrome.Arrange(new Rect(new Point(0.0, 0.0), arrangeBounds)); 32 | return arrangeBounds; 33 | } 34 | } -------------------------------------------------------------------------------- /src/GeometryTool/GeometryTool/Model/SizeChrome.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Globalization; 3 | using System.Windows; 4 | using System.Windows.Controls; 5 | using System.Windows.Data; 6 | 7 | namespace GeometryTool; 8 | 9 | public class SizeChrome : Control 10 | { 11 | static SizeChrome() 12 | { 13 | DefaultStyleKeyProperty.OverrideMetadata(typeof(SizeChrome), new FrameworkPropertyMetadata(typeof(SizeChrome))); 14 | } 15 | } 16 | 17 | public class DoubleFormatConverter : IValueConverter 18 | { 19 | public object Convert(object value, Type targetType, object parameter, CultureInfo culture) 20 | { 21 | var d = (double)value; 22 | return Math.Round(d); 23 | } 24 | 25 | public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) 26 | { 27 | return null; 28 | } 29 | } -------------------------------------------------------------------------------- /src/GeometryTool/GeometryTool/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.InteropServices; 3 | using System.Windows; 4 | 5 | // 关于程序集的一般信息通过以下 6 | // 属性集来控制。请更改这些属性值以修改 7 | // 与程序集关联的信息。 8 | [assembly: AssemblyTitle("GeometryTool")] 9 | [assembly: AssemblyDescription("")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("")] 12 | [assembly: AssemblyProduct("GeometryTool")] 13 | [assembly: AssemblyCopyright("Copyright © 2015")] 14 | [assembly: AssemblyTrademark("")] 15 | [assembly: AssemblyCulture("")] 16 | 17 | // 如果将 ComVisible 设置为 False,则此程序集中的类型 18 | // 对于 COM 组件不可见。如果需要从 COM 访问此程序集中的 19 | // 类型,请针对该类型将 ComVisible 属性设置为 True。 20 | [assembly: ComVisible(false)] 21 | 22 | //为了开始生成可本地化的应用程序,请 23 | //在 .csproj 文件中 内 24 | //设置 CultureYouAreCodingWith。例如,如果您在源 25 | //文件中使用美国英语,请将 设置为 "en-US"。然后取消注释 26 | //以下 NeutralResourceLanguage 属性。请更新以下行中的 "en-US" 以 27 | //与项目文件中的 UICulture 设置匹配。 28 | 29 | //[assembly: NeutralResourcesLanguage("en-US", UltimateResourceFallbackLocation.Satellite)] 30 | 31 | 32 | [assembly: ThemeInfo( 33 | ResourceDictionaryLocation.None, //主题特定资源字典的位置 34 | //(在页面或应用程序资源字典中 35 | // 未找到资源的情况下使用) 36 | ResourceDictionaryLocation.SourceAssembly //普通资源字典的位置 37 | //(在页面、应用程序或任何主题特定资源字典中 38 | // 未找到资源的情况下使用) 39 | )] 40 | 41 | 42 | // 程序集的版本信息包含以下四个值: 43 | // 44 | // 主要版本 45 | // 次要版本 46 | // 内部版本号 47 | // 修订 48 | // 49 | // 您可以指定所有值,或者可以默认修订号和内部版本号, 50 | // 方法是按如下方式使用 "*": 51 | // [assembly: AssemblyVersion("1.0.*")] 52 | [assembly: AssemblyVersion("1.0.0.0")] 53 | [assembly: AssemblyFileVersion("1.0.0.0")] -------------------------------------------------------------------------------- /src/GeometryTool/GeometryTool/Properties/Resources.Designer.cs: -------------------------------------------------------------------------------- 1 | //------------------------------------------------------------------------------ 2 | // 3 | // This code was generated by a tool. 4 | // Runtime Version:4.0.30319.34014 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 GeometryTool.Properties { 12 | 13 | 14 | /// 15 | /// A strongly-typed resource class, for looking up localized strings, etc. 16 | /// 17 | // This class was auto-generated by the StronglyTypedResourceBuilder 18 | // class via a tool like ResGen or Visual Studio. 19 | // To add or remove a member, edit your .ResX file then rerun ResGen 20 | // with the /str option, or rebuild your VS project. 21 | [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")] 22 | [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] 23 | [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] 24 | internal class Resources { 25 | 26 | private static global::System.Resources.ResourceManager resourceMan; 27 | 28 | private static global::System.Globalization.CultureInfo resourceCulture; 29 | 30 | [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] 31 | internal Resources() { 32 | } 33 | 34 | /// 35 | /// Returns the cached ResourceManager instance used by this class. 36 | /// 37 | [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] 38 | internal static global::System.Resources.ResourceManager ResourceManager { 39 | get { 40 | if ((resourceMan == null)) { 41 | global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("GeometryTool.Properties.Resources", typeof(Resources).Assembly); 42 | resourceMan = temp; 43 | } 44 | return resourceMan; 45 | } 46 | } 47 | 48 | /// 49 | /// Overrides the current thread's CurrentUICulture property for all 50 | /// resource lookups using this strongly typed resource class. 51 | /// 52 | [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] 53 | internal static global::System.Globalization.CultureInfo Culture { 54 | get { 55 | return resourceCulture; 56 | } 57 | set { 58 | resourceCulture = value; 59 | } 60 | } 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /src/GeometryTool/GeometryTool/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 | -------------------------------------------------------------------------------- /src/GeometryTool/GeometryTool/Properties/Settings.Designer.cs: -------------------------------------------------------------------------------- 1 | //------------------------------------------------------------------------------ 2 | // 3 | // This code was generated by a tool. 4 | // Runtime Version:4.0.30319.34014 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 GeometryTool.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 | -------------------------------------------------------------------------------- /src/GeometryTool/GeometryTool/Properties/Settings.settings: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /src/GeometryTool/GeometryTool/Resources/ButtonImageStyle.xaml: -------------------------------------------------------------------------------- 1 |  3 | 4 | 5 | 6 | 37 | -------------------------------------------------------------------------------- /src/GeometryTool/GeometryTool/Resources/ColorsStyle.xaml: -------------------------------------------------------------------------------- 1 |  3 | 13 | 14 | 15 | 16 | 17 | 27 | 28 | 29 | 30 | 31 | 56 | 77 | 98 | 131 | 132 | 133 | 134 | 135 | 167 | 168 | 169 | 170 | 171 | 172 | 206 | -------------------------------------------------------------------------------- /src/GeometryTool/GeometryTool/Resources/GeometryChromeStyle.xaml: -------------------------------------------------------------------------------- 1 |  4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 33 | -------------------------------------------------------------------------------- /src/GeometryTool/GeometryTool/Resources/LableStyle.xaml: -------------------------------------------------------------------------------- 1 |  3 | 7 | -------------------------------------------------------------------------------- /src/GeometryTool/GeometryTool/Resources/PulseButtonStyle.xaml: -------------------------------------------------------------------------------- 1 |  4 | 5 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 39 | 40 | 41 | 42 | 43 | 45 | 46 | 47 | 48 | 49 | 51 | 52 | 53 | 54 | 55 | 56 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | -------------------------------------------------------------------------------- /src/GeometryTool/GeometryTool/Resources/RadioButtonStyle.xaml: -------------------------------------------------------------------------------- 1 |  3 | 79 | -------------------------------------------------------------------------------- /src/GeometryTool/GeometryTool/Resources/ResizeRotateChromeStyle.xaml: -------------------------------------------------------------------------------- 1 |  4 | 5 | 19 | 20 | 27 | 28 | 102 | 103 | -------------------------------------------------------------------------------- /src/GeometryTool/GeometryTool/Resources/RightTabControlStyle.xaml: -------------------------------------------------------------------------------- 1 |  3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 83 | 84 | 85 | 86 | 113 | 114 | 141 | -------------------------------------------------------------------------------- /src/GeometryTool/GeometryTool/Resources/SizeChrome.xaml: -------------------------------------------------------------------------------- 1 |  4 | 5 | 6 | 7 | 49 | -------------------------------------------------------------------------------- /src/GeometryTool/GeometryTool/app.manifest: -------------------------------------------------------------------------------- 1 |  2 | 3 | 6 | 7 | 8 | 9 | 10 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 57 | 58 | 59 | 60 | --------------------------------------------------------------------------------