├── .gitattributes ├── .gitignore ├── LICENSE.txt ├── Maui.NullableDateTimePicker.Samples ├── App.xaml ├── App.xaml.cs ├── AppShell.xaml ├── AppShell.xaml.cs ├── MainPage.xaml ├── MainPage.xaml.cs ├── Maui.NullableDateTimePicker.Samples.csproj ├── MauiProgram.cs ├── NewPage.xaml ├── NewPage.xaml.cs ├── Platforms │ ├── Android │ │ ├── AndroidManifest.xml │ │ ├── MainActivity.cs │ │ ├── MainApplication.cs │ │ └── Resources │ │ │ └── values │ │ │ └── colors.xml │ ├── MacCatalyst │ │ ├── AppDelegate.cs │ │ ├── Info.plist │ │ └── Program.cs │ ├── Tizen │ │ ├── Main.cs │ │ └── tizen-manifest.xml │ ├── Windows │ │ ├── App.xaml │ │ ├── App.xaml.cs │ │ ├── Package.appxmanifest │ │ └── app.manifest │ └── iOS │ │ ├── AppDelegate.cs │ │ ├── Info.plist │ │ └── Program.cs ├── Properties │ └── launchSettings.json └── Resources │ ├── AppIcon │ ├── appicon.svg │ └── appiconfg.svg │ ├── Fonts │ ├── OpenSans-Regular.ttf │ ├── OpenSans-Semibold.ttf │ └── fa-solid-900.ttf │ ├── Images │ └── dotnet_bot.svg │ ├── Raw │ └── AboutAssets.txt │ ├── Splash │ └── splash.svg │ └── Styles │ ├── Colors.xaml │ └── Styles.xaml ├── Maui.NullableDateTimePicker.sln ├── Maui.NullableDateTimePicker ├── AppBuilderExtensions.cs ├── AssemblyInfo.cs ├── Controls │ ├── NullableDateTimePickerEntry.cs │ └── NullableDateTimePickerSelectList.cs ├── DefaultStyles.cs ├── Enums │ ├── PickerModes.cs │ └── PopupButtons.cs ├── Helpers │ ├── MainThreadHelper.cs │ └── PopupResultTask.cs ├── Images │ ├── date_icon.png │ ├── date_time_icon.png │ └── time_icon.png ├── Interfaces │ └── INullableDateTimePickerOptions.cs ├── Maui.NullableDateTimePicker.csproj ├── Models │ ├── DateTimeChangedEventArgs.cs │ ├── MonthModel.cs │ ├── NullableDateTimePickerOptions.cs │ ├── PickerItem.cs │ ├── PopupResult.cs │ └── YearModel.cs ├── NullableDateTimePicker.cs └── Popup │ ├── NullableDateTimePickerContent.cs │ └── NullableDateTimePickerPopup.cs ├── README.md └── screenshot.png /.gitattributes: -------------------------------------------------------------------------------- 1 | ############################################################################### 2 | # Set default behavior to automatically normalize line endings. 3 | ############################################################################### 4 | * text=auto 5 | 6 | ############################################################################### 7 | # Set default behavior for command prompt diff. 8 | # 9 | # This is need for earlier builds of msysgit that does not have it on by 10 | # default for csharp files. 11 | # Note: This is only used by command line 12 | ############################################################################### 13 | #*.cs diff=csharp 14 | 15 | ############################################################################### 16 | # Set the merge driver for project and solution files 17 | # 18 | # Merging from the command prompt will add diff markers to the files if there 19 | # are conflicts (Merging from VS is not affected by the settings below, in VS 20 | # the diff markers are never inserted). Diff markers may cause the following 21 | # file extensions to fail to load in VS. An alternative would be to treat 22 | # these files as binary and thus will always conflict and require user 23 | # intervention with every merge. To do so, just uncomment the entries below 24 | ############################################################################### 25 | #*.sln merge=binary 26 | #*.csproj merge=binary 27 | #*.vbproj merge=binary 28 | #*.vcxproj merge=binary 29 | #*.vcproj merge=binary 30 | #*.dbproj merge=binary 31 | #*.fsproj merge=binary 32 | #*.lsproj merge=binary 33 | #*.wixproj merge=binary 34 | #*.modelproj merge=binary 35 | #*.sqlproj merge=binary 36 | #*.wwaproj merge=binary 37 | 38 | ############################################################################### 39 | # behavior for image files 40 | # 41 | # image files are treated as binary by default. 42 | ############################################################################### 43 | #*.jpg binary 44 | #*.png binary 45 | #*.gif binary 46 | 47 | ############################################################################### 48 | # diff behavior for common document formats 49 | # 50 | # Convert binary document formats to text before diffing them. This feature 51 | # is only available from the command line. Turn it on by uncommenting the 52 | # entries below. 53 | ############################################################################### 54 | #*.doc diff=astextplain 55 | #*.DOC diff=astextplain 56 | #*.docx diff=astextplain 57 | #*.DOCX diff=astextplain 58 | #*.dot diff=astextplain 59 | #*.DOT diff=astextplain 60 | #*.pdf diff=astextplain 61 | #*.PDF diff=astextplain 62 | #*.rtf diff=astextplain 63 | #*.RTF diff=astextplain 64 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | ## 4 | ## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore 5 | 6 | # User-specific files 7 | *.rsuser 8 | *.suo 9 | *.user 10 | *.userosscache 11 | *.sln.docstates 12 | **/bin/ 13 | **/obj/ 14 | 15 | # User-specific files (MonoDevelop/Xamarin Studio) 16 | *.userprefs 17 | 18 | # Mono auto generated files 19 | mono_crash.* 20 | 21 | # Build results 22 | [Dd]ebug/ 23 | [Dd]ebugPublic/ 24 | [Rr]elease/ 25 | [Rr]eleases/ 26 | x64/ 27 | x86/ 28 | [Ww][Ii][Nn]32/ 29 | [Aa][Rr][Mm]/ 30 | [Aa][Rr][Mm]64/ 31 | bld/ 32 | [Bb]in/ 33 | [Oo]bj/ 34 | [Oo]ut/ 35 | [Ll]og/ 36 | [Ll]ogs/ 37 | 38 | # Visual Studio 2015/2017 cache/options directory 39 | .vs/ 40 | # Uncomment if you have tasks that create the project's static files in wwwroot 41 | #wwwroot/ 42 | 43 | # Visual Studio 2017 auto generated files 44 | Generated\ Files/ 45 | 46 | # MSTest test Results 47 | [Tt]est[Rr]esult*/ 48 | [Bb]uild[Ll]og.* 49 | 50 | # NUnit 51 | *.VisualState.xml 52 | TestResult.xml 53 | nunit-*.xml 54 | 55 | # Build Results of an ATL Project 56 | [Dd]ebugPS/ 57 | [Rr]eleasePS/ 58 | dlldata.c 59 | 60 | # Benchmark Results 61 | BenchmarkDotNet.Artifacts/ 62 | 63 | # .NET Core 64 | project.lock.json 65 | project.fragment.lock.json 66 | artifacts/ 67 | 68 | # ASP.NET Scaffolding 69 | ScaffoldingReadMe.txt 70 | 71 | # StyleCop 72 | StyleCopReport.xml 73 | 74 | # Files built by Visual Studio 75 | *_i.c 76 | *_p.c 77 | *_h.h 78 | *.ilk 79 | *.meta 80 | *.obj 81 | *.iobj 82 | *.pch 83 | *.pdb 84 | *.ipdb 85 | *.pgc 86 | *.pgd 87 | *.rsp 88 | *.sbr 89 | *.tlb 90 | *.tli 91 | *.tlh 92 | *.tmp 93 | *.tmp_proj 94 | *_wpftmp.csproj 95 | *.log 96 | *.vspscc 97 | *.vssscc 98 | .builds 99 | *.pidb 100 | *.svclog 101 | *.scc 102 | 103 | # Chutzpah Test files 104 | _Chutzpah* 105 | 106 | # Visual C++ cache files 107 | ipch/ 108 | *.aps 109 | *.ncb 110 | *.opendb 111 | *.opensdf 112 | *.sdf 113 | *.cachefile 114 | *.VC.db 115 | *.VC.VC.opendb 116 | 117 | # Visual Studio profiler 118 | *.psess 119 | *.vsp 120 | *.vspx 121 | *.sap 122 | 123 | # Visual Studio Trace Files 124 | *.e2e 125 | 126 | # TFS 2012 Local Workspace 127 | $tf/ 128 | 129 | # Guidance Automation Toolkit 130 | *.gpState 131 | 132 | # ReSharper is a .NET coding add-in 133 | _ReSharper*/ 134 | *.[Rr]e[Ss]harper 135 | *.DotSettings.user 136 | 137 | # TeamCity is a build add-in 138 | _TeamCity* 139 | 140 | # DotCover is a Code Coverage Tool 141 | *.dotCover 142 | 143 | # AxoCover is a Code Coverage Tool 144 | .axoCover/* 145 | !.axoCover/settings.json 146 | 147 | # Coverlet is a free, cross platform Code Coverage Tool 148 | coverage*.json 149 | coverage*.xml 150 | coverage*.info 151 | 152 | # Visual Studio code coverage results 153 | *.coverage 154 | *.coveragexml 155 | 156 | # NCrunch 157 | _NCrunch_* 158 | .*crunch*.local.xml 159 | nCrunchTemp_* 160 | 161 | # MightyMoose 162 | *.mm.* 163 | AutoTest.Net/ 164 | 165 | # Web workbench (sass) 166 | .sass-cache/ 167 | 168 | # Installshield output folder 169 | [Ee]xpress/ 170 | 171 | # DocProject is a documentation generator add-in 172 | DocProject/buildhelp/ 173 | DocProject/Help/*.HxT 174 | DocProject/Help/*.HxC 175 | DocProject/Help/*.hhc 176 | DocProject/Help/*.hhk 177 | DocProject/Help/*.hhp 178 | DocProject/Help/Html2 179 | DocProject/Help/html 180 | 181 | # Click-Once directory 182 | publish/ 183 | 184 | # Publish Web Output 185 | *.[Pp]ublish.xml 186 | *.azurePubxml 187 | # Note: Comment the next line if you want to checkin your web deploy settings, 188 | # but database connection strings (with potential passwords) will be unencrypted 189 | *.pubxml 190 | *.publishproj 191 | 192 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 193 | # checkin your Azure Web App publish settings, but sensitive information contained 194 | # in these scripts will be unencrypted 195 | PublishScripts/ 196 | 197 | # NuGet Packages 198 | *.nupkg 199 | # NuGet Symbol Packages 200 | *.snupkg 201 | # The packages folder can be ignored because of Package Restore 202 | **/[Pp]ackages/* 203 | # except build/, which is used as an MSBuild target. 204 | !**/[Pp]ackages/build/ 205 | # Uncomment if necessary however generally it will be regenerated when needed 206 | #!**/[Pp]ackages/repositories.config 207 | # NuGet v3's project.json files produces more ignorable files 208 | *.nuget.props 209 | *.nuget.targets 210 | 211 | # Microsoft Azure Build Output 212 | csx/ 213 | *.build.csdef 214 | 215 | # Microsoft Azure Emulator 216 | ecf/ 217 | rcf/ 218 | 219 | # Windows Store app package directories and files 220 | AppPackages/ 221 | BundleArtifacts/ 222 | Package.StoreAssociation.xml 223 | _pkginfo.txt 224 | *.appx 225 | *.appxbundle 226 | *.appxupload 227 | 228 | # Visual Studio cache files 229 | # files ending in .cache can be ignored 230 | *.[Cc]ache 231 | # but keep track of directories ending in .cache 232 | !?*.[Cc]ache/ 233 | 234 | # Others 235 | ClientBin/ 236 | ~$* 237 | *~ 238 | *.dbmdl 239 | *.dbproj.schemaview 240 | *.jfm 241 | *.pfx 242 | *.publishsettings 243 | orleans.codegen.cs 244 | 245 | # Including strong name files can present a security risk 246 | # (https://github.com/github/gitignore/pull/2483#issue-259490424) 247 | #*.snk 248 | 249 | # Since there are multiple workflows, uncomment next line to ignore bower_components 250 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 251 | #bower_components/ 252 | 253 | # RIA/Silverlight projects 254 | Generated_Code/ 255 | 256 | # Backup & report files from converting an old project file 257 | # to a newer Visual Studio version. Backup files are not needed, 258 | # because we have git ;-) 259 | _UpgradeReport_Files/ 260 | Backup*/ 261 | UpgradeLog*.XML 262 | UpgradeLog*.htm 263 | ServiceFabricBackup/ 264 | *.rptproj.bak 265 | 266 | # SQL Server files 267 | *.mdf 268 | *.ldf 269 | *.ndf 270 | 271 | # Business Intelligence projects 272 | *.rdl.data 273 | *.bim.layout 274 | *.bim_*.settings 275 | *.rptproj.rsuser 276 | *- [Bb]ackup.rdl 277 | *- [Bb]ackup ([0-9]).rdl 278 | *- [Bb]ackup ([0-9][0-9]).rdl 279 | 280 | # Microsoft Fakes 281 | FakesAssemblies/ 282 | 283 | # GhostDoc plugin setting file 284 | *.GhostDoc.xml 285 | 286 | # Node.js Tools for Visual Studio 287 | .ntvs_analysis.dat 288 | node_modules/ 289 | 290 | # Visual Studio 6 build log 291 | *.plg 292 | 293 | # Visual Studio 6 workspace options file 294 | *.opt 295 | 296 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.) 297 | *.vbw 298 | 299 | # Visual Studio LightSwitch build output 300 | **/*.HTMLClient/GeneratedArtifacts 301 | **/*.DesktopClient/GeneratedArtifacts 302 | **/*.DesktopClient/ModelManifest.xml 303 | **/*.Server/GeneratedArtifacts 304 | **/*.Server/ModelManifest.xml 305 | _Pvt_Extensions 306 | 307 | # Paket dependency manager 308 | .paket/paket.exe 309 | paket-files/ 310 | 311 | # FAKE - F# Make 312 | .fake/ 313 | 314 | # CodeRush personal settings 315 | .cr/personal 316 | 317 | # Python Tools for Visual Studio (PTVS) 318 | __pycache__/ 319 | *.pyc 320 | 321 | # Cake - Uncomment if you are using it 322 | # tools/** 323 | # !tools/packages.config 324 | 325 | # Tabs Studio 326 | *.tss 327 | 328 | # Telerik's JustMock configuration file 329 | *.jmconfig 330 | 331 | # BizTalk build output 332 | *.btp.cs 333 | *.btm.cs 334 | *.odx.cs 335 | *.xsd.cs 336 | 337 | # OpenCover UI analysis results 338 | OpenCover/ 339 | 340 | # Azure Stream Analytics local run output 341 | ASALocalRun/ 342 | 343 | # MSBuild Binary and Structured Log 344 | *.binlog 345 | 346 | # NVidia Nsight GPU debugger configuration file 347 | *.nvuser 348 | 349 | # MFractors (Xamarin productivity tool) working folder 350 | .mfractor/ 351 | 352 | # Local History for Visual Studio 353 | .localhistory/ 354 | 355 | # BeatPulse healthcheck temp database 356 | healthchecksdb 357 | 358 | # Backup folder for Package Reference Convert tool in Visual Studio 2017 359 | MigrationBackup/ 360 | 361 | # Ionide (cross platform F# VS Code tools) working folder 362 | .ionide/ 363 | 364 | # Fody - auto-generated XML schema 365 | FodyWeavers.xsd 366 | .idea 367 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) [year] [fullname] 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 | -------------------------------------------------------------------------------- /Maui.NullableDateTimePicker.Samples/App.xaml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /Maui.NullableDateTimePicker.Samples/App.xaml.cs: -------------------------------------------------------------------------------- 1 | namespace Maui.NullableDateTimePicker.Samples 2 | { 3 | public partial class App : Application 4 | { 5 | public App() 6 | { 7 | InitializeComponent(); 8 | 9 | MainPage = new AppShell(); 10 | 11 | // Register a handler for unhandled XAML exceptions 12 | 13 | AppDomain.CurrentDomain.UnhandledException += OnUnhandledException; 14 | } 15 | 16 | 17 | // Handler for unhandled XAML exceptions 18 | private void OnUnhandledException(object sender, UnhandledExceptionEventArgs args) 19 | { 20 | // Handle the exception here 21 | Exception exception = (Exception)args.ExceptionObject; 22 | Console.WriteLine($"Unhandled exception occurred: {exception}"); 23 | } 24 | } 25 | } -------------------------------------------------------------------------------- /Maui.NullableDateTimePicker.Samples/AppShell.xaml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /Maui.NullableDateTimePicker.Samples/AppShell.xaml.cs: -------------------------------------------------------------------------------- 1 | namespace Maui.NullableDateTimePicker.Samples 2 | { 3 | public partial class AppShell : Shell 4 | { 5 | public AppShell() 6 | { 7 | InitializeComponent(); 8 | } 9 | } 10 | } -------------------------------------------------------------------------------- /Maui.NullableDateTimePicker.Samples/MainPage.xaml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 11 | 12 | 13 | 17 | 18 | 19 | 20 | 21 | 24 | 25 | 28 | 29 | 30 | 31 | 32 | 33 | 42 | 43 | 44 | 45 | 60 | 61 | 62 | 71 | 72 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 98 | 99 | 107 | 108 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 133 | 134 | 138 | 139 | 140 | 141 | 142 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | -------------------------------------------------------------------------------- /Maui.NullableDateTimePicker.Samples/MainPage.xaml.cs: -------------------------------------------------------------------------------- 1 | using System.Windows.Input; 2 | 3 | namespace Maui.NullableDateTimePicker.Samples; 4 | 5 | public partial class MainPage : ContentPage 6 | { 7 | public MainPage() 8 | { 9 | BindingContext = this; 10 | InitializeComponent(); 11 | themeSwitch.IsToggled = Application.Current.RequestedTheme == AppTheme.Dark; 12 | 13 | // Create Datetimepicker Programmatically 14 | CreateDateTimePickerProgrammatically(); 15 | } 16 | 17 | 18 | DateTime? myDateTime = DateTime.Now; 19 | public DateTime? MyDateTime 20 | { 21 | get => myDateTime; 22 | set 23 | { 24 | myDateTime = value; 25 | OnPropertyChanged(nameof(MyDateTime)); 26 | } 27 | } 28 | 29 | public bool Is12HourFormat => System.Globalization.CultureInfo.CurrentCulture.DateTimeFormat.ShortTimePattern.Contains("tt"); 30 | 31 | private ICommand _OpenModalCommand; 32 | public ICommand OpenModalCommand => _OpenModalCommand ??= new Command(async () => await Navigation.PushModalAsync(new NavigationPage(new NewPage()))); 33 | 34 | // Datepicker programmatically 35 | private void CreateDateTimePickerProgrammatically() 36 | { 37 | NullableDateTimePicker datePicker = new() 38 | { 39 | Mode = PickerModes.Date, 40 | Format = Thread.CurrentThread.CurrentUICulture.DateTimeFormat.ShortDatePattern, 41 | ShowWeekNumbers = true, 42 | ShowOtherMonthDays = true, 43 | HorizontalOptions = LayoutOptions.Fill, 44 | VerticalOptions = LayoutOptions.Center 45 | }; 46 | 47 | datePicker.SetAppThemeColor(NullableDateTimePicker.ForeColorProperty, Colors.Black, Colors.White); 48 | datePicker.SetAppThemeColor(NullableDateTimePicker.BodyBackgroundColorProperty, Colors.White, Colors.Black); 49 | 50 | 51 | // binding 52 | datePicker.BindingContext = this; 53 | datePicker.SetBinding(NullableDateTimePicker.NullableDateTimeProperty, nameof(MyDateTime), BindingMode.TwoWay); 54 | 55 | DateTimePlaceStackLayout.Add(datePicker); 56 | } 57 | 58 | 59 | // Calling nullabledatetimepicker calendar popup directly with own entry and button 60 | private async void DateTimePicker_Clicked(object sender, EventArgs e) 61 | { 62 | INullableDateTimePickerOptions nullableDateTimePickerOptions = new NullableDateTimePickerOptions 63 | { 64 | NullableDateTime = MyDateTime, 65 | Mode = PickerModes.DateTime, 66 | ShowWeekNumbers = true, 67 | CloseOnOutsideClick = true, 68 | PopupBorderCornerRadius = 10, 69 | PopupBorderThemeColor = new CommunityToolkit.Maui.AppThemeColor 70 | { 71 | Light = Colors.Black, 72 | Dark = Colors.White, 73 | }, 74 | PopupBorderWidth = 1, 75 | PopupPadding = 5, 76 | }; 77 | 78 | var result = await NullableDateTimePicker.OpenCalendarAsync(nullableDateTimePickerOptions); 79 | if (result is PopupResult popupResult && popupResult.ButtonResult != PopupButtons.Cancel) 80 | { 81 | MyDateTime = popupResult.NullableDateTime; 82 | //DateTimeEntry.Text = popupResult.NullableDateTime?.ToString("g"); //If you are not using ViewModel 83 | } 84 | } 85 | 86 | public DateTime? MyMinDate => new DateTime(DateTime.Now.Year, DateTime.Now.Month, 10); 87 | public DateTime? MyMaxDate => new DateTime(DateTime.Now.Year, DateTime.Now.Month, 20); 88 | 89 | private void OnThemeToggled(object sender, ToggledEventArgs e) 90 | { 91 | bool isDarkMode = e.Value; 92 | 93 | App.Current.UserAppTheme = isDarkMode ? AppTheme.Dark : AppTheme.Light; 94 | } 95 | } 96 | 97 | static class IconFont 98 | { 99 | public const string Calendar = "\uf133"; 100 | public const string CalendarDay = "\uf783"; 101 | public const string CalendarDays = "\uf073"; 102 | } -------------------------------------------------------------------------------- /Maui.NullableDateTimePicker.Samples/Maui.NullableDateTimePicker.Samples.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net9.0-android;net9.0-ios;net9.0-maccatalyst 5 | $(TargetFrameworks);net9.0-windows10.0.19041.0 6 | 7 | 8 | Exe 9 | Maui.NullableDateTimePicker.Samples 10 | true 11 | true 12 | enable 13 | 14 | 15 | Maui.NullableDateTimePicker.Samples 16 | 17 | 18 | com.companyname.Maui.NullableDateTimePicker.Samples 19 | 0ca67e9a-2d25-41f0-9470-9e5c7d792cd6 20 | 21 | 22 | 1.0 23 | 1 24 | 25 | 26 | 27 | 28 | 15.0 29 | 15.0 30 | 21.0 31 | 10.0.17763.0 32 | 10.0.17763.0 33 | 6.5 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | MSBuild:Compile 66 | 67 | 68 | 69 | -------------------------------------------------------------------------------- /Maui.NullableDateTimePicker.Samples/MauiProgram.cs: -------------------------------------------------------------------------------- 1 | using CommunityToolkit.Maui; 2 | using Microsoft.Extensions.Logging; 3 | using Microsoft.Maui.Platform; 4 | 5 | namespace Maui.NullableDateTimePicker.Samples 6 | { 7 | public static class MauiProgram 8 | { 9 | public static MauiApp CreateMauiApp() 10 | { 11 | var builder = MauiApp.CreateBuilder(); 12 | builder.UseMauiApp(); 13 | builder.UseMauiCommunityToolkit(); 14 | builder.ConfigureNullableDateTimePicker() 15 | .ConfigureFonts(fonts => 16 | { 17 | fonts.AddFont("fa-solid-900.ttf", "FontAwesome"); 18 | fonts.AddFont("OpenSans-Regular.ttf", "OpenSansRegular"); 19 | fonts.AddFont("OpenSans-Semibold.ttf", "OpenSansSemibold"); 20 | }); 21 | #if DEBUG 22 | builder.Logging.AddDebug(); 23 | #endif 24 | 25 | // Remove Entry control underline, padding and background color 26 | Microsoft.Maui.Handlers.EntryHandler.Mapper.AppendToMapping("NullableDateTimePickerEntryCustomization", (handler, view) => 27 | { 28 | if (view is Maui.NullableDateTimePicker.NullableDateTimePickerEntry) 29 | { 30 | #if ANDROID 31 | handler.PlatformView.SetBackgroundColor(Android.Graphics.Color.Transparent); 32 | handler.PlatformView.SetPadding(0, 0, 0, 0); 33 | #if NET8_0_OR_GREATER 34 | handler.PlatformView.BackgroundTintList = Android.Content.Res.ColorStateList.ValueOf(view.Background.ToColor().ToPlatform()); 35 | #endif 36 | #elif IOS || MACCATALYST 37 | handler.PlatformView.BackgroundColor = Colors.Transparent.ToPlatform(); 38 | handler.PlatformView.BorderStyle = UIKit.UITextBorderStyle.None; 39 | #elif WINDOWS 40 | //handler.PlatformView.Background = Colors.Transparent.ToPlatform(); 41 | handler.PlatformView.Background = view.Background.ToColor().ToPlatform(); 42 | handler.PlatformView.Padding = new Microsoft.UI.Xaml.Thickness(0); 43 | handler.PlatformView.BorderThickness = new Microsoft.UI.Xaml.Thickness() 44 | { 45 | Bottom = 0, 46 | Top = 0, 47 | Left = 0, 48 | Right = 0, 49 | }; 50 | #endif 51 | } 52 | }); 53 | 54 | return builder.Build(); 55 | } 56 | } 57 | } -------------------------------------------------------------------------------- /Maui.NullableDateTimePicker.Samples/NewPage.xaml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 11 | 12 | 15 | 19 | 26 | 27 | 31 | 32 | -------------------------------------------------------------------------------- /Maui.NullableDateTimePicker.Samples/NewPage.xaml.cs: -------------------------------------------------------------------------------- 1 | using System.Windows.Input; 2 | 3 | namespace Maui.NullableDateTimePicker.Samples; 4 | 5 | public partial class NewPage : ContentPage 6 | { 7 | public NewPage() 8 | { 9 | BindingContext = this; 10 | InitializeComponent(); 11 | } 12 | 13 | private ICommand _CloseModalCommand; 14 | public ICommand CloseModalCommand => _CloseModalCommand ??= new Command(async () => await Navigation.PopModalAsync()); 15 | 16 | private async void Button_Clicked(object sender, EventArgs e) 17 | { 18 | var result = await NullableDateTimePicker.OpenCalendarAsync(new NullableDateTimePickerOptions 19 | { 20 | Mode = PickerModes.Date, 21 | CloseOnOutsideClick = true, 22 | ShowClearButton = true, 23 | PopupBorderCornerRadius = 10, 24 | PopupBorderThemeColor = new CommunityToolkit.Maui.AppThemeColor 25 | { 26 | Light = Colors.Black, 27 | Dark = Colors.White, 28 | }, 29 | PopupBorderWidth = 1, 30 | PopupPadding = 5, 31 | }); 32 | 33 | if (result is PopupResult popupResult && popupResult.ButtonResult != PopupButtons.Cancel) 34 | { 35 | if (sender is Button button) 36 | { 37 | button.Text = popupResult.NullableDateTime.HasValue ? popupResult.NullableDateTime.Value.ToString("D") : "NULL"; 38 | } 39 | } 40 | } 41 | } -------------------------------------------------------------------------------- /Maui.NullableDateTimePicker.Samples/Platforms/Android/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /Maui.NullableDateTimePicker.Samples/Platforms/Android/MainActivity.cs: -------------------------------------------------------------------------------- 1 | using Android.App; 2 | using Android.Content.PM; 3 | using Android.OS; 4 | 5 | namespace Maui.NullableDateTimePicker.Samples 6 | { 7 | [Activity(Theme = "@style/Maui.SplashTheme", MainLauncher = true, ConfigurationChanges = ConfigChanges.ScreenSize | ConfigChanges.Orientation | ConfigChanges.UiMode | ConfigChanges.ScreenLayout | ConfigChanges.SmallestScreenSize | ConfigChanges.Density)] 8 | public class MainActivity : MauiAppCompatActivity 9 | { 10 | } 11 | } -------------------------------------------------------------------------------- /Maui.NullableDateTimePicker.Samples/Platforms/Android/MainApplication.cs: -------------------------------------------------------------------------------- 1 | using Android.App; 2 | using Android.Runtime; 3 | 4 | namespace Maui.NullableDateTimePicker.Samples 5 | { 6 | [Application] 7 | public class MainApplication : MauiApplication 8 | { 9 | public MainApplication(IntPtr handle, JniHandleOwnership ownership) 10 | : base(handle, ownership) 11 | { 12 | } 13 | 14 | protected override MauiApp CreateMauiApp() => MauiProgram.CreateMauiApp(); 15 | } 16 | } -------------------------------------------------------------------------------- /Maui.NullableDateTimePicker.Samples/Platforms/Android/Resources/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #512BD4 4 | #2B0B98 5 | #2B0B98 6 | -------------------------------------------------------------------------------- /Maui.NullableDateTimePicker.Samples/Platforms/MacCatalyst/AppDelegate.cs: -------------------------------------------------------------------------------- 1 | using Foundation; 2 | 3 | namespace Maui.NullableDateTimePicker.Samples 4 | { 5 | [Register("AppDelegate")] 6 | public class AppDelegate : MauiUIApplicationDelegate 7 | { 8 | protected override MauiApp CreateMauiApp() => MauiProgram.CreateMauiApp(); 9 | } 10 | } -------------------------------------------------------------------------------- /Maui.NullableDateTimePicker.Samples/Platforms/MacCatalyst/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | UIDeviceFamily 6 | 7 | 1 8 | 2 9 | 10 | UIRequiredDeviceCapabilities 11 | 12 | arm64 13 | 14 | UISupportedInterfaceOrientations 15 | 16 | UIInterfaceOrientationPortrait 17 | UIInterfaceOrientationLandscapeLeft 18 | UIInterfaceOrientationLandscapeRight 19 | 20 | UISupportedInterfaceOrientations~ipad 21 | 22 | UIInterfaceOrientationPortrait 23 | UIInterfaceOrientationPortraitUpsideDown 24 | UIInterfaceOrientationLandscapeLeft 25 | UIInterfaceOrientationLandscapeRight 26 | 27 | XSAppIconAssets 28 | Assets.xcassets/appicon.appiconset 29 | 30 | 31 | -------------------------------------------------------------------------------- /Maui.NullableDateTimePicker.Samples/Platforms/MacCatalyst/Program.cs: -------------------------------------------------------------------------------- 1 | using ObjCRuntime; 2 | using UIKit; 3 | 4 | namespace Maui.NullableDateTimePicker.Samples 5 | { 6 | public class Program 7 | { 8 | // This is the main entry point of the application. 9 | static void Main(string[] args) 10 | { 11 | // if you want to use a different Application Delegate class from "AppDelegate" 12 | // you can specify it here. 13 | UIApplication.Main(args, null, typeof(AppDelegate)); 14 | } 15 | } 16 | } -------------------------------------------------------------------------------- /Maui.NullableDateTimePicker.Samples/Platforms/Tizen/Main.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Maui; 2 | using Microsoft.Maui.Hosting; 3 | using System; 4 | 5 | namespace Maui.NullableDateTimePicker.Samples 6 | { 7 | internal class Program : MauiApplication 8 | { 9 | protected override MauiApp CreateMauiApp() => MauiProgram.CreateMauiApp(); 10 | 11 | static void Main(string[] args) 12 | { 13 | var app = new Program(); 14 | app.Run(args); 15 | } 16 | } 17 | } -------------------------------------------------------------------------------- /Maui.NullableDateTimePicker.Samples/Platforms/Tizen/tizen-manifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | maui-application-title-placeholder 6 | maui-appicon-placeholder 7 | 8 | 9 | 10 | 11 | http://tizen.org/privilege/internet 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /Maui.NullableDateTimePicker.Samples/Platforms/Windows/App.xaml: -------------------------------------------------------------------------------- 1 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /Maui.NullableDateTimePicker.Samples/Platforms/Windows/App.xaml.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.UI.Xaml; 2 | 3 | // To learn more about WinUI, the WinUI project structure, 4 | // and more about our project templates, see: http://aka.ms/winui-project-info. 5 | 6 | namespace Maui.NullableDateTimePicker.Samples.WinUI 7 | { 8 | /// 9 | /// Provides application-specific behavior to supplement the default Application class. 10 | /// 11 | public partial class App : MauiWinUIApplication 12 | { 13 | /// 14 | /// Initializes the singleton application object. This is the first line of authored code 15 | /// executed, and as such is the logical equivalent of main() or WinMain(). 16 | /// 17 | public App() 18 | { 19 | this.InitializeComponent(); 20 | } 21 | 22 | protected override MauiApp CreateMauiApp() => MauiProgram.CreateMauiApp(); 23 | } 24 | } -------------------------------------------------------------------------------- /Maui.NullableDateTimePicker.Samples/Platforms/Windows/Package.appxmanifest: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | $placeholder$ 15 | User Name 16 | $placeholder$.png 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | -------------------------------------------------------------------------------- /Maui.NullableDateTimePicker.Samples/Platforms/Windows/app.manifest: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 11 | true/PM 12 | PerMonitorV2, PerMonitor 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /Maui.NullableDateTimePicker.Samples/Platforms/iOS/AppDelegate.cs: -------------------------------------------------------------------------------- 1 | using Foundation; 2 | 3 | namespace Maui.NullableDateTimePicker.Samples 4 | { 5 | [Register("AppDelegate")] 6 | public class AppDelegate : MauiUIApplicationDelegate 7 | { 8 | protected override MauiApp CreateMauiApp() => MauiProgram.CreateMauiApp(); 9 | } 10 | } -------------------------------------------------------------------------------- /Maui.NullableDateTimePicker.Samples/Platforms/iOS/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | LSRequiresIPhoneOS 6 | 7 | UIDeviceFamily 8 | 9 | 1 10 | 2 11 | 12 | UIRequiredDeviceCapabilities 13 | 14 | arm64 15 | 16 | UISupportedInterfaceOrientations 17 | 18 | UIInterfaceOrientationPortrait 19 | UIInterfaceOrientationLandscapeLeft 20 | UIInterfaceOrientationLandscapeRight 21 | 22 | UISupportedInterfaceOrientations~ipad 23 | 24 | UIInterfaceOrientationPortrait 25 | UIInterfaceOrientationPortraitUpsideDown 26 | UIInterfaceOrientationLandscapeLeft 27 | UIInterfaceOrientationLandscapeRight 28 | 29 | XSAppIconAssets 30 | Assets.xcassets/appicon.appiconset 31 | 32 | 33 | -------------------------------------------------------------------------------- /Maui.NullableDateTimePicker.Samples/Platforms/iOS/Program.cs: -------------------------------------------------------------------------------- 1 | using ObjCRuntime; 2 | using UIKit; 3 | 4 | namespace Maui.NullableDateTimePicker.Samples 5 | { 6 | public class Program 7 | { 8 | // This is the main entry point of the application. 9 | static void Main(string[] args) 10 | { 11 | // if you want to use a different Application Delegate class from "AppDelegate" 12 | // you can specify it here. 13 | UIApplication.Main(args, null, typeof(AppDelegate)); 14 | } 15 | } 16 | } -------------------------------------------------------------------------------- /Maui.NullableDateTimePicker.Samples/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "profiles": { 3 | "Windows Machine": { 4 | "commandName": "MsixPackage", 5 | "nativeDebugging": false 6 | } 7 | } 8 | } -------------------------------------------------------------------------------- /Maui.NullableDateTimePicker.Samples/Resources/AppIcon/appicon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /Maui.NullableDateTimePicker.Samples/Resources/AppIcon/appiconfg.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Maui.NullableDateTimePicker.Samples/Resources/Fonts/OpenSans-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sebarslan/Maui.NullableDateTimePicker/5acac06b1c4366c28a434ee59c26bda8afe7baa8/Maui.NullableDateTimePicker.Samples/Resources/Fonts/OpenSans-Regular.ttf -------------------------------------------------------------------------------- /Maui.NullableDateTimePicker.Samples/Resources/Fonts/OpenSans-Semibold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sebarslan/Maui.NullableDateTimePicker/5acac06b1c4366c28a434ee59c26bda8afe7baa8/Maui.NullableDateTimePicker.Samples/Resources/Fonts/OpenSans-Semibold.ttf -------------------------------------------------------------------------------- /Maui.NullableDateTimePicker.Samples/Resources/Fonts/fa-solid-900.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sebarslan/Maui.NullableDateTimePicker/5acac06b1c4366c28a434ee59c26bda8afe7baa8/Maui.NullableDateTimePicker.Samples/Resources/Fonts/fa-solid-900.ttf -------------------------------------------------------------------------------- /Maui.NullableDateTimePicker.Samples/Resources/Images/dotnet_bot.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | -------------------------------------------------------------------------------- /Maui.NullableDateTimePicker.Samples/Resources/Raw/AboutAssets.txt: -------------------------------------------------------------------------------- 1 | Any raw assets you want to be deployed with your application can be placed in 2 | this directory (and child directories). Deployment of the asset to your application 3 | is automatically handled by the following `MauiAsset` Build Action within your `.csproj`. 4 | 5 | 6 | 7 | These files will be deployed with you package and will be accessible using Essentials: 8 | 9 | async Task LoadMauiAsset() 10 | { 11 | using var stream = await FileSystem.OpenAppPackageFileAsync("AboutAssets.txt"); 12 | using var reader = new StreamReader(stream); 13 | 14 | var contents = reader.ReadToEnd(); 15 | } 16 | -------------------------------------------------------------------------------- /Maui.NullableDateTimePicker.Samples/Resources/Splash/splash.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Maui.NullableDateTimePicker.Samples/Resources/Styles/Colors.xaml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 7 | #512BD4 8 | #DFD8F7 9 | #2B0B98 10 | White 11 | Black 12 | #E1E1E1 13 | #C8C8C8 14 | #ACACAC 15 | #919191 16 | #6E6E6E 17 | #404040 18 | #212121 19 | #141414 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | #F7B548 35 | #FFD590 36 | #FFE5B9 37 | #28C2D1 38 | #7BDDEF 39 | #C3F2F4 40 | #3E8EED 41 | #72ACF1 42 | #A7CBF6 43 | 44 | -------------------------------------------------------------------------------- /Maui.NullableDateTimePicker.Samples/Resources/Styles/Styles.xaml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 7 | 10 | 11 | 15 | 16 | 21 | 22 | 25 | 26 | 27 | 28 | 51 | 52 | 69 | 70 | 90 | 91 | 112 | 113 | 134 | 135 | 140 | 141 | 161 | 162 | 180 | 181 | 185 | 186 | 208 | 209 | 224 | 225 | 245 | 246 | 249 | 250 | 273 | 274 | 294 | 295 | 301 | 302 | 321 | 322 | 325 | 326 | 354 | 355 | 375 | 376 | 380 | 381 | 393 | 394 | 399 | 400 | 406 | 407 | 410 | 411 | -------------------------------------------------------------------------------- /Maui.NullableDateTimePicker.sln: -------------------------------------------------------------------------------- 1 | 2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 17 4 | VisualStudioVersion = 17.5.33530.505 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Maui.NullableDateTimePicker", "Maui.NullableDateTimePicker\Maui.NullableDateTimePicker.csproj", "{B500469A-CB4A-425E-8322-8AEFADB313A2}" 7 | EndProject 8 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Maui.NullableDateTimePicker.Samples", "Maui.NullableDateTimePicker.Samples\Maui.NullableDateTimePicker.Samples.csproj", "{92E070F8-D50E-4946-A79F-5C0450A63E82}" 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 | {B500469A-CB4A-425E-8322-8AEFADB313A2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 17 | {B500469A-CB4A-425E-8322-8AEFADB313A2}.Debug|Any CPU.Build.0 = Debug|Any CPU 18 | {B500469A-CB4A-425E-8322-8AEFADB313A2}.Release|Any CPU.ActiveCfg = Release|Any CPU 19 | {B500469A-CB4A-425E-8322-8AEFADB313A2}.Release|Any CPU.Build.0 = Release|Any CPU 20 | {92E070F8-D50E-4946-A79F-5C0450A63E82}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 21 | {92E070F8-D50E-4946-A79F-5C0450A63E82}.Debug|Any CPU.Build.0 = Debug|Any CPU 22 | {92E070F8-D50E-4946-A79F-5C0450A63E82}.Debug|Any CPU.Deploy.0 = Debug|Any CPU 23 | {92E070F8-D50E-4946-A79F-5C0450A63E82}.Release|Any CPU.ActiveCfg = Release|Any CPU 24 | {92E070F8-D50E-4946-A79F-5C0450A63E82}.Release|Any CPU.Build.0 = Release|Any CPU 25 | {92E070F8-D50E-4946-A79F-5C0450A63E82}.Release|Any CPU.Deploy.0 = Release|Any CPU 26 | EndGlobalSection 27 | GlobalSection(SolutionProperties) = preSolution 28 | HideSolutionNode = FALSE 29 | EndGlobalSection 30 | GlobalSection(ExtensibilityGlobals) = postSolution 31 | SolutionGuid = {C9C3C9D3-1544-4E01-90A1-D09241F7F1C1} 32 | EndGlobalSection 33 | EndGlobal 34 | -------------------------------------------------------------------------------- /Maui.NullableDateTimePicker/AppBuilderExtensions.cs: -------------------------------------------------------------------------------- 1 | using CommunityToolkit.Maui; 2 | #if WINDOWS 3 | using Mopups.Hosting; 4 | #endif 5 | 6 | namespace Maui.NullableDateTimePicker 7 | { 8 | public static class AppBuilderExtensions 9 | { 10 | public static MauiAppBuilder ConfigureNullableDateTimePicker(this MauiAppBuilder builder) 11 | { 12 | #if WINDOWS 13 | builder.ConfigureMopups(); 14 | #endif 15 | 16 | #if DEBUG 17 | builder.UseMauiCommunityToolkit(); 18 | #else 19 | builder.UseMauiCommunityToolkit(options => 20 | { 21 | options.SetShouldSuppressExceptionsInConverters(true); 22 | options.SetShouldSuppressExceptionsInBehaviors(true); 23 | options.SetShouldSuppressExceptionsInAnimations(true); 24 | }); 25 | #endif 26 | return builder; 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /Maui.NullableDateTimePicker/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Runtime.CompilerServices; 2 | [assembly: InternalsVisibleTo("Maui.NullableDateTimePicker.Tests")] -------------------------------------------------------------------------------- /Maui.NullableDateTimePicker/Controls/NullableDateTimePickerEntry.cs: -------------------------------------------------------------------------------- 1 | namespace Maui.NullableDateTimePicker; 2 | 3 | public class NullableDateTimePickerEntry : Entry 4 | { 5 | } 6 | -------------------------------------------------------------------------------- /Maui.NullableDateTimePicker/Controls/NullableDateTimePickerSelectList.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Maui.Controls.Shapes; 2 | using System.Collections; 3 | using System.Runtime.CompilerServices; 4 | 5 | namespace Maui.NullableDateTimePicker; 6 | 7 | internal class NullableDateTimePickerSelectList : ContentView 8 | { 9 | public event EventHandler SelectedIndexChanged; 10 | public event EventHandler Closed; 11 | 12 | #region BindableProperties 13 | // ItemsSource BindableProperty 14 | public static readonly BindableProperty ItemsSourceProperty = 15 | BindableProperty.Create(nameof(ItemsSource), typeof(IList), typeof(NullableDateTimePickerSelectList), null, propertyChanged: OnItemsSourceChanged); 16 | 17 | public IList ItemsSource 18 | { 19 | get => (IList)GetValue(ItemsSourceProperty); 20 | set => SetValue(ItemsSourceProperty, value); 21 | } 22 | 23 | // SelectedIndex BindableProperty 24 | public static readonly BindableProperty SelectedIndexProperty = 25 | BindableProperty.Create(nameof(SelectedIndex), typeof(int), typeof(NullableDateTimePickerSelectList), -1, BindingMode.TwoWay, propertyChanged: OnSelectedIndexChanged); 26 | 27 | public int SelectedIndex 28 | { 29 | get => (int)GetValue(SelectedIndexProperty); 30 | set => SetValue(SelectedIndexProperty, value); 31 | } 32 | 33 | // SelectedItem BindableProperty 34 | public static readonly BindableProperty SelectedItemProperty = 35 | BindableProperty.Create(nameof(SelectedItem), typeof(object), typeof(NullableDateTimePickerSelectList), null, BindingMode.TwoWay, propertyChanged: OnSelectedItemChanged); 36 | 37 | public object SelectedItem 38 | { 39 | get => GetValue(SelectedItemProperty); 40 | set => SetValue(SelectedItemProperty, value); 41 | } 42 | 43 | // ItemDisplayBinding BindableProperty 44 | public static readonly BindableProperty ItemDisplayBindingProperty = 45 | BindableProperty.Create(nameof(ItemDisplayBinding), typeof(string), typeof(NullableDateTimePickerSelectList), null); 46 | 47 | public string ItemDisplayBinding 48 | { 49 | get => (string)GetValue(ItemDisplayBindingProperty); 50 | set => SetValue(ItemDisplayBindingProperty, value); 51 | } 52 | 53 | // ItemTextColor BindableProperty 54 | public static readonly BindableProperty ItemTextColorProperty = 55 | BindableProperty.Create( 56 | nameof(ItemTextColor), // Property name 57 | typeof(Color), // Property type 58 | typeof(NullableDateTimePickerSelectList), // Declaring type 59 | Colors.Black, // Default value 60 | BindingMode.OneWay // Binding mode 61 | ); 62 | 63 | public Color ItemTextColor 64 | { 65 | get => (Color)GetValue(ItemTextColorProperty); 66 | set => SetValue(ItemTextColorProperty, value); 67 | } 68 | 69 | // ItemBackgroundColor BindableProperty 70 | public static readonly BindableProperty ItemBackgroundColorProperty = 71 | BindableProperty.Create( 72 | nameof(ItemBackgroundColor), // Property name 73 | typeof(Color), // Property type 74 | typeof(NullableDateTimePickerSelectList), // Declaring type 75 | Colors.White, // Default value 76 | BindingMode.OneWay // Binding mode 77 | ); 78 | 79 | public Color ItemBackgroundColor 80 | { 81 | get => (Color)GetValue(ItemBackgroundColorProperty); 82 | set => SetValue(ItemBackgroundColorProperty, value); 83 | } 84 | 85 | // SelectedItemTextColor BindableProperty 86 | public static readonly BindableProperty SelectedItemTextColorProperty = 87 | BindableProperty.Create( 88 | nameof(SelectedItemTextColor), // Property name 89 | typeof(Color), // Property type 90 | typeof(NullableDateTimePickerSelectList), // Declaring type 91 | Colors.Black, // Default value 92 | BindingMode.OneWay // Binding mode 93 | ); 94 | 95 | public Color SelectedItemTextColor 96 | { 97 | get => (Color)GetValue(SelectedItemTextColorProperty); 98 | set => SetValue(SelectedItemTextColorProperty, value); 99 | } 100 | // SelectedItemBackgroundColor BindableProperty 101 | public static readonly BindableProperty SelectedItemBackgroundColorProperty = 102 | BindableProperty.Create( 103 | nameof(SelectedItemBackgroundColor), // Property name 104 | typeof(Color), // Property type 105 | typeof(NullableDateTimePickerSelectList), // Declaring type 106 | Colors.LightBlue, // Default value 107 | BindingMode.OneWay // Binding mode 108 | ); 109 | 110 | public Color SelectedItemBackgroundColor 111 | { 112 | get => (Color)GetValue(SelectedItemBackgroundColorProperty); 113 | set => SetValue(SelectedItemBackgroundColorProperty, value); 114 | } 115 | #endregion //BindableProperties 116 | 117 | private readonly CollectionView _collectionView; 118 | 119 | public NullableDateTimePickerSelectList() 120 | { 121 | VerticalOptions = LayoutOptions.Fill; 122 | HorizontalOptions = LayoutOptions.Fill; 123 | 124 | _collectionView = new CollectionView 125 | { 126 | ItemsLayout = new GridItemsLayout(3, ItemsLayoutOrientation.Vertical) 127 | { 128 | HorizontalItemSpacing = DeviceInfo.Platform == DevicePlatform.WinUI ? 2 : 4, 129 | VerticalItemSpacing = DeviceInfo.Platform == DevicePlatform.WinUI ? 2 : 4 130 | }, 131 | SelectionMode = SelectionMode.Single, 132 | HorizontalOptions = LayoutOptions.Fill, 133 | VerticalOptions = LayoutOptions.Fill 134 | }; 135 | 136 | _collectionView.SelectionChanged += OnSelectionChanged; 137 | 138 | // Define the item template 139 | _collectionView.ItemTemplate = new DataTemplate(() => 140 | { 141 | var label = new Label 142 | { 143 | FontSize = 12, 144 | VerticalOptions = LayoutOptions.Fill, 145 | HorizontalOptions = LayoutOptions.Fill, 146 | FontAttributes = FontAttributes.Bold, 147 | VerticalTextAlignment = TextAlignment.Center, 148 | HorizontalTextAlignment = TextAlignment.Center, 149 | Padding = 0, 150 | Margin = 0 151 | }; 152 | 153 | 154 | label.SetBinding(Label.TextProperty, new Binding(ItemDisplayBinding ?? ".")); 155 | 156 | VisualStateManager.SetVisualStateGroups(label, new VisualStateGroupList 157 | { 158 | new VisualStateGroup 159 | { 160 | Name = "CommonStates", 161 | States = 162 | { 163 | new VisualState 164 | { 165 | Name = "Normal", 166 | 167 | Setters = { new Setter { Property = Label.TextColorProperty, Value = ItemTextColor ?? Colors.Black } 168 | } 169 | }, 170 | new VisualState 171 | { 172 | Name = "Selected", 173 | Setters = { 174 | new Setter { Property = Label.TextColorProperty, Value = SelectedItemTextColor ?? Colors.Black } 175 | } 176 | } 177 | } 178 | } 179 | 180 | }); 181 | 182 | var border = new Border 183 | { 184 | StrokeShape = new RoundRectangle 185 | { 186 | CornerRadius = new CornerRadius(5, 5, 5, 5) 187 | }, 188 | Margin = 0, 189 | Padding = 0, 190 | HorizontalOptions = LayoutOptions.Fill, 191 | HeightRequest = 35, 192 | Content = label 193 | }; 194 | 195 | // Define visual states for the border 196 | VisualStateManager.SetVisualStateGroups(border, new VisualStateGroupList 197 | { 198 | new VisualStateGroup 199 | { 200 | Name = "CommonStates", 201 | States = 202 | { 203 | new VisualState 204 | { 205 | Name = "Normal", 206 | Setters = 207 | { 208 | new Setter { Property = Border.BackgroundColorProperty, Value = ItemBackgroundColor ?? Colors.White }, 209 | new Setter { Property = Border.StrokeProperty, Value = Colors.Gray }, 210 | new Setter { Property = Border.StrokeThicknessProperty, Value = 1 } 211 | } 212 | }, 213 | new VisualState 214 | { 215 | Name = "Selected", 216 | Setters = 217 | { 218 | new Setter { Property = Border.BackgroundColorProperty, Value = SelectedItemBackgroundColor ?? Colors.LightBlue }, 219 | new Setter { Property = Border.StrokeProperty, Value = Colors.Blue }, 220 | new Setter { Property = Border.StrokeThicknessProperty, Value = 2 } 221 | } 222 | } 223 | } 224 | } 225 | }); 226 | 227 | return border; 228 | }); 229 | 230 | var grid = new Grid 231 | { 232 | Background = Colors.Transparent, 233 | BackgroundColor = Colors.Transparent, 234 | HorizontalOptions = LayoutOptions.Fill, 235 | VerticalOptions = LayoutOptions.Fill, 236 | RowDefinitions = 237 | { 238 | new RowDefinition { Height = new GridLength(1, GridUnitType.Auto) }, 239 | new RowDefinition { Height = new GridLength(1, GridUnitType.Star) } 240 | } 241 | }; 242 | 243 | var closeButton = new Button 244 | { 245 | Text = "X", 246 | FontAttributes = FontAttributes.Bold, 247 | HorizontalOptions = LayoutOptions.End, 248 | FontSize = 16, 249 | MaximumWidthRequest = DeviceInfo.Platform == DevicePlatform.WinUI ? 40 : 50, 250 | MaximumHeightRequest = DeviceInfo.Platform == DevicePlatform.WinUI ? 20 : 25, 251 | MinimumHeightRequest = DeviceInfo.Platform == DevicePlatform.WinUI ? 20 : 25, 252 | BorderWidth = 1, 253 | WidthRequest = DeviceInfo.Platform == DevicePlatform.WinUI ? 40 : 50, 254 | HeightRequest = DeviceInfo.Platform == DevicePlatform.WinUI ? 20 : 25, 255 | Padding = 0, 256 | Margin = new Thickness(5) 257 | }; 258 | closeButton.Clicked += (s, e) => 259 | { 260 | Closed?.Invoke(this, EventArgs.Empty); 261 | }; 262 | grid.Add(closeButton, 0, 0); 263 | grid.Add(_collectionView, 0, 1); 264 | 265 | Content = grid; 266 | } 267 | 268 | // Update the CollectionView's ItemsSource when ItemsSource changes 269 | private static void OnItemsSourceChanged(BindableObject bindable, object oldValue, object newValue) 270 | { 271 | var control = (NullableDateTimePickerSelectList)bindable; 272 | 273 | control._collectionView.ItemsSource = (IList)newValue; 274 | } 275 | 276 | // Update the CollectionView's selected item when SelectedItem changes 277 | private static void OnSelectedItemChanged(BindableObject bindable, object oldValue, object newValue) 278 | { 279 | var control = (NullableDateTimePickerSelectList)bindable; 280 | 281 | control._collectionView.SelectedItem = newValue; 282 | } 283 | 284 | // Update the selected item in CollectionView when SelectedIndex changes 285 | private static void OnSelectedIndexChanged(BindableObject bindable, object oldValue, object newValue) 286 | { 287 | var control = (NullableDateTimePickerSelectList)bindable; 288 | 289 | int selectedIndex = (int)newValue; 290 | 291 | // If the index is out of range, set selection to null 292 | if (selectedIndex >= 0 && selectedIndex < control.ItemsSource?.Count) 293 | { 294 | control._collectionView.SelectedItem = control.ItemsSource[selectedIndex]; 295 | } 296 | else 297 | { 298 | control._collectionView.SelectedItem = null; 299 | } 300 | } 301 | 302 | // Update SelectedIndex and SelectedItem when selection changes 303 | private void OnSelectionChanged(object sender, SelectionChangedEventArgs e) 304 | { 305 | if (e.CurrentSelection != null && e.CurrentSelection.Count > 0) 306 | { 307 | var selectedItem = e.CurrentSelection[0]; 308 | 309 | int selectedIndex = ItemsSource?.IndexOf(selectedItem) ?? -1; 310 | SelectedItem = selectedItem; 311 | SelectedIndex = selectedIndex; 312 | SelectedIndexChanged?.Invoke(this, EventArgs.Empty); 313 | ScrollToSelectedItem(); 314 | } 315 | } 316 | 317 | // Scroll to the selected item 318 | private void ScrollToSelectedItem() 319 | { 320 | if (ItemsSource == null || SelectedItem == null || SelectedIndex == -1) 321 | return; 322 | 323 | MainThread.BeginInvokeOnMainThread(async () => 324 | { 325 | int delay = Math.Min(ItemsSource.Count * 2, 2000); 326 | await Task.Delay(delay); 327 | _collectionView.ScrollTo(SelectedIndex, position: ScrollToPosition.Center, animate: false); 328 | }); 329 | } 330 | 331 | protected override void OnPropertyChanged([CallerMemberName] string propertyName = null) 332 | { 333 | base.OnPropertyChanged(propertyName); 334 | if (propertyName == nameof(IsEnabled)) 335 | { 336 | _collectionView.IsEnabled = this.IsEnabled; 337 | this.IsEnabled = true; 338 | } 339 | } 340 | } -------------------------------------------------------------------------------- /Maui.NullableDateTimePicker/DefaultStyles.cs: -------------------------------------------------------------------------------- 1 | using CommunityToolkit.Maui; 2 | 3 | namespace Maui.NullableDateTimePicker 4 | { 5 | internal static class DefaultStyles 6 | { 7 | private static Style _dayStyle; 8 | internal static Style DayStyle 9 | { 10 | get 11 | { 12 | _dayStyle ??= new Style(typeof(Button)) 13 | { 14 | Setters = { 15 | new Setter { Property = VisualElement.BackgroundColorProperty, Value = Colors.Transparent }, 16 | new Setter { Property = Button.TextColorProperty, Value = new AppThemeColor { Light = Colors.Black, Dark = Colors.White }.GetBinding() }, 17 | new Setter { Property = View.MarginProperty, Value = new Thickness(0) }, 18 | new Setter { Property = Button.PaddingProperty, Value = new Thickness(0) }, 19 | new Setter { Property = Button.FontSizeProperty, Value = 12 }, 20 | new Setter { Property = Button.BorderColorProperty, Value = Colors.Transparent }, 21 | new Setter { Property = View.HorizontalOptionsProperty, Value = LayoutOptions.Center }, 22 | new Setter { Property = View.VerticalOptionsProperty, Value = LayoutOptions.Center }, 23 | new Setter { Property = VisualElement.WidthRequestProperty, Value = 30 }, 24 | new Setter { Property = VisualElement.HeightRequestProperty, Value = 30 }, 25 | new Setter { Property = VisualElement.MaximumWidthRequestProperty, Value = 30 }, 26 | new Setter { Property = VisualElement.MaximumHeightRequestProperty, Value = 30 }, 27 | new Setter { Property = VisualElement.MinimumWidthRequestProperty, Value = 30 }, 28 | new Setter { Property = VisualElement.MinimumHeightRequestProperty, Value = 30 } 29 | } 30 | }; 31 | return _dayStyle; 32 | } 33 | } 34 | 35 | private static Style _disabledDayStyle; 36 | internal static Style DisabledDayStyle 37 | { 38 | get 39 | { 40 | _disabledDayStyle ??= new Style(typeof(Button)) 41 | { 42 | BasedOn = DayStyle, 43 | Setters = { 44 | new Setter { Property = Button.TextColorProperty, Value = Color.FromRgba("#919191")} 45 | } 46 | }; 47 | return _disabledDayStyle; 48 | } 49 | } 50 | 51 | private static Style _otherMonthDayStyle; 52 | internal static Style OtherMonthDayStyle 53 | { 54 | get 55 | { 56 | if (_otherMonthDayStyle == null) 57 | { 58 | _otherMonthDayStyle = new Style(typeof(Button)) 59 | { 60 | BasedOn = DayStyle 61 | }; 62 | _otherMonthDayStyle.Setters.Add(new Setter 63 | { 64 | Property = Button.TextColorProperty, 65 | Value = new AppThemeColor { Light = Colors.Gray, Dark = Color.FromRgba("#ACACAC") }.GetBinding() 66 | }); 67 | } 68 | return _otherMonthDayStyle; 69 | } 70 | } 71 | 72 | private static Style _selectedDayStyle; 73 | internal static Style SelectedDayStyle 74 | { 75 | get 76 | { 77 | if (_selectedDayStyle == null) 78 | { 79 | _selectedDayStyle = new Style(typeof(Button)) 80 | { 81 | BasedOn = DayStyle 82 | }; 83 | 84 | _selectedDayStyle.Setters.Add(new Setter { Property = Button.CornerRadiusProperty, Value = DeviceInfo.Platform == DevicePlatform.iOS ? 15 : 50 }); 85 | _selectedDayStyle.Setters.Add(new Setter { Property = Button.TextColorProperty, Value = Colors.White }); 86 | _selectedDayStyle.Setters.Add(new Setter 87 | { 88 | Property = VisualElement.BackgroundColorProperty, 89 | Value = new AppThemeColor { Light = Colors.Blue, Dark = Color.FromRgba("#6e5df2") }.GetBinding() 90 | }); 91 | } 92 | 93 | return _selectedDayStyle; 94 | } 95 | } 96 | 97 | private static Style _weekNumberStyle; 98 | internal static Style WeekNumberStyle 99 | { 100 | get 101 | { 102 | _weekNumberStyle ??= new Style(typeof(Label)) 103 | { 104 | Setters = { 105 | new Setter { Property = View.HorizontalOptionsProperty, Value = LayoutOptions.Center }, 106 | new Setter { Property = View.VerticalOptionsProperty, Value = LayoutOptions.Center}, 107 | new Setter { Property = Label.FontSizeProperty, Value = 12 }, 108 | new Setter { Property = Label.TextColorProperty, Value = Color.FromRgba("#6e5df2") }, 109 | new Setter { Property = Label.FontAttributesProperty, Value = FontAttributes.Bold }, 110 | new Setter { Property = Label.MarginProperty, Value = 0 }, 111 | new Setter { Property = Label.PaddingProperty, Value = 0 }, 112 | new Setter { Property = Label.LineBreakModeProperty, Value = LineBreakMode.NoWrap } 113 | } 114 | }; 115 | 116 | return _weekNumberStyle; 117 | } 118 | } 119 | 120 | private static Style _dayNamesStyle; 121 | internal static Style DayNamesStyle 122 | { 123 | get 124 | { 125 | _dayNamesStyle ??= new Style(typeof(Label)) 126 | { 127 | Setters = { 128 | new Setter { Property = VisualElement.BackgroundColorProperty, Value = Colors.Transparent }, 129 | new Setter { Property = View.HorizontalOptionsProperty, Value = LayoutOptions.Center }, 130 | new Setter { Property = View.VerticalOptionsProperty, Value = LayoutOptions.Center }, 131 | new Setter { Property = VisualElement.MinimumWidthRequestProperty, Value = 30 }, 132 | new Setter { Property = VisualElement.MinimumHeightRequestProperty, Value = 30 }, 133 | new Setter { Property = Label.FontSizeProperty, Value = 12 }, 134 | new Setter { Property = Label.TextColorProperty, Value = new AppThemeColor { Light = Colors.Black, Dark = Colors.White }.GetBinding() }, 135 | } 136 | }; 137 | 138 | return _dayNamesStyle; 139 | } 140 | } 141 | 142 | private static Style _toolButtonsStyle; 143 | internal static Style ToolButtonsStyle 144 | { 145 | get 146 | { 147 | _toolButtonsStyle ??= new Style(typeof(Button)) 148 | { 149 | Setters = { 150 | new Setter { Property = VisualElement.BackgroundColorProperty, Value = Colors.Transparent }, 151 | new Setter { Property = Button.BorderColorProperty, Value = Colors.Transparent }, 152 | new Setter { Property = View.HorizontalOptionsProperty, Value = LayoutOptions.Center }, 153 | new Setter { Property = View.VerticalOptionsProperty, Value = LayoutOptions.Center }, 154 | new Setter { Property = View.MarginProperty, Value = new Thickness(10, 0, 10, 0) }, 155 | new Setter { Property = Button.TextColorProperty, Value = Color.FromRgba("#6e5df2") }, 156 | new Setter { Property = Button.FontAttributesProperty, Value = FontAttributes.Bold } 157 | } 158 | }; 159 | 160 | return _toolButtonsStyle; 161 | } 162 | } 163 | } 164 | } 165 | -------------------------------------------------------------------------------- /Maui.NullableDateTimePicker/Enums/PickerModes.cs: -------------------------------------------------------------------------------- 1 | namespace Maui.NullableDateTimePicker; 2 | 3 | public enum PickerModes 4 | { 5 | Date, 6 | DateTime, 7 | Time 8 | } 9 | -------------------------------------------------------------------------------- /Maui.NullableDateTimePicker/Enums/PopupButtons.cs: -------------------------------------------------------------------------------- 1 | namespace Maui.NullableDateTimePicker; 2 | 3 | public enum PopupButtons 4 | { 5 | Ok, 6 | Clear, 7 | Cancel 8 | } 9 | -------------------------------------------------------------------------------- /Maui.NullableDateTimePicker/Helpers/MainThreadHelper.cs: -------------------------------------------------------------------------------- 1 | namespace Maui.NullableDateTimePicker; 2 | 3 | internal class MainThreadHelper 4 | { 5 | internal static void SafeBeginInvokeOnMainThread(Action action) 6 | { 7 | if (DeviceInfo.Platform == DevicePlatform.WinUI) 8 | { 9 | Application.Current.Dispatcher.Dispatch(action); 10 | } 11 | else 12 | { 13 | MainThread.BeginInvokeOnMainThread(action); 14 | } 15 | } 16 | 17 | internal static async Task SafeInvokeOnMainThreadAsync(Action action) 18 | { 19 | if (DeviceInfo.Platform == DevicePlatform.WinUI) 20 | { 21 | await Application.Current.Dispatcher.DispatchAsync(action); 22 | } 23 | else 24 | { 25 | await MainThread.InvokeOnMainThreadAsync(action); 26 | } 27 | } 28 | 29 | internal static async Task SafeInvokeOnMainThreadAsync(Func> funcTask) 30 | { 31 | if (DeviceInfo.Platform == DevicePlatform.WinUI) 32 | { 33 | return await Application.Current.Dispatcher.DispatchAsync(funcTask); 34 | } 35 | else 36 | { 37 | return await MainThread.InvokeOnMainThreadAsync(funcTask); 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /Maui.NullableDateTimePicker/Helpers/PopupResultTask.cs: -------------------------------------------------------------------------------- 1 | namespace Maui.NullableDateTimePicker; 2 | 3 | internal class PopupResultTask 4 | { 5 | internal Task Result { get; } 6 | private readonly TaskCompletionSource _tcs = new(); 7 | 8 | internal PopupResultTask() 9 | { 10 | Result = _tcs.Task; 11 | } 12 | 13 | internal void SetResult(T result) 14 | { 15 | _tcs.SetResult(result); 16 | } 17 | 18 | internal void SetException(Exception exception) 19 | { 20 | _tcs.SetException(exception); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /Maui.NullableDateTimePicker/Images/date_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sebarslan/Maui.NullableDateTimePicker/5acac06b1c4366c28a434ee59c26bda8afe7baa8/Maui.NullableDateTimePicker/Images/date_icon.png -------------------------------------------------------------------------------- /Maui.NullableDateTimePicker/Images/date_time_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sebarslan/Maui.NullableDateTimePicker/5acac06b1c4366c28a434ee59c26bda8afe7baa8/Maui.NullableDateTimePicker/Images/date_time_icon.png -------------------------------------------------------------------------------- /Maui.NullableDateTimePicker/Images/time_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sebarslan/Maui.NullableDateTimePicker/5acac06b1c4366c28a434ee59c26bda8afe7baa8/Maui.NullableDateTimePicker/Images/time_icon.png -------------------------------------------------------------------------------- /Maui.NullableDateTimePicker/Interfaces/INullableDateTimePickerOptions.cs: -------------------------------------------------------------------------------- 1 | #nullable enable 2 | using CommunityToolkit.Maui; 3 | 4 | namespace Maui.NullableDateTimePicker; 5 | 6 | public interface INullableDateTimePickerOptions 7 | { 8 | DateTime? NullableDateTime { get; set; } 9 | PickerModes Mode { get; set; } 10 | DateTime? MinDate { get; set; } 11 | DateTime? MaxDate { get; set; } 12 | string OkButtonText { get; set; } 13 | string CancelButtonText { get; set; } 14 | string ClearButtonText { get; set; } 15 | Color? PopupBorderColor { get; set; } 16 | AppThemeColor? PopupBorderThemeColor { get; set; } 17 | double PopupBorderWidth { get; set; } 18 | CornerRadius PopupBorderCornerRadius { get; set; } 19 | Thickness PopupPadding { get; set; } 20 | Color? BodyBackgroundColor { get; set; } 21 | AppThemeColor? BodyBackgroundThemeColor { get; set; } 22 | Color? ForeColor { get; set; } 23 | AppThemeColor? ForeColorThemeColor { get; set; } 24 | Color? HeaderForeColor { get; set; } 25 | AppThemeColor? HeaderForeThemeColor { get; set; } 26 | Color? HeaderBackgroundColor { get; set; } 27 | AppThemeColor? HeaderBackgroundThemeColor { get; set; } 28 | Style? ToolButtonsStyle { get; set; } 29 | Style? DayStyle { get; set; } 30 | Style? DisabledDayStyle { get; set; } 31 | Style? OtherMonthDayStyle { get; set; } 32 | Style? SelectedDayStyle { get; set; } 33 | Style? DayNamesStyle { get; set; } 34 | bool ShowWeekNumbers { get; set; } 35 | Style? WeekNumberStyle { get; set; } 36 | bool ShowOtherMonthDays { get; set; } 37 | Color? ActivityIndicatorColor { get; set; } 38 | AppThemeColor? ActivityIndicatorThemeColor { get; set; } 39 | bool ShowClearButton { get; set; } 40 | bool CloseOnOutsideClick { get; set; } 41 | bool Is12HourFormat { get; set; } 42 | string AutomationId { get; set; } 43 | }; 44 | -------------------------------------------------------------------------------- /Maui.NullableDateTimePicker/Maui.NullableDateTimePicker.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | net8.0;net9.0 4 | $(TargetFrameworks);net8.0-windows10.0.19041.0;net9.0-windows10.0.19041.0 5 | true 6 | true 7 | enable 8 | Maui.NullableDateTimePicker 9 | Sebarslan 10 | Nullable and clerable datetime picker component for Maui 11 | https://github.com/sebarslan/Maui.NullableDateTimePicker 12 | https://github.com/sebarslan/Maui.NullableDateTimePicker 13 | nullable clerable datepicker,maui,cross-platform,.net,ios,android,windows,mac catalyst 14 | en-US 15 | git 16 | True 17 | Sebarslan.Maui.NullableDateTimePicker 18 | README.md 19 | MIT 20 | True 21 | 2.4.0.0 22 | 2.4.0 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | True 32 | \ 33 | 34 | 35 | True 36 | \ 37 | 38 | 39 | 40 | 9.0.21 41 | 42 | 43 | 8.0.83 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | -------------------------------------------------------------------------------- /Maui.NullableDateTimePicker/Models/DateTimeChangedEventArgs.cs: -------------------------------------------------------------------------------- 1 | namespace Maui.NullableDateTimePicker; 2 | 3 | public class DateTimeChangedEventArgs : EventArgs 4 | { 5 | public DateTimeChangedEventArgs(DateTime? oldDateTime, DateTime? newDateTime) 6 | { 7 | NewDateTime = newDateTime; 8 | OldDateTime = oldDateTime; 9 | } 10 | 11 | public DateTime? NewDateTime { get; } 12 | public DateTime? OldDateTime { get; } 13 | } 14 | -------------------------------------------------------------------------------- /Maui.NullableDateTimePicker/Models/MonthModel.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 Maui.NullableDateTimePicker.Models 8 | { 9 | internal class MonthModel 10 | { 11 | public int Number { get; set; } 12 | public string Name { get; set; } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /Maui.NullableDateTimePicker/Models/NullableDateTimePickerOptions.cs: -------------------------------------------------------------------------------- 1 | #nullable enable 2 | using CommunityToolkit.Maui; 3 | 4 | namespace Maui.NullableDateTimePicker; 5 | 6 | public class NullableDateTimePickerOptions : INullableDateTimePickerOptions 7 | { 8 | public DateTime? NullableDateTime { get; set; } 9 | public PickerModes Mode { get; set; } = PickerModes.Date; 10 | public DateTime? MinDate { get; set; } 11 | public DateTime? MaxDate { get; set; } 12 | public string OkButtonText { get; set; } = "OK"; 13 | public string CancelButtonText { get; set; } = "Cancel"; 14 | public string ClearButtonText { get; set; } = "Clear"; 15 | public Color? PopupBorderColor { get; set; } 16 | public AppThemeColor? PopupBorderThemeColor { get; set; } 17 | public double PopupBorderWidth { get; set; } = 0; 18 | public CornerRadius PopupBorderCornerRadius { get; set; } = new CornerRadius(0); 19 | public Thickness PopupPadding { get; set; } = new Thickness(0); 20 | public Color? BodyBackgroundColor { get; set; } 21 | public AppThemeColor? BodyBackgroundThemeColor { get; set; } = new AppThemeColor { Light = Colors.White, Dark = Color.FromRgba("#434343") }; 22 | public Color? ForeColor { get; set; } 23 | public AppThemeColor? ForeColorThemeColor { get; set; } = new AppThemeColor { Light = Colors.Black, Dark = Colors.White }; 24 | public Color? HeaderForeColor { get; set; } 25 | public AppThemeColor? HeaderForeThemeColor { get; set; } = new AppThemeColor { Light = Colors.White, Dark = Colors.White }; 26 | public Color? HeaderBackgroundColor { get; set; } 27 | public AppThemeColor? HeaderBackgroundThemeColor { get; set; } = new AppThemeColor { Light = Color.FromRgba("#2b0b98"), Dark = Color.FromRgba("#252626") }; 28 | public Style? ToolButtonsStyle { get; set; } 29 | public Style? DayStyle { get; set; } 30 | public Style? DisabledDayStyle { get; set; } 31 | public Style? OtherMonthDayStyle { get; set; } 32 | public Style? SelectedDayStyle { get; set; } 33 | public Style? DayNamesStyle { get; set; } 34 | public bool ShowWeekNumbers { get; set; } 35 | public Style? WeekNumberStyle { get; set; } 36 | public bool ShowOtherMonthDays { get; set; } = true; 37 | public bool ShowClearButton { get; set; } = true; 38 | public Color? ActivityIndicatorColor { get; set; } 39 | public AppThemeColor? ActivityIndicatorThemeColor { get; set; } 40 | public bool CloseOnOutsideClick { get; set; } 41 | public bool Is12HourFormat { get; set; } 42 | public string AutomationId { get; set; } 43 | 44 | } 45 | -------------------------------------------------------------------------------- /Maui.NullableDateTimePicker/Models/PickerItem.cs: -------------------------------------------------------------------------------- 1 | namespace Maui.NullableDateTimePicker.Models 2 | { 3 | internal class PickerItem 4 | { 5 | public string Text { get; set; } 6 | public int Value { get; set; } 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /Maui.NullableDateTimePicker/Models/PopupResult.cs: -------------------------------------------------------------------------------- 1 | namespace Maui.NullableDateTimePicker; 2 | 3 | public class PopupResult 4 | { 5 | internal PopupResult(DateTime? nullableDateTime, PopupButtons buttonResult) 6 | { 7 | NullableDateTime = nullableDateTime; 8 | ButtonResult = buttonResult; 9 | } 10 | 11 | public PopupButtons ButtonResult { get; } 12 | public DateTime? NullableDateTime { get; } 13 | } 14 | -------------------------------------------------------------------------------- /Maui.NullableDateTimePicker/Models/YearModel.cs: -------------------------------------------------------------------------------- 1 | namespace Maui.NullableDateTimePicker.Models 2 | { 3 | internal class YearModel 4 | { 5 | public YearModel() { } 6 | public YearModel(int year) { Year = year; } 7 | public int Year { get; set; } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /Maui.NullableDateTimePicker/NullableDateTimePicker.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Maui.Controls.Shapes; 2 | using System.Reflection; 3 | 4 | namespace Maui.NullableDateTimePicker; 5 | 6 | // All the code in this file is included in all platforms. 7 | public class NullableDateTimePicker : ContentView 8 | { 9 | public event EventHandler NullableDateTimeChanged; 10 | private Grid _dateTimePickerGrid; 11 | private Entry _dateTimePickerEntry; 12 | private Image _dateTimePickerIcon; 13 | private Border _dateTimePickerBorder; 14 | const double defaultHeightRequest = 40; 15 | static Page Page => Application.Current?.MainPage ?? throw new NullReferenceException(); 16 | 17 | #region bindable properties 18 | 19 | public static readonly BindableProperty NullableDateTimeProperty = 20 | BindableProperty.Create(nameof(NullableDateTime), 21 | typeof(DateTime?), 22 | typeof(NullableDateTimePicker), 23 | null, 24 | defaultBindingMode: BindingMode.TwoWay, 25 | null, 26 | (bindable, oldValue, newValue) => 27 | { 28 | var self = (NullableDateTimePicker)bindable; 29 | var oldNullableDateTime = (DateTime?)oldValue; 30 | var newNullableDateTime = (DateTime?)newValue; 31 | 32 | // Reset the text, so that the text color will be updated 33 | // https://github.com/dotnet/maui/issues/17843 34 | self._dateTimePickerEntry.Text = ""; 35 | self._dateTimePickerEntry.Text = newNullableDateTime?.ToString(self.Format); 36 | 37 | //Date changed event 38 | bool isDateTimeChanged = false; 39 | if (self.Mode == PickerModes.Date && oldNullableDateTime?.Date != newNullableDateTime?.Date) 40 | { 41 | isDateTimeChanged = true; 42 | } 43 | else if (self.Mode == PickerModes.DateTime && (oldNullableDateTime?.Date != newNullableDateTime?.Date || oldNullableDateTime?.TimeOfDay != newNullableDateTime?.TimeOfDay)) 44 | { 45 | isDateTimeChanged = true; 46 | } 47 | else if (self.Mode == PickerModes.Time && oldNullableDateTime?.TimeOfDay != newNullableDateTime?.TimeOfDay) 48 | { 49 | isDateTimeChanged = true; 50 | } 51 | 52 | if (isDateTimeChanged) 53 | self.NullableDateTimeChanged?.Invoke(self, new DateTimeChangedEventArgs(oldNullableDateTime, newNullableDateTime)); 54 | }); 55 | 56 | public DateTime? NullableDateTime 57 | { 58 | get { return (DateTime?)GetValue(NullableDateTimeProperty); } 59 | set 60 | { 61 | SetValue(NullableDateTimeProperty, value); 62 | } 63 | } 64 | 65 | public static readonly BindableProperty ToolButtonsStyleProperty = 66 | BindableProperty.Create(nameof(ToolButtonsStyle), typeof(Style), typeof(NullableDateTimePicker), defaultValue: null, defaultBindingMode: BindingMode.OneWay, 67 | propertyChanged: (bindable, oldValue, newValue) => 68 | { 69 | }); 70 | 71 | public Style ToolButtonsStyle 72 | { 73 | get { return (Style)GetValue(ToolButtonsStyleProperty); } 74 | set { SetValue(ToolButtonsStyleProperty, value); } 75 | } 76 | 77 | public static readonly BindableProperty ModeProperty = 78 | BindableProperty.Create( 79 | nameof(Mode), 80 | typeof(PickerModes), 81 | typeof(NullableDateTimePicker), 82 | PickerModes.Date, 83 | propertyChanged: (bindable, oldValue, newValue) => 84 | { 85 | if (newValue is string strValue) 86 | { 87 | if (PickerModes.TryParse(strValue, out PickerModes pickerMode)) 88 | { 89 | newValue = pickerMode; 90 | } 91 | } 92 | ((NullableDateTimePicker)bindable).SetCalendarIcon(); 93 | }); 94 | 95 | public PickerModes Mode 96 | { 97 | get { return (PickerModes)GetValue(ModeProperty); } 98 | set { SetValue(ModeProperty, value); } 99 | } 100 | 101 | public static readonly BindableProperty MinDateProperty = 102 | BindableProperty.Create(nameof(MinDate), typeof(DateTime?), typeof(NullableDateTimePicker), null, defaultBindingMode: BindingMode.OneWay, null, 103 | propertyChanged: (bindable, oldValue, newValue) => 104 | { 105 | newValue = ParseDateTime(newValue); 106 | }); 107 | 108 | public DateTime? MinDate 109 | { 110 | get { return (DateTime?)GetValue(MinDateProperty); } 111 | set 112 | { 113 | SetValue(MinDateProperty, value); 114 | } 115 | } 116 | 117 | public static readonly BindableProperty MaxDateProperty = 118 | BindableProperty.Create(nameof(MaxDate), typeof(DateTime?), typeof(NullableDateTimePicker), null, defaultBindingMode: BindingMode.OneWay, null, propertyChanged: (bindable, oldValue, newValue) => 119 | { 120 | newValue = ParseDateTime(newValue); 121 | }); 122 | 123 | public DateTime? MaxDate 124 | { 125 | get { return (DateTime?)GetValue(MaxDateProperty); } 126 | set 127 | { 128 | SetValue(MaxDateProperty, value); 129 | } 130 | } 131 | 132 | public static readonly BindableProperty BodyBackgroundColorProperty = 133 | BindableProperty.Create(nameof(BodyBackgroundColor), 134 | typeof(Color), 135 | typeof(NullableDateTimePicker), 136 | null, 137 | defaultBindingMode: BindingMode.OneWay); 138 | 139 | public Color BodyBackgroundColor 140 | { 141 | get { return (Color)GetValue(BodyBackgroundColorProperty); } 142 | set 143 | { 144 | SetValue(BodyBackgroundColorProperty, value); 145 | } 146 | } 147 | 148 | public static readonly BindableProperty HeaderBackgroundColorProperty = 149 | BindableProperty.Create(nameof(HeaderBackgroundColor), 150 | typeof(Color), 151 | typeof(NullableDateTimePicker), 152 | null, 153 | defaultBindingMode: BindingMode.OneWay); 154 | 155 | public Color HeaderBackgroundColor 156 | { 157 | get { return (Color)GetValue(HeaderBackgroundColorProperty); } 158 | set 159 | { 160 | SetValue(HeaderBackgroundColorProperty, value); 161 | } 162 | } 163 | 164 | public static readonly BindableProperty HeaderForeColorProperty = 165 | BindableProperty.Create(nameof(HeaderForeColor), typeof(Color), typeof(NullableDateTimePicker), null, defaultBindingMode: BindingMode.OneWay, null, (b, o, n) => 166 | { 167 | }); 168 | 169 | public Color HeaderForeColor 170 | { 171 | get { return (Color)GetValue(HeaderForeColorProperty); } 172 | set 173 | { 174 | SetValue(HeaderForeColorProperty, value); 175 | } 176 | } 177 | 178 | public static readonly BindableProperty DayStyleProperty = 179 | BindableProperty.Create(nameof(DayStyle), typeof(Style), typeof(NullableDateTimePicker), null, defaultBindingMode: BindingMode.OneWay, null, (b, o, n) => 180 | { 181 | }); 182 | 183 | public Style DayStyle 184 | { 185 | get { return (Style)GetValue(DayStyleProperty); } 186 | set 187 | { 188 | SetValue(DayStyleProperty, value); 189 | } 190 | } 191 | 192 | public static readonly BindableProperty DisabledDayStyleProperty = 193 | BindableProperty.Create(nameof(DisabledDayStyle), typeof(Style), typeof(NullableDateTimePicker), null, defaultBindingMode: BindingMode.OneWay, null, (b, o, n) => 194 | { 195 | }); 196 | 197 | public Style DisabledDayStyle 198 | { 199 | get { return (Style)GetValue(DisabledDayStyleProperty); } 200 | set 201 | { 202 | SetValue(DisabledDayStyleProperty, value); 203 | } 204 | } 205 | 206 | public static readonly BindableProperty OtherMonthDayStyleProperty = 207 | BindableProperty.Create(nameof(OtherMonthDayStyle), typeof(Style), typeof(NullableDateTimePicker), null, defaultBindingMode: BindingMode.OneWay, null, (b, o, n) => 208 | { 209 | }); 210 | 211 | public Style OtherMonthDayStyle 212 | { 213 | get { return (Style)GetValue(OtherMonthDayStyleProperty); } 214 | set 215 | { 216 | SetValue(OtherMonthDayStyleProperty, value); 217 | } 218 | } 219 | 220 | public static readonly BindableProperty SelectedDayStyleProperty = 221 | BindableProperty.Create(nameof(SelectedDayStyle), typeof(Style), typeof(NullableDateTimePicker), null, defaultBindingMode: BindingMode.OneWay, null); 222 | 223 | public Style SelectedDayStyle 224 | { 225 | get { return (Style)GetValue(SelectedDayStyleProperty); } 226 | set 227 | { 228 | SetValue(SelectedDayStyleProperty, value); 229 | } 230 | } 231 | 232 | public static readonly BindableProperty DayNamesStyleProperty = 233 | BindableProperty.Create( 234 | nameof(DayNamesStyle), 235 | typeof(Style), 236 | typeof(NullableDateTimePicker), 237 | null, 238 | defaultBindingMode: BindingMode.OneWay); 239 | 240 | public Style DayNamesStyle 241 | { 242 | get { return (Style)GetValue(DayNamesStyleProperty); } 243 | set 244 | { 245 | SetValue(DayNamesStyleProperty, value); 246 | } 247 | } 248 | 249 | public static readonly BindableProperty WeekNumberStyleProperty = 250 | BindableProperty.Create(nameof(WeekNumberStyle), typeof(Style), typeof(NullableDateTimePicker), null, defaultBindingMode: BindingMode.OneWay, null, (b, o, n) => 251 | { 252 | }); 253 | 254 | public Style WeekNumberStyle 255 | { 256 | get { return (Style)GetValue(WeekNumberStyleProperty); } 257 | set 258 | { 259 | SetValue(WeekNumberStyleProperty, value); 260 | } 261 | } 262 | 263 | public static readonly BindableProperty OkButtonTextProperty = 264 | BindableProperty.Create( 265 | nameof(OkButtonText), 266 | typeof(string), 267 | typeof(NullableDateTimePicker), 268 | "OK", 269 | defaultBindingMode: BindingMode.OneWay, null, (b, o, n) => 270 | { 271 | }); 272 | 273 | public string OkButtonText 274 | { 275 | get { return (string)GetValue(OkButtonTextProperty); } 276 | set 277 | { 278 | SetValue(OkButtonTextProperty, value); 279 | } 280 | } 281 | 282 | 283 | public static readonly BindableProperty ClearButtonTextProperty = 284 | BindableProperty.Create(nameof(ClearButtonText), typeof(string), typeof(NullableDateTimePicker), "Clear", defaultBindingMode: BindingMode.OneWay, null, (b, o, n) => 285 | { 286 | }); 287 | 288 | public string ClearButtonText 289 | { 290 | get { return (string)GetValue(ClearButtonTextProperty); } 291 | set 292 | { 293 | SetValue(ClearButtonTextProperty, value); 294 | } 295 | } 296 | 297 | public static readonly BindableProperty CancelButtonTextProperty = 298 | BindableProperty.Create( 299 | nameof(CancelButtonText), 300 | typeof(string), 301 | typeof(NullableDateTimePicker), 302 | "Cancel", 303 | defaultBindingMode: BindingMode.OneWay, null, (b, o, n) => 304 | { 305 | }); 306 | 307 | public string CancelButtonText 308 | { 309 | get { return (string)GetValue(CancelButtonTextProperty); } 310 | set 311 | { 312 | SetValue(CancelButtonTextProperty, value); 313 | } 314 | } 315 | 316 | public static readonly BindableProperty ShowWeekNumbersProperty = BindableProperty.Create( 317 | nameof(ShowWeekNumbers), 318 | typeof(bool), 319 | typeof(NullableDateTimePicker), 320 | defaultValue: false, 321 | defaultBindingMode: BindingMode.OneWay); 322 | 323 | public bool ShowWeekNumbers 324 | { 325 | get { return (bool)GetValue(ShowWeekNumbersProperty); } 326 | set { SetValue(ShowWeekNumbersProperty, value); } 327 | } 328 | 329 | public static readonly BindableProperty ShowOtherMonthDaysProperty = BindableProperty.Create( 330 | nameof(ShowOtherMonthDays), 331 | typeof(bool), 332 | typeof(NullableDateTimePicker), 333 | defaultValue: true, 334 | defaultBindingMode: BindingMode.OneWay); 335 | 336 | public bool ShowOtherMonthDays 337 | { 338 | get { return (bool)GetValue(ShowOtherMonthDaysProperty); } 339 | set { SetValue(ShowOtherMonthDaysProperty, value); } 340 | } 341 | 342 | public static readonly BindableProperty PopupBorderColorProperty = BindableProperty.Create( 343 | nameof(PopupBorderColor), 344 | typeof(Color), 345 | typeof(NullableDateTimePicker), 346 | defaultValue: Colors.Transparent, 347 | defaultBindingMode: BindingMode.OneWay); 348 | public Color PopupBorderColor 349 | { 350 | get { return (Color)GetValue(PopupBorderColorProperty); } 351 | set { SetValue(PopupBorderColorProperty, value); } 352 | } 353 | 354 | public static readonly BindableProperty PopupBorderWidthProperty = BindableProperty.Create( 355 | nameof(PopupBorderWidth), 356 | typeof(double), 357 | typeof(NullableDateTimePicker), 358 | defaultValue: 0.0d, 359 | defaultBindingMode: BindingMode.OneWay); 360 | public double PopupBorderWidth 361 | { 362 | get { return (double)GetValue(PopupBorderWidthProperty); } 363 | set { SetValue(PopupBorderWidthProperty, value); } 364 | } 365 | 366 | public static readonly BindableProperty PopupCornerRadiusProperty = BindableProperty.Create( 367 | nameof(PopupCornerRadius), 368 | typeof(CornerRadius), 369 | typeof(NullableDateTimePicker), 370 | defaultValue: new CornerRadius(0), 371 | defaultBindingMode: BindingMode.OneWay); 372 | 373 | public CornerRadius PopupCornerRadius 374 | { 375 | get { return (CornerRadius)GetValue(PopupCornerRadiusProperty); } 376 | set { SetValue(PopupCornerRadiusProperty, value); } 377 | } 378 | 379 | public static readonly BindableProperty PopupPaddingProperty = BindableProperty.Create( 380 | nameof(PopupPadding), 381 | typeof(Thickness), 382 | typeof(NullableDateTimePicker), 383 | defaultValue: new Thickness(0), 384 | defaultBindingMode: BindingMode.OneWay); 385 | 386 | public Thickness PopupPadding 387 | { 388 | get { return (Thickness)GetValue(PopupPaddingProperty); } 389 | set { SetValue(PopupPaddingProperty, value); } 390 | } 391 | 392 | public static readonly BindableProperty ForeColorProperty = BindableProperty.Create( 393 | nameof(ForeColor), 394 | typeof(Color), 395 | typeof(NullableDateTimePicker), 396 | defaultValue: null, 397 | defaultBindingMode: BindingMode.OneWay); 398 | 399 | public Color ForeColor 400 | { 401 | get { return (Color)GetValue(ForeColorProperty); } 402 | set { SetValue(ForeColorProperty, value); } 403 | } 404 | 405 | 406 | public static readonly BindableProperty ActivityIndicatorColorProperty = BindableProperty.Create( 407 | nameof(ActivityIndicatorColor), 408 | typeof(Color), 409 | typeof(NullableDateTimePicker), 410 | defaultValue: null, 411 | defaultBindingMode: BindingMode.OneWay); 412 | 413 | public Color ActivityIndicatorColor 414 | { 415 | get { return (Color)GetValue(ActivityIndicatorColorProperty); } 416 | set { SetValue(ActivityIndicatorColorProperty, value); } 417 | } 418 | 419 | public static readonly BindableProperty ShowClearButtonProperty = BindableProperty.Create( 420 | nameof(ShowClearButton), 421 | typeof(bool), 422 | typeof(NullableDateTimePicker), 423 | defaultValue: true, 424 | defaultBindingMode: BindingMode.OneWay); 425 | 426 | public bool ShowClearButton 427 | { 428 | get { return (bool)GetValue(ShowClearButtonProperty); } 429 | set { SetValue(ShowClearButtonProperty, value); } 430 | } 431 | 432 | public static readonly BindableProperty CloseOnOutsideClickProperty = BindableProperty.Create( 433 | nameof(CloseOnOutsideClick), 434 | typeof(bool), 435 | typeof(NullableDateTimePicker), 436 | defaultValue: false, 437 | defaultBindingMode: BindingMode.OneWay); 438 | 439 | public bool CloseOnOutsideClick 440 | { 441 | get { return (bool)GetValue(CloseOnOutsideClickProperty); } 442 | set { SetValue(CloseOnOutsideClickProperty, value); } 443 | } 444 | 445 | 446 | public static readonly BindableProperty FormatProperty = 447 | BindableProperty.Create(nameof(Format), typeof(string), typeof(NullableDateTimePicker), null, 448 | defaultBindingMode: BindingMode.OneWay, 449 | null, (b, o, n) => 450 | { 451 | var self = (NullableDateTimePicker)b; 452 | var oldValue = (string)o; 453 | var newValue = (string)n; 454 | if (oldValue != newValue) 455 | { 456 | self._dateTimePickerEntry.Text = self.NullableDateTime?.ToString(self.Format); 457 | } 458 | }); 459 | 460 | public string Format 461 | { 462 | get 463 | { 464 | var format = (string)GetValue(FormatProperty); 465 | if (string.IsNullOrEmpty(format)) 466 | { 467 | if (Mode == PickerModes.Time) 468 | format = "t"; 469 | else if (Mode == PickerModes.DateTime) 470 | format = "g"; 471 | else 472 | format = "d"; 473 | } 474 | return format; 475 | } 476 | set 477 | { 478 | SetValue(FormatProperty, value); 479 | } 480 | } 481 | 482 | public static readonly BindableProperty IconProperty = BindableProperty.Create( 483 | nameof(Icon), 484 | typeof(ImageSource), 485 | typeof(NullableDateTimePicker), 486 | defaultValue: null, 487 | defaultBindingMode: BindingMode.OneWay, 488 | propertyChanged: (bindable, oldValue, newValue) => 489 | { 490 | ((NullableDateTimePicker)bindable).SetCalendarIcon(); 491 | }); 492 | 493 | public ImageSource Icon 494 | { 495 | get { return (ImageSource)GetValue(IconProperty); } 496 | set { SetValue(IconProperty, value); } 497 | } 498 | 499 | public static readonly BindableProperty IconBackgroundColorProperty = 500 | BindableProperty.Create( 501 | nameof(IconBackgroundColor), 502 | typeof(Color), 503 | typeof(NullableDateTimePicker), 504 | defaultValue: Colors.Transparent, 505 | defaultBindingMode: BindingMode.OneWay, 506 | propertyChanged: (bindable, oldValue, newValue) => 507 | { 508 | if (newValue != null) 509 | ((NullableDateTimePicker)bindable)._dateTimePickerIcon.BackgroundColor = (Color)newValue; 510 | }); 511 | 512 | public Color IconBackgroundColor 513 | { 514 | get { return (Color)GetValue(IconBackgroundColorProperty); } 515 | set 516 | { 517 | SetValue(IconBackgroundColorProperty, value); 518 | } 519 | } 520 | 521 | 522 | public static readonly BindableProperty PlaceholderProperty = BindableProperty.Create( 523 | nameof(Placeholder), 524 | typeof(string), 525 | typeof(NullableDateTimePicker), 526 | defaultValue: string.Empty, 527 | defaultBindingMode: BindingMode.OneWay, 528 | propertyChanged: (bindable, oldValue, newValue) => 529 | { 530 | ((NullableDateTimePicker)bindable)._dateTimePickerEntry.Placeholder = (string)newValue ?? string.Empty; 531 | }); 532 | 533 | public string Placeholder 534 | { 535 | get { return (string)GetValue(PlaceholderProperty); } 536 | set { SetValue(PlaceholderProperty, value); } 537 | } 538 | 539 | public static readonly BindableProperty PlaceholderColorProperty = BindableProperty.Create( 540 | nameof(PlaceholderColor), 541 | typeof(Color), 542 | typeof(NullableDateTimePicker), 543 | defaultValue: Colors.Gray, 544 | defaultBindingMode: BindingMode.OneWay, 545 | propertyChanged: (bindable, oldValue, newValue) => 546 | { 547 | if (bindable is NullableDateTimePicker nullableDateTimePickerBindable && newValue is Color placeholderColor) 548 | { 549 | nullableDateTimePickerBindable._dateTimePickerEntry.PlaceholderColor = placeholderColor; 550 | } 551 | }); 552 | public Color PlaceholderColor 553 | { 554 | get { return (Color)GetValue(PlaceholderColorProperty); } 555 | set { SetValue(PlaceholderColorProperty, value); } 556 | } 557 | 558 | public static readonly BindableProperty FontSizeProperty = BindableProperty.Create( 559 | nameof(FontSize), 560 | typeof(double), 561 | typeof(NullableDateTimePicker), 562 | defaultValue: 14d, 563 | defaultBindingMode: BindingMode.OneWay, 564 | propertyChanged: (bindable, oldValue, newValue) => 565 | { 566 | double fontSize = 14d; 567 | if (newValue is double) 568 | fontSize = (double)newValue; 569 | else if (newValue is string) 570 | double.TryParse((string)newValue, out fontSize); 571 | 572 | ((NullableDateTimePicker)bindable)._dateTimePickerEntry.FontSize = fontSize; 573 | }); 574 | 575 | public double FontSize 576 | { 577 | get { return (double)GetValue(FontSizeProperty); } 578 | set { SetValue(FontSizeProperty, value); } 579 | } 580 | 581 | public static readonly BindableProperty TextColorProperty = BindableProperty.Create( 582 | nameof(TextColor), 583 | typeof(Color), 584 | typeof(NullableDateTimePicker), 585 | defaultValue: Colors.Black, 586 | defaultBindingMode: BindingMode.OneWay, 587 | propertyChanged: (bindable, oldValue, newValue) => 588 | { 589 | if (bindable is NullableDateTimePicker nullableDateTimePickerBindable && newValue is Color textColor) 590 | { 591 | nullableDateTimePickerBindable._dateTimePickerEntry.TextColor = textColor; 592 | } 593 | }); 594 | 595 | public Color TextColor 596 | { 597 | get { return (Color)GetValue(TextColorProperty); } 598 | set { SetValue(TextColorProperty, value); } 599 | } 600 | 601 | public static readonly BindableProperty FontFamilyProperty = BindableProperty.Create( 602 | nameof(FontFamily), 603 | typeof(string), 604 | typeof(NullableDateTimePicker), 605 | defaultValue: "Arial", 606 | defaultBindingMode: BindingMode.OneWay, 607 | propertyChanged: (bindable, oldValue, newValue) => 608 | { 609 | if (bindable is NullableDateTimePicker nullableDateTimePickerBindable && newValue is string fontFamily) 610 | { 611 | nullableDateTimePickerBindable._dateTimePickerEntry.FontFamily = fontFamily; 612 | } 613 | }); 614 | 615 | public string FontFamily 616 | { 617 | get { return (string)GetValue(FontFamilyProperty); } 618 | set { SetValue(FontFamilyProperty, value); } 619 | } 620 | 621 | public new static readonly BindableProperty BackgroundColorProperty = BindableProperty.Create( 622 | nameof(BackgroundColor), 623 | typeof(Color), 624 | typeof(NullableDateTimePicker), 625 | defaultValue: Colors.White, 626 | defaultBindingMode: BindingMode.OneWay, 627 | propertyChanged: (bindable, oldValue, newValue) => 628 | { 629 | if (bindable is NullableDateTimePicker nullableDateTimePickerBindable && newValue is Color backgroundColor) 630 | { 631 | nullableDateTimePickerBindable._dateTimePickerBorder.BackgroundColor = backgroundColor; 632 | } 633 | }); 634 | 635 | public new Color BackgroundColor 636 | { 637 | get { return (Color)GetValue(BackgroundColorProperty); } 638 | set { SetValue(BackgroundColorProperty, value); } 639 | } 640 | 641 | public static readonly BindableProperty BorderColorProperty = BindableProperty.Create( 642 | nameof(BorderColor), 643 | typeof(Color), 644 | typeof(NullableDateTimePicker), 645 | defaultValue: Colors.Transparent, 646 | defaultBindingMode: BindingMode.OneWay, 647 | propertyChanged: (bindable, oldValue, newValue) => 648 | { 649 | if (bindable is NullableDateTimePicker nullableDateTimePickerBindable && newValue is Color borderColor) 650 | { 651 | nullableDateTimePickerBindable._dateTimePickerBorder.Stroke = borderColor; 652 | } 653 | }); 654 | public Color BorderColor 655 | { 656 | get { return (Color)GetValue(BorderColorProperty); } 657 | set { SetValue(BorderColorProperty, value); } 658 | } 659 | 660 | public static readonly BindableProperty BorderWidthProperty = BindableProperty.Create( 661 | nameof(BorderWidth), 662 | typeof(double), 663 | typeof(NullableDateTimePicker), 664 | defaultValue: 0.0d, 665 | defaultBindingMode: BindingMode.OneWay, 666 | propertyChanged: (bindable, oldValue, newValue) => 667 | { 668 | if (newValue is double) 669 | { 670 | ((NullableDateTimePicker)bindable)._dateTimePickerBorder.StrokeThickness = (double)newValue; 671 | } 672 | 673 | }); 674 | public double BorderWidth 675 | { 676 | get { return (double)GetValue(BorderWidthProperty); } 677 | set { SetValue(BorderWidthProperty, value); } 678 | } 679 | 680 | public static readonly BindableProperty CornerRadiusProperty = BindableProperty.Create( 681 | nameof(CornerRadius), 682 | typeof(Thickness), 683 | typeof(NullableDateTimePicker), 684 | defaultValue: new Thickness(0), 685 | defaultBindingMode: BindingMode.OneWay, 686 | propertyChanged: (bindable, oldValue, newValue) => 687 | { 688 | if (bindable is NullableDateTimePicker nullableDateTimePickerBindable && newValue is Thickness cornerRadius) 689 | { 690 | nullableDateTimePickerBindable._dateTimePickerBorder.StrokeShape = new RoundRectangle 691 | { 692 | CornerRadius = new Microsoft.Maui.CornerRadius(cornerRadius.Left, cornerRadius.Top, cornerRadius.Right, cornerRadius.Bottom) 693 | }; 694 | } 695 | }); 696 | 697 | public Thickness CornerRadius 698 | { 699 | get { return (Thickness)GetValue(CornerRadiusProperty); } 700 | set { SetValue(CornerRadiusProperty, value); } 701 | } 702 | 703 | public new static readonly BindableProperty PaddingProperty = BindableProperty.Create( 704 | nameof(Padding), 705 | typeof(Thickness), 706 | typeof(NullableDateTimePicker), 707 | defaultValue: new Thickness(0), 708 | defaultBindingMode: BindingMode.OneWay, 709 | propertyChanged: (bindable, oldValue, newValue) => 710 | { 711 | if (bindable is NullableDateTimePicker nullableDateTimePickerBindable && newValue is Thickness padding) 712 | { 713 | nullableDateTimePickerBindable._dateTimePickerBorder.Padding = new Thickness(padding.Left, padding.Top, padding.Right, padding.Bottom); 714 | } 715 | }); 716 | public new Thickness Padding 717 | { 718 | get { return (Thickness)GetValue(PaddingProperty); } 719 | set { SetValue(PaddingProperty, value); } 720 | } 721 | 722 | 723 | public static readonly BindableProperty VerticalTextAlignmentProperty = BindableProperty.Create( 724 | nameof(VerticalTextAlignment), 725 | typeof(TextAlignment), 726 | typeof(NullableDateTimePicker), 727 | defaultValue: TextAlignment.Center, 728 | defaultBindingMode: BindingMode.OneWay, 729 | propertyChanged: (bindable, oldValue, newValue) => 730 | { 731 | if (bindable is NullableDateTimePicker nullableDateTimePickerBindable && newValue is TextAlignment verticalTextAlignment) 732 | { 733 | nullableDateTimePickerBindable._dateTimePickerEntry.VerticalTextAlignment = verticalTextAlignment; 734 | } 735 | }); 736 | public TextAlignment VerticalTextAlignment 737 | { 738 | get { return (TextAlignment)GetValue(VerticalTextAlignmentProperty); } 739 | set { SetValue(VerticalTextAlignmentProperty, value); } 740 | } 741 | 742 | 743 | public static readonly BindableProperty Is12HourFormatProperty = BindableProperty.Create( 744 | nameof(Is12HourFormat), 745 | typeof(bool), 746 | typeof(NullableDateTimePicker), 747 | defaultValue: false, 748 | defaultBindingMode: BindingMode.OneWay); 749 | 750 | public bool Is12HourFormat 751 | { 752 | get { return (bool)GetValue(Is12HourFormatProperty); } 753 | set { SetValue(Is12HourFormatProperty, value); } 754 | } 755 | 756 | public static readonly BindableProperty HideIconProperty = BindableProperty.Create( 757 | nameof(HideIcon), 758 | typeof(bool), 759 | typeof(NullableDateTimePicker), 760 | defaultValue: false, 761 | defaultBindingMode: BindingMode.OneWay, 762 | propertyChanged: (bindable, oldValue, newValue) => 763 | { 764 | if (bindable is NullableDateTimePicker nullableDateTimePickerBindable && newValue is bool hideIcon) 765 | { 766 | nullableDateTimePickerBindable._dateTimePickerGrid.ColumnDefinitions[1].Width = hideIcon ? new GridLength(0) : GridLength.Auto; 767 | nullableDateTimePickerBindable._dateTimePickerIcon.IsVisible = !hideIcon; 768 | } 769 | }); 770 | 771 | public bool HideIcon 772 | { 773 | get { return (bool)GetValue(HideIconProperty); } 774 | set { SetValue(HideIconProperty, value); } 775 | } 776 | 777 | 778 | #endregion //bindable properties 779 | 780 | #region constructor 781 | public NullableDateTimePicker() 782 | { 783 | base.Padding = 0; 784 | base.Margin = 0; 785 | base.BackgroundColor = Colors.Transparent; 786 | base.HeightRequest = defaultHeightRequest; 787 | 788 | _dateTimePickerGrid = new Microsoft.Maui.Controls.Grid 789 | { 790 | Margin = 0, 791 | Padding = 0, 792 | RowSpacing = 0, 793 | ColumnSpacing = 0, 794 | HorizontalOptions = LayoutOptions.Fill, 795 | VerticalOptions = LayoutOptions.Fill, 796 | BackgroundColor = Colors.Transparent, 797 | ColumnDefinitions = 798 | { 799 | new ColumnDefinition { Width = new GridLength(1, GridUnitType.Star) }, 800 | new ColumnDefinition { Width = new GridLength(1, GridUnitType.Auto) } 801 | } 802 | }; 803 | 804 | _dateTimePickerEntry = new NullableDateTimePickerEntry() 805 | { 806 | IsReadOnly = true, 807 | Margin = 0, 808 | BackgroundColor = Colors.Transparent, 809 | FontSize = this.FontSize, 810 | TextColor = this.TextColor, 811 | FontFamily = FontFamily, 812 | HorizontalOptions = LayoutOptions.Fill, 813 | VerticalOptions = LayoutOptions.Fill, 814 | HorizontalTextAlignment = TextAlignment.Start, 815 | VerticalTextAlignment = this.VerticalTextAlignment, 816 | PlaceholderColor = this.PlaceholderColor 817 | }; 818 | 819 | _dateTimePickerGrid.SetColumn(_dateTimePickerEntry, 0); 820 | _dateTimePickerGrid.Add(_dateTimePickerEntry); 821 | 822 | 823 | _dateTimePickerIcon = new Image 824 | { 825 | BackgroundColor = this.IconBackgroundColor, 826 | Aspect = Aspect.AspectFit, 827 | Margin = 0, 828 | HorizontalOptions = LayoutOptions.End, 829 | VerticalOptions = LayoutOptions.Fill 830 | }; 831 | _dateTimePickerGrid.SetColumn(_dateTimePickerIcon, 1); 832 | _dateTimePickerGrid.Add(_dateTimePickerIcon); 833 | 834 | var clickableView = new BoxView { Color = Colors.Transparent, Background = Colors.Transparent, BackgroundColor = Colors.Transparent, HorizontalOptions = LayoutOptions.Fill, VerticalOptions = LayoutOptions.Fill }; 835 | _dateTimePickerGrid.SetColumn(clickableView, 0); 836 | 837 | _dateTimePickerGrid.SetColumnSpan(clickableView, 2); 838 | _dateTimePickerGrid.Add(clickableView); 839 | 840 | TapGestureRecognizer tapGestureRecognizer = new TapGestureRecognizer 841 | { 842 | NumberOfTapsRequired = 1 843 | }; 844 | tapGestureRecognizer.Tapped += (s, e) => 845 | { 846 | OnDatePickerClicked(s, e); 847 | }; 848 | 849 | clickableView.GestureRecognizers.Add(tapGestureRecognizer); 850 | 851 | 852 | _dateTimePickerIcon.SetBinding(Image.WidthRequestProperty, new Binding("Height", source: clickableView)); 853 | _dateTimePickerIcon.SetBinding(Image.MaximumWidthRequestProperty, new Binding("Height", source: clickableView)); 854 | _dateTimePickerIcon.SetBinding(Image.HeightRequestProperty, new Binding("Height", source: clickableView)); 855 | _dateTimePickerIcon.SetBinding(Image.MaximumHeightRequestProperty, new Binding("Height", source: clickableView)); 856 | _dateTimePickerEntry.SetBinding(Entry.HeightRequestProperty, new Binding("Height", source: clickableView)); 857 | 858 | _dateTimePickerBorder = new Border 859 | { 860 | BackgroundColor = this.BackgroundColor, 861 | Stroke = this.BorderColor, 862 | StrokeShape = new RoundRectangle 863 | { 864 | CornerRadius = new CornerRadius(0, 0, 0, 0) 865 | }, 866 | StrokeThickness = this.BorderWidth, 867 | Content = _dateTimePickerGrid, 868 | Margin = 0, 869 | Padding = this.Padding, 870 | HorizontalOptions = LayoutOptions.Fill, 871 | VerticalOptions = LayoutOptions.Fill 872 | }; 873 | 874 | this.Loaded += (s, e) => 875 | { 876 | SetCalendarIcon(); 877 | }; 878 | 879 | Content = _dateTimePickerBorder; 880 | } 881 | #endregion //consturctor 882 | 883 | 884 | #region public methods 885 | public static async Task OpenCalendarAsync(INullableDateTimePickerOptions options) 886 | { 887 | PopupResultTask popupResultTask = null; 888 | try 889 | { 890 | popupResultTask = new PopupResultTask(); 891 | 892 | using (var popupControl = new NullableDateTimePickerPopup(options)) 893 | { 894 | #if WINDOWS 895 | await Mopups.Services.MopupService.Instance.PushAsync(popupControl); 896 | var result = await popupControl.WaitForResultAsync(); 897 | _ = Mopups.Services.MopupService.Instance.RemovePageAsync(popupControl); 898 | #else 899 | var result = await CommunityToolkit.Maui.Views.PopupExtensions.ShowPopupAsync(Page, popupControl); 900 | #endif 901 | if (result is PopupResult popupResult) 902 | { 903 | popupResultTask.SetResult(popupResult); 904 | } 905 | else 906 | { 907 | popupResultTask.SetResult(null); 908 | } 909 | }; 910 | } 911 | catch (Exception ex) 912 | { 913 | Console.WriteLine(ex.ToString()); 914 | } 915 | 916 | return await (popupResultTask?.Result ?? Task.FromResult(null)); 917 | } 918 | #endregion //public metlods 919 | 920 | #region private methods 921 | private async void OnDatePickerClicked(object sender, EventArgs e) 922 | { 923 | await OpenCalendarPopupAsync(); 924 | } 925 | 926 | bool isPopupOpen = false; 927 | private async Task OpenCalendarPopupAsync() 928 | { 929 | if (!base.IsEnabled || isPopupOpen) 930 | return; 931 | 932 | isPopupOpen = true; 933 | 934 | try 935 | { 936 | INullableDateTimePickerOptions options = new NullableDateTimePickerOptions 937 | { 938 | NullableDateTime = this.NullableDateTime, 939 | Mode = this.Mode, 940 | MinDate = this.MinDate, 941 | MaxDate = this.MaxDate, 942 | OkButtonText = this.OkButtonText, 943 | CancelButtonText = this.CancelButtonText, 944 | ClearButtonText = this.ClearButtonText, 945 | PopupBorderColor = this.PopupBorderColor, 946 | PopupBorderWidth = this.PopupBorderWidth, 947 | PopupBorderCornerRadius = this.PopupCornerRadius, 948 | PopupPadding = this.PopupPadding, 949 | ForeColor = this.ForeColor, 950 | BodyBackgroundColor = this.BodyBackgroundColor, 951 | HeaderForeColor = this.HeaderForeColor, 952 | HeaderBackgroundColor = this.HeaderBackgroundColor, 953 | ToolButtonsStyle = this.ToolButtonsStyle, 954 | DayStyle = this.DayStyle, 955 | DisabledDayStyle = this.DisabledDayStyle, 956 | OtherMonthDayStyle = this.OtherMonthDayStyle, 957 | SelectedDayStyle = this.SelectedDayStyle, 958 | DayNamesStyle = this.DayNamesStyle, 959 | ShowWeekNumbers = this.ShowWeekNumbers, 960 | WeekNumberStyle = this.WeekNumberStyle, 961 | ShowOtherMonthDays = this.ShowOtherMonthDays, 962 | ActivityIndicatorColor = this.ActivityIndicatorColor, 963 | ShowClearButton = this.ShowClearButton, 964 | CloseOnOutsideClick = this.CloseOnOutsideClick, 965 | Is12HourFormat = this.Is12HourFormat, 966 | AutomationId = base.AutomationId 967 | }; 968 | 969 | var result = await NullableDateTimePicker.OpenCalendarAsync(options); 970 | if (result is PopupResult popupResult && popupResult.ButtonResult != PopupButtons.Cancel) 971 | { 972 | NullableDateTime = popupResult.NullableDateTime; 973 | } 974 | } 975 | catch (Exception ex) 976 | { 977 | Console.WriteLine(ex.ToString()); 978 | } 979 | finally 980 | { 981 | isPopupOpen = false; 982 | } 983 | } 984 | 985 | private void SetCalendarIcon() 986 | { 987 | if (HideIcon) 988 | return; 989 | 990 | MainThreadHelper.SafeBeginInvokeOnMainThread(async () => 991 | { 992 | await Task.Delay(100); 993 | if (Icon != null) 994 | { 995 | _dateTimePickerIcon.Source = Icon; 996 | } 997 | else 998 | { 999 | string imageName = Mode switch 1000 | { 1001 | PickerModes.DateTime => "date_time_icon.png", 1002 | PickerModes.Time => "time_icon.png", 1003 | _ => "date_icon.png" 1004 | }; 1005 | try 1006 | { 1007 | var assembly = typeof(NullableDateTimePicker).GetTypeInfo().Assembly; 1008 | var assemblyName = assembly.GetName().Name; 1009 | var imagePath = $"{assemblyName}.Images.{imageName}"; 1010 | _dateTimePickerIcon.Source = ImageSource.FromResource($"{imagePath}"); 1011 | } 1012 | catch { } 1013 | } 1014 | }); 1015 | } 1016 | 1017 | private static DateTime? ParseDateTime(object objectValue) 1018 | { 1019 | DateTime? dateValue = null; 1020 | if (objectValue is DateTime) 1021 | { 1022 | dateValue = (DateTime?)objectValue; 1023 | } 1024 | else if (objectValue is string strValue) 1025 | { 1026 | if (DateTime.TryParse(strValue, out DateTime outputDate)) 1027 | { 1028 | dateValue = outputDate; 1029 | } 1030 | } 1031 | return dateValue; 1032 | } 1033 | protected override void OnPropertyChanged(string propertyName = null) 1034 | { 1035 | base.OnPropertyChanged(propertyName); 1036 | 1037 | switch (propertyName) 1038 | { 1039 | case nameof(base.IsEnabled): 1040 | _dateTimePickerBorder.IsEnabled = base.IsEnabled; 1041 | _dateTimePickerIcon.IsEnabled = base.IsEnabled; 1042 | break; 1043 | case nameof(base.AutomationId): 1044 | _dateTimePickerEntry.AutomationId = base.AutomationId + "_DatetimePickerEntry"; 1045 | _dateTimePickerIcon.AutomationId = base.AutomationId + "_DatetimePickerIcon"; 1046 | break; 1047 | } 1048 | } 1049 | #endregion //private methods 1050 | } -------------------------------------------------------------------------------- /Maui.NullableDateTimePicker/Popup/NullableDateTimePickerPopup.cs: -------------------------------------------------------------------------------- 1 | // The MauiCommunityToolkit popup crashes on Windows when used in a Modal Page. See: https://github.com/CommunityToolkit/Maui/issues/2459 2 | // The Mopup popup displays behind Modal on Android. See: https://github.com/LuckyDucko/Mopups/issues/82 3 | // => if Windows Use Mopup else use MauiCommunityToolkit 4 | #if WINDOWS 5 | using LibraryPopup = Mopups.Pages.PopupPage; 6 | #else 7 | using LibraryPopup = CommunityToolkit.Maui.Views.Popup; 8 | #endif 9 | 10 | namespace Maui.NullableDateTimePicker; 11 | 12 | internal class NullableDateTimePickerPopup : LibraryPopup, IDisposable 13 | { 14 | private readonly EventHandler okButtonClickedHandler = null; 15 | private readonly EventHandler clearButtonClickedHandler = null; 16 | private readonly EventHandler cancelButtonClickedHandler = null; 17 | private NullableDateTimePickerContent _content = null; 18 | private bool _disposed = false; 19 | internal NullableDateTimePickerPopup(INullableDateTimePickerOptions options) 20 | { 21 | _content = new NullableDateTimePickerContent(options); 22 | 23 | if (options.AutomationId == null) 24 | options.AutomationId = ""; 25 | 26 | this.AutomationId = options.AutomationId + "_DatetimePickerPopup"; 27 | 28 | DisplayInfo displayMetrics = DeviceDisplay.MainDisplayInfo; 29 | #if WINDOWS 30 | this.BackgroundColor = Colors.Transparent; 31 | #else 32 | this.Color = Colors.Transparent; 33 | #endif 34 | 35 | var popupWidth = Math.Max(Math.Min(displayMetrics.Width / displayMetrics.Density, 300), 100); 36 | var popupHeight = Math.Max(Math.Min(displayMetrics.Height / displayMetrics.Density, 450), 100); 37 | 38 | #if WINDOWS 39 | _content.WidthRequest = popupWidth; 40 | _content.HeightRequest = popupHeight; 41 | this.CloseWhenBackgroundIsClicked = options.CloseOnOutsideClick; 42 | #else 43 | this.Size = new Size(popupWidth, popupHeight); 44 | this.CanBeDismissedByTappingOutsideOfPopup = options.CloseOnOutsideClick; 45 | #endif 46 | 47 | 48 | #if WINDOWS 49 | this.Appearing += _content.NullableDateTimePickerPopupAppearing; 50 | #else 51 | this.Opened += _content.NullableDateTimePickerPopupOpened; 52 | #endif 53 | 54 | 55 | okButtonClickedHandler = (s, e) => 56 | { 57 | ClosePopup(PopupButtons.Ok); 58 | }; 59 | _content.OkButtonClicked += okButtonClickedHandler; 60 | 61 | clearButtonClickedHandler = (s, e) => 62 | { 63 | ClosePopup(PopupButtons.Clear); 64 | }; 65 | _content.ClearButtonClicked += clearButtonClickedHandler; 66 | 67 | cancelButtonClickedHandler = (s, e) => 68 | { 69 | ClosePopup(PopupButtons.Cancel); 70 | }; 71 | _content.CancelButtonClicked += cancelButtonClickedHandler; 72 | 73 | Content = _content; 74 | } 75 | 76 | 77 | #if WINDOWS 78 | private TaskCompletionSource _tcs = new TaskCompletionSource(); 79 | public Task WaitForResultAsync() 80 | { 81 | return _tcs.Task; 82 | } 83 | 84 | protected override void OnDisappearing() 85 | { 86 | base.OnDisappearing(); 87 | _tcs.TrySetResult(null); 88 | } 89 | #endif 90 | 91 | internal void ClosePopup(PopupButtons buttonResult) 92 | { 93 | try 94 | { 95 | _content.OkButtonClicked -= okButtonClickedHandler; 96 | _content.ClearButtonClicked -= clearButtonClickedHandler; 97 | _content.CancelButtonClicked -= cancelButtonClickedHandler; 98 | 99 | #if WINDOWS 100 | _tcs.TrySetResult(new PopupResult(_content.SelectedDate, buttonResult)); 101 | #else 102 | Close(new PopupResult(_content.SelectedDate, buttonResult)); 103 | #endif 104 | 105 | } 106 | catch (Exception ex) 107 | { 108 | Console.WriteLine(ex.ToString()); 109 | } 110 | finally 111 | { 112 | Content = null; 113 | } 114 | } 115 | 116 | public void Dispose() 117 | { 118 | Dispose(true); 119 | 120 | GC.SuppressFinalize(this); 121 | } 122 | 123 | ~NullableDateTimePickerPopup() 124 | { 125 | Dispose(false); 126 | } 127 | 128 | protected virtual void Dispose(bool disposing) 129 | { 130 | if (!_disposed) 131 | { 132 | if (disposing) 133 | { 134 | _content = null; 135 | } 136 | _disposed = true; 137 | } 138 | } 139 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Maui Nullable and Clearable DateTimePicker 2 | The Nullable DateTimePicker is a custom calendar control for selecting a nullable date and time value in a .NET MAUI application. It provides a consistent and platform-independent user interface for selecting dates, and allows the user to clear the value if needed. 3 | 4 | 5 | This control uses the CommunityToolkit.Maui Popup. 6 | 7 | [](https://www.nuget.org/packages/Sebarslan.Maui.NullableDateTimePicker) 8 | 9 | 10 | # Usage 11 | To use the Nullable DateTimePicker control in your .NET MAUI application, follow these steps: 12 | 13 | 0- Add the Sebarslan.Maui.NullableDateTimePicker nuget package to your project and add the .ConfigureNullableDateTimePicker() element to the MauiProgram.cs file in your project. 14 | 15 | 16 | 17 | public static MauiApp CreateMauiApp() 18 | { 19 | var builder = MauiApp.CreateBuilder() 20 | .UseMauiApp<App>() 21 | .ConfigureNullableDateTimePicker() 22 | .... 23 | 24 | 25 | 26 | ### Usage 1: Use DateTimePicker as ContentView control (With input field and icon) 27 | 1- Add the NullableDateTimePicker control to your XAML layout file: 28 | 29 | xmlns:ndtp="clr-namespace:Maui.NullableDateTimePicker;assembly=Maui.NullableDateTimePicker" 30 | 31 | 32 | <ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui" 33 | xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" 34 | xmlns:ndtp="clr-namespace:Maui.NullableDateTimePicker;assembly=Maui.NullableDateTimePicker" 35 | x:Class="Maui.NullableDateTimePicker.Samples.MainPage"> 36 | 37 | 38 | 39 | 2- And add the following code to the place where you want to use DateTimePicker and then connect the NullableDateTime property with the Datetime Property in your ViewModel. 40 | 41 | 42 | <ndtp:NullableDateTimePicker NullableDateTime="{Binding MyDateTime}" Mode="Date" /> 43 | 44 | 45 | 46 | 47 | ### Usage 2: Use direct calendar popup with your own entry and button 48 | 49 | 1- Add your entry and button for datetime in your xaml page (eg. MainPage.xaml) 50 | 51 | 52 | <HorizontalStackLayout HorizontalOptions="Fill" HeightRequest="40"> 53 | <Entry x:Name="DateTimeEntry" Text="{Binding MyDateTime, StringFormat='{0:g}'}" 54 | HorizontalOptions="Fill" 55 | VerticalOptions="Fill" 56 | IsReadOnly="True" /> 57 | <ImageButton Source="{Binding CalendarIcon}" 58 | Clicked="DateTimePicker_Clicked" 59 | Margin="0" 60 | Padding="2" 61 | WidthRequest="30" /> 62 | </HorizontalStackLayout> 63 | 64 | 65 | 66 | 2- Then, when you click on the button or entry, define the options and call NullableDateTimePicker.OpenCalendarAsync(options) to open the calendar in your xaml.cs file. (eg. MainPage.xaml.cs) 67 | 68 | 69 | private async void DateTimePicker_Clicked(object sender, EventArgs e) 70 | { 71 | INullableDateTimePickerOptions nullableDateTimePickerOptions = new NullableDateTimePickerOptions 72 | { 73 | NullableDateTime = MyDateTime, 74 | Mode = PickerModes.DateTime, 75 | ShowWeekNumbers = true 76 | // .. other calendar options 77 | }; 78 | 79 | var result = await NullableDateTimePicker.OpenCalendarAsync(nullableDateTimePickerOptions); 80 | if (result is PopupResult popupResult && popupResult.ButtonResult != PopupButtons.Cancel) 81 | { 82 | MyDateTime = popupResult.NullableDateTime; 83 | // DateTimeEntry.Text = popupResult.NullableDateTime?.ToString("g"); //If you are not using ViewModel 84 | } 85 | } 86 | 87 | 88 | 89 | 90 | More examples, please see the samples project 91 | 92 | # Options 93 | ## DateTimePicker Calendar options 94 | | Option | Description | Default Value | 95 | |--------|-------------|---------| 96 | | NullableDateTime | Gets or sets the nullable date and time value of the control. | null | 97 | | Mode | Specifies the picker mode of the control. Valid values are Date, DateTime, and Time. | Date | 98 | | MinDate | Minimum selectable date of the control. | DateTime.MinValue | 99 | | MaxDate | Maximum selectable date of the control. | DateTime.MaxValue | 100 | | OkButtonText | The text for the OK button. | OK | 101 | | CancelButtonText | The text for the Cancel button. | Cancel | 102 | | ClearButtonText | Gets or sets the text for the Clear button. | Clear | 103 | | ShowClearButton | Clear button can be hidden/shown. If true, the button is displayed. | true | 104 | | HeaderForeColor | Gets or sets the foreground color of the control's header. | White | 105 | | HeaderBackgroundColor | Background color of the calendar's header. | #2b0b98 | 106 | | ForeColor | It is used for the color of texts that cannot be styled in the calendar. | Dark:White, Light:Black | 107 | | BodyBackgroundColor | Background color of the calendar. | White | 108 | | ToolButtonsStyle | Style of the control's tool buttons. | null | 109 | | DayStyle | Style of the days in the calendar. | null | 110 | | SelectedDayStyle | Style of the selected day in the calendar. | null | 111 | | DayNamesStyle | Style of the day names in the calendar. | null | 112 | | OtherMonthDayStyle | Style of the other month days in the calendar. | null | 113 | | DisabledDayStyle | Style of the disabled days in the calendar. | null | 114 | | WeekNumberStyle | Style of the week numbers in the calendar. | null | 115 | | ShowWeekNumbers | Determines whether to display week numbers in the calendar. | false | 116 | | ShowOtherMonthDays | Determines whether to display other month days in the calendar. | true | 117 | | Is12HourFormat | Determines whether to display the am/pm picker for the 12-hour format. | false | 118 | | AutomationId | You can give your own automation id. With this you can access Calendar elements. Example: {Your-AutomationId}_CalendarYearsPicker, {Your-AutomationId}_CalendarOkButton | empty | 119 | 120 | 121 | ## Datetimepicker Input Options (If NullableDateTimePicker is used as ContentView) 122 | | Option | Description | Default Value | 123 | |--------|-------------|---------| 124 | | Format | Specifies the display format for the date or time. | for date: d, for datetime: g, for time: t | 125 | | BackgroundColor | Background color of the datetimepicker control. | White | 126 | | Icon | Imagesource for the icon. | null | 127 | | TextColor | Text color of the entry. | Black | 128 | | FontFamily | Font family of the entry. | Arial | 129 | | FontSize | Font size of the entry. | 14 | 130 | | BorderColor | Border color of the datetimepicker control | none | 131 | | BorderWidth | Border width of the control | 0 | 132 | | CornerRadius | Corner radius of the control | 0 | 133 | | PlaceHolder | Placeholder of the entry | empty | 134 | | PlaceHolderColor | Placeholder color of the entry | Gray | 135 | | HideIcon | Determines whether to show or hide the calendar icon. | false | 136 | 137 | 138 | ## NullableDateTimeChanged Event (If NullableDateTimePicker is used as ContentView) 139 | The NullableDateTimeChanged event is used to indicate when a NullableDateTime value has been changed. 140 | This event is commonly used in programming or software environments and is triggered when the NullableDateTime value is modified. 141 | 142 | The event utilizes the DateTimeChangedEventArgs class as its argument. The DateTimeChangedEventArgs class contains additional information that is carried at the moment the event is triggered. It may include details about the date and time change, such as the old DateTime value and the new DateTime value. 143 | 144 | Below is an example code snippet illustrating the usage of the "NullableDateTimeChanged" event and the "DateTimeChangedEventArgs" argument class: 145 | 146 | 147 | NullableDateTimePicker dateTimePicker = new NullableDateTimePicker(); 148 | dateTimePicker.NullableDateTimeChanged += OnNullableDateTimeChanged; 149 | 150 | private static void OnNullableDateTimeChanged(object sender, DateTimeChangedEventArgs e) 151 | { 152 | Console.WriteLine("DateTime changed!"); 153 | Console.WriteLine("Old DateTime: " + e.OldDateTime); 154 | Console.WriteLine("New DateTime: " + e.NewDateTime); 155 | } 156 | 157 | 158 | 159 | 160 | > .NET MAUI handler was used in the test project to remove the underline in the original entry. 161 | > Please refer to the MauiProgram.cs file in the sample project. 162 | > For more detailed information about handlers, please check: 163 | > https://learn.microsoft.com/en-us/dotnet/maui/user-interface/handlers/customize?view=net-maui-8.0 164 | 165 | 166 | 167 | # License 168 | The Nullable DateTimePicker control is licensed under the MIT License. See LICENSE file for more information. 169 | 170 | # Contributing 171 | Contributions are welcome! 172 | 173 | # Screenshot 174 | on ios, android, windows 175 | 176 |  177 | 178 | # Changelog 179 | 180 | ### 2.4.1-preview 181 | - Since CommunityToolkit Popup does not work on modal pages in windows, Mopup-Library is used temporarily in windows. (Thanks @sferhah) 182 | 183 | ### 2.4.0 184 | - Added HideIcon property 185 | - You can access calendar elements by providing your own AutomationId. 186 | - Net 9.0 compatibility 187 | - net7.0 support removed 188 | - Added new version of .NET MAUI Communitytoolkit 189 | - A new select list is created for years and months and the scroll bar is moved to the selected year. 190 | 191 | ### 2.3.1 192 | - Fixed issue: Icons displayed inconsistently 193 | 194 | ### 2.3.0 195 | - Setting TextColor had no effect. 196 | - FontFamily added. 197 | - Fixed am/pm picker issue in windows 198 | 199 | ### 2.2.0 200 | - 12 hour format added (AM/PM picker) 201 | - Time icon fixed 202 | - The calendar can also be opened by clicking on the entry. 203 | - In time picker, only time is shown in the header 204 | - Various improvements. 205 | 206 | ### 2.1.0 207 | - Fixed: Content is already a child of Microsoft.Maui.Controls.ContentPage. 208 | - Fixed: When the months were changed in Windows, the application would hang. 209 | 210 | ### 2.0.0 211 | - Layout adjustments 212 | - Fixed: The months list was opening in Time mode. 213 | - If the months list is open, the year changes with the next/previous button. 214 | - Default height: 40 215 | - Net 8.0 compatibility 216 | 217 | ### 1.2.0 218 | - Months can be quickly selected from the list. 219 | - MinDate and MaxDate validations have been reviewed and revised. 220 | - A border and CornerRadius has been added to the datetimepicker control. 221 | 222 | ### 1.1.1 223 | - The .NET MAUI Communitytoolkit version (6.1.0) was added to the project. 224 | - Updated the week number logic to align with ISO 8601 standards. 225 | - Bug fixed: When going back from the 1st month, the year was also decreased. 226 | 227 | ### 1.1.0 228 | - The calendar popup can be opened directly via the NullableDateTimePicker, so you can use your own entry and button. 229 | - On some screens, week and day numbers were not displayed on the same line. 230 | - Dark theme adjusted 231 | - Various bugs fixed 232 | 233 | ### 1.0.2 234 | - The problem of displaying the default icon in default mode has been fixed. 235 | 236 | - Various improvements. 237 | 238 | ### 1.0.1 239 | - The problem of not setting the margin has been fixed. 240 | -------------------------------------------------------------------------------- /screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sebarslan/Maui.NullableDateTimePicker/5acac06b1c4366c28a434ee59c26bda8afe7baa8/screenshot.png --------------------------------------------------------------------------------
16 | 17 | public static MauiApp CreateMauiApp() 18 | { 19 | var builder = MauiApp.CreateBuilder() 20 | .UseMauiApp<App>() 21 | .ConfigureNullableDateTimePicker() 22 | .... 23 | 24 |
17 | public static MauiApp CreateMauiApp() 18 | { 19 | var builder = MauiApp.CreateBuilder() 20 | .UseMauiApp<App>() 21 | .ConfigureNullableDateTimePicker() 22 | .... 23 |
31 | 32 | <ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui" 33 | xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" 34 | xmlns:ndtp="clr-namespace:Maui.NullableDateTimePicker;assembly=Maui.NullableDateTimePicker" 35 | x:Class="Maui.NullableDateTimePicker.Samples.MainPage"> 36 | 37 |
32 | <ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui" 33 | xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" 34 | xmlns:ndtp="clr-namespace:Maui.NullableDateTimePicker;assembly=Maui.NullableDateTimePicker" 35 | x:Class="Maui.NullableDateTimePicker.Samples.MainPage"> 36 |
41 | 42 | <ndtp:NullableDateTimePicker NullableDateTime="{Binding MyDateTime}" Mode="Date" /> 43 | 44 |
42 | <ndtp:NullableDateTimePicker NullableDateTime="{Binding MyDateTime}" Mode="Date" /> 43 |
51 | 52 | <HorizontalStackLayout HorizontalOptions="Fill" HeightRequest="40"> 53 | <Entry x:Name="DateTimeEntry" Text="{Binding MyDateTime, StringFormat='{0:g}'}" 54 | HorizontalOptions="Fill" 55 | VerticalOptions="Fill" 56 | IsReadOnly="True" /> 57 | <ImageButton Source="{Binding CalendarIcon}" 58 | Clicked="DateTimePicker_Clicked" 59 | Margin="0" 60 | Padding="2" 61 | WidthRequest="30" /> 62 | </HorizontalStackLayout> 63 | 64 |
52 | <HorizontalStackLayout HorizontalOptions="Fill" HeightRequest="40"> 53 | <Entry x:Name="DateTimeEntry" Text="{Binding MyDateTime, StringFormat='{0:g}'}" 54 | HorizontalOptions="Fill" 55 | VerticalOptions="Fill" 56 | IsReadOnly="True" /> 57 | <ImageButton Source="{Binding CalendarIcon}" 58 | Clicked="DateTimePicker_Clicked" 59 | Margin="0" 60 | Padding="2" 61 | WidthRequest="30" /> 62 | </HorizontalStackLayout> 63 |
68 | 69 | private async void DateTimePicker_Clicked(object sender, EventArgs e) 70 | { 71 | INullableDateTimePickerOptions nullableDateTimePickerOptions = new NullableDateTimePickerOptions 72 | { 73 | NullableDateTime = MyDateTime, 74 | Mode = PickerModes.DateTime, 75 | ShowWeekNumbers = true 76 | // .. other calendar options 77 | }; 78 | 79 | var result = await NullableDateTimePicker.OpenCalendarAsync(nullableDateTimePickerOptions); 80 | if (result is PopupResult popupResult && popupResult.ButtonResult != PopupButtons.Cancel) 81 | { 82 | MyDateTime = popupResult.NullableDateTime; 83 | // DateTimeEntry.Text = popupResult.NullableDateTime?.ToString("g"); //If you are not using ViewModel 84 | } 85 | } 86 | 87 |
69 | private async void DateTimePicker_Clicked(object sender, EventArgs e) 70 | { 71 | INullableDateTimePickerOptions nullableDateTimePickerOptions = new NullableDateTimePickerOptions 72 | { 73 | NullableDateTime = MyDateTime, 74 | Mode = PickerModes.DateTime, 75 | ShowWeekNumbers = true 76 | // .. other calendar options 77 | }; 78 | 79 | var result = await NullableDateTimePicker.OpenCalendarAsync(nullableDateTimePickerOptions); 80 | if (result is PopupResult popupResult && popupResult.ButtonResult != PopupButtons.Cancel) 81 | { 82 | MyDateTime = popupResult.NullableDateTime; 83 | // DateTimeEntry.Text = popupResult.NullableDateTime?.ToString("g"); //If you are not using ViewModel 84 | } 85 | } 86 |
146 | 147 | NullableDateTimePicker dateTimePicker = new NullableDateTimePicker(); 148 | dateTimePicker.NullableDateTimeChanged += OnNullableDateTimeChanged; 149 | 150 | private static void OnNullableDateTimeChanged(object sender, DateTimeChangedEventArgs e) 151 | { 152 | Console.WriteLine("DateTime changed!"); 153 | Console.WriteLine("Old DateTime: " + e.OldDateTime); 154 | Console.WriteLine("New DateTime: " + e.NewDateTime); 155 | } 156 | 157 |
147 | NullableDateTimePicker dateTimePicker = new NullableDateTimePicker(); 148 | dateTimePicker.NullableDateTimeChanged += OnNullableDateTimeChanged; 149 | 150 | private static void OnNullableDateTimeChanged(object sender, DateTimeChangedEventArgs e) 151 | { 152 | Console.WriteLine("DateTime changed!"); 153 | Console.WriteLine("Old DateTime: " + e.OldDateTime); 154 | Console.WriteLine("New DateTime: " + e.NewDateTime); 155 | } 156 |