├── .github └── FUNDING.yml ├── .gitignore ├── LICENSE ├── README.ko.md ├── README.md ├── README.zh-CN.md ├── src ├── DemoApp │ ├── App.xaml │ ├── App.xaml.cs │ ├── AssemblyInfo.cs │ ├── DemoApp.csproj │ ├── MainWindow.xaml │ └── MainWindow.xaml.cs ├── SliderControl.sln └── SliderControl │ ├── Properties │ └── AssemblyInfo.cs │ ├── RiotSlider.cs │ ├── SliderControl.csproj │ └── Themes │ └── Generic.xaml ├── temp.md └── temp_cn.md /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: [vickyqu115] 2 | -------------------------------------------------------------------------------- /.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/main/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 | [Ll]og/ 33 | [Ll]ogs/ 34 | 35 | # Visual Studio 2015/2017 cache/options directory 36 | .vs/ 37 | # Uncomment if you have tasks that create the project's static files in wwwroot 38 | #wwwroot/ 39 | 40 | # Visual Studio 2017 auto generated files 41 | Generated\ Files/ 42 | 43 | # MSTest test Results 44 | [Tt]est[Rr]esult*/ 45 | [Bb]uild[Ll]og.* 46 | 47 | # NUnit 48 | *.VisualState.xml 49 | TestResult.xml 50 | nunit-*.xml 51 | 52 | # Build Results of an ATL Project 53 | [Dd]ebugPS/ 54 | [Rr]eleasePS/ 55 | dlldata.c 56 | 57 | # Benchmark Results 58 | BenchmarkDotNet.Artifacts/ 59 | 60 | # .NET Core 61 | project.lock.json 62 | project.fragment.lock.json 63 | artifacts/ 64 | 65 | # ASP.NET Scaffolding 66 | ScaffoldingReadMe.txt 67 | 68 | # StyleCop 69 | StyleCopReport.xml 70 | 71 | # Files built by Visual Studio 72 | *_i.c 73 | *_p.c 74 | *_h.h 75 | *.ilk 76 | *.meta 77 | *.obj 78 | *.iobj 79 | *.pch 80 | *.pdb 81 | *.ipdb 82 | *.pgc 83 | *.pgd 84 | *.rsp 85 | *.sbr 86 | *.tlb 87 | *.tli 88 | *.tlh 89 | *.tmp 90 | *.tmp_proj 91 | *_wpftmp.csproj 92 | *.log 93 | *.tlog 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 6 auto-generated project file (contains which files were open etc.) 298 | *.vbp 299 | 300 | # Visual Studio 6 workspace and project file (working project files containing files to include in project) 301 | *.dsw 302 | *.dsp 303 | 304 | # Visual Studio 6 technical files 305 | *.ncb 306 | *.aps 307 | 308 | # Visual Studio LightSwitch build output 309 | **/*.HTMLClient/GeneratedArtifacts 310 | **/*.DesktopClient/GeneratedArtifacts 311 | **/*.DesktopClient/ModelManifest.xml 312 | **/*.Server/GeneratedArtifacts 313 | **/*.Server/ModelManifest.xml 314 | _Pvt_Extensions 315 | 316 | # Paket dependency manager 317 | .paket/paket.exe 318 | paket-files/ 319 | 320 | # FAKE - F# Make 321 | .fake/ 322 | 323 | # CodeRush personal settings 324 | .cr/personal 325 | 326 | # Python Tools for Visual Studio (PTVS) 327 | __pycache__/ 328 | *.pyc 329 | 330 | # Cake - Uncomment if you are using it 331 | # tools/** 332 | # !tools/packages.config 333 | 334 | # Tabs Studio 335 | *.tss 336 | 337 | # Telerik's JustMock configuration file 338 | *.jmconfig 339 | 340 | # BizTalk build output 341 | *.btp.cs 342 | *.btm.cs 343 | *.odx.cs 344 | *.xsd.cs 345 | 346 | # OpenCover UI analysis results 347 | OpenCover/ 348 | 349 | # Azure Stream Analytics local run output 350 | ASALocalRun/ 351 | 352 | # MSBuild Binary and Structured Log 353 | *.binlog 354 | 355 | # NVidia Nsight GPU debugger configuration file 356 | *.nvuser 357 | 358 | # MFractors (Xamarin productivity tool) working folder 359 | .mfractor/ 360 | 361 | # Local History for Visual Studio 362 | .localhistory/ 363 | 364 | # Visual Studio History (VSHistory) files 365 | .vshistory/ 366 | 367 | # BeatPulse healthcheck temp database 368 | healthchecksdb 369 | 370 | # Backup folder for Package Reference Convert tool in Visual Studio 2017 371 | MigrationBackup/ 372 | 373 | # Ionide (cross platform F# VS Code tools) working folder 374 | .ionide/ 375 | 376 | # Fody - auto-generated XML schema 377 | FodyWeavers.xsd 378 | 379 | # VS Code files for those working on multiple tools 380 | .vscode/* 381 | !.vscode/settings.json 382 | !.vscode/tasks.json 383 | !.vscode/launch.json 384 | !.vscode/extensions.json 385 | *.code-workspace 386 | 387 | # Local History for Visual Studio Code 388 | .history/ 389 | 390 | # Windows Installer files from build outputs 391 | *.cab 392 | *.msi 393 | *.msix 394 | *.msm 395 | *.msp 396 | 397 | # JetBrains Rider 398 | *.sln.iml 399 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 vickyqu115 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.zh-CN.md: -------------------------------------------------------------------------------- 1 | # RiotSlider [![English](https://img.shields.io/badge/Language-English-blue.svg)](README.md) [![中文](https://img.shields.io/badge/Language-中文-red.svg)](README.zh-CN.md) [![한국어](https://img.shields.io/badge/Language-한국어-red.svg)](README.ko.md) 2 | 3 | 受英雄联盟启发的WPF CustomControl Slider实现,展示了高级WPF技术和控件自定义 4 | 5 | [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT) 6 | [![.NET](https://img.shields.io/badge/.NET-8.0-blue.svg)](https://dotnet.microsoft.com/download) 7 | [![Stars](https://img.shields.io/github/stars/vickyqu115/riotslider.svg)](https://github.com/vickyqu115/riotslider/stargazers) 8 | [![Issues](https://img.shields.io/github/issues/vickyqu115/riotslider.svg)](https://github.com/vickyqu115/riotslider/issues) 9 | 10 | ## 项目概述 11 | 12 | RiotSlider是一个展示WPF控件开发高级技术的自定义WPF Slider控件。它将标准WPF Slider重新实现为CustomControl,利用原始WPF开源实现进行PART_重用,并展示了WPF控件的设计模式。 13 | 14 | 15 | 16 | 17 | 18 | 19 | ## 主要特性和实现 20 | #### 1. 高级CustomControl开发 21 | - [x] 继承WPF Slider控件以实现特殊功能 22 | - [x] 将Slider重新实现为CustomControl 23 | - [x] 利用WPF GitHub开源仓库作为参考 24 | 25 | #### 2. PART_控件重用 26 | - [x] 战略性重用PART_Track和PART_SelectionRange 27 | - [x] 分析和应用WPF控件设计模式 28 | 29 | #### 3. Primitives控件集成 30 | - [x] 有效使用Primitives命名空间中的Thumb和Track控件 31 | - [x] 原始控件的自定义样式和模板 32 | 33 | #### 4. 高级XAML技术 34 | - [x] 使用纯XAML实现复杂的UI元素 35 | - [x] 利用Geometry实现基于矢量的图形 36 | 37 | #### 5. 性能和设计优化 38 | - [x] 使用WPF的布局和绘图技术进行高效渲染 39 | - [x] 受英雄联盟启发的视觉设计 40 | 41 | ## 技术深入探讨 42 | - **CustomControl架构**:展示了WPF中CustomControl的强大功能,允许完全控制行为和外观。 43 | - **PART_命名约定**:为关键元素使用PART_命名约定,确保与基本Slider功能的兼容性。 44 | - **Primitives集成**:展示了如何使用Thumb和Track等低级WPF控件来构建复杂的UI元素。 45 | - **开源分析**:利用WPF开源仓库来理解和实现高级控件功能。 46 | - **WPF中的矢量图形**:利用Geometry和Path创建可缩放的高质量视觉元素。 47 | 48 | ## 技术栈 49 | - WPF (Windows Presentation Foundation) 50 | - .NET 8.0 51 | - C# 10.0 52 | - XAML 53 | 54 | ## 入门指南 55 | ### 前提条件 56 | - Visual Studio 2022或更高版本 57 | - .NET 8.0 SDK 58 | 59 | ### 安装和执行 60 | #### 1. 克隆仓库: 61 | 62 | ``` 63 | git clone https://github.com/vickyqu115/riotslider.git 64 | ``` 65 | 66 | #### 2. 打开解决方案 67 | - [x] Visual Studio 68 | - [x] Visual Studio Code 69 | - [x] JetBrains Rider 70 | 71 | 72 | 73 | 74 | 75 | #### 3. 构建和运行 76 | - [x] 设置启动项目 77 | - [x] 按F5或点击运行按钮 78 | - [x] 推荐使用Windows 11 79 | 80 | ## 学习资源 81 | - [详细实现文章 (jamesnet.dev)](https://jamesnet.dev/article/111) 82 | - [YouTube教程 (英文)](https://bit.ly/4dpsr3m) 83 | - [BiliBili教程 (中文)](https://bit.ly/3QiZvkJ) 84 | - [CodeProject](https://bit.ly/3JyibsM) 85 | 86 | ## 贡献 87 | 欢迎为RiotSlider做出贡献!随时提交问题、创建拉取请求或提出改进建议。 88 | 89 | ## 许可证 90 | 该项目基于MIT许可证分发。有关详细信息,请参阅[LICENSE](LICENSE)文件。 91 | 92 | ## 联系方式 93 | - 网站:https://jamesnet.dev 94 | - 电子邮件:vickyqu115@hotmail.com, james@jamesnet.dev 95 | 96 | 通过RiotSlider探索高级WPF控件开发技术! 97 | 98 | ----- 99 | 100 | ## Analyzing and Customizing the Detailed Mechanisms of WPF Slider Control 101 | 102 | In WPF, basic controls such as Buttons and ToggleButtons are structurally and logically simple, designed to be fully implemented with XAML without needing code-behind. In contrast, more complex controls like TextBoxes, ComboBoxes, and Sliders require intricate C# code alongside XAML for their functionalities. 103 | 104 | Understanding and applying the intricate configurations of WPF controls can lead to more elegant and flexible CustomControl designs and developments. Being adept with these fundamental components allows for addressing gaps in the MVVM development pattern, leading towards the creation of high-quality WPF applications. 105 | 106 | This exploration into the WPF Slider control aims to provide a deep understanding of how WPF designs its controls and their internal mechanisms. While it's nearly impossible to delve into every WPF control's internals due to the vast source code, there's no urgent need to worry or complain. 107 | 108 | The entire source code of WPF is openly available and managed on GitHub. This accessibility means that specific controls can be found and analyzed as needed without any rush. Despite the potential for exhaustion, there's no need for complaints. 109 | 110 | Beyond the Slider control, there are plans to dissect and analyze even more complex and varied controls. Support, interest, and backing for future tutorials provided via our GitHub repository, CodeProject, and tutorial videos on YouTube and BiliBili are greatly appreciated. 111 | 112 | 113 | ![20240201163433798](https://github.com/vickyqu115/riotslider/assets/101777355/f30773e5-9ad3-4b19-b104-5c026af2577e) 114 | 115 | ## Contents 116 | 117 | 1. WPF Tutorial Series 118 | 2. Specification 119 | 3. Creating an Application Project 120 | 4. Analyzing the Main Features of Slider 121 | 5. Extracting the Original Style Process 122 | 6. Analysis of Extracted Source Code 123 | 7. Checking Code Behind (GitHub Open Source) 124 | 8. OnApplyTemplate in Cross-Platform 125 | 9. Concluding the Slider Analysis 126 | 10. Creating a Riot-Style Slider (CustomControl) Control 127 | 11. Project Creation and Preparation for Start 128 | 12. TextBlock (Hi Slider) 129 | 13. Adding References and Testing Execution 130 | 14. Setting the Size of Riot Slider 131 | 15. PART_Track 132 | 16. Adding the Slider Bar 133 | 17. Aligning the Gap Between Slider Bar and Track 134 | 18. PART_SelectionRange 135 | 19. Adding Riot-Style Design Elements 136 | 20. Implementing a Riot-Style Thumb 137 | 21. Declaring Thumb Resources 138 | 22. Completing the RiotSlider Template (Finishing Touches) 139 | 23. Final Remarks 140 | 141 | 142 | 143 | ## Specifications: 144 | 145 | This project is based on .NET Core but is designated for Windows only due to the use of WPF. It is executable through VS2022, which is mandatory for running NET 8.0. Alternatively, JetBrains' Rider can also be used. 146 | review 147 | - [x] OS: Microsoft Windows 11 148 | - [x] IDE: Microsoft Visual Studio 2022 149 | - [x] Version: C# / NET 8.0 / WPF / windows target only 150 | - [x] NuGet: Jamesnet.Wpf 151 | 152 | Using the latest version of Windows as your operating system is recommended. However, if you are considering platform expansion to Avalonia UI, Uno Platform, MAUI, etc., it's also worth considering MacOS as a sub-device. We are using Thinkpad/Macbooks as well. Note that Visual Studio is not available on MacOS or Linux-based systems, so Rider is the only alternative. ~~vscode~~ 153 | 154 | ## 3. Creating an Application Project 155 | 156 | To get started, you first need to create a WPF Application project. 157 | 158 | - [x] Project Type:WPF Application 159 | - [x] Project Name: DemoApp 160 | - [x] Project Version: .NET 8.0 161 | 162 | ## 4. Analyzing the Main Features of Slider 163 | 164 | The WPF Slider control, unlike simpler controls such as Button, has a variety of properties. These properties play crucial functional roles in the control, and some operate in unique ways, making them particularly worthy of attention. 165 | 166 | **Orientation:** 167 | 168 | Controls in WPF often have a versatile nature, and the Orientation property of the Slider control is a prime example. This property allows for specifying the direction as either horizontal or vertical. 169 | 170 | The Orientation property can also be found in the StackPanel control. While the default value of Orientation in StackPanel is Vertical, the default for Slider's Orientation is Horizontal. Thus, it is common to use the Slider in a Horizontal format, which might be why the Orientation feature is not widely known. 171 | 172 | Let's take a closer look at a simplified part of the Slider to better understand Orientation: 173 | 174 | ```xaml 175 | 183 | ``` 184 | You can see that the (ControlTemplate) template switches based on the Orientation property in the trigger. Thus, a closer look at the actual configuration of this control can easily illustrate the significant role of the Orientation property. 185 | 186 | > It's an interesting part. Could you have imagined or applied the concept of switching templates through Orientation before seeing the original source? Open source can inspire in such ways. And let's note that the optimal timing for switching templates is indeed through the "Style.Trigger". 187 | 188 | For this tutorial video, we will only implement the Horizontal direction, so we will not perform any branch switching through Orientation. However, you are encouraged to try creating a Vertical version and submit a Pull Request via Fork. Consider it a mission. 189 | 190 | Let's also take a look at how the Horizontal/Vertical properties are applied: 191 | 192 | - [x] Orientation: **Horizontal** 193 | 194 | 195 | 196 | > The SelectionRange (blue) area that will be discussed below is also visible. 197 | 198 | - [x] Orientation: **Vertical** 199 | 200 | 201 | 202 | > Similarly, you will find quite a few controls that switch the (ControlTemplate) template itself in a similar manner (e.g., ScrollViewer). 203 | 204 | ##### **Minimum, Maximum, and Value:** 205 | 206 | These are double type properties that represent the minimum range, maximum range, and value, respectively. Internally, the control's size and ratio calculate the position of the Range and Value automatically based on these values. 207 | 208 | Since all these properties are DependencyProperty, dynamic interactions through binding are possible. For example, in an MVVM structure, leveraging these three values allows for dynamic changes to the Range according to specific scenarios or enables interesting implementations through various applications. 209 | 210 | ##### SelectionStart, SelectionEnd, and IsSelectionRangeEnabled: 211 | 212 | These two properties (SelectionStart/SelectionEnd) serve to set a specific area. In reality, this area doesn't include any special functionality; it's merely for designating a segment and visually highlighting it. IsSelectionRangeEnabled is a property that indicates whether this area is active, and depending on its activation status, the area's Visibility property value switches through a trigger (Visible/Collapsed). 213 | 214 | Upon examination, these features might seem merely for area marking, leading to questions about their necessity. However, given their versatile use across designs and fields, understanding and anticipating their necessity is possible. ~~Respecting style preferences from 20 years ago~~ 215 | 216 | Interestingly, applying these with the Value can produce a fascinating effect as shown below: 217 | 218 | ```xaml 219 | 226 | ``` 227 | 228 | Surprisingly, linking the Value to SelectionEnd through Binding allows for a dynamic change in the Selection (Range) as the value changes. Was this intended by the WPF developers? It's impressive, and the clean implementation method is quite satisfying. 229 | 230 | > This will play a crucial role in the implementation of the Riot-style Slider (CustomControl) discussed later in the article, so keep it in mind. 231 | 232 | ## 5. Extracting the Original Style Process 233 | 234 | As mentioned earlier, since WPF is managed as open-source through the GitHub repository, it's possible to examine the source code of all controls. However, given that the repository contains solutions, all projects, and files, extracting content for a specific control part is a task close to impossible. 235 | 236 | Fortunately, Visual Studio provides a GUI feature for extracting the default style (Template) of a specific control. Thus, without the need to sift through open-source, you can easily and simply extract the relevant code. 237 | 238 | > It's okay to think of this similar to Identity scaffolding in Blazor. (Though the nature is slightly different, it helps in understanding) 239 | 240 | Moreover, extracting the original style through Visual Studio links you to an actual modifiable resource form, allowing for immediate customization of design and functionality. Therefore, since the original style and template extraction is possible not only for Slider but for all controls, this is a highly valuable element in WPF research/learning. 241 | 242 | > If you look at commercial components like Infragistics, Syncfusion, ArticPro, not all provide this extraction feature. Each company has its disclosure scope and policy, and most prefer to modularize via DataTemplate for customization rather than exposing the ControlTemplate. It's interesting to take a look at the components you are using. 243 | 244 | ##### Extraction Method and Procedure: Visual Studio 245 | 246 | - [x] Extracting the default control (Slider) style (Edit a Copy...) 247 | - [x] Extract to the current file (This document) 248 | - [x] Extract to the App.xaml file (Application) 249 | - [x] Create a new ResourceDictionary file for extraction (Resource Dictionary) 250 | 251 | Note, the extraction process can only proceed in the design area of a Partial UserControl, by selecting the control and right-clicking to proceed. This step involves choosing the "specify style name/define copy location of the extracted style" option. 252 | 253 | > Try looking up the method in VScode or Rider, do they offer it? 254 | 255 | Let's take a closer look at the process. 256 | 257 | - [x] Style extraction command: Slider > Right click > Edit Template > Edit a Copy... 258 | 259 | image 260 | 261 | > If no extractable style is provided, this item will not be activated. 262 | 263 | - [x] Style Extraction Options Window: Create ControlTemplate Resource (Window) 264 | 265 | 266 | 267 | 268 | > Select Name (Key) and Define in options, 269 | 270 | Typically, specifying a Name is the right choice for testing and management perspectives. If you choose "Apply to all" without specifying a name, the style created based on the Define location will be applied globally. Therefore, understand this point well and proceed with the extraction carefully. 271 | 272 | In the video, the name is set, and the Define location is specified as Application. Thus, the extracted resource is included in the Resources area of the App.xaml file (if the file exists). 273 | 274 | Personally, when performing such extraction work, it's recommended to proceed in a test nature in a new project. Actually conducting this process in a live project may result in minor mistakes and problems, so it's a good choice also from the perspective of preventing such side effects. 275 | 276 | ## 6. Analysis of Extracted Source Code 277 | 278 | As demonstrated in the tutorial video, the Slider control style has been successfully extracted. Let's take a look at the related resources within the App.xaml file and examine the elements that are important to note one by one. 279 | 280 | ##### Checking Orientation Branch: 281 | 282 | As briefly mentioned when explaining the Orientation property earlier, it's time to check the actual source code implemented. 283 | 284 | The style below is the original WPF default style containing the extracted SliderStyle1 template. (It works without errors upon immediate application.) 285 | 286 | ```xaml 287 | 299 | ``` 300 | From this, we can see that the default Template is set to the SliderHorizontal (ControlTemplate) template, and through a trigger, it switches to the SliderVertical (ControlTemplate) template when the Orientation property value is Vertical. 301 | 302 | > By modularizing the (ControlTemplate) template like this, you gain the advantage of being able to see the actual style at a glance, which is a management structure worth trying even in non-switching situations. I do it often. You can also get inspiration from these aspects. 303 | 304 | Thus, the Slider control's functionalities are essentially implemented within both the SliderHorizontal and SliderVertical (ControlTemplate) areas. 305 | 306 | Let's now check the default SliderHorizontal (ControlTemplate) template. 307 | 308 | ##### Checking ControlTemplate: 309 | Let's examine each of the Horizontal/Vertical specific templates, which can be found continuously within the App.xaml file. 310 | 311 | - [x] Check Horizontal specific template 312 | - [x] Check Vertical specific template 313 | 314 | ControlTemplate: **SliderHorizontal** 315 | 316 | ```xaml 317 | 318 | 319 | ... 320 | 321 | 322 | ... 323 | 324 | 325 | ``` 326 | 327 | ControlTemplate: **SliderVertical** 328 | 329 | ```xaml 330 | 331 | 332 | ... 333 | 334 | 335 | ... 336 | 337 | 338 | ``` 339 | 340 | 341 | As seen, both the Horizontal/Vertical source codes are branched and implemented separately. Therefore, the implemented content is the same for both, differing only in design orientation. 342 | 343 | Let's verify this precisely. The common elements included are as follows: 344 | 345 | - [ ] Name: TopTick 346 | - [ ] Name: BottomTick 347 | - [ ] Name: TrackBackground 348 | - [ ] **Name: PART_SelectionRange** 349 | - [ ] **Name: PART_Track** 350 | - [ ] Name: Thumb 351 | - [ ] Trigger: TickPlacement 352 | - [ ] Trigger: IsSelectionRangeEnabled 353 | - [ ] Trigger: IsKeyboardFocused 354 | 355 | 356 | We can see that the common elements are included in both ControlTemplates, confirming that both have the same composition. Now, let's focus on and examine only the SliderHorizontal part. 357 | 358 | 359 | ##### Naming rule: `PART_` 360 | 361 | In the structure of (CustomControl) controls, maintaining a tight connection between XAML and Code-behind is crucial. However, connecting them through the GetTemplateChild method to find control names can be visually unappealing. To mitigate this development approach and manage it systematically, the `PART_` naming rule is used. 362 | 363 | This rule prefixes all control names found through GetTemplateChild with `PART_`, allowing you to guess the function in XAML. Thus, when analyzing (ControlTemplate) controls, discovering a control named starting with `PART_` suggests it's likely an essential element, and you can anticipate the side effects that might occur if it's removed. 364 | 365 | Ultimately, this is immensely helpful in implementing CustomControls. Moreover, this rule is common not only in WPF but also in other cross-platforms sharing XAML, emphasizing its importance. 366 | 367 | _Slider contains two `PART_` controls._ 368 | 369 | - [x] PART_Track 370 | - [x] PART_SelectionRange 371 | 372 | Consequently, aside from these two `PART_` controls, the rest are not used in Code-behind, ensured by this naming rule. Therefore, adhering strictly to this rule in CustomControl development is crucial. 373 | 374 | ##### Test: Check the impact after intentionally changing the name of PART_Track 375 | 376 | Let's intentionally change the name of the `PART_Track` control. 377 | 378 | ```xaml 379 | 380 | ... 381 | 382 | ``` 383 | 384 | 385 | > Ensure you're in the correct Sliderhorizontal area. 386 | 387 | Now, when you run the application, dragging the Track's Thumb will no longer move it left or right, as seen in the tutorial video. The reason the Thumb no longer moves is that the intentional name change prevents Code-behind from finding the PART_Track control through GetTemplateChild. 388 | 389 | Since the PART_Track control cannot be found, there's no target for the mouse drag to move. Reverting the name to PART_Track1 will restore functionality. 390 | 391 | > This phenomenon can be observed in many other standard controls, notably the TextBox’s PART_ContentHost. 392 | 393 | ##### Test: Check the impact after intentionally changing the name of PART_SelectionRange 394 | Next, let's intentionally change the name of the PART_SelectionRange control. 395 | 396 | ```xaml 397 | 398 | ``` 399 | > Ensure you're in the correct Sliderhorizontal area (x2). 400 | 401 | And if you look at the trigger section, there are more parts using PART_SelectionRange, so this part should be changed as well. 402 | 403 | 404 | ```xaml 405 | 406 | 407 | 408 | ``` 409 | > Ensure you're in the correct Sliderhorizontal area (x3). 410 | 411 | Also, in Slider, ensure all properties are set to activate the PART_SelectionRange. 412 | 413 | ```xaml 414 | 418 | ``` 419 | 420 | > You need to set Minimum/Maximum, SelectionStart/SelectionEnd, and IsSelectionRange to activate the Range area. 421 | 422 | - [x] Before name change: PART_SelectionRange 423 | 424 | 425 | 426 | > Before the change, you can see the Range area appearing normally. 427 | 428 | - [x] After name change: PART_SelectionRange1 429 | 430 | 431 | 432 | 433 | > Now, the Range area no longer appears. 434 | 435 | Similarly, because the PART_SelectionRange control cannot be internally found, there's no target for calculating the Range area. 436 | 437 | Thus, WPF controls are implemented more loosely than expected while forming a modular structure. Taking advantage of these characteristics allows for efficient use of already implemented functionalities or excluding unnecessary ones. 438 | 439 | 440 | ## 7. Checking Code Behind (GitHub Open Source) 441 | 442 | After a detailed look at the `PART_` control naming rule and its impact, it's time to explore how these controls are utilized in actual classes. 443 | 444 | The Code behind (class) area cannot be further examined through extraction. Therefore, it's necessary to review the Official source code through the WPF repository. For a more detailed examination, watching tutorial videos is recommended. 445 | 446 | In the actual source code, the names of each `PART_` control are agreed upon as strings like below: 447 | 448 | ```csharp 449 | private const string TrackName = "PART_Track"; 450 | private const string SelectionRangeElementName = "PART_SelectionRange"; 451 | ``` 452 | 453 | > The names are defined fixedly, emphasizing the importance of adhering to this naming rule. 454 | 455 | ##### WPF: OnApplyTemplate 456 | Let's examine the part where Track and SelectionRange are retrieved from the (ControlTemplate) template. 457 | 458 | ```csharp 459 | public override void OnApplyTemplate() 460 | { 461 | base.OnApplyTemplate(); 462 | 463 | SelectionRangeElement = GetTemplateChild(SelectionRangeElementName) as FrameworkElement; 464 | Track = GetTemplateChild(TrackName) as Track; 465 | 466 | if (_autoToolTip != null) 467 | { 468 | _autoToolTip.PlacementTarget = Track != null ? Track.Thumb : null; 469 | } 470 | } 471 | ``` 472 | 473 | > The (Override) OnApplyTemplate method is called after the class and style are connected, making it the optimal time to use GetTemplateChild. 474 | 475 | Upon reviewing the original source code, they are defined as FrameworkElement and Track, respectively. 476 | 477 | - [x] PART_SelectionRange: SelectionRangeElement (FrameworkElement) 478 | - [x] PART_Track: TrackName (Track) 479 | 480 | It's noteworthy that while Track is the same type as in XAML, SelectionRange is defined as a FrameworkElement, different from the original Rectangle. This implies that the Range area can use any control, not just a Rectangle, indicating the type definition is intentionally flexible. 481 | 482 | Therefore, it's reasonable to assume that (defined as a FrameworkElement type) SelectionRangeElement will handle only the basic functionalities available to this type. 483 | 484 | Next, let's look at how the SelectionRangeElement is managed. 485 | 486 | 487 | ```csharp 488 | private void UpdateSelectionRangeElementPositionAndSize() 489 | { 490 | Size trackSize = new Size(0d, 0d); 491 | Size thumbSize = new Size(0d, 0d); 492 | 493 | if (Track == null || DoubleUtil.LessThan(SelectionEnd,SelectionStart)) 494 | { 495 | return; 496 | } 497 | 498 | trackSize = Track.RenderSize; 499 | thumbSize = (Track.Thumb != null) ? Track.Thumb.RenderSize : new Size(0d, 0d); 500 | 501 | double range = Maximum - Minimum; 502 | double valueToSize; 503 | 504 | FrameworkElement rangeElement = this.SelectionRangeElement as FrameworkElement; 505 | 506 | if (rangeElement == null) 507 | { 508 | return; 509 | } 510 | 511 | if (Orientation == Orientation.Horizontal) 512 | { 513 | // Calculate part size for HorizontalSlider 514 | if (DoubleUtil.AreClose(range, 0d) || (DoubleUtil.AreClose(trackSize.Width, thumbSize.Width))) 515 | { 516 | valueToSize = 0d; 517 | } 518 | else 519 | { 520 | valueToSize = Math.Max(0.0, (trackSize.Width - thumbSize.Width) / range); 521 | } 522 | 523 | rangeElement.Width = ((SelectionEnd - SelectionStart) * valueToSize); 524 | if (IsDirectionReversed) 525 | { 526 | Canvas.SetLeft(rangeElement, (thumbSize.Width * 0.5) + Math.Max(Maximum - SelectionEnd, 0) * valueToSize); 527 | } 528 | else 529 | { 530 | Canvas.SetLeft(rangeElement, (thumbSize.Width * 0.5) + Math.Max(SelectionStart - Minimum, 0) * valueToSize); 531 | } 532 | } 533 | else 534 | { 535 | // Calculate part size for VerticalSlider 536 | if (DoubleUtil.AreClose(range, 0d) || (DoubleUtil.AreClose(trackSize.Height, thumbSize.Height))) 537 | { 538 | valueToSize = 0d; 539 | } 540 | else 541 | { 542 | valueToSize = Math.Max(0.0, (trackSize.Height - thumbSize.Height) / range); 543 | } 544 | 545 | rangeElement.Height = ((SelectionEnd - SelectionStart) * valueToSize); 546 | if (IsDirectionReversed) 547 | { 548 | Canvas.SetTop(rangeElement, (thumbSize.Height * 0.5) + Math.Max(SelectionStart - Minimum, 0) * valueToSize); 549 | } 550 | else 551 | { 552 | Canvas.SetTop(rangeElement, (thumbSize.Height * 0.5) + Math.Max(Maximum - SelectionEnd,0) * valueToSize); 553 | } 554 | } 555 | } 556 | ``` 557 | 558 | > The logic for branching Orientation (Horizontal/Vertical) is essentially the same, so we only need to examine it based on Horizontal. 559 | 560 | The (UpdateSelectionRangeElementPositionAndSize) method determines the size and position of the SelectionRange. Although the amount of source code might seem daunting, considering the duplicated source code for branching Orientation, it's easy to see that the handling of the SelectionRange is done succinctly. 561 | 562 | This way, by extracting (CustomControl) controls and examining how `PART_` controls are internally processed, it's possible to reverse-engineer and analyze them. 563 | 564 | ## 8. OnApplyTemplate in Cross-Platform 565 | 566 | Cross-platforms, which retain many aspects of WPF's design, follow a similar flow. Let's take a look at how OnApplyTemplate is utilized in other platforms, based on our analysis. 567 | 568 | List of platforms sharing the OnApplyTemplate design: 569 | 570 | - [x] **AvaloniaUI** 571 | - [x] **Uno Platform** 572 | - [x] **OpenSilver** 573 | - [x] **MAUI** 574 | - [x] **Xamarin** 575 | - [ ] UWP 576 | - [ ] WinUI 3 577 | - [ ] Silverlight 578 | 579 | Among these, let's examine the actual source code for AvaloniaUI, Uno Platform, OpenSilver, MAUI, and Xamarin, which are checked. 580 | 581 | > Note that except for Silverlight, all are managed through GitHub's official Dotnet or Xamarin Microsoft Organization, making it easy to find the repositories on GitHub. 582 | 583 | ##### AvaloniaUI: OnApplyTemplate 584 | 585 | Below is a part of the Slider control's OnApplyTemplate in AvaloniaUI: 586 | 587 | ```csharp 588 | protected override void OnApplyTemplate(TemplateAppliedEventArgs e) 589 | { 590 | ... 591 | base.OnApplyTemplate(e); 592 | _decreaseButton = e.NameScope.Find