├── .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 [](README.md) [](README.zh-CN.md) [](README.ko.md)
2 |
3 | 受英雄联盟启发的WPF CustomControl Slider实现,展示了高级WPF技术和控件自定义
4 |
5 | [](https://opensource.org/licenses/MIT)
6 | [](https://dotnet.microsoft.com/download)
7 | [](https://github.com/vickyqu115/riotslider/stargazers)
8 | [](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 | 
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 |
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 |
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