How to build a DateTimePicker using the .NET MAUI Picker (SfPicker)
How to Build a DateTimePicker Using the .NET MAUI SfPicker
.NET MAUI SfPicker control supports multi-column pickers, making it ideal for building a custom DateTimePicker with separate columns for month, day, year, hour, and minute.
Step 1: Create a Custom Control
Create a new control (e.g., CustomDateTimePicker) that encapsulates all logic and collections for date and time selection. Inherit from ContentView and add the SfPicker as a Children.
C#
public partial class CustomDateTimePicker : ContentView
{
private DateTimePickerViewModel viewModel = null!;
/// <summary>
/// Bindable property for the selected DateTime
/// </summary>
public static readonly BindableProperty SelectedDateTimeProperty =
BindableProperty.Create(
nameof(SelectedDateTime),
typeof(DateTime),
typeof(CustomDateTimePicker),
DateTime.Now,
BindingMode.TwoWay,
propertyChanged: OnSelectedDateTimePropertyChanged);
/// <summary>
/// Event raised when the selected DateTime changes
/// </summary>
public event EventHandler<DateTimeChangedEventArgs>? DateTimeChanged;
public DateTime SelectedDateTime
{
get => (DateTime)GetValue(SelectedDateTimeProperty);
set => SetValue(SelectedDateTimeProperty, value);
}
public CustomDateTimePicker()
{
InitializeComponent();
// Initialize the ViewModel
viewModel = new DateTimePickerViewModel();
this.BindingContext = viewModel;
// Set initial selected values in UI
UpdateDisplayLabel();
}
/// <summary>
/// Opens the DateTime picker dialog
/// </summary>
public void OpenPicker()
{
if (DateTimePickerControl != null)
{
DateTimePickerControl.IsOpen = true;
}
}
/// <summary>
/// Handle button click to open picker
/// </summary>
private void OnOpenPickerClicked(object sender, EventArgs e)
{
OpenPicker();
}
/// <summary>
/// Handle selection changed event from SfPicker
/// </summary>
private void OnDateTimePickerSelectionChanged(object sender, PickerSelectionChangedEventArgs e)
{
if (DateTimePickerControl?.Columns != null)
{
try
{
// Get selected values from each column
int selectedMonth = GetSelectedValue(0, viewModel.Months, viewModel.SelectedMonth);
int selectedDay = GetSelectedValue(1, viewModel.Days, viewModel.SelectedDay);
int selectedYear = GetSelectedValue(2, viewModel.Years, viewModel.SelectedYear);
int selectedHour = GetSelectedValue(3, viewModel.Hours, viewModel.SelectedHour);
int selectedMinute = GetSelectedValue(4, viewModel.Minutes, viewModel.SelectedMinute);
// Update ViewModel
viewModel.SelectedMonth = selectedMonth;
viewModel.SelectedDay = selectedDay;
viewModel.SelectedYear = selectedYear;
viewModel.SelectedHour = selectedHour;
viewModel.SelectedMinute = selectedMinute;
// Create DateTime from selected values
var selectedDateTime = new DateTime(selectedYear, selectedMonth, selectedDay, selectedHour, selectedMinute, 0);
// Update the BindableProperty
SelectedDateTime = selectedDateTime;
// Update display label
UpdateDisplayLabel();
// Raise the DateTimeChanged event
DateTimeChanged?.Invoke(this, new DateTimeChangedEventArgs(selectedDateTime));
}
catch (Exception ex)
{
SelectedDateTimeLabel.Text = $"Error: {ex.Message}";
}
}
}
/// <summary>
/// Helper method to get selected value from a column
/// </summary>
private int GetSelectedValue(int columnIndex, ObservableCollection<int>? itemsSource, int defaultValue)
{
if (DateTimePickerControl?.Columns != null &&
itemsSource != null &&
columnIndex < DateTimePickerControl.Columns.Count &&
itemsSource.Count > 0)
{
int selectedIndex = DateTimePickerControl.Columns[columnIndex].SelectedIndex;
if (selectedIndex >= 0 && selectedIndex < itemsSource.Count)
{
return itemsSource[selectedIndex];
}
}
return defaultValue;
}
/// <summary>
/// Update the display label with the formatted DateTime
/// </summary>
private void UpdateDisplayLabel()
{
try
{
var dateTime = viewModel.GetSelectedDateTime();
SelectedDateTimeLabel.Text = dateTime.ToString("MMMM dd, yyyy HH:mm");
}
catch (Exception ex)
{
SelectedDateTimeLabel.Text = $"Error: {ex.Message}";
}
}
/// <summary>
/// Handle SelectedDateTime property changes
/// </summary>
private static void OnSelectedDateTimePropertyChanged(BindableObject bindable, object oldValue, object newValue)
{
if (bindable is CustomDateTimePicker control && newValue is DateTime dateTime)
{
// Update the ViewModel's selected values
control.viewModel.SelectedYear = dateTime.Year;
control.viewModel.SelectedMonth = dateTime.Month;
control.viewModel.SelectedDay = dateTime.Day;
control.viewModel.SelectedHour = dateTime.Hour;
control.viewModel.SelectedMinute = dateTime.Minute;
control.UpdateDisplayLabel();
}
}
}
Step 2: Define XAML for the Custom Picker
Define the UI in CustomDateTimePicker.xaml using a VerticalStackLayout, a label for the selected value, and the SfPicker with five columns (Month, Day, Year, Hour, Minute):
XAML:
<ContentView xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:picker="clr-namespace:Syncfusion.Maui.Picker;assembly=Syncfusion.Maui.Picker"
x:Class="DateTimePickerSample.Controls.CustomDateTimePicker">
<VerticalStackLayout Spacing="10">
<!-- Display Selected DateTime -->
<Border
Stroke="#512BD4"
StrokeThickness="2"
Padding="15">
<Border.StrokeShape>
<RoundRectangle CornerRadius="10"/>
</Border.StrokeShape>
<VerticalStackLayout Spacing="5">
<Label Text="Selected DateTime:" FontAttributes="Bold" />
<Label
x:Name="SelectedDateTimeLabel"
Text="Not selected"
FontSize="16"
TextColor="#512BD4" />
</VerticalStackLayout>
</Border>
<!-- SfPicker with 5 Columns for Date and Time (Hidden from user) -->
<picker:SfPicker
x:Name="DateTimePickerControl"
Mode="Dialog"
Height="350"
ItemHeight="50"
SelectionChanged="OnDateTimePickerSelectionChanged">
<picker:SfPicker.HeaderView>
<picker:PickerHeaderView
Text="Select Date and Time"
Height="40" />
</picker:SfPicker.HeaderView>
<picker:SfPicker.ColumnHeaderView>
<picker:PickerColumnHeaderView Height="40"/>
</picker:SfPicker.ColumnHeaderView>
<picker:SfPicker.Columns>
<!-- Month Column -->
<picker:PickerColumn
HeaderText="Month"
ItemsSource="{Binding Months}" />
<!-- Day Column -->
<picker:PickerColumn
HeaderText="Day"
ItemsSource="{Binding Days}" />
<!-- Year Column -->
<picker:PickerColumn
HeaderText="Year"
ItemsSource="{Binding Years}" />
<!-- Hour Column -->
<picker:PickerColumn
HeaderText="Hour"
ItemsSource="{Binding Hours}" />
<!-- Minute Column -->
<picker:PickerColumn
HeaderText="Minute"
ItemsSource="{Binding Minutes}" />
</picker:SfPicker.Columns>
</picker:SfPicker>
<!-- Open Picker Button -->
<Button
x:Name="OpenPickerBtn"
Text="Open DateTime Picker"
Clicked="OnOpenPickerClicked"
BackgroundColor="#512BD4"
TextColor="White"
Padding="15"
CornerRadius="10"
FontAttributes="Bold"
HorizontalOptions="Fill" />
</VerticalStackLayout>
Step 3: Define ObservableCollections in the ViewModel
Define collections for months, days, years, hours, and minutes in a ViewModel (e.g., DateTimePickerViewModel).
C#
public class DateTimePickerViewModel
{
public ObservableCollection<int>? Months { get; set; }
public ObservableCollection<int>? Days { get; set; }
public ObservableCollection<int>? Years { get; set; }
public ObservableCollection<int>? Hours { get; set; }
public ObservableCollection<int>? Minutes { get; set; }
public int SelectedMonth { get; set; }
public int SelectedDay { get; set; }
public int SelectedYear { get; set; }
public int SelectedHour { get; set; }
public int SelectedMinute { get; set; }
public DateTimePickerViewModel()
{
InitializeCollections();
SetDefaultValues();
}
private void InitializeCollections()
{
// Initialize Months (1-12)
Months = new ObservableCollection<int>();
for (int i = 1; i <= 12; i++)
{
Months.Add(i);
}
// Initialize Days (1-31)
Days = new ObservableCollection<int>();
for (int i = 1; i <= 31; i++)
{
Days.Add(i);
}
// Initialize Years (2020-2030)
Years = new ObservableCollection<int>();
int currentYear = DateTime.Now.Year;
for (int i = currentYear - 5; i <= currentYear + 5; i++)
{
Years.Add(i);
}
// Initialize Hours (0-23)
Hours = new ObservableCollection<int>();
for (int i = 0; i < 24; i++)
{
Hours.Add(i);
}
// Initialize Minutes (0-59)
Minutes = new ObservableCollection<int>();
for (int i = 0; i < 60; i++)
{
Minutes.Add(i);
}
}
private void SetDefaultValues()
{
var now = DateTime.Now;
SelectedMonth = now.Month;
SelectedDay = now.Day;
SelectedYear = now.Year;
SelectedHour = now.Hour;
SelectedMinute = now.Minute;
}
public DateTime GetSelectedDateTime()
{
try
{
return new DateTime(SelectedYear, SelectedMonth, SelectedDay, SelectedHour, SelectedMinute,0);
}
catch
{
return DateTime.Now;
}
}
}
Step 4: Handle Selection Changes
In your custom control, handle the SelectionChanged event of the SfPicker to update the ViewModel and propagate the selected DateTime.
C#:
private void OnDateTimePickerSelectionChanged(object sender, PickerSelectionChangedEventArgs e)
{
if (DateTimePickerControl?.Columns != null)
{
try
{
// Get selected values from each column
int selectedMonth = GetSelectedValue(0, viewModel.Months, viewModel.SelectedMonth);
int selectedDay = GetSelectedValue(1, viewModel.Days, viewModel.SelectedDay);
int selectedYear = GetSelectedValue(2, viewModel.Years, viewModel.SelectedYear);
int selectedHour = GetSelectedValue(3, viewModel.Hours, viewModel.SelectedHour);
int selectedMinute = GetSelectedValue(4, viewModel.Minutes, viewModel.SelectedMinute);
// Update ViewModel
viewModel.SelectedMonth = selectedMonth;
viewModel.SelectedDay = selectedDay;
viewModel.SelectedYear = selectedYear;
viewModel.SelectedHour = selectedHour;
viewModel.SelectedMinute = selectedMinute;
// Create DateTime from selected values
var selectedDateTime = new DateTime(selectedYear, selectedMonth, selectedDay, selectedHour, selectedMinute, 0);
// Update the BindableProperty
SelectedDateTime = selectedDateTime;
// Update display label
UpdateDisplayLabel();
// Raise the DateTimeChanged event
DateTimeChanged?.Invoke(this, new DateTimeChangedEventArgs(selectedDateTime));
}
catch (Exception ex)
{
SelectedDateTimeLabel.Text = $"Error: {ex.Message}";
}
}
}
Step 5: Use the Custom Picker in MainPage
Add the custom picker to your MainPage and bind its SelectedDateTime property to your ViewModel.
XAML:
<ScrollView>
<VerticalStackLayout
Padding="30,20"
Spacing="20">
<Label
Text="DateTime Picker"
FontSize="28"
FontAttributes="Bold"
TextColor="#512BD4"
HorizontalOptions="Center" />
<Label
Text="Select Date and Time"
FontSize="16"
TextColor="#666666"
HorizontalOptions="Center" />
<!-- Custom DateTime Picker Component -->
<controls:CustomDateTimePicker
x:Name="CustomDateTimePicker"
SelectedDateTime="{Binding SelectedDateTime, Mode=TwoWay, StringFormat='{0:G}'}"
DateTimeChanged="OnDateTimeChanged" />
</VerticalStackLayout>
</ScrollView>
C#
public partial class MainPage : ContentPage
{
private MainPageViewModel viewModel;
public MainPage()
{
InitializeComponent();
// Initialize the ViewModel
viewModel = new MainPageViewModel();
this.BindingContext = viewModel;
}
/// <summary>
/// Handle the DateTimeChanged event from the custom DateTime picker component
/// </summary>
private void OnDateTimeChanged(object sender, DateTimeChangedEventArgs e)
{
// Update the ViewModel's SelectedDateTime property for binding
viewModel.SelectedDateTime = e.SelectedDateTime;
}
}
ViewModel
public class MainPageViewModel : INotifyPropertyChanged
{
private DateTime selectedDateTime = DateTime.Now;
public DateTime SelectedDateTime
{
get => selectedDateTime;
set
{
if (selectedDateTime != value)
{
selectedDateTime = value;
OnPropertyChanged();
}
}
}
public event PropertyChangedEventHandler? PropertyChanged;
protected void OnPropertyChanged([CallerMemberName] string? name = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
}
}
Output
Download the complete sample on GitHub.
Conclusion:
This article demonstrated how to build a custom DateTimePicker using the Syncfusion .NET MAUI SfPicker in dialog mode. It covers creating a CustomDateTimePicker control, preparing ViewModel collections for months, days, years, hours, and minutes, defining the XAML layout, handling SelectionChanged to update a bindable SelectedDateTime property, and using the control in MainPage.
For more information, refer to the following resources:
For current customers, check out our .NET MAUI components from the License and Downloads page. If you are new to Syncfusion®, try our 30-day free trial to check out our .NET MAUI Picker and other .NET MAUI components.
Please let us know in the following comments section if you have any queries or require clarification. You can also contact us through our support forums, Direct-Trac, or feedback portal. We are always happy to assist you!