├── .github └── FUNDING.yml ├── .gitignore ├── LICENSE ├── README.ko.md ├── README.md ├── README.zh-CN.md ├── images ├── result.png └── solution.png └── src ├── DemoApp.sln ├── DemoApp ├── App.cs ├── DemoApp.csproj ├── Properties │ └── AssemblyInfo.cs ├── Starter.cs ├── Themes │ └── Generic.xaml └── UI │ └── Views │ └── VickyWindow.cs └── SmartDateControl ├── Properties └── AssemblyInfo.cs ├── SmartDateControl.csproj ├── Themes ├── Generic.xaml └── Units │ ├── CalendarBox.xaml │ ├── CalendarBoxItem.xaml │ ├── CalendarSwitch.xaml │ ├── ChevronButton.xaml │ ├── DayOfWeek.xaml │ └── SmartDate.xaml └── UI └── Units ├── CalendarBox.cs ├── CalendarBoxItem.cs ├── CalendarSwitch.cs ├── ChevronButton.cs ├── DayOfWeek.cs └── SmartDate.cs /.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.ko.md: -------------------------------------------------------------------------------- 1 | # SmartDate [![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용 현대적이고 사용자 정의 가능한 DatePicker 컨트롤 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/smartdate.svg)](https://github.com/vickyqu115/smartdate/stargazers) 8 | [![Issues](https://img.shields.io/github/issues/vickyqu115/smartdate.svg)](https://github.com/vickyqu115/smartdate/issues) 9 | 10 | ## 프로젝트 개요 11 | 12 | SmartDate는 전통적인 DatePicker를 새롭게 구상한 사용자 정의 WPF 컨트롤입니다. 기본 제공되는 DatePicker를 상속받는 대신 Control 클래스부터 처음부터 구축하여, 현대적이고 유연하며 쉽게 사용자 정의할 수 있는 대안을 제공합니다. 이 프로젝트는 고급 WPF 기술과 컨트롤 개발 사례를 보여줍니다. 13 | 14 | 15 | 16 | 17 | ## 주요 기능 및 구현 사항 18 | #### 1. 기초부터 시작한 사용자 정의 컨트롤 개발 19 | - [x] 표준 DatePicker의 복잡성을 우회하여 Control 클래스 기반으로 구축 20 | - [x] 날짜 선택 기능의 효율적인 구현 21 | - [x] 현재 UI/UX 트렌드에 맞춘 현대적인 디자인 22 | 23 | #### 2. 고급 WPF 기술 24 | - [x] 중요 요소에 PART_ 명명 규칙 사용 25 | - [x] 달력 레이아웃을 위한 UniformGrid가 적용된 사용자 정의 ListBox (CalendarBox) 26 | - [x] 드롭다운 달력 표시를 위한 Popup 컨트롤 27 | 28 | #### 3. Primitives 및 사용자 정의 컨트롤 통합 29 | - [x] 달력 활성화를 위한 사용자 정의 ToggleButton (CalendarSwitch) 30 | - [x] 월 이동을 위한 ChevronButton 31 | - [x] 요일 표시를 위한 DayOfWeek 컨트롤 32 | 33 | #### 4. 정교한 날짜 처리 34 | - [x] 효율적인 달력 생성 알고리즘 35 | - [x] 원활한 월 이동 및 날짜 선택 36 | 37 | #### 5. MVVM 친화적 설계 38 | - [x] 쉬운 데이터 바인딩을 위한 DependencyProperties (SelectedDate, CurrentMonth) 39 | - [x] 사용자 상호작용을 위한 이벤트 기반 아키텍처 40 | 41 | ## 기술 심층 분석 42 | - **사용자 정의 컨트롤 아키텍처**: DatePicker 상속의 복잡성을 피하고 Control 클래스에서 복잡한 컨트롤을 구축하는 방법을 보여줍니다. 43 | - **PART_ 컨트롤 상호작용**: WPF의 핵심 패턴인 코드 비하인드 상호작용을 위한 PART_ 명명 요소 사용을 보여줍니다. 44 | - **ListBox 사용자 정의**: UniformGrid를 사용한 수정된 ListBox로 사용자 정의 달력을 구현하여 고급 ItemsPanel 사용자 정의를 보여줍니다. 45 | - **Popup 관리**: 드롭다운 기능을 위한 Popup 컨트롤의 효율적인 처리를 보여줍니다. 46 | - **날짜 로직**: 정교한 날짜 계산 및 달력 생성 알고리즘을 구현합니다. 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/smartdate.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] SmartDateApp을 시작 프로젝트로 설정 77 | - [x] F5를 누르거나 실행 버튼 클릭 78 | - [x] Windows 11 권장 79 | 80 | ## 학습 자료 81 | - [구현에 대한 상세 아티클 (jamesnet.dev)](https://jamesnet.dev/article/43) 82 | - [YouTube 튜토리얼 (영어)](https://bit.ly/4c8uGr3) 83 | - [BiliBili 튜토리얼 (중국어)](https://bit.ly/3xOeyMJ) 84 | - [CodeProject 아티클](https://bit.ly/4du4hVD) 85 | 86 | ## 기여하기 87 | SmartDate에 대한 기여를 환영합니다! 이슈를 제출하거나, 풀 리퀘스트를 생성하거나, 개선 사항을 제안해 주세요. 88 | 89 | ## 라이선스 90 | 이 프로젝트는 MIT 라이선스 하에 배포됩니다. 자세한 내용은 [LICENSE](LICENSE) 파일을 참조하세요. 91 | 92 | ## 연락처 93 | - 웹사이트: https://jamesnet.dev 94 | - 이메일: vickyqu115@hotmail.com, james@jamesnet.dev 95 | 96 | SmartDate로 고급 WPF 컨트롤 개발 기술을 탐험해보세요! 97 | 98 | ---- 99 | 100 | ## Recognizing the Issues with the WPF DatePicker 101 | The WPF DatePicker is one of the core controls in WPF, with a history spanning nearly 20 years. Compared to simpler controls like Buttons, TextBoxes, or CheckBoxes, the DatePicker has a more complex structure and stages, composed of multiple controls. This complexity necessitates high expertise for customization, making it difficult to use or modify the provided outdated controls. 102 | 103 | ## Understanding the WPF DatePicker 104 | Analyzing and understanding the structure of the DatePicker and the interaction of its internal elements within the Template is extremely beneficial for enhancing fundamental design and analysis skills in WPF. This applies to all WPF controls, not just the DatePicker. However, since the DatePicker was designed according to outdated trends, it might be more efficient to implement a new CustomControl based on the basic Control. 105 | 106 | ## Source Code Download and Setup 107 | This article identifies issues with using the basic DatePicker and demonstrates how to redesign it using a CustomControl approach. It is also beneficial to download the source code via GitHub to check the results firsthand and read along with this article. 108 | 109 | First, download the source code using the following git command: 110 | ``` 111 | git clone https://github.com/vickyqu115/smartdate 112 | ``` 113 | 114 | Next, to run the solution file from the source code, you need an environment with Windows 10 or higher, Visual Studio 2022 or Rider, and .NET 8.0. 115 | 116 | _SmartDate.sln_ 117 | 118 | 119 | 120 | ## Project Structure 121 | SmartDate consists of two projects: 122 | - SmartDateControl 123 | - SmartDateApp 124 | 125 | SmartDateControl is a CustomControl Library that includes the SmartDate class along with all other subordinate CustomControl classes. SmartDateApp is a simple application project that guides users on how to use this control. 126 | 127 | ## Declaring and Using SmartDate 128 | The usage is straightforward. Declare the namespace with xmlns and use SmartDate just like the standard DatePicker. 129 | 130 | ```xml 131 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | ``` 149 | 150 | SelectedDate is a DependencyProperty and uses the same DateTime? type as DatePicker’s SelectedDate. 151 | 152 | _Execution Results_ 153 | 154 | 155 | 156 | ## Definition and Utilization of CustomControl 157 | I have previously discussed the technology behind CustomControls in detail through four articles on CodeProject. If you need to understand and master CustomControls, please refer to these articles. Particularly, the article on RiotSlider delves deeply into the architecture of WPF CustomControls, so if you haven’t read it yet, I strongly recommend doing so. 158 | 159 | Returning to the main discussion, let’s define CustomControl. Typically, a CustomControl targets classes derived from Control, but in reality, it includes all classes derived from DependencyObject, not just those inheriting Control, such as Panels, up to Visuals like Animations. However, as mentioned earlier, it only makes sense to implement CustomControls in layers that can utilize Templates, or at least DataContext. Therefore, implementing classes derived from FrameworkElement in the CustomControl style is seen as wise. 160 | 161 | ## Designing a New DatePicker: SmartDate 162 | This article will detail how to implement a new CustomControl called SmartDate, derived from the most basic class, Control, without using the existing DatePicker. 163 | 164 | ## Choosing Control Over ContentControl 165 | First, let’s examine the differences between ContentControl and Control. ContentControl offers not just the basic Template but also properties for Content and ContentTemplate. These properties are automatically linked through the ContentPresenter, setting up the relationship between ContentPresenter, Content, and ContentTemplate automatically. Consequently, choosing a derived control based on the basic usage of DataTemplate is advisable. 166 | 167 | Is DatePicker fundamentally a control that utilizes DataTemplate? While opinions may vary, a complex control like DatePicker typically requires multiple DataTemplates and does not resemble a standard ContentControl. Indeed, DatePicker is derived from Control, and similar types of controls usually inherit from Control. For example, ComboBox might look similar to DatePicker but is an ItemsControl with an ItemsSource property. 168 | 169 | Therefore, it is appropriate to base the implementation of SmartDate on Control, especially since SmartDate does not provide its own DataTemplate. 170 | 171 | ## Utilizing DataTemplate 172 | Though SmartDate does not provide a DataTemplate by default, there are many points within various areas of the control where extending through DataTemplate could be beneficial. 173 | 174 | For example, you can extend the ContentPresenter of the DayOfWeek control to add specific date processing, a common requirement among clients. This allows for various extensions such as triggers or converters for special dates. 175 | 176 | By extending the SelectedDate binding area to a ContentPresenter, you can flexibly use it for selecting dates, incorporating formats ranging from a simple TextBlock to an editable TextBox or even including time. 177 | 178 | ## Negative Views on DataTemplate 179 | DataTemplate fundamentally maintains versatility even in complex situations and is an essential template area for customization. However, whether to apply this versatility to specific controls like date pickers should be carefully considered. Using a DataTemplate means that all related logic must be separated into interactively implementable components. While this may seem practical, it is crucial to make sound judgments. 180 | 181 | ## Key Binding Properties of SmartDate (DependencyProperty) 182 | This control includes a binding property named SelectedDate of type DateTime?. Since the default value might be null, it is declared as a nullable type, used for setting the date value selected through the calendar. 183 | 184 | ## SmartDate Template Design 185 | 186 | The essential components that must be included in the ControlTemplate design are as follows: 187 | 188 | - Popup 189 | - ListBox 190 | - ToggleButton 191 | 192 | The Popup acts as a panel to contain the ListBox, which is the calendar, and the ListBox uses an internal ItemsPanel to implement the calendar with a UniformGrid. The ToggleButton is used as the calendar icon, and toggling the button changes the IsOpen property of the Popup to control the calendar window. This setup is similar in the basic DatePicker control as well, making it very beneficial to compare it with the actual open-source code of DatePicker. 193 | 194 | Let’s now examine how the SmartDate control is structured in its Template. 195 | 196 | _SmartDate: ControlTemplate_ 197 | 198 | ```xml 199 | 200 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | 222 | 223 | 224 | 225 | 226 | 227 | 228 | 229 | ``` 230 | 231 | As you can see in the ControlTemplate, all previously mentioned components are included. The Popup is used as a basic control, and the CalendarSwitch is a calendar switching button that inherits from ToggleButton. Lastly, the CalendarBox, which inherits from ListBox, is used as the list control for selecting dates on the calendar. 232 | 233 | Moreover, other components included are buttons to navigate to the previous and next months, a TextBlock to display the current month, and design elements to display the days of the week. 234 | 235 | ## CustomControl Not Intended for Reuse but for Internal Use Only 236 | The SmartDate control is not only used by itself but also employs CustomControls within its Template. Not all CustomControls are intended for universal control implementation. In cases like SmartDate, they are implemented for specific purposes, which is a common practice from the perspective of WPF architecture. 237 | 238 | Such types of controls are often categorized under the namespace 'Primitives.' This category includes controls like ToggleButton, Thumb, and ScrollBar, which are typically used not directly but within the internals of other controls. 239 | 240 | Based on these architectural facts about WPF, it can be seen that the structure of the SmartDate control's Template does not significantly differ from the basic patterns of WPF. 241 | 242 | ## Understanding PART_ Control Items and Their Roles 243 | The CustomControl structure does not automatically connect code and XAML like UserControls do. Thus, all interactions between the two are exclusively managed by _PART controls. 244 | 245 | The predefined _PART controls include: 246 | - PART_Switch 247 | - PART_ListBox 248 | - PART_Left 249 | - PART_Right 250 | 251 | These are assigned during the override of the SmartDate class's OnApplyTemplate method, where all necessary processes such as button events and date generation are implemented. It's a good practice to name controls with the PART_ prefix when passed through OnApplyTemplate. Moreover, naming these elements in XAML in a way that allows developers to anticipate what processes occur within the class based on the PART_ name would be exemplary. 252 | 253 | ## SmartDate.cs Source Code 254 | Next, we will examine the core implementation contained within the SmartDate.cs class file. Key areas to focus on include: 255 | - Declared DependencyProperty 256 | - Definition of PART_ elements via OnApplyTemplate 257 | - Date selection control logic through the SelectedDate property 258 | - Utilization of SelectedItem/SelectedValue in CalendarBox 259 | 260 | _SmartDate: CustomControl_ 261 | 262 | ```csharp 263 | using System; 264 | using System.Collections.Generic; 265 | using System.Linq; 266 | using System.Text; 267 | using System.Threading.Tasks; 268 | using System.Windows; 269 | using System.Windows.Controls; 270 | using System.Windows.Controls.Primitives; 271 | using System.Windows.Data; 272 | using System.Windows.Documents; 273 | using System.Windows.Input; 274 | using System.Windows.Media; 275 | using System.Windows.Media.Imaging; 276 | using System.Windows.Navigation; 277 | using System.Windows.Shapes; 278 | 279 | namespace SmartDateControl.UI.Units 280 | { 281 | public class SmartDate : Control 282 | { 283 | private Popup _popup; 284 | private CalendarSwitch _switch; 285 | private CalendarBox _listbox; 286 | 287 | public bool KeepPopupOpen 288 | { 289 | get { return (bool)GetValue(KeepPopupOpenProperty); } 290 | set { SetValue(KeepPopupOpenProperty, value); } 291 | } 292 | 293 | public static readonly DependencyProperty KeepPopupOpenProperty = 294 | DependencyProperty.Register("KeepPopupOpen", typeof(bool), typeof(SmartDate), new PropertyMetadata(true)); 295 | 296 | 297 | 298 | public DateTime CurrentMonth 299 | { 300 | get { return (DateTime)GetValue(CurrentMonthProperty); } 301 | set { SetValue(CurrentMonthProperty, value); } 302 | } 303 | 304 | public static readonly DependencyProperty CurrentMonthProperty = 305 | DependencyProperty.Register("CurrentMonth", typeof(DateTime), typeof(SmartDate), new PropertyMetadata(null)); 306 | 307 | 308 | 309 | public DateTime? SelectedDate 310 | { 311 | get { return (DateTime?)GetValue(SelectedDateProperty); } 312 | set { SetValue(SelectedDateProperty, value); } 313 | } 314 | 315 | public static readonly DependencyProperty SelectedDateProperty = 316 | DependencyProperty.Register("SelectedDate", typeof(DateTime?), typeof(SmartDate), new PropertyMetadata(null)); 317 | 318 | 319 | 320 | static SmartDate() 321 | { 322 | DefaultStyleKeyProperty.OverrideMetadata(typeof(SmartDate), new FrameworkPropertyMetadata(typeof(SmartDate))); 323 | } 324 | public override void OnApplyTemplate() 325 | { 326 | base.OnApplyTemplate(); 327 | 328 | _popup = (Popup)GetTemplateChild("PART_Popup"); 329 | _switch = (CalendarSwitch)GetTemplateChild("PART_Switch"); 330 | _listbox = (CalendarBox)GetTemplateChild("PART_ListBox"); 331 | ChevronButton leftButton = (ChevronButton)GetTemplateChild("PART_Left"); 332 | ChevronButton rightButton = (ChevronButton)GetTemplateChild("PART_Right"); 333 | 334 | _popup.Closed += _popup_Closed; 335 | _switch.Click += _switch_Click; 336 | _listbox.MouseLeftButtonUp += _listbox_MouseLeftButtonUp; 337 | 338 | leftButton.Click += (s, e) => MoveMonthClick(-1); 339 | rightButton.Click += (s, e) => MoveMonthClick(1); 340 | 341 | } 342 | 343 | private void MoveMonthClick(int month) 344 | { 345 | GenerateCalendar(CurrentMonth.AddMonths(month)); 346 | } 347 | 348 | private void _popup_Closed(object sender, EventArgs e) 349 | { 350 | _switch.IsChecked = IsMouseOver; 351 | } 352 | 353 | private void _switch_Click(object sender, RoutedEventArgs e) 354 | { 355 | if (_switch.IsChecked == true) 356 | { 357 | _popup.IsOpen = true; 358 | 359 | GenerateCalendar(SelectedDate ?? DateTime.Now); 360 | } 361 | } 362 | 363 | private void _listbox_MouseLeftButtonUp(object sender, MouseButtonEventArgs e) 364 | { 365 | if (_listbox.SelectedItem is CalendarBoxItem selected) 366 | { 367 | SelectedDate = selected.Date; 368 | GenerateCalendar(selected.Date); 369 | 370 | _popup.IsOpen = KeepPopupOpen; 371 | } 372 | } 373 | private void GenerateCalendar(DateTime current) 374 | { 375 | if (current.ToString("yyyyMM") == CurrentMonth.ToString("yyyyMM")) return; 376 | 377 | CurrentMonth = current; 378 | _listbox.Items.Clear(); 379 | DateTime fDayOfMonth = new(current.Year,current.Month,1); 380 | DateTime lDayOfMonth = fDayOfMonth.AddMonths(1).AddDays(-1); 381 | 382 | int fOffset = (int)fDayOfMonth.DayOfWeek; 383 | int lOffset = 6 - (int)lDayOfMonth.DayOfWeek; 384 | 385 | DateTime fDay = fDayOfMonth.AddDays(-fOffset); 386 | DateTime lDay = lDayOfMonth.AddDays(lOffset); 387 | 388 | for (DateTime day = fDay; day <= lDay; day = day.AddDays(1)) 389 | { 390 | CalendarBoxItem boxItem = new(); 391 | boxItem.Date = day; 392 | boxItem.DateFormat = day.ToString("yyyyMMdd"); 393 | boxItem.Content = day.Day; 394 | boxItem.IsCurrentMonth = day.Month == current.Month; 395 | 396 | _listbox.Items.Add(boxItem); 397 | } 398 | if (SelectedDate != null) 399 | { 400 | _listbox.SelectedValue = SelectedDate.Value.ToString("yyyyMMdd"); 401 | } 402 | } 403 | } 404 | } 405 | ``` 406 | 407 | Firstly, the DependencyProperty is scrutinized, including essential properties like SelectedDate, which maintains the selected date. The KeepPopupOpen property determines whether to keep the window open after a date selection, and the CurrentMonth property, a DateTime property unseen in standard DatePicker controls, retains the current month's position to facilitate navigation through calendar months. 408 | 409 | The GenerateCalendar method incorporates logic to recreate the calendar based on the selected date. The Offset calculation part is noteworthy here. Current dates set the calendar display, and to include preview dates from the previous and next months, a simple but crucial calculation is required. 410 | 411 | ```csharp 412 | DateTime fDayOfMonth = new(current.Year,current.Month,1); 413 | DateTime lDayOfMonth = fDayOfMonth.AddMonths(1).AddDays(-1); 414 | 415 | int fOffset = (int)fDayOfMonth.DayOfWeek; 416 | int lOffset = 6 - (int)lDayOfMonth.DayOfWeek; 417 | 418 | DateTime fDay = fDayOfMonth.AddDays(-fOffset); 419 | DateTime lDay = lDayOfMonth.AddDays(lOffset); 420 | ``` 421 | 422 | In terms of event handling, the calendar selection event utilizes MouseLeftButtonUp to align with typical button click behaviors. It’s apt because SelectionChanged events do not trigger if the selected value is chosen again, making it unsuitable in this context. 423 | 424 | The interaction between the ToggleButton's IsChecked state, Popup's IsOpen, and Close functionalities are all implemented via events, providing a comprehensive interaction mechanism that is beneficial to learn through direct implementation. 425 | 426 | ## Additional Implementations 427 | This application, crafted for tutorial purposes, allows for further functional expansions such as time selection or manual value adjustments. Implementing a calendar display tailored to specific customer requirements is also feasible within this framework. 428 | 429 | ## Introduction to SmartDate Implementation Tutorials and Source Code 430 | The entire process of implementing the SmartDate control is available in tutorial videos on [YouTube](https://bit.ly/3xOeyMJ) and [Bilibili](https://bit.ly/3xI9DNh) and can be inspected on [GitHub](https://github.com/vickyqu115/smartdate). The videos, just over 50 minutes long, were developed over two months while balancing other professional duties, making them high-quality educational resources available for free. It's recommended to approach these tutorials with ample time and patience to ensure thorough learning. 431 | 432 | Should you have any questions regarding WPF or related studies, feel free to engage in discussion. Our community is eager to assist in your exploration. 433 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # SmartDate [![English](https://img.shields.io/badge/docs-English-blue.svg)](README.md) [![中文](https://img.shields.io/badge/docs-中文-red.svg)](README.zh-CN.md) [![한국어](https://img.shields.io/badge/docs-한국어-green.svg)](README.ko.md) 2 | 3 | A modern, customizable DatePicker control for WPF, reimagined from the ground up 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/smartdate.svg)](https://github.com/vickyqu115/smartdate/stargazers) 8 | [![Issues](https://img.shields.io/github/issues/vickyqu115/smartdate.svg)](https://github.com/vickyqu115/smartdate/issues) 9 | 10 | ## Project Overview 11 | 12 | SmartDate is a custom WPF control that reimagines the traditional DatePicker. Built from scratch by inheriting from Control rather than the built-in DatePicker, it offers a modern, flexible, and easily customizable alternative. This project demonstrates advanced WPF techniques and control development practices. 13 | 14 | 15 | 16 | 17 | ## Key Features and Implementations 18 | #### 1. Ground-Up Custom Control Development 19 | - [x] Built on Control class, bypassing the complexities of the standard DatePicker 20 | - [x] Efficient implementation of date selection functionality 21 | - [x] Modern design aligned with current UI/UX trends 22 | 23 | #### 2. Advanced WPF Techniques 24 | - [x] Use of PART_ naming convention for critical elements 25 | - [x] Custom ListBox (CalendarBox) with UniformGrid for calendar layout 26 | - [x] Popup control for dropdown calendar display 27 | 28 | #### 3. Primitives and Custom Controls Integration 29 | - [x] Custom ToggleButton (CalendarSwitch) for calendar activation 30 | - [x] ChevronButton for month navigation 31 | - [x] DayOfWeek control for weekday display 32 | 33 | #### 4. Sophisticated Date Handling 34 | - [x] Efficient calendar generation algorithm 35 | - [x] Seamless month navigation and date selection 36 | 37 | #### 5. MVVM-Friendly Design 38 | - [x] DependencyProperties for easy data binding (SelectedDate, CurrentMonth) 39 | - [x] Event-driven architecture for user interactions 40 | 41 | ## Technical Deep Dive 42 | - **CustomControl Architecture**: Demonstrates building a complex control from Control class, avoiding the intricacies of DatePicker inheritance. 43 | - **PART_ Control Interaction**: Showcases the use of PART_ named elements for code-behind interactions, a key WPF pattern. 44 | - **ListBox Customization**: Implements a custom calendar using a modified ListBox with UniformGrid, demonstrating advanced ItemsPanel customization. 45 | - **Popup Management**: Illustrates efficient handling of Popup control for dropdown functionality. 46 | - **Date Logic**: Implements sophisticated date calculation and calendar generation algorithms. 47 | 48 | ## Technology Stack 49 | - WPF (Windows Presentation Foundation) 50 | - .NET 8.0 51 | - C# 10.0 52 | - XAML 53 | 54 | ## Getting Started 55 | ### Prerequisites 56 | - Visual Studio 2022 or later 57 | - .NET 8.0 SDK 58 | 59 | ### Installation and Execution 60 | #### 1. Clone the repository: 61 | 62 | ``` 63 | git clone https://github.com/vickyqu115/smartdate.git 64 | ``` 65 | 66 | #### 2. Open the solution 67 | - [x] Visual Studio 68 | - [x] Visual Studio Code 69 | - [x] JetBrains Rider 70 | 71 | 72 | 73 | 74 | 75 | #### 3. Build and Run 76 | - [x] Set SmartDateApp as the startup project 77 | - [x] Press F5 or click the Run button 78 | - [x] Windows 11 recommended 79 | 80 | ## Learning Resources 81 | - [Detailed Article on Implementation (jamesnet.dev)](https://jamesnet.dev/article/43) 82 | - [YouTube Tutorial (English)](https://bit.ly/4c8uGr3) 83 | - [BiliBili Tutorial (Chinese)](https://bit.ly/3xOeyMJ) 84 | - [CodeProject Article](https://bit.ly/4du4hVD) 85 | 86 | ## Contributing 87 | Contributions to SmartDate are welcome! Feel free to submit issues, create pull requests, or suggest improvements. 88 | 89 | ## License 90 | This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details. 91 | 92 | ## Contact 93 | - Website: https://jamesnet.dev 94 | - Email: vickyqu115@hotmail.com, james@jamesnet.dev 95 | 96 | Explore advanced WPF control development techniques with SmartDate! 97 | 98 | ---- 99 | 100 | ## Recognizing the Issues with the WPF DatePicker 101 | The WPF DatePicker is one of the core controls in WPF, with a history spanning nearly 20 years. Compared to simpler controls like Buttons, TextBoxes, or CheckBoxes, the DatePicker has a more complex structure and stages, composed of multiple controls. This complexity necessitates high expertise for customization, making it difficult to use or modify the provided outdated controls. 102 | 103 | ## Understanding the WPF DatePicker 104 | Analyzing and understanding the structure of the DatePicker and the interaction of its internal elements within the Template is extremely beneficial for enhancing fundamental design and analysis skills in WPF. This applies to all WPF controls, not just the DatePicker. However, since the DatePicker was designed according to outdated trends, it might be more efficient to implement a new CustomControl based on the basic Control. 105 | 106 | ## Source Code Download and Setup 107 | This article identifies issues with using the basic DatePicker and demonstrates how to redesign it using a CustomControl approach. It is also beneficial to download the source code via GitHub to check the results firsthand and read along with this article. 108 | 109 | First, download the source code using the following git command: 110 | ``` 111 | git clone https://github.com/vickyqu115/smartdate 112 | ``` 113 | 114 | Next, to run the solution file from the source code, you need an environment with Windows 10 or higher, Visual Studio 2022 or Rider, and .NET 8.0. 115 | 116 | _SmartDate.sln_ 117 | 118 | 119 | 120 | ## Project Structure 121 | SmartDate consists of two projects: 122 | - SmartDateControl 123 | - SmartDateApp 124 | 125 | SmartDateControl is a CustomControl Library that includes the SmartDate class along with all other subordinate CustomControl classes. SmartDateApp is a simple application project that guides users on how to use this control. 126 | 127 | ## Declaring and Using SmartDate 128 | The usage is straightforward. Declare the namespace with xmlns and use SmartDate just like the standard DatePicker. 129 | 130 | ```xml 131 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | ``` 149 | 150 | SelectedDate is a DependencyProperty and uses the same DateTime? type as DatePicker’s SelectedDate. 151 | 152 | _Execution Results_ 153 | 154 | 155 | 156 | ## Definition and Utilization of CustomControl 157 | I have previously discussed the technology behind CustomControls in detail through four articles on CodeProject. If you need to understand and master CustomControls, please refer to these articles. Particularly, the article on RiotSlider delves deeply into the architecture of WPF CustomControls, so if you haven’t read it yet, I strongly recommend doing so. 158 | 159 | Returning to the main discussion, let’s define CustomControl. Typically, a CustomControl targets classes derived from Control, but in reality, it includes all classes derived from DependencyObject, not just those inheriting Control, such as Panels, up to Visuals like Animations. However, as mentioned earlier, it only makes sense to implement CustomControls in layers that can utilize Templates, or at least DataContext. Therefore, implementing classes derived from FrameworkElement in the CustomControl style is seen as wise. 160 | 161 | ## Designing a New DatePicker: SmartDate 162 | This article will detail how to implement a new CustomControl called SmartDate, derived from the most basic class, Control, without using the existing DatePicker. 163 | 164 | ## Choosing Control Over ContentControl 165 | First, let’s examine the differences between ContentControl and Control. ContentControl offers not just the basic Template but also properties for Content and ContentTemplate. These properties are automatically linked through the ContentPresenter, setting up the relationship between ContentPresenter, Content, and ContentTemplate automatically. Consequently, choosing a derived control based on the basic usage of DataTemplate is advisable. 166 | 167 | Is DatePicker fundamentally a control that utilizes DataTemplate? While opinions may vary, a complex control like DatePicker typically requires multiple DataTemplates and does not resemble a standard ContentControl. Indeed, DatePicker is derived from Control, and similar types of controls usually inherit from Control. For example, ComboBox might look similar to DatePicker but is an ItemsControl with an ItemsSource property. 168 | 169 | Therefore, it is appropriate to base the implementation of SmartDate on Control, especially since SmartDate does not provide its own DataTemplate. 170 | 171 | ## Utilizing DataTemplate 172 | Though SmartDate does not provide a DataTemplate by default, there are many points within various areas of the control where extending through DataTemplate could be beneficial. 173 | 174 | For example, you can extend the ContentPresenter of the DayOfWeek control to add specific date processing, a common requirement among clients. This allows for various extensions such as triggers or converters for special dates. 175 | 176 | By extending the SelectedDate binding area to a ContentPresenter, you can flexibly use it for selecting dates, incorporating formats ranging from a simple TextBlock to an editable TextBox or even including time. 177 | 178 | ## Negative Views on DataTemplate 179 | DataTemplate fundamentally maintains versatility even in complex situations and is an essential template area for customization. However, whether to apply this versatility to specific controls like date pickers should be carefully considered. Using a DataTemplate means that all related logic must be separated into interactively implementable components. While this may seem practical, it is crucial to make sound judgments. 180 | 181 | ## Key Binding Properties of SmartDate (DependencyProperty) 182 | This control includes a binding property named SelectedDate of type DateTime?. Since the default value might be null, it is declared as a nullable type, used for setting the date value selected through the calendar. 183 | 184 | ## SmartDate Template Design 185 | 186 | The essential components that must be included in the ControlTemplate design are as follows: 187 | 188 | - Popup 189 | - ListBox 190 | - ToggleButton 191 | 192 | The Popup acts as a panel to contain the ListBox, which is the calendar, and the ListBox uses an internal ItemsPanel to implement the calendar with a UniformGrid. The ToggleButton is used as the calendar icon, and toggling the button changes the IsOpen property of the Popup to control the calendar window. This setup is similar in the basic DatePicker control as well, making it very beneficial to compare it with the actual open-source code of DatePicker. 193 | 194 | Let’s now examine how the SmartDate control is structured in its Template. 195 | 196 | _SmartDate: ControlTemplate_ 197 | 198 | ```xml 199 | 200 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | 222 | 223 | 224 | 225 | 226 | 227 | 228 | 229 | ``` 230 | 231 | As you can see in the ControlTemplate, all previously mentioned components are included. The Popup is used as a basic control, and the CalendarSwitch is a calendar switching button that inherits from ToggleButton. Lastly, the CalendarBox, which inherits from ListBox, is used as the list control for selecting dates on the calendar. 232 | 233 | Moreover, other components included are buttons to navigate to the previous and next months, a TextBlock to display the current month, and design elements to display the days of the week. 234 | 235 | ## CustomControl Not Intended for Reuse but for Internal Use Only 236 | The SmartDate control is not only used by itself but also employs CustomControls within its Template. Not all CustomControls are intended for universal control implementation. In cases like SmartDate, they are implemented for specific purposes, which is a common practice from the perspective of WPF architecture. 237 | 238 | Such types of controls are often categorized under the namespace 'Primitives.' This category includes controls like ToggleButton, Thumb, and ScrollBar, which are typically used not directly but within the internals of other controls. 239 | 240 | Based on these architectural facts about WPF, it can be seen that the structure of the SmartDate control's Template does not significantly differ from the basic patterns of WPF. 241 | 242 | ## Understanding PART_ Control Items and Their Roles 243 | The CustomControl structure does not automatically connect code and XAML like UserControls do. Thus, all interactions between the two are exclusively managed by _PART controls. 244 | 245 | The predefined _PART controls include: 246 | - PART_Switch 247 | - PART_ListBox 248 | - PART_Left 249 | - PART_Right 250 | 251 | These are assigned during the override of the SmartDate class's OnApplyTemplate method, where all necessary processes such as button events and date generation are implemented. It's a good practice to name controls with the PART_ prefix when passed through OnApplyTemplate. Moreover, naming these elements in XAML in a way that allows developers to anticipate what processes occur within the class based on the PART_ name would be exemplary. 252 | 253 | ## SmartDate.cs Source Code 254 | Next, we will examine the core implementation contained within the SmartDate.cs class file. Key areas to focus on include: 255 | - Declared DependencyProperty 256 | - Definition of PART_ elements via OnApplyTemplate 257 | - Date selection control logic through the SelectedDate property 258 | - Utilization of SelectedItem/SelectedValue in CalendarBox 259 | 260 | _SmartDate: CustomControl_ 261 | 262 | ```csharp 263 | using System; 264 | using System.Collections.Generic; 265 | using System.Linq; 266 | using System.Text; 267 | using System.Threading.Tasks; 268 | using System.Windows; 269 | using System.Windows.Controls; 270 | using System.Windows.Controls.Primitives; 271 | using System.Windows.Data; 272 | using System.Windows.Documents; 273 | using System.Windows.Input; 274 | using System.Windows.Media; 275 | using System.Windows.Media.Imaging; 276 | using System.Windows.Navigation; 277 | using System.Windows.Shapes; 278 | 279 | namespace SmartDateControl.UI.Units 280 | { 281 | public class SmartDate : Control 282 | { 283 | private Popup _popup; 284 | private CalendarSwitch _switch; 285 | private CalendarBox _listbox; 286 | 287 | public bool KeepPopupOpen 288 | { 289 | get { return (bool)GetValue(KeepPopupOpenProperty); } 290 | set { SetValue(KeepPopupOpenProperty, value); } 291 | } 292 | 293 | public static readonly DependencyProperty KeepPopupOpenProperty = 294 | DependencyProperty.Register("KeepPopupOpen", typeof(bool), typeof(SmartDate), new PropertyMetadata(true)); 295 | 296 | 297 | 298 | public DateTime CurrentMonth 299 | { 300 | get { return (DateTime)GetValue(CurrentMonthProperty); } 301 | set { SetValue(CurrentMonthProperty, value); } 302 | } 303 | 304 | public static readonly DependencyProperty CurrentMonthProperty = 305 | DependencyProperty.Register("CurrentMonth", typeof(DateTime), typeof(SmartDate), new PropertyMetadata(null)); 306 | 307 | 308 | 309 | public DateTime? SelectedDate 310 | { 311 | get { return (DateTime?)GetValue(SelectedDateProperty); } 312 | set { SetValue(SelectedDateProperty, value); } 313 | } 314 | 315 | public static readonly DependencyProperty SelectedDateProperty = 316 | DependencyProperty.Register("SelectedDate", typeof(DateTime?), typeof(SmartDate), new PropertyMetadata(null)); 317 | 318 | 319 | 320 | static SmartDate() 321 | { 322 | DefaultStyleKeyProperty.OverrideMetadata(typeof(SmartDate), new FrameworkPropertyMetadata(typeof(SmartDate))); 323 | } 324 | public override void OnApplyTemplate() 325 | { 326 | base.OnApplyTemplate(); 327 | 328 | _popup = (Popup)GetTemplateChild("PART_Popup"); 329 | _switch = (CalendarSwitch)GetTemplateChild("PART_Switch"); 330 | _listbox = (CalendarBox)GetTemplateChild("PART_ListBox"); 331 | ChevronButton leftButton = (ChevronButton)GetTemplateChild("PART_Left"); 332 | ChevronButton rightButton = (ChevronButton)GetTemplateChild("PART_Right"); 333 | 334 | _popup.Closed += _popup_Closed; 335 | _switch.Click += _switch_Click; 336 | _listbox.MouseLeftButtonUp += _listbox_MouseLeftButtonUp; 337 | 338 | leftButton.Click += (s, e) => MoveMonthClick(-1); 339 | rightButton.Click += (s, e) => MoveMonthClick(1); 340 | 341 | } 342 | 343 | private void MoveMonthClick(int month) 344 | { 345 | GenerateCalendar(CurrentMonth.AddMonths(month)); 346 | } 347 | 348 | private void _popup_Closed(object sender, EventArgs e) 349 | { 350 | _switch.IsChecked = IsMouseOver; 351 | } 352 | 353 | private void _switch_Click(object sender, RoutedEventArgs e) 354 | { 355 | if (_switch.IsChecked == true) 356 | { 357 | _popup.IsOpen = true; 358 | 359 | GenerateCalendar(SelectedDate ?? DateTime.Now); 360 | } 361 | } 362 | 363 | private void _listbox_MouseLeftButtonUp(object sender, MouseButtonEventArgs e) 364 | { 365 | if (_listbox.SelectedItem is CalendarBoxItem selected) 366 | { 367 | SelectedDate = selected.Date; 368 | GenerateCalendar(selected.Date); 369 | 370 | _popup.IsOpen = KeepPopupOpen; 371 | } 372 | } 373 | private void GenerateCalendar(DateTime current) 374 | { 375 | if (current.ToString("yyyyMM") == CurrentMonth.ToString("yyyyMM")) return; 376 | 377 | CurrentMonth = current; 378 | _listbox.Items.Clear(); 379 | DateTime fDayOfMonth = new(current.Year,current.Month,1); 380 | DateTime lDayOfMonth = fDayOfMonth.AddMonths(1).AddDays(-1); 381 | 382 | int fOffset = (int)fDayOfMonth.DayOfWeek; 383 | int lOffset = 6 - (int)lDayOfMonth.DayOfWeek; 384 | 385 | DateTime fDay = fDayOfMonth.AddDays(-fOffset); 386 | DateTime lDay = lDayOfMonth.AddDays(lOffset); 387 | 388 | for (DateTime day = fDay; day <= lDay; day = day.AddDays(1)) 389 | { 390 | CalendarBoxItem boxItem = new(); 391 | boxItem.Date = day; 392 | boxItem.DateFormat = day.ToString("yyyyMMdd"); 393 | boxItem.Content = day.Day; 394 | boxItem.IsCurrentMonth = day.Month == current.Month; 395 | 396 | _listbox.Items.Add(boxItem); 397 | } 398 | if (SelectedDate != null) 399 | { 400 | _listbox.SelectedValue = SelectedDate.Value.ToString("yyyyMMdd"); 401 | } 402 | } 403 | } 404 | } 405 | ``` 406 | 407 | Firstly, the DependencyProperty is scrutinized, including essential properties like SelectedDate, which maintains the selected date. The KeepPopupOpen property determines whether to keep the window open after a date selection, and the CurrentMonth property, a DateTime property unseen in standard DatePicker controls, retains the current month's position to facilitate navigation through calendar months. 408 | 409 | The GenerateCalendar method incorporates logic to recreate the calendar based on the selected date. The Offset calculation part is noteworthy here. Current dates set the calendar display, and to include preview dates from the previous and next months, a simple but crucial calculation is required. 410 | 411 | ```csharp 412 | DateTime fDayOfMonth = new(current.Year,current.Month,1); 413 | DateTime lDayOfMonth = fDayOfMonth.AddMonths(1).AddDays(-1); 414 | 415 | int fOffset = (int)fDayOfMonth.DayOfWeek; 416 | int lOffset = 6 - (int)lDayOfMonth.DayOfWeek; 417 | 418 | DateTime fDay = fDayOfMonth.AddDays(-fOffset); 419 | DateTime lDay = lDayOfMonth.AddDays(lOffset); 420 | ``` 421 | 422 | In terms of event handling, the calendar selection event utilizes MouseLeftButtonUp to align with typical button click behaviors. It’s apt because SelectionChanged events do not trigger if the selected value is chosen again, making it unsuitable in this context. 423 | 424 | The interaction between the ToggleButton's IsChecked state, Popup's IsOpen, and Close functionalities are all implemented via events, providing a comprehensive interaction mechanism that is beneficial to learn through direct implementation. 425 | 426 | ## Additional Implementations 427 | This application, crafted for tutorial purposes, allows for further functional expansions such as time selection or manual value adjustments. Implementing a calendar display tailored to specific customer requirements is also feasible within this framework. 428 | 429 | ## Introduction to SmartDate Implementation Tutorials and Source Code 430 | The entire process of implementing the SmartDate control is available in tutorial videos on [YouTube](https://bit.ly/3xOeyMJ) and [Bilibili](https://bit.ly/3xI9DNh) and can be inspected on [GitHub](https://github.com/vickyqu115/smartdate). The videos, just over 50 minutes long, were developed over two months while balancing other professional duties, making them high-quality educational resources available for free. It's recommended to approach these tutorials with ample time and patience to ensure thorough learning. 431 | 432 | Should you have any questions regarding WPF or related studies, feel free to engage in discussion. Our community is eager to assist in your exploration. 433 | -------------------------------------------------------------------------------- /README.zh-CN.md: -------------------------------------------------------------------------------- 1 | # SmartDate [![English](https://img.shields.io/badge/docs-English-blue.svg)](README.md) [![中文](https://img.shields.io/badge/docs-中文-red.svg)](README.zh-CN.md) [![한국어](https://img.shields.io/badge/docs-한국어-green.svg)](README.ko.md) 2 | 3 | 为WPF重新设计的现代化、可定制的DatePicker控件 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/smartdate.svg)](https://github.com/vickyqu115/smartdate/stargazers) 8 | [![Issues](https://img.shields.io/github/issues/vickyqu115/smartdate.svg)](https://github.com/vickyqu115/smartdate/issues) 9 | 10 | ## 项目概述 11 | 12 | SmartDate是一个重新构想传统DatePicker的自定义WPF控件。它通过继承Control类而不是内置的DatePicker从头开始构建,提供了一个现代、灵活且易于定制的替代方案。这个项目展示了高级WPF技术和控件开发实践。 13 | 14 | 15 | 16 | 17 | ## 主要特性和实现 18 | #### 1. 从头开始的自定义控件开发 19 | - [x] 基于Control类构建,绕过标准DatePicker的复杂性 20 | - [x] 高效实现日期选择功能 21 | - [x] 符合当前UI/UX趋势的现代设计 22 | 23 | #### 2. 高级WPF技术 24 | - [x] 为关键元素使用PART_命名约定 25 | - [x] 使用UniformGrid的自定义ListBox(CalendarBox)实现日历布局 26 | - [x] 使用Popup控件实现下拉日历显示 27 | 28 | #### 3. 原始控件和自定义控件的集成 29 | - [x] 用于激活日历的自定义ToggleButton(CalendarSwitch) 30 | - [x] 用于月份导航的ChevronButton 31 | - [x] 用于显示星期几的DayOfWeek控件 32 | 33 | #### 4. 复杂的日期处理 34 | - [x] 高效的日历生成算法 35 | - [x] 无缝的月份导航和日期选择 36 | 37 | #### 5. MVVM友好设计 38 | - [x] 使用DependencyProperties实现简单的数据绑定(SelectedDate, CurrentMonth) 39 | - [x] 基于事件的用户交互架构 40 | 41 | ## 技术深入探讨 42 | - **自定义控件架构**:展示了如何从Control类构建复杂控件,避免了DatePicker继承的复杂性。 43 | - **PART_控件交互**:展示了使用PART_命名元素进行代码后台交互的关键WPF模式。 44 | - **ListBox自定义**:使用修改过的带UniformGrid的ListBox实现自定义日历,展示了高级ItemsPanel自定义。 45 | - **Popup管理**:说明了对下拉功能的Popup控件的高效处理。 46 | - **日期逻辑**:实现了复杂的日期计算和日历生成算法。 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/smartdate.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] 将SmartDateApp设置为启动项目 77 | - [x] 按F5或点击运行按钮 78 | - [x] 推荐使用Windows 11 79 | 80 | ## 学习资源 81 | - [详细实现文章 (jamesnet.dev)](https://jamesnet.dev/article/43) 82 | - [YouTube教程 (英文)](https://bit.ly/4c8uGr3) 83 | - [BiliBili教程 (中文)](https://bit.ly/3xOeyMJ) 84 | - [CodeProject文章](https://bit.ly/4du4hVD) 85 | 86 | ## 贡献 87 | 欢迎为SmartDate做出贡献!随时提交问题、创建拉取请求或提出改进建议。 88 | 89 | ## 许可证 90 | 该项目基于MIT许可证分发。有关详细信息,请参阅[LICENSE](LICENSE)文件。 91 | 92 | ## 联系方式 93 | - 网站:https://jamesnet.dev 94 | - 电子邮件:vickyqu115@hotmail.com, james@jamesnet.dev 95 | 96 | 通过SmartDate探索高级WPF控件开发技术! 97 | 98 | ---- 99 | 100 | ## Recognizing the Issues with the WPF DatePicker 101 | The WPF DatePicker is one of the core controls in WPF, with a history spanning nearly 20 years. Compared to simpler controls like Buttons, TextBoxes, or CheckBoxes, the DatePicker has a more complex structure and stages, composed of multiple controls. This complexity necessitates high expertise for customization, making it difficult to use or modify the provided outdated controls. 102 | 103 | ## Understanding the WPF DatePicker 104 | Analyzing and understanding the structure of the DatePicker and the interaction of its internal elements within the Template is extremely beneficial for enhancing fundamental design and analysis skills in WPF. This applies to all WPF controls, not just the DatePicker. However, since the DatePicker was designed according to outdated trends, it might be more efficient to implement a new CustomControl based on the basic Control. 105 | 106 | ## Source Code Download and Setup 107 | This article identifies issues with using the basic DatePicker and demonstrates how to redesign it using a CustomControl approach. It is also beneficial to download the source code via GitHub to check the results firsthand and read along with this article. 108 | 109 | First, download the source code using the following git command: 110 | ``` 111 | git clone https://github.com/vickyqu115/smartdate 112 | ``` 113 | 114 | Next, to run the solution file from the source code, you need an environment with Windows 10 or higher, Visual Studio 2022 or Rider, and .NET 8.0. 115 | 116 | _SmartDate.sln_ 117 | 118 | 119 | 120 | ## Project Structure 121 | SmartDate consists of two projects: 122 | - SmartDateControl 123 | - SmartDateApp 124 | 125 | SmartDateControl is a CustomControl Library that includes the SmartDate class along with all other subordinate CustomControl classes. SmartDateApp is a simple application project that guides users on how to use this control. 126 | 127 | ## Declaring and Using SmartDate 128 | The usage is straightforward. Declare the namespace with xmlns and use SmartDate just like the standard DatePicker. 129 | 130 | ```xml 131 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | ``` 149 | 150 | SelectedDate is a DependencyProperty and uses the same DateTime? type as DatePicker’s SelectedDate. 151 | 152 | _Execution Results_ 153 | 154 | 155 | 156 | ## Definition and Utilization of CustomControl 157 | I have previously discussed the technology behind CustomControls in detail through four articles on CodeProject. If you need to understand and master CustomControls, please refer to these articles. Particularly, the article on RiotSlider delves deeply into the architecture of WPF CustomControls, so if you haven’t read it yet, I strongly recommend doing so. 158 | 159 | Returning to the main discussion, let’s define CustomControl. Typically, a CustomControl targets classes derived from Control, but in reality, it includes all classes derived from DependencyObject, not just those inheriting Control, such as Panels, up to Visuals like Animations. However, as mentioned earlier, it only makes sense to implement CustomControls in layers that can utilize Templates, or at least DataContext. Therefore, implementing classes derived from FrameworkElement in the CustomControl style is seen as wise. 160 | 161 | ## Designing a New DatePicker: SmartDate 162 | This article will detail how to implement a new CustomControl called SmartDate, derived from the most basic class, Control, without using the existing DatePicker. 163 | 164 | ## Choosing Control Over ContentControl 165 | First, let’s examine the differences between ContentControl and Control. ContentControl offers not just the basic Template but also properties for Content and ContentTemplate. These properties are automatically linked through the ContentPresenter, setting up the relationship between ContentPresenter, Content, and ContentTemplate automatically. Consequently, choosing a derived control based on the basic usage of DataTemplate is advisable. 166 | 167 | Is DatePicker fundamentally a control that utilizes DataTemplate? While opinions may vary, a complex control like DatePicker typically requires multiple DataTemplates and does not resemble a standard ContentControl. Indeed, DatePicker is derived from Control, and similar types of controls usually inherit from Control. For example, ComboBox might look similar to DatePicker but is an ItemsControl with an ItemsSource property. 168 | 169 | Therefore, it is appropriate to base the implementation of SmartDate on Control, especially since SmartDate does not provide its own DataTemplate. 170 | 171 | ## Utilizing DataTemplate 172 | Though SmartDate does not provide a DataTemplate by default, there are many points within various areas of the control where extending through DataTemplate could be beneficial. 173 | 174 | For example, you can extend the ContentPresenter of the DayOfWeek control to add specific date processing, a common requirement among clients. This allows for various extensions such as triggers or converters for special dates. 175 | 176 | By extending the SelectedDate binding area to a ContentPresenter, you can flexibly use it for selecting dates, incorporating formats ranging from a simple TextBlock to an editable TextBox or even including time. 177 | 178 | ## Negative Views on DataTemplate 179 | DataTemplate fundamentally maintains versatility even in complex situations and is an essential template area for customization. However, whether to apply this versatility to specific controls like date pickers should be carefully considered. Using a DataTemplate means that all related logic must be separated into interactively implementable components. While this may seem practical, it is crucial to make sound judgments. 180 | 181 | ## Key Binding Properties of SmartDate (DependencyProperty) 182 | This control includes a binding property named SelectedDate of type DateTime?. Since the default value might be null, it is declared as a nullable type, used for setting the date value selected through the calendar. 183 | 184 | ## SmartDate Template Design 185 | 186 | The essential components that must be included in the ControlTemplate design are as follows: 187 | 188 | - Popup 189 | - ListBox 190 | - ToggleButton 191 | 192 | The Popup acts as a panel to contain the ListBox, which is the calendar, and the ListBox uses an internal ItemsPanel to implement the calendar with a UniformGrid. The ToggleButton is used as the calendar icon, and toggling the button changes the IsOpen property of the Popup to control the calendar window. This setup is similar in the basic DatePicker control as well, making it very beneficial to compare it with the actual open-source code of DatePicker. 193 | 194 | Let’s now examine how the SmartDate control is structured in its Template. 195 | 196 | _SmartDate: ControlTemplate_ 197 | 198 | ```xml 199 | 200 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | 222 | 223 | 224 | 225 | 226 | 227 | 228 | 229 | ``` 230 | 231 | As you can see in the ControlTemplate, all previously mentioned components are included. The Popup is used as a basic control, and the CalendarSwitch is a calendar switching button that inherits from ToggleButton. Lastly, the CalendarBox, which inherits from ListBox, is used as the list control for selecting dates on the calendar. 232 | 233 | Moreover, other components included are buttons to navigate to the previous and next months, a TextBlock to display the current month, and design elements to display the days of the week. 234 | 235 | ## CustomControl Not Intended for Reuse but for Internal Use Only 236 | The SmartDate control is not only used by itself but also employs CustomControls within its Template. Not all CustomControls are intended for universal control implementation. In cases like SmartDate, they are implemented for specific purposes, which is a common practice from the perspective of WPF architecture. 237 | 238 | Such types of controls are often categorized under the namespace 'Primitives.' This category includes controls like ToggleButton, Thumb, and ScrollBar, which are typically used not directly but within the internals of other controls. 239 | 240 | Based on these architectural facts about WPF, it can be seen that the structure of the SmartDate control's Template does not significantly differ from the basic patterns of WPF. 241 | 242 | ## Understanding PART_ Control Items and Their Roles 243 | The CustomControl structure does not automatically connect code and XAML like UserControls do. Thus, all interactions between the two are exclusively managed by _PART controls. 244 | 245 | The predefined _PART controls include: 246 | - PART_Switch 247 | - PART_ListBox 248 | - PART_Left 249 | - PART_Right 250 | 251 | These are assigned during the override of the SmartDate class's OnApplyTemplate method, where all necessary processes such as button events and date generation are implemented. It's a good practice to name controls with the PART_ prefix when passed through OnApplyTemplate. Moreover, naming these elements in XAML in a way that allows developers to anticipate what processes occur within the class based on the PART_ name would be exemplary. 252 | 253 | ## SmartDate.cs Source Code 254 | Next, we will examine the core implementation contained within the SmartDate.cs class file. Key areas to focus on include: 255 | - Declared DependencyProperty 256 | - Definition of PART_ elements via OnApplyTemplate 257 | - Date selection control logic through the SelectedDate property 258 | - Utilization of SelectedItem/SelectedValue in CalendarBox 259 | 260 | _SmartDate: CustomControl_ 261 | 262 | ```csharp 263 | using System; 264 | using System.Collections.Generic; 265 | using System.Linq; 266 | using System.Text; 267 | using System.Threading.Tasks; 268 | using System.Windows; 269 | using System.Windows.Controls; 270 | using System.Windows.Controls.Primitives; 271 | using System.Windows.Data; 272 | using System.Windows.Documents; 273 | using System.Windows.Input; 274 | using System.Windows.Media; 275 | using System.Windows.Media.Imaging; 276 | using System.Windows.Navigation; 277 | using System.Windows.Shapes; 278 | 279 | namespace SmartDateControl.UI.Units 280 | { 281 | public class SmartDate : Control 282 | { 283 | private Popup _popup; 284 | private CalendarSwitch _switch; 285 | private CalendarBox _listbox; 286 | 287 | public bool KeepPopupOpen 288 | { 289 | get { return (bool)GetValue(KeepPopupOpenProperty); } 290 | set { SetValue(KeepPopupOpenProperty, value); } 291 | } 292 | 293 | public static readonly DependencyProperty KeepPopupOpenProperty = 294 | DependencyProperty.Register("KeepPopupOpen", typeof(bool), typeof(SmartDate), new PropertyMetadata(true)); 295 | 296 | 297 | 298 | public DateTime CurrentMonth 299 | { 300 | get { return (DateTime)GetValue(CurrentMonthProperty); } 301 | set { SetValue(CurrentMonthProperty, value); } 302 | } 303 | 304 | public static readonly DependencyProperty CurrentMonthProperty = 305 | DependencyProperty.Register("CurrentMonth", typeof(DateTime), typeof(SmartDate), new PropertyMetadata(null)); 306 | 307 | 308 | 309 | public DateTime? SelectedDate 310 | { 311 | get { return (DateTime?)GetValue(SelectedDateProperty); } 312 | set { SetValue(SelectedDateProperty, value); } 313 | } 314 | 315 | public static readonly DependencyProperty SelectedDateProperty = 316 | DependencyProperty.Register("SelectedDate", typeof(DateTime?), typeof(SmartDate), new PropertyMetadata(null)); 317 | 318 | 319 | 320 | static SmartDate() 321 | { 322 | DefaultStyleKeyProperty.OverrideMetadata(typeof(SmartDate), new FrameworkPropertyMetadata(typeof(SmartDate))); 323 | } 324 | public override void OnApplyTemplate() 325 | { 326 | base.OnApplyTemplate(); 327 | 328 | _popup = (Popup)GetTemplateChild("PART_Popup"); 329 | _switch = (CalendarSwitch)GetTemplateChild("PART_Switch"); 330 | _listbox = (CalendarBox)GetTemplateChild("PART_ListBox"); 331 | ChevronButton leftButton = (ChevronButton)GetTemplateChild("PART_Left"); 332 | ChevronButton rightButton = (ChevronButton)GetTemplateChild("PART_Right"); 333 | 334 | _popup.Closed += _popup_Closed; 335 | _switch.Click += _switch_Click; 336 | _listbox.MouseLeftButtonUp += _listbox_MouseLeftButtonUp; 337 | 338 | leftButton.Click += (s, e) => MoveMonthClick(-1); 339 | rightButton.Click += (s, e) => MoveMonthClick(1); 340 | 341 | } 342 | 343 | private void MoveMonthClick(int month) 344 | { 345 | GenerateCalendar(CurrentMonth.AddMonths(month)); 346 | } 347 | 348 | private void _popup_Closed(object sender, EventArgs e) 349 | { 350 | _switch.IsChecked = IsMouseOver; 351 | } 352 | 353 | private void _switch_Click(object sender, RoutedEventArgs e) 354 | { 355 | if (_switch.IsChecked == true) 356 | { 357 | _popup.IsOpen = true; 358 | 359 | GenerateCalendar(SelectedDate ?? DateTime.Now); 360 | } 361 | } 362 | 363 | private void _listbox_MouseLeftButtonUp(object sender, MouseButtonEventArgs e) 364 | { 365 | if (_listbox.SelectedItem is CalendarBoxItem selected) 366 | { 367 | SelectedDate = selected.Date; 368 | GenerateCalendar(selected.Date); 369 | 370 | _popup.IsOpen = KeepPopupOpen; 371 | } 372 | } 373 | private void GenerateCalendar(DateTime current) 374 | { 375 | if (current.ToString("yyyyMM") == CurrentMonth.ToString("yyyyMM")) return; 376 | 377 | CurrentMonth = current; 378 | _listbox.Items.Clear(); 379 | DateTime fDayOfMonth = new(current.Year,current.Month,1); 380 | DateTime lDayOfMonth = fDayOfMonth.AddMonths(1).AddDays(-1); 381 | 382 | int fOffset = (int)fDayOfMonth.DayOfWeek; 383 | int lOffset = 6 - (int)lDayOfMonth.DayOfWeek; 384 | 385 | DateTime fDay = fDayOfMonth.AddDays(-fOffset); 386 | DateTime lDay = lDayOfMonth.AddDays(lOffset); 387 | 388 | for (DateTime day = fDay; day <= lDay; day = day.AddDays(1)) 389 | { 390 | CalendarBoxItem boxItem = new(); 391 | boxItem.Date = day; 392 | boxItem.DateFormat = day.ToString("yyyyMMdd"); 393 | boxItem.Content = day.Day; 394 | boxItem.IsCurrentMonth = day.Month == current.Month; 395 | 396 | _listbox.Items.Add(boxItem); 397 | } 398 | if (SelectedDate != null) 399 | { 400 | _listbox.SelectedValue = SelectedDate.Value.ToString("yyyyMMdd"); 401 | } 402 | } 403 | } 404 | } 405 | ``` 406 | 407 | Firstly, the DependencyProperty is scrutinized, including essential properties like SelectedDate, which maintains the selected date. The KeepPopupOpen property determines whether to keep the window open after a date selection, and the CurrentMonth property, a DateTime property unseen in standard DatePicker controls, retains the current month's position to facilitate navigation through calendar months. 408 | 409 | The GenerateCalendar method incorporates logic to recreate the calendar based on the selected date. The Offset calculation part is noteworthy here. Current dates set the calendar display, and to include preview dates from the previous and next months, a simple but crucial calculation is required. 410 | 411 | ```csharp 412 | DateTime fDayOfMonth = new(current.Year,current.Month,1); 413 | DateTime lDayOfMonth = fDayOfMonth.AddMonths(1).AddDays(-1); 414 | 415 | int fOffset = (int)fDayOfMonth.DayOfWeek; 416 | int lOffset = 6 - (int)lDayOfMonth.DayOfWeek; 417 | 418 | DateTime fDay = fDayOfMonth.AddDays(-fOffset); 419 | DateTime lDay = lDayOfMonth.AddDays(lOffset); 420 | ``` 421 | 422 | In terms of event handling, the calendar selection event utilizes MouseLeftButtonUp to align with typical button click behaviors. It’s apt because SelectionChanged events do not trigger if the selected value is chosen again, making it unsuitable in this context. 423 | 424 | The interaction between the ToggleButton's IsChecked state, Popup's IsOpen, and Close functionalities are all implemented via events, providing a comprehensive interaction mechanism that is beneficial to learn through direct implementation. 425 | 426 | ## Additional Implementations 427 | This application, crafted for tutorial purposes, allows for further functional expansions such as time selection or manual value adjustments. Implementing a calendar display tailored to specific customer requirements is also feasible within this framework. 428 | 429 | ## Introduction to SmartDate Implementation Tutorials and Source Code 430 | The entire process of implementing the SmartDate control is available in tutorial videos on [YouTube](https://bit.ly/3xOeyMJ) and [Bilibili](https://bit.ly/3xI9DNh) and can be inspected on [GitHub](https://github.com/vickyqu115/smartdate). The videos, just over 50 minutes long, were developed over two months while balancing other professional duties, making them high-quality educational resources available for free. It's recommended to approach these tutorials with ample time and patience to ensure thorough learning. 431 | 432 | Should you have any questions regarding WPF or related studies, feel free to engage in discussion. Our community is eager to assist in your exploration. 433 | -------------------------------------------------------------------------------- /images/result.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JamesnetGroup/smartdate/1498b16aba9cbcc190ad35f871ff6eb9556f6fb2/images/result.png -------------------------------------------------------------------------------- /images/solution.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JamesnetGroup/smartdate/1498b16aba9cbcc190ad35f871ff6eb9556f6fb2/images/solution.png -------------------------------------------------------------------------------- /src/DemoApp.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 17 4 | VisualStudioVersion = 17.8.34525.116 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DemoApp", "DemoApp\DemoApp.csproj", "{76276CD9-7741-4F81-A1D9-DD9537730E46}" 7 | EndProject 8 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SmartDateControl", "SmartDateControl\SmartDateControl.csproj", "{D62AD56A-180C-4080-A52B-D38E254A899C}" 9 | EndProject 10 | Global 11 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 12 | Debug|Any CPU = Debug|Any CPU 13 | Release|Any CPU = Release|Any CPU 14 | EndGlobalSection 15 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 16 | {76276CD9-7741-4F81-A1D9-DD9537730E46}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 17 | {76276CD9-7741-4F81-A1D9-DD9537730E46}.Debug|Any CPU.Build.0 = Debug|Any CPU 18 | {76276CD9-7741-4F81-A1D9-DD9537730E46}.Release|Any CPU.ActiveCfg = Release|Any CPU 19 | {76276CD9-7741-4F81-A1D9-DD9537730E46}.Release|Any CPU.Build.0 = Release|Any CPU 20 | {D62AD56A-180C-4080-A52B-D38E254A899C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 21 | {D62AD56A-180C-4080-A52B-D38E254A899C}.Debug|Any CPU.Build.0 = Debug|Any CPU 22 | {D62AD56A-180C-4080-A52B-D38E254A899C}.Release|Any CPU.ActiveCfg = Release|Any CPU 23 | {D62AD56A-180C-4080-A52B-D38E254A899C}.Release|Any CPU.Build.0 = Release|Any CPU 24 | EndGlobalSection 25 | GlobalSection(SolutionProperties) = preSolution 26 | HideSolutionNode = FALSE 27 | EndGlobalSection 28 | GlobalSection(ExtensibilityGlobals) = postSolution 29 | SolutionGuid = {AC5A800E-8E01-4867-98C9-EAC0DF0ED3E4} 30 | EndGlobalSection 31 | EndGlobal 32 | -------------------------------------------------------------------------------- /src/DemoApp/App.cs: -------------------------------------------------------------------------------- 1 | using DemoApp.UI.Views; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | using System.Windows; 8 | 9 | namespace DemoApp 10 | { 11 | internal class App : Application 12 | { 13 | protected override void OnStartup(StartupEventArgs e) 14 | { 15 | base.OnStartup(e); 16 | 17 | VickyWindow window = new(); 18 | window.ShowDialog(); 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/DemoApp/DemoApp.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | WinExe 5 | net8.0-windows 6 | enable 7 | enable 8 | true 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /src/DemoApp/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Runtime.InteropServices; 2 | 3 | [assembly: System.Windows.ThemeInfo(System.Windows.ResourceDictionaryLocation.None, System.Windows.ResourceDictionaryLocation.SourceAssembly)] 4 | 5 | // In SDK-style projects such as this one, several assembly attributes that were historically 6 | // defined in this file are now automatically added during build and populated with 7 | // values defined in project properties. For details of which attributes are included 8 | // and how to customise this process see: https://aka.ms/assembly-info-properties 9 | 10 | 11 | // Setting ComVisible to false makes the types in this assembly not visible to COM 12 | // components. If you need to access a type in this assembly from COM, set the ComVisible 13 | // attribute to true on that type. 14 | 15 | [assembly: ComVisible(false)] 16 | 17 | // The following GUID is for the ID of the typelib if this project is exposed to COM. 18 | 19 | [assembly: Guid("0a0c88b8-62e1-4e4a-a046-e172351f3111")] 20 | -------------------------------------------------------------------------------- /src/DemoApp/Starter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace DemoApp 8 | { 9 | internal class Starter 10 | { 11 | [STAThread] 12 | private static void Main(string[] args) 13 | { 14 | App app = new(); 15 | app.Run(); 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/DemoApp/Themes/Generic.xaml: -------------------------------------------------------------------------------- 1 | 6 | 7 | 28 | 29 | -------------------------------------------------------------------------------- /src/DemoApp/UI/Views/VickyWindow.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using System.Windows; 7 | using System.Windows.Controls; 8 | using System.Windows.Data; 9 | using System.Windows.Documents; 10 | using System.Windows.Input; 11 | using System.Windows.Media; 12 | using System.Windows.Media.Imaging; 13 | using System.Windows.Navigation; 14 | using System.Windows.Shapes; 15 | 16 | namespace DemoApp.UI.Views 17 | { 18 | public class VickyWindow : Window 19 | { 20 | static VickyWindow() 21 | { 22 | DefaultStyleKeyProperty.OverrideMetadata(typeof(VickyWindow), new FrameworkPropertyMetadata(typeof(VickyWindow))); 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/SmartDateControl/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Runtime.InteropServices; 2 | 3 | [assembly: System.Windows.ThemeInfo(System.Windows.ResourceDictionaryLocation.None, System.Windows.ResourceDictionaryLocation.SourceAssembly)] 4 | 5 | // In SDK-style projects such as this one, several assembly attributes that were historically 6 | // defined in this file are now automatically added during build and populated with 7 | // values defined in project properties. For details of which attributes are included 8 | // and how to customise this process see: https://aka.ms/assembly-info-properties 9 | 10 | 11 | // Setting ComVisible to false makes the types in this assembly not visible to COM 12 | // components. If you need to access a type in this assembly from COM, set the ComVisible 13 | // attribute to true on that type. 14 | 15 | [assembly: ComVisible(false)] 16 | 17 | // The following GUID is for the ID of the typelib if this project is exposed to COM. 18 | 19 | [assembly: Guid("b09bd711-39bc-4e36-ab4e-0ea5c1e5557f")] 20 | -------------------------------------------------------------------------------- /src/SmartDateControl/SmartDateControl.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | net8.0-windows 5 | true 6 | enable 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /src/SmartDateControl/Themes/Generic.xaml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /src/SmartDateControl/Themes/Units/CalendarBox.xaml: -------------------------------------------------------------------------------- 1 | 6 | 7 | 24 | 25 | -------------------------------------------------------------------------------- /src/SmartDateControl/Themes/Units/CalendarBoxItem.xaml: -------------------------------------------------------------------------------- 1 | 5 | 6 | 11 | 12 | 37 | 38 | -------------------------------------------------------------------------------- /src/SmartDateControl/Themes/Units/CalendarSwitch.xaml: -------------------------------------------------------------------------------- 1 | 5 | 6 | M9,10V12H7V10H9M13,10V12H11V10H13M17,10V12H15V10H17M19,3A2,2 0 0,1 21,5V19A2,2 0 0,1 19,21H5C3.89,21 3,20.1 3,19V5A2,2 0 0,1 5,3H6V1H8V3H16V1H18V3H19M19,19V8H5V19H19M9,14V16H7V14H9M13,14V16H11V14H13M17,14V16H15V14H17Z 7 | M19,19H5V8H19M16,1V3H8V1H6V3H5C3.89,3 3,3.89 3,5V19A2,2 0 0,0 5,21H19A2,2 0 0,0 21,19V5C21,3.89 20.1,3 19,3H18V1M17,12H12V17H17V12Z 8 | 9 | 17 | 18 | 24 | 25 | 52 | 53 | -------------------------------------------------------------------------------- /src/SmartDateControl/Themes/Units/ChevronButton.xaml: -------------------------------------------------------------------------------- 1 | 6 | 7 | M15.41,16.58L10.83,12L15.41,7.41L14,6L8,12L14,18L15.41,16.58Z 8 | M8.59,16.58L13.17,12L8.59,7.41L10,6L16,12L10,18L8.59,16.58Z 9 | 10 | 37 | 38 | -------------------------------------------------------------------------------- /src/SmartDateControl/Themes/Units/DayOfWeek.xaml: -------------------------------------------------------------------------------- 1 | 5 | 6 | 22 | 23 | -------------------------------------------------------------------------------- /src/SmartDateControl/Themes/Units/SmartDate.xaml: -------------------------------------------------------------------------------- 1 | 6 | 7 | 15 | 16 | 54 | 55 | -------------------------------------------------------------------------------- /src/SmartDateControl/UI/Units/CalendarBox.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using System.Windows; 7 | using System.Windows.Controls; 8 | using System.Windows.Data; 9 | using System.Windows.Documents; 10 | using System.Windows.Input; 11 | using System.Windows.Media; 12 | using System.Windows.Media.Imaging; 13 | using System.Windows.Navigation; 14 | using System.Windows.Shapes; 15 | 16 | namespace SmartDateControl.UI.Units 17 | { 18 | public class CalendarBox : ListBox 19 | { 20 | static CalendarBox() 21 | { 22 | DefaultStyleKeyProperty.OverrideMetadata(typeof(CalendarBox), new FrameworkPropertyMetadata(typeof(CalendarBox))); 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/SmartDateControl/UI/Units/CalendarBoxItem.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using System.Windows; 7 | using System.Windows.Controls; 8 | using System.Windows.Data; 9 | using System.Windows.Documents; 10 | using System.Windows.Input; 11 | using System.Windows.Media; 12 | using System.Windows.Media.Imaging; 13 | using System.Windows.Navigation; 14 | using System.Windows.Shapes; 15 | 16 | namespace SmartDateControl.UI.Units 17 | { 18 | public class CalendarBoxItem : ListBoxItem 19 | { 20 | public string DateFormat { get; set; } 21 | 22 | public DateTime Date; 23 | public bool IsCurrentMonth 24 | { 25 | get { return (bool)GetValue(IsCurrentMonthProperty); } 26 | set { SetValue(IsCurrentMonthProperty, value); } 27 | } 28 | 29 | public static readonly DependencyProperty IsCurrentMonthProperty = 30 | DependencyProperty.Register("IsCurrentMonth", typeof(bool), typeof(CalendarBoxItem), new PropertyMetadata(false)); 31 | 32 | 33 | static CalendarBoxItem() 34 | { 35 | DefaultStyleKeyProperty.OverrideMetadata(typeof(CalendarBoxItem), new FrameworkPropertyMetadata(typeof(CalendarBoxItem))); 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/SmartDateControl/UI/Units/CalendarSwitch.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using System.Windows; 7 | using System.Windows.Controls; 8 | using System.Windows.Controls.Primitives; 9 | using System.Windows.Data; 10 | using System.Windows.Documents; 11 | using System.Windows.Input; 12 | using System.Windows.Media; 13 | using System.Windows.Media.Imaging; 14 | using System.Windows.Navigation; 15 | using System.Windows.Shapes; 16 | 17 | namespace SmartDateControl.UI.Units 18 | { 19 | public class CalendarSwitch : ToggleButton 20 | { 21 | static CalendarSwitch() 22 | { 23 | DefaultStyleKeyProperty.OverrideMetadata(typeof(CalendarSwitch), new FrameworkPropertyMetadata(typeof(CalendarSwitch))); 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/SmartDateControl/UI/Units/ChevronButton.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using System.Windows; 7 | using System.Windows.Controls; 8 | using System.Windows.Data; 9 | using System.Windows.Documents; 10 | using System.Windows.Input; 11 | using System.Windows.Media; 12 | using System.Windows.Media.Imaging; 13 | using System.Windows.Navigation; 14 | using System.Windows.Shapes; 15 | 16 | namespace SmartDateControl.UI.Units 17 | { 18 | public class ChevronButton : Button 19 | { 20 | static ChevronButton() 21 | { 22 | DefaultStyleKeyProperty.OverrideMetadata(typeof(ChevronButton), new FrameworkPropertyMetadata(typeof(ChevronButton))); 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/SmartDateControl/UI/Units/DayOfWeek.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using System.Windows; 7 | using System.Windows.Controls; 8 | using System.Windows.Data; 9 | using System.Windows.Documents; 10 | using System.Windows.Input; 11 | using System.Windows.Media; 12 | using System.Windows.Media.Imaging; 13 | using System.Windows.Navigation; 14 | using System.Windows.Shapes; 15 | 16 | namespace SmartDateControl.UI.Units 17 | { 18 | public class DayOfWeek : Label 19 | { 20 | static DayOfWeek() 21 | { 22 | DefaultStyleKeyProperty.OverrideMetadata(typeof(DayOfWeek), new FrameworkPropertyMetadata(typeof(DayOfWeek))); 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/SmartDateControl/UI/Units/SmartDate.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using System.Windows; 7 | using System.Windows.Controls; 8 | using System.Windows.Controls.Primitives; 9 | using System.Windows.Data; 10 | using System.Windows.Documents; 11 | using System.Windows.Input; 12 | using System.Windows.Media; 13 | using System.Windows.Media.Imaging; 14 | using System.Windows.Navigation; 15 | using System.Windows.Shapes; 16 | 17 | namespace SmartDateControl.UI.Units 18 | { 19 | public class SmartDate : Control 20 | { 21 | private Popup _popup; 22 | private CalendarSwitch _switch; 23 | private CalendarBox _listbox; 24 | 25 | public bool KeepPopupOpen 26 | { 27 | get { return (bool)GetValue(KeepPopupOpenProperty); } 28 | set { SetValue(KeepPopupOpenProperty, value); } 29 | } 30 | 31 | public static readonly DependencyProperty KeepPopupOpenProperty = 32 | DependencyProperty.Register("KeepPopupOpen", typeof(bool), typeof(SmartDate), new PropertyMetadata(true)); 33 | 34 | 35 | 36 | public DateTime CurrentMonth 37 | { 38 | get { return (DateTime)GetValue(CurrentMonthProperty); } 39 | set { SetValue(CurrentMonthProperty, value); } 40 | } 41 | 42 | public static readonly DependencyProperty CurrentMonthProperty = 43 | DependencyProperty.Register("CurrentMonth", typeof(DateTime), typeof(SmartDate), new PropertyMetadata(null)); 44 | 45 | 46 | 47 | public DateTime? SelectedDate 48 | { 49 | get { return (DateTime?)GetValue(SelectedDateProperty); } 50 | set { SetValue(SelectedDateProperty, value); } 51 | } 52 | 53 | public static readonly DependencyProperty SelectedDateProperty = 54 | DependencyProperty.Register("SelectedDate", typeof(DateTime?), typeof(SmartDate), new PropertyMetadata(null)); 55 | 56 | 57 | 58 | static SmartDate() 59 | { 60 | DefaultStyleKeyProperty.OverrideMetadata(typeof(SmartDate), new FrameworkPropertyMetadata(typeof(SmartDate))); 61 | } 62 | public override void OnApplyTemplate() 63 | { 64 | base.OnApplyTemplate(); 65 | 66 | _popup = (Popup)GetTemplateChild("PART_Popup"); 67 | _switch = (CalendarSwitch)GetTemplateChild("PART_Switch"); 68 | _listbox = (CalendarBox)GetTemplateChild("PART_ListBox"); 69 | ChevronButton leftButton = (ChevronButton)GetTemplateChild("PART_Left"); 70 | ChevronButton rightButton = (ChevronButton)GetTemplateChild("PART_Right"); 71 | 72 | _popup.Closed += _popup_Closed; 73 | _switch.Click += _switch_Click; 74 | _listbox.MouseLeftButtonUp += _listbox_MouseLeftButtonUp; 75 | 76 | leftButton.Click += (s, e) => MoveMonthClick(-1); 77 | rightButton.Click += (s, e) => MoveMonthClick(1); 78 | 79 | } 80 | 81 | private void MoveMonthClick(int month) 82 | { 83 | GenerateCalendar(CurrentMonth.AddMonths(month)); 84 | } 85 | 86 | private void _popup_Closed(object sender, EventArgs e) 87 | { 88 | _switch.IsChecked = IsMouseOver; 89 | } 90 | 91 | private void _switch_Click(object sender, RoutedEventArgs e) 92 | { 93 | if (_switch.IsChecked == true) 94 | { 95 | _popup.IsOpen = true; 96 | 97 | GenerateCalendar(SelectedDate ?? DateTime.Now); 98 | } 99 | } 100 | 101 | private void _listbox_MouseLeftButtonUp(object sender, MouseButtonEventArgs e) 102 | { 103 | if (_listbox.SelectedItem is CalendarBoxItem selected) 104 | { 105 | SelectedDate = selected.Date; 106 | GenerateCalendar(selected.Date); 107 | 108 | _popup.IsOpen = KeepPopupOpen; 109 | } 110 | } 111 | private void GenerateCalendar(DateTime current) 112 | { 113 | if (current.ToString("yyyyMM") == CurrentMonth.ToString("yyyyMM")) return; 114 | 115 | CurrentMonth = current; 116 | _listbox.Items.Clear(); 117 | DateTime fDayOfMonth = new(current.Year,current.Month,1); 118 | DateTime lDayOfMonth = fDayOfMonth.AddMonths(1).AddDays(-1); 119 | 120 | int fOffset = (int)fDayOfMonth.DayOfWeek; 121 | int lOffset = 6 - (int)lDayOfMonth.DayOfWeek; 122 | 123 | DateTime fDay = fDayOfMonth.AddDays(-fOffset); 124 | DateTime lDay = lDayOfMonth.AddDays(lOffset); 125 | 126 | for (DateTime day = fDay; day <= lDay; day = day.AddDays(1)) 127 | { 128 | CalendarBoxItem boxItem = new(); 129 | boxItem.Date = day; 130 | boxItem.DateFormat = day.ToString("yyyyMMdd"); 131 | boxItem.Content = day.Day; 132 | boxItem.IsCurrentMonth = day.Month == current.Month; 133 | 134 | _listbox.Items.Add(boxItem); 135 | } 136 | if (SelectedDate != null) 137 | { 138 | _listbox.SelectedValue = SelectedDate.Value.ToString("yyyyMMdd"); 139 | } 140 | } 141 | } 142 | } 143 | --------------------------------------------------------------------------------