diff --git a/maui/samples/Gallery/ControlList.xml b/maui/samples/Gallery/ControlList.xml
index 23131249..e8218467 100644
--- a/maui/samples/Gallery/ControlList.xml
+++ b/maui/samples/Gallery/ControlList.xml
@@ -24,5 +24,7 @@
+
+
\ No newline at end of file
diff --git a/maui/samples/Gallery/Converters/StringToVisibilityConverter .cs b/maui/samples/Gallery/Converters/StringToVisibilityConverter .cs
new file mode 100644
index 00000000..68c7980e
--- /dev/null
+++ b/maui/samples/Gallery/Converters/StringToVisibilityConverter .cs
@@ -0,0 +1,18 @@
+using System.Globalization;
+
+namespace Syncfusion.Maui.ControlsGallery.Converters
+{
+ public class StringToVisibilityConverter : IValueConverter
+ {
+ public object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture)
+ {
+ return !string.IsNullOrEmpty(value as string);
+ }
+
+ public object? ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture)
+ {
+ return null;
+ }
+
+ }
+}
diff --git a/maui/samples/Gallery/Resources/Images/circularprogressbar.png b/maui/samples/Gallery/Resources/Images/circularprogressbar.png
new file mode 100644
index 00000000..39543a03
Binary files /dev/null and b/maui/samples/Gallery/Resources/Images/circularprogressbar.png differ
diff --git a/maui/samples/Gallery/Resources/Images/datepicker.png b/maui/samples/Gallery/Resources/Images/datepicker.png
new file mode 100644
index 00000000..a0fae170
Binary files /dev/null and b/maui/samples/Gallery/Resources/Images/datepicker.png differ
diff --git a/maui/samples/Gallery/Resources/Images/datetimepicker.png b/maui/samples/Gallery/Resources/Images/datetimepicker.png
new file mode 100644
index 00000000..70dd4e8b
Binary files /dev/null and b/maui/samples/Gallery/Resources/Images/datetimepicker.png differ
diff --git a/maui/samples/Gallery/Resources/Images/linearprogressbar.png b/maui/samples/Gallery/Resources/Images/linearprogressbar.png
new file mode 100644
index 00000000..3e5bd527
Binary files /dev/null and b/maui/samples/Gallery/Resources/Images/linearprogressbar.png differ
diff --git a/maui/samples/Gallery/Resources/Images/picker.png b/maui/samples/Gallery/Resources/Images/picker.png
new file mode 100644
index 00000000..446bba29
Binary files /dev/null and b/maui/samples/Gallery/Resources/Images/picker.png differ
diff --git a/maui/samples/Gallery/Resources/Images/timepicker.png b/maui/samples/Gallery/Resources/Images/timepicker.png
new file mode 100644
index 00000000..583f7186
Binary files /dev/null and b/maui/samples/Gallery/Resources/Images/timepicker.png differ
diff --git a/maui/samples/Gallery/SampleList/PickerSamplesList.xml b/maui/samples/Gallery/SampleList/PickerSamplesList.xml
new file mode 100644
index 00000000..c1960833
--- /dev/null
+++ b/maui/samples/Gallery/SampleList/PickerSamplesList.xml
@@ -0,0 +1,37 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/maui/samples/Gallery/SampleList/ProgressBarSamplesList.xml b/maui/samples/Gallery/SampleList/ProgressBarSamplesList.xml
new file mode 100644
index 00000000..217fc850
--- /dev/null
+++ b/maui/samples/Gallery/SampleList/ProgressBarSamplesList.xml
@@ -0,0 +1,14 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/maui/samples/Gallery/Samples/Button/Customizations/View/CustomizationsDesktop.xaml b/maui/samples/Gallery/Samples/Button/Customizations/View/CustomizationsDesktop.xaml
index e520160e..d0125b9d 100644
--- a/maui/samples/Gallery/Samples/Button/Customizations/View/CustomizationsDesktop.xaml
+++ b/maui/samples/Gallery/Samples/Button/Customizations/View/CustomizationsDesktop.xaml
@@ -466,7 +466,7 @@
-
diff --git a/maui/samples/Gallery/Samples/Button/Customizations/View/CustomizationsMobile.xaml b/maui/samples/Gallery/Samples/Button/Customizations/View/CustomizationsMobile.xaml
index e48ae3e4..9c89572a 100644
--- a/maui/samples/Gallery/Samples/Button/Customizations/View/CustomizationsMobile.xaml
+++ b/maui/samples/Gallery/Samples/Button/Customizations/View/CustomizationsMobile.xaml
@@ -433,7 +433,7 @@
-
diff --git a/maui/samples/Gallery/Samples/Button/Customizations/ViewModel/ViewModel.cs b/maui/samples/Gallery/Samples/Button/Customizations/ViewModel/ViewModel.cs
index 45d8be9a..2ae246d8 100644
--- a/maui/samples/Gallery/Samples/Button/Customizations/ViewModel/ViewModel.cs
+++ b/maui/samples/Gallery/Samples/Button/Customizations/ViewModel/ViewModel.cs
@@ -104,6 +104,12 @@ public partial class ViewModel : INotifyPropertyChanged
/// Selected Index of BackgroundImageAspect.
///
private int _aspectSelectedIndex = 0;
+
+ ///
+ /// Represents whether the AspectList feature is enabled.
+ ///
+ private bool _enableAspectList = false;
+
#endregion
#region Property
@@ -314,10 +320,12 @@ public bool CanShowBackgroundImage
if (value)
{
BackgroundImage = "april.png";
+ EnableAspectList = true;
}
else
{
BackgroundImage = null;
+ EnableAspectList = false;
}
OnPropertyChanged("CanShowBackgroundImage");
OnPropertyChanged("BackgroundImage");
@@ -414,6 +422,15 @@ public int TextTransformSelectedIndex
}
}
+ public bool EnableAspectList
+ {
+ get { return _enableAspectList; }
+ set
+ {
+ _enableAspectList = value;
+ OnPropertyChanged("EnableAspectList");
+ }
+ }
#endregion
diff --git a/maui/samples/Gallery/Samples/Calendar/AppearanceCustomization/Behavior/AppearanceCustomizationBehavior.cs b/maui/samples/Gallery/Samples/Calendar/AppearanceCustomization/Behavior/AppearanceCustomizationBehavior.cs
index 832ee9c4..1a6b4f5c 100644
--- a/maui/samples/Gallery/Samples/Calendar/AppearanceCustomization/Behavior/AppearanceCustomizationBehavior.cs
+++ b/maui/samples/Gallery/Samples/Calendar/AppearanceCustomization/Behavior/AppearanceCustomizationBehavior.cs
@@ -15,7 +15,7 @@ internal class AppearanceCustomizationBehavior : Behavior
///
/// The combo box that allows users to choose to whether to select date or a range.
///
- Picker? _comboBox;
+ Microsoft.Maui.Controls.Picker? _comboBox;
///
/// Check the application theme is light or dark.
@@ -35,7 +35,7 @@ protected override void OnAttachedTo(SampleView bindable)
_calendar.SelectionBackground = _isLightTheme ? Color.FromRgba("#6750A4").WithAlpha(0.5f) : Color.FromRgba("#D0BCFF").WithAlpha(0.5f);
_calendar.TodayHighlightBrush = _isLightTheme ? Color.FromRgba("#6750A4") : Color.FromRgba("#D0BCFF");
_calendar.SelectionShape = CalendarSelectionShape.Circle;
- _comboBox = bindable.Content.FindByName("comboBox");
+ _comboBox = bindable.Content.FindByName("comboBox");
_comboBox.ItemsSource = new List() { "Circle", "Rectangle" };
_comboBox.SelectedIndex = 0;
_comboBox.SelectedIndexChanged += ComboBox_SelectionChanged;
@@ -62,7 +62,7 @@ protected override void OnDetachingFrom(SampleView bindable)
/// Event Arguments
void ComboBox_SelectionChanged(object? sender, EventArgs e)
{
- if (_calendar != null && sender is Picker picker && picker.SelectedItem is string selectionShape)
+ if (_calendar != null && sender is Microsoft.Maui.Controls.Picker picker && picker.SelectedItem is string selectionShape)
{
if (_calendar.BindingContext is AppearanceViewModel)
{
diff --git a/maui/samples/Gallery/Samples/Calendar/DateSelection/Behavior/DateSelectionBehavior.cs b/maui/samples/Gallery/Samples/Calendar/DateSelection/Behavior/DateSelectionBehavior.cs
index 7a97f949..bb904204 100644
--- a/maui/samples/Gallery/Samples/Calendar/DateSelection/Behavior/DateSelectionBehavior.cs
+++ b/maui/samples/Gallery/Samples/Calendar/DateSelection/Behavior/DateSelectionBehavior.cs
@@ -15,7 +15,7 @@ internal class DateSelectionBehavior : Behavior
///
/// The combo box that allows users to choose to whether to select date or a range.
///
- Picker? _comboBox;
+ Microsoft.Maui.Controls.Picker? _comboBox;
///
/// The label to display the selected date or range.
@@ -67,7 +67,7 @@ protected override void OnAttachedTo(SampleView bindable)
_label = bindable.Content.FindByName
- Picker? _comboBox;
+ Microsoft.Maui.Controls.Picker? _comboBox;
///
/// This combo box is used to choose the selection mode of the calendar
///
- Picker? _selectionComboBox;
+ Microsoft.Maui.Controls.Picker? _selectionComboBox;
///
/// This combo box is used to choose the selection shape of the calendar
///
- Picker? _selectionShapeComboBox;
+ Microsoft.Maui.Controls.Picker? _selectionShapeComboBox;
///
/// This combo box is used to choose the selection direction of the calendar
///
- Picker? _directionComboBox;
+ Microsoft.Maui.Controls.Picker? _directionComboBox;
///
/// Grid for SelectionDirection, TrailingDates and EnableSwipeSelection
@@ -119,23 +119,23 @@ protected override void OnAttachedTo(SampleView bindable)
_cornerRadiusSlider = bindable.Content.FindByName("cornerRadiusSlider");
- _comboBox = bindable.Content.FindByName("comboBox");
+ _comboBox = bindable.Content.FindByName("comboBox");
_comboBox.ItemsSource = Enum.GetValues(typeof(CalendarView)).Cast().ToList();
_comboBox.SelectedIndex = 0;
_comboBox.SelectedIndexChanged += ComboBox_SelectionChanged;
- _selectionComboBox = bindable.Content.FindByName("selectionComboBox");
+ _selectionComboBox = bindable.Content.FindByName("selectionComboBox");
_selectionComboBox.ItemsSource = Enum.GetValues(typeof(CalendarSelectionMode)).Cast().ToList();
_selectionComboBox.SelectedIndex = 0;
_selectionComboBox.SelectedIndexChanged += ComboBox_SelectionTypeChanged;
- _selectionShapeComboBox = bindable.Content.FindByName("selectionShapeComboBox");
+ _selectionShapeComboBox = bindable.Content.FindByName("selectionShapeComboBox");
_selectionShapeComboBox.ItemsSource = Enum.GetValues(typeof(CalendarSelectionShape)).Cast().ToList();
_selectionShapeComboBox.SelectedIndex = 0;
_selectionShapeComboBox.SelectedIndexChanged += ComboBox_SelectionShapeChanged;
_selectionDirectionGrid = bindable.Content.FindByName("selectionDirectionGrid");
- _directionComboBox = bindable.Content.FindByName("directionComboBox");
+ _directionComboBox = bindable.Content.FindByName("directionComboBox");
_directionComboBox.ItemsSource = Enum.GetValues(typeof(CalendarRangeSelectionDirection)).Cast().ToList();
_directionComboBox.SelectedIndex = 0;
_directionComboBox.SelectedIndexChanged += DirectionComboBox_SelectionChanged;
@@ -240,7 +240,7 @@ void DatePicker_DateSelected(object? sender, DateChangedEventArgs e)
/// Event Arguments
void DirectionComboBox_SelectionChanged(object? sender, EventArgs e)
{
- if (_calendar != null && sender is Picker picker && picker.SelectedItem is CalendarRangeSelectionDirection selectionDirection)
+ if (_calendar != null && sender is Microsoft.Maui.Controls.Picker picker && picker.SelectedItem is CalendarRangeSelectionDirection selectionDirection)
{
_calendar.RangeSelectionDirection = selectionDirection;
}
@@ -300,7 +300,7 @@ void Calendar_ViewChanged(object? sender, CalendarViewChangedEventArgs e)
/// Event Arguments
void ComboBox_SelectionTypeChanged(object? sender, EventArgs e)
{
- if (_calendar != null && sender is Picker picker && picker.SelectedItem is CalendarSelectionMode selectionMode)
+ if (_calendar != null && sender is Microsoft.Maui.Controls.Picker picker && picker.SelectedItem is CalendarSelectionMode selectionMode)
{
_calendar.SelectionMode = selectionMode;
if (_calendar.SelectionMode == CalendarSelectionMode.Range)
@@ -349,7 +349,7 @@ void ComboBox_SelectionTypeChanged(object? sender, EventArgs e)
/// Event Arguments
void ComboBox_SelectionShapeChanged(object? sender, EventArgs e)
{
- if (_calendar != null && sender is Picker picker && picker.SelectedItem is CalendarSelectionShape selectionShape)
+ if (_calendar != null && sender is Microsoft.Maui.Controls.Picker picker && picker.SelectedItem is CalendarSelectionShape selectionShape)
{
_calendar.SelectionShape = selectionShape;
}
@@ -362,7 +362,7 @@ void ComboBox_SelectionShapeChanged(object? sender, EventArgs e)
/// Event Arguments
void ComboBox_SelectionChanged(object? sender, EventArgs e)
{
- if (_calendar != null && sender is Picker picker && picker.SelectedItem is CalendarView view)
+ if (_calendar != null && sender is Microsoft.Maui.Controls.Picker picker && picker.SelectedItem is CalendarView view)
{
_calendar.View = view;
if (_trailingDatesGrid != null)
diff --git a/maui/samples/Gallery/Samples/Calendar/GettingStarted/View/GettingStarted.xaml b/maui/samples/Gallery/Samples/Calendar/GettingStarted/View/GettingStarted.xaml
index 5ef3dab6..58b6560d 100644
--- a/maui/samples/Gallery/Samples/Calendar/GettingStarted/View/GettingStarted.xaml
+++ b/maui/samples/Gallery/Samples/Calendar/GettingStarted/View/GettingStarted.xaml
@@ -30,7 +30,7 @@
-
-
-
-
-
+ HorizontalOptions="Start"
+ HeightRequest="37"
+ VerticalTextAlignment="Center"/>
+
@@ -187,7 +190,7 @@
-
- Event Arguments
void comboBox_SelectionChanged(object sender, EventArgs e)
{
- if (sender is Picker picker && picker.SelectedItem is string theme)
+ if (sender is Microsoft.Maui.Controls.Picker picker && picker.SelectedItem is string theme)
{
if (theme == null)
{
diff --git a/maui/samples/Gallery/Samples/Cards/GettingStarted/Behavior/GettingStartedBehavior.cs b/maui/samples/Gallery/Samples/Cards/GettingStarted/Behavior/GettingStartedBehavior.cs
index abce3f68..c23ec845 100644
--- a/maui/samples/Gallery/Samples/Cards/GettingStarted/Behavior/GettingStartedBehavior.cs
+++ b/maui/samples/Gallery/Samples/Cards/GettingStarted/Behavior/GettingStartedBehavior.cs
@@ -35,7 +35,7 @@ public class GettingStartedBehavior : Behavior
///
/// This combo box is used to choose the indicator position of the cards.
///
- Picker? _indicatorPositionComboBox;
+ Microsoft.Maui.Controls.Picker? _indicatorPositionComboBox;
///
/// Begins when the behavior attached to the view
@@ -55,7 +55,7 @@ protected override void OnAttachedTo(SampleView sampleView)
_indicatorSwitch = sampleView.Content.FindByName("indicatorSwitch");
_indicatorPositionOption = sampleView.Content.FindByName("indicatorPositionOption");
- _indicatorPositionComboBox = sampleView.Content.FindByName("indicatorPositionComboBox");
+ _indicatorPositionComboBox = sampleView.Content.FindByName("indicatorPositionComboBox");
_indicatorPositionComboBox.ItemsSource = Enum.GetValues(typeof(CardIndicatorPosition)).Cast().ToList();
_indicatorPositionComboBox.SelectedIndex = 0;
_indicatorPositionComboBox.SelectedIndexChanged += IndicatorPositionComboBox_SelectedIndexChanged;
@@ -110,7 +110,7 @@ void IndicatorSwitch_Toggled(object? sender, ToggledEventArgs e)
/// Event Arguments.
void IndicatorPositionComboBox_SelectedIndexChanged(object? sender, EventArgs e)
{
- if (sender is Picker picker && _firstCard != null && _secondCard != null && _thirdCard != null && _fourthCard != null && _fifthCard != null)
+ if (sender is Microsoft.Maui.Controls.Picker picker && _firstCard != null && _secondCard != null && _thirdCard != null && _fourthCard != null && _fifthCard != null)
{
string? selectedPosition = picker.SelectedItem.ToString();
switch (selectedPosition)
@@ -161,7 +161,7 @@ void CornerRadiusSlider_ValueChanged(object? sender, ValueChangedEventArgs e)
_thirdCard.CornerRadius = new CornerRadius(Math.Round(e.NewValue));
_fourthCard.CornerRadius = new CornerRadius(Math.Round(e.NewValue));
_fifthCard.CornerRadius = new CornerRadius(Math.Round(e.NewValue));
- _cornerRadiusLabel.Text = "CornerRadius: " + Math.Round(e.NewValue);
+ _cornerRadiusLabel.Text = "Corner radius: " + Math.Round(e.NewValue);
}
}
diff --git a/maui/samples/Gallery/Samples/CartesianChart/BoxAndWhisker/BoxAndWhisker.xaml b/maui/samples/Gallery/Samples/CartesianChart/BoxAndWhisker/BoxAndWhisker.xaml
index d947ef87..a469d319 100644
--- a/maui/samples/Gallery/Samples/CartesianChart/BoxAndWhisker/BoxAndWhisker.xaml
+++ b/maui/samples/Gallery/Samples/CartesianChart/BoxAndWhisker/BoxAndWhisker.xaml
@@ -53,13 +53,13 @@
+ SelectedItem="{Binding Source={x:Reference ViewModel},Path=BoxPlotMode[0]}"
+ HorizontalOptions="Fill" VerticalOptions="Start"
+ SelectedIndex="0"
+ SelectedIndexChanged="ModePicker_SelectedIndexChanged"
+ Background="{OnPlatform Default={AppThemeBinding Dark={StaticResource TextColour1Dark}, Light={StaticResource BackgroundLight}},Android=Transparent}"
+ TextColor="{OnPlatform Default={StaticResource TextColourLight}, Android={AppThemeBinding Light={StaticResource TextColourLight}, Dark={StaticResource TextColourDark}}}">
+
diff --git a/maui/samples/Gallery/Samples/CartesianChart/DataLabel/DataLabelTemplate.xaml b/maui/samples/Gallery/Samples/CartesianChart/DataLabel/DataLabelTemplate.xaml
index 7187aa7d..7e3eeaee 100644
--- a/maui/samples/Gallery/Samples/CartesianChart/DataLabel/DataLabelTemplate.xaml
+++ b/maui/samples/Gallery/Samples/CartesianChart/DataLabel/DataLabelTemplate.xaml
@@ -17,14 +17,14 @@
-
+
-
-
diff --git a/maui/samples/Gallery/Samples/CartesianChart/DataLabel/DataLabelTemplate.xaml.cs b/maui/samples/Gallery/Samples/CartesianChart/DataLabel/DataLabelTemplate.xaml.cs
index 8c90752b..15c82575 100644
--- a/maui/samples/Gallery/Samples/CartesianChart/DataLabel/DataLabelTemplate.xaml.cs
+++ b/maui/samples/Gallery/Samples/CartesianChart/DataLabel/DataLabelTemplate.xaml.cs
@@ -36,12 +36,19 @@ public class DoubleToFontIconConverter : IValueConverter
{
public object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture)
{
- if (value is not double || parameter is not string para)
- {
+ if (parameter is not string para)
return null;
- }
- var doubleValue = (double)value;
+ double doubleValue = 0;
+
+ if (value is ChartDataModel model)
+ {
+ if (para == "GrossLastYearDelta")
+ {
+ return model.GrossLastYearDelta;
+ }
+ doubleValue = (double)model.GrossLastYearDelta;
+ }
string text = "\ue704";
Color color = Colors.Red;
diff --git a/maui/samples/Gallery/Samples/CartesianChart/EmptyPoints/EmptyPointSupport.xaml b/maui/samples/Gallery/Samples/CartesianChart/EmptyPoints/EmptyPointSupport.xaml
index 5b7636cf..65ef4f83 100644
--- a/maui/samples/Gallery/Samples/CartesianChart/EmptyPoints/EmptyPointSupport.xaml
+++ b/maui/samples/Gallery/Samples/CartesianChart/EmptyPoints/EmptyPointSupport.xaml
@@ -102,15 +102,15 @@
-
+
+ SelectedIndexChanged="picker_SelectedIndexChanged"
+ Background="{OnPlatform Default={AppThemeBinding Dark={StaticResource TextColour1Dark}, Light={StaticResource BackgroundLight}},Android=Transparent}"
+ TextColor="{OnPlatform Default={StaticResource TextColourLight}, Android={AppThemeBinding Light={StaticResource TextColourLight}, Dark={StaticResource TextColourDark}}}">
diff --git a/maui/samples/Gallery/Samples/CartesianChart/EmptyPoints/EmptyPointSupport.xaml.cs b/maui/samples/Gallery/Samples/CartesianChart/EmptyPoints/EmptyPointSupport.xaml.cs
index 9518d217..4043ecce 100644
--- a/maui/samples/Gallery/Samples/CartesianChart/EmptyPoints/EmptyPointSupport.xaml.cs
+++ b/maui/samples/Gallery/Samples/CartesianChart/EmptyPoints/EmptyPointSupport.xaml.cs
@@ -12,7 +12,7 @@ public EmptyPointSupport()
private void picker_SelectedIndexChanged(object sender, EventArgs e)
{
- var picker = (Picker)sender;
+ var picker = (Microsoft.Maui.Controls.Picker)sender;
int selectedIndex = picker.SelectedIndex;
switch (selectedIndex)
{
diff --git a/maui/samples/Gallery/Samples/CartesianChart/ErrorBar/ErrorBarChart.xaml b/maui/samples/Gallery/Samples/CartesianChart/ErrorBar/ErrorBarChart.xaml
index 49a63c66..721ec2c9 100644
--- a/maui/samples/Gallery/Samples/CartesianChart/ErrorBar/ErrorBarChart.xaml
+++ b/maui/samples/Gallery/Samples/CartesianChart/ErrorBar/ErrorBarChart.xaml
@@ -80,15 +80,15 @@
-
+ SelectedIndexChanged="typePicker_SelectedIndexChanged"
+ Background="{OnPlatform Default={AppThemeBinding Dark={StaticResource TextColour1Dark}, Light={StaticResource BackgroundLight}},Android=Transparent}"
+ TextColor="{OnPlatform Default={StaticResource TextColourLight}, Android={AppThemeBinding Light={StaticResource TextColourLight}, Dark={StaticResource TextColourDark}}}">
@@ -97,15 +97,15 @@
-
+ SelectedIndexChanged="modePicker_SelectedIndexChanged"
+ Background="{OnPlatform Default={AppThemeBinding Dark={StaticResource TextColour1Dark}, Light={StaticResource BackgroundLight}},Android=Transparent}"
+ TextColor="{OnPlatform Default={StaticResource TextColourLight}, Android={AppThemeBinding Light={StaticResource TextColourLight}, Dark={StaticResource TextColourDark}}}">
@@ -114,15 +114,15 @@
-
+
diff --git a/maui/samples/Gallery/Samples/CartesianChart/PlotBand/HorizontalPlotBand.xaml b/maui/samples/Gallery/Samples/CartesianChart/PlotBand/HorizontalPlotBand.xaml
index e1bc280b..20d597c1 100644
--- a/maui/samples/Gallery/Samples/CartesianChart/PlotBand/HorizontalPlotBand.xaml
+++ b/maui/samples/Gallery/Samples/CartesianChart/PlotBand/HorizontalPlotBand.xaml
@@ -72,8 +72,10 @@
-
+
diff --git a/maui/samples/Gallery/Samples/CartesianChart/PlotBand/HorizontalPlotBandWindows.xaml b/maui/samples/Gallery/Samples/CartesianChart/PlotBand/HorizontalPlotBandWindows.xaml
index 4887ce0b..cb63e74d 100644
--- a/maui/samples/Gallery/Samples/CartesianChart/PlotBand/HorizontalPlotBandWindows.xaml
+++ b/maui/samples/Gallery/Samples/CartesianChart/PlotBand/HorizontalPlotBandWindows.xaml
@@ -71,8 +71,10 @@
-
+
diff --git a/maui/samples/Gallery/Samples/CartesianChart/Selection/Selection.xaml b/maui/samples/Gallery/Samples/CartesianChart/Selection/Selection.xaml
index dc372938..0cdd425c 100644
--- a/maui/samples/Gallery/Samples/CartesianChart/Selection/Selection.xaml
+++ b/maui/samples/Gallery/Samples/CartesianChart/Selection/Selection.xaml
@@ -75,7 +75,7 @@
-
+
diff --git a/maui/samples/Gallery/Samples/CartesianChart/Selection/SeriesSelection.xaml b/maui/samples/Gallery/Samples/CartesianChart/Selection/SeriesSelection.xaml
index 0ce8e920..aa393edd 100644
--- a/maui/samples/Gallery/Samples/CartesianChart/Selection/SeriesSelection.xaml
+++ b/maui/samples/Gallery/Samples/CartesianChart/Selection/SeriesSelection.xaml
@@ -77,7 +77,7 @@
-
+
diff --git a/maui/samples/Gallery/Samples/CartesianChart/Tooltip/CartesianTooltip.xaml b/maui/samples/Gallery/Samples/CartesianChart/Tooltip/CartesianTooltip.xaml
index b4953db9..b978d652 100644
--- a/maui/samples/Gallery/Samples/CartesianChart/Tooltip/CartesianTooltip.xaml
+++ b/maui/samples/Gallery/Samples/CartesianChart/Tooltip/CartesianTooltip.xaml
@@ -25,16 +25,17 @@
-
-
+
+
+
-
+
-
-
+
+
@@ -42,10 +43,10 @@
-
+
-
-
+
+
diff --git a/maui/samples/Gallery/Samples/CartesianChart/Tooltip/TooltipViewModel.cs b/maui/samples/Gallery/Samples/CartesianChart/Tooltip/TooltipViewModel.cs
index d82ea1fe..7bfaa8f3 100644
--- a/maui/samples/Gallery/Samples/CartesianChart/Tooltip/TooltipViewModel.cs
+++ b/maui/samples/Gallery/Samples/CartesianChart/Tooltip/TooltipViewModel.cs
@@ -1,4 +1,5 @@
using System.Collections.ObjectModel;
+using System.Globalization;
namespace Syncfusion.Maui.ControlsGallery.CartesianChart.SfCartesianChart
{
@@ -22,4 +23,32 @@ public TooltipViewModel()
];
}
}
+
+ public class TooltipValuesConverter : IValueConverter
+ {
+ public object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture)
+ {
+ if (value is ChartDataModel model)
+ {
+ var param = parameter?.ToString();
+
+ switch (param)
+ {
+ case "Value":
+ return $": {model.Value}M";
+ case "Size":
+ return $": {model.Size}M";
+ case "Value1":
+ return model.Value1;
+ }
+ }
+
+ return value;
+ }
+
+ public object? ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture)
+ {
+ return value;
+ }
+ }
}
diff --git a/maui/samples/Gallery/Samples/CartesianChart/Trackball/CartesianTrackball.xaml b/maui/samples/Gallery/Samples/CartesianChart/Trackball/CartesianTrackball.xaml
index 5f800c4f..eb9123b4 100644
--- a/maui/samples/Gallery/Samples/CartesianChart/Trackball/CartesianTrackball.xaml
+++ b/maui/samples/Gallery/Samples/CartesianChart/Trackball/CartesianTrackball.xaml
@@ -114,13 +114,13 @@
+ SelectedItem="{Binding Source={x:Reference ViewModel}, Path=DisplayMode[0]}"
+ VerticalOptions="Start" HorizontalOptions="Fill"
+ x:Name="picker"
+ SelectedIndex="0"
+ SelectedIndexChanged="picker_SelectedIndexChanged"
+ Background="{OnPlatform Default={AppThemeBinding Dark={StaticResource TextColour1Dark}, Light={StaticResource BackgroundLight}},Android=Transparent}"
+ TextColor="{OnPlatform Default={StaticResource TextColourLight}, Android={AppThemeBinding Light={StaticResource TextColourLight}, Dark={StaticResource TextColourDark}}}">
diff --git a/maui/samples/Gallery/Samples/CartesianChart/Zoom/ChartZooming.xaml b/maui/samples/Gallery/Samples/CartesianChart/Zoom/ChartZooming.xaml
index dab0631e..a80a779a 100644
--- a/maui/samples/Gallery/Samples/CartesianChart/Zoom/ChartZooming.xaml
+++ b/maui/samples/Gallery/Samples/CartesianChart/Zoom/ChartZooming.xaml
@@ -70,15 +70,15 @@
-
+
diff --git a/maui/samples/Gallery/Samples/Chips/ChipGettingStarted/ChipGettingStaredMobile.xaml.cs b/maui/samples/Gallery/Samples/Chips/ChipGettingStarted/ChipGettingStaredMobile.xaml.cs
index 3143ee62..891d9424 100644
--- a/maui/samples/Gallery/Samples/Chips/ChipGettingStarted/ChipGettingStaredMobile.xaml.cs
+++ b/maui/samples/Gallery/Samples/Chips/ChipGettingStarted/ChipGettingStaredMobile.xaml.cs
@@ -10,12 +10,10 @@ public ChipGettingStartedMobile()
}
private void SfChipGroup_SelectionChanged(object sender, Syncfusion.Maui.Toolkit.Chips.SelectionChangedEventArgs e)
{
- if (sender != null && ((sender as SfChipGroup)?.BindingContext as ChipViewModel) != null)
+ if (sender != null && sender is SfChipGroup chipGroup && chipGroup.BindingContext is ChipViewModel viewModel)
{
-#pragma warning disable CS8602 // Dereference of a possibly null reference.
if (!string.IsNullOrEmpty("SelectedAddOnItems"))
{
- var viewModel = ((sender as SfChipGroup).BindingContext as ChipViewModel);
if (viewModel.SelectedAddOnItems.Contains("Fast Charge"))
{
viewModel.FastChargePrice = 399;
@@ -26,7 +24,7 @@ private void SfChipGroup_SelectionChanged(object sender, Syncfusion.Maui.Toolkit
}
if (viewModel.SelectedAddOnItems.Contains("2 Years Extended Warranty"))
{
- viewModel.WarrentyPrice = 799;
+ viewModel.WarrantyPrice = 799;
}
if (!viewModel.SelectedAddOnItems.Contains("Fast Charge"))
{
@@ -38,11 +36,11 @@ private void SfChipGroup_SelectionChanged(object sender, Syncfusion.Maui.Toolkit
}
if (!viewModel.SelectedAddOnItems.Contains("2 Years Extended Warranty"))
{
- viewModel.WarrentyPrice = 0;
+ viewModel.WarrantyPrice = 0;
}
if (viewModel.SelectedAddOnItems.Contains("Fast Charge") || viewModel.SelectedAddOnItems.Contains("512 MB SD Card") || viewModel.SelectedAddOnItems.Contains("2 Years Extended Warranty"))
{
- viewModel.TotalAmount = viewModel.FastChargePrice + viewModel.SDCardPrice + viewModel.WarrentyPrice;
+ viewModel.TotalAmount = viewModel.FastChargePrice + viewModel.SDCardPrice + viewModel.WarrantyPrice;
viewModel.TotalPrice = "$ " + viewModel.TotalAmount;
}
else
@@ -51,8 +49,10 @@ private void SfChipGroup_SelectionChanged(object sender, Syncfusion.Maui.Toolkit
viewModel.TotalPrice = "$ " + viewModel.TotalAmount;
}
}
- } ((sender as SfChipGroup).BindingContext as ChipViewModel).FinalAmount = ((sender as SfChipGroup).BindingContext as ChipViewModel).TotalAmount;
-#pragma warning restore CS8602 // Dereference of a possibly null reference.
+
+ viewModel.FinalAmount = viewModel.TotalAmount;
+ }
+
}
}
}
\ No newline at end of file
diff --git a/maui/samples/Gallery/Samples/Chips/ChipGettingStarted/ChipGettingStartedDesktop.xaml.cs b/maui/samples/Gallery/Samples/Chips/ChipGettingStarted/ChipGettingStartedDesktop.xaml.cs
index 9e654cff..d41312d3 100644
--- a/maui/samples/Gallery/Samples/Chips/ChipGettingStarted/ChipGettingStartedDesktop.xaml.cs
+++ b/maui/samples/Gallery/Samples/Chips/ChipGettingStarted/ChipGettingStartedDesktop.xaml.cs
@@ -11,12 +11,10 @@ public ChipGettingStartedDesktop()
}
private void SfChipGroup_SelectionChanged(object sender, Syncfusion.Maui.Toolkit.Chips.SelectionChangedEventArgs e)
{
- if (sender != null && ((sender as SfChipGroup)?.BindingContext as ChipViewModel) != null)
+ if (sender != null && sender is SfChipGroup chipGroup && chipGroup.BindingContext is ChipViewModel viewModel)
{
-#pragma warning disable CS8602 // Dereference of a possibly null reference.
if (!string.IsNullOrEmpty("SelectedAddOnItems"))
{
- var viewModel = ((sender as SfChipGroup).BindingContext as ChipViewModel);
if (viewModel.SelectedAddOnItems.Contains("Fast Charge"))
{
viewModel.FastChargePrice = 657;
@@ -27,7 +25,7 @@ private void SfChipGroup_SelectionChanged(object sender, Syncfusion.Maui.Toolkit
}
if (viewModel.SelectedAddOnItems.Contains("2 Years Extended Warranty"))
{
- viewModel.WarrentyPrice = 799;
+ viewModel.WarrantyPrice = 799;
}
if (!viewModel.SelectedAddOnItems.Contains("Fast Charge"))
{
@@ -39,11 +37,11 @@ private void SfChipGroup_SelectionChanged(object sender, Syncfusion.Maui.Toolkit
}
if (!viewModel.SelectedAddOnItems.Contains("2 Years Extended Warranty"))
{
- viewModel.WarrentyPrice = 0;
+ viewModel.WarrantyPrice = 0;
}
if (viewModel.SelectedAddOnItems.Contains("Fast Charge") || viewModel.SelectedAddOnItems.Contains("512 MB SD Card") || viewModel.SelectedAddOnItems.Contains("2 Years Extended Warranty"))
{
- viewModel.TotalAmount = viewModel.FastChargePrice + viewModel.SDCardPrice + viewModel.WarrentyPrice;
+ viewModel.TotalAmount = viewModel.FastChargePrice + viewModel.SDCardPrice + viewModel.WarrantyPrice;
viewModel.TotalPrice = "$ " + viewModel.TotalAmount;
}
else
@@ -53,8 +51,9 @@ private void SfChipGroup_SelectionChanged(object sender, Syncfusion.Maui.Toolkit
}
}
- } ((sender as SfChipGroup).BindingContext as ChipViewModel).FinalAmount = ((sender as SfChipGroup).BindingContext as ChipViewModel).TotalAmount;
-#pragma warning restore CS8602 // Dereference of a possibly null reference.
+
+ viewModel.FinalAmount = viewModel.TotalAmount;
+ }
}
}
}
\ No newline at end of file
diff --git a/maui/samples/Gallery/Samples/Chips/ViewModel/ChipViewModel.cs b/maui/samples/Gallery/Samples/Chips/ViewModel/ChipViewModel.cs
index 93904570..8417fb82 100644
--- a/maui/samples/Gallery/Samples/Chips/ViewModel/ChipViewModel.cs
+++ b/maui/samples/Gallery/Samples/Chips/ViewModel/ChipViewModel.cs
@@ -192,7 +192,7 @@ public void OnPropertyChanged([CallerMemberName] string? name = null) =>
List? _addOnItems = ["Fast Charge", "512 MB SD Card", "2 Years Extended Warranty"];
///
- /// The devivery option choice collection.
+ /// The delivery option choice collection.
///
List? _deliveryOptions = ["Free Delivery", "+$5 for 1 Day Delivery"];
@@ -236,9 +236,9 @@ public void OnPropertyChanged([CallerMemberName] string? name = null) =>
int _sdCardPrice = 0;
///
- /// The warrenty price.
+ /// The warranty price.
///
- int _warrentyPrice = 0;
+ int _warrantyPrice = 0;
///
/// The delivery date.
@@ -922,7 +922,7 @@ public List? DeliveryOptions
}
///
- /// Gets or sets the contols list.
+ /// Gets or sets the controls list.
///
/// The ControlsList.
public List? ControlsList
@@ -1020,13 +1020,13 @@ public int SDCardPrice
}
///
- /// Gets or sets the warrenty price.
+ /// Gets or sets the warranty price.
///
- /// The WarrentyPrice.
- public int WarrentyPrice
+ /// The WarrantyPrice.
+ public int WarrantyPrice
{
- get { return _warrentyPrice; }
- set { _warrentyPrice = value; OnPropertyChanged(nameof(WarrentyPrice)); }
+ get { return _warrantyPrice; }
+ set { _warrantyPrice = value; OnPropertyChanged(nameof(WarrantyPrice)); }
}
///
@@ -1183,7 +1183,6 @@ public SfChipsType ChipType
///
/// Initializes a new instance of the class.
///
- [Obsolete]
public ChipViewModel()
{
ActionCommand = new Command(HandleAction);
@@ -1196,9 +1195,7 @@ public ChipViewModel()
void HandleAction(object obj)
{
Result.Clear();
-
-#pragma warning disable CS8602 // Dereference of a possibly null reference.
- if (obj != null && obj.ToString().Equals("Search by brands", StringComparison.Ordinal) && _selectedItem != null && _brands.Count > 0)
+ if (obj?.ToString()?.Equals("Search by brands", StringComparison.Ordinal) == true && _selectedItem != null && _brands?.Count > 0)
{
foreach (var brand in _brands)
{
@@ -1206,10 +1203,8 @@ void HandleAction(object obj)
}
}
- else if (FilterItems.Count > 0 && _selectedItem != null && _selectedFilterItems.Count > 0 && _brands.Count > 0)
+ else if (FilterItems.Count > 0 && _selectedItem != null && _selectedFilterItems.Count > 0 && _brands?.Count > 0)
{
-
-
foreach (var feature in _selectedFilterItems)
{
foreach (var brand in _brands)
diff --git a/maui/samples/Gallery/Samples/CircularChart/Doughnut/GroupToDoughnutChart.xaml b/maui/samples/Gallery/Samples/CircularChart/Doughnut/GroupToDoughnutChart.xaml
index fb92e959..750eff1a 100644
--- a/maui/samples/Gallery/Samples/CircularChart/Doughnut/GroupToDoughnutChart.xaml
+++ b/maui/samples/Gallery/Samples/CircularChart/Doughnut/GroupToDoughnutChart.xaml
@@ -99,10 +99,10 @@
Margin="0,13,0,0" Grid.Column="0" />
+ ItemsSource="{Binding PieGroupMode}" SelectedIndexChanged="groupTo_SelectedIndexChanged"
+ Background="{OnPlatform Default={AppThemeBinding Dark={StaticResource TextColour1Dark}, Light={StaticResource BackgroundLight}},Android=Transparent}"
+ TextColor="{OnPlatform Default={StaticResource TextColourLight}, Android={AppThemeBinding Light={StaticResource TextColourLight}, Dark={StaticResource TextColourDark}}}">
diff --git a/maui/samples/Gallery/Samples/CircularChart/Interaction/Tooltip.xaml b/maui/samples/Gallery/Samples/CircularChart/Interaction/Tooltip.xaml
index a5324b47..18317211 100644
--- a/maui/samples/Gallery/Samples/CircularChart/Interaction/Tooltip.xaml
+++ b/maui/samples/Gallery/Samples/CircularChart/Interaction/Tooltip.xaml
@@ -18,11 +18,12 @@
-
+
+
-
-
+
+
diff --git a/maui/samples/Gallery/Samples/CircularChart/Interaction/Tooltip.xaml.cs b/maui/samples/Gallery/Samples/CircularChart/Interaction/Tooltip.xaml.cs
index 80b173ec..79459a2b 100644
--- a/maui/samples/Gallery/Samples/CircularChart/Interaction/Tooltip.xaml.cs
+++ b/maui/samples/Gallery/Samples/CircularChart/Interaction/Tooltip.xaml.cs
@@ -1,4 +1,6 @@
-namespace Syncfusion.Maui.ControlsGallery.CircularChart.SfCircularChart
+using System.Globalization;
+
+namespace Syncfusion.Maui.ControlsGallery.CircularChart.SfCircularChart
{
public partial class Tooltip : SampleView
{
@@ -13,4 +15,27 @@ public override void OnDisappearing()
Chart.Handler?.DisconnectHandler();
}
}
+
+ public class TooltipValueConverter : IValueConverter
+ {
+ public object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture)
+ {
+ if (value is ChartDataModel model)
+ {
+ switch (parameter?.ToString())
+ {
+ case "Name":
+ return model.Name;
+ case "Value":
+ return model.Value;
+ }
+ }
+
+ return value;
+ }
+ public object? ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture)
+ {
+ return value;
+ }
+ }
}
diff --git a/maui/samples/Gallery/Samples/CircularChart/Pie/GroupToPieChart.xaml b/maui/samples/Gallery/Samples/CircularChart/Pie/GroupToPieChart.xaml
index 82a434bb..40ebab78 100644
--- a/maui/samples/Gallery/Samples/CircularChart/Pie/GroupToPieChart.xaml
+++ b/maui/samples/Gallery/Samples/CircularChart/Pie/GroupToPieChart.xaml
@@ -20,10 +20,10 @@
-
+
-
+
+ ItemsSource="{Binding PieGroupMode}" SelectedIndexChanged="groupTo_SelectedIndexChanged"
+ Background="{OnPlatform Default={AppThemeBinding Dark={StaticResource TextColour1Dark}, Light={StaticResource BackgroundLight}},Android=Transparent}"
+ TextColor="{OnPlatform Default={StaticResource TextColourLight}, Android={AppThemeBinding Light={StaticResource TextColourLight}, Dark={StaticResource TextColourDark}}}">
diff --git a/maui/samples/Gallery/Samples/CircularChart/Pie/SmartDataLabels.xaml b/maui/samples/Gallery/Samples/CircularChart/Pie/SmartDataLabels.xaml
index 5e0240d3..78ad1a49 100644
--- a/maui/samples/Gallery/Samples/CircularChart/Pie/SmartDataLabels.xaml
+++ b/maui/samples/Gallery/Samples/CircularChart/Pie/SmartDataLabels.xaml
@@ -21,13 +21,14 @@
+
-
-
+
@@ -66,7 +67,7 @@
-
+
@@ -74,11 +75,11 @@
Margin="0,15,0,0" Grid.Column="0" />
+ SelectedIndexChanged="smartDataLabel_SelectedIndexChanged"
+ Background="{OnPlatform Default={AppThemeBinding Dark={StaticResource TextColour1Dark}, Light={StaticResource BackgroundLight}},Android=Transparent}"
+ TextColor="{OnPlatform Default={StaticResource TextColourLight}, Android={AppThemeBinding Light={StaticResource TextColourLight}, Dark={StaticResource TextColourDark}}}">
diff --git a/maui/samples/Gallery/Samples/CircularChart/RadialBar/RadialBarChart.xaml b/maui/samples/Gallery/Samples/CircularChart/RadialBar/RadialBarChart.xaml
index 5092484c..fba1c0ca 100644
--- a/maui/samples/Gallery/Samples/CircularChart/RadialBar/RadialBarChart.xaml
+++ b/maui/samples/Gallery/Samples/CircularChart/RadialBar/RadialBarChart.xaml
@@ -47,7 +47,7 @@
-
+ WidthRequest="{OnPlatform Default=130, WinUI=138}" Margin="0,13,0,0"
+ ItemsSource="{Binding CapStyles}"
+ Background="{OnPlatform Default={AppThemeBinding Dark={StaticResource TextColour1Dark}, Light={StaticResource BackgroundLight}},Android=Transparent}"
+ TextColor="{OnPlatform Default={StaticResource TextColourLight}, Android={AppThemeBinding Light={StaticResource TextColourLight}, Dark={StaticResource TextColourDark}}}">
@@ -98,9 +98,9 @@
+ WidthRequest="{OnPlatform Default=130, WinUI=138}"
+ Background="{OnPlatform Default={AppThemeBinding Dark={StaticResource TextColour1Dark}, Light={StaticResource BackgroundLight}},Android=Transparent}"
+ TextColor="{OnPlatform Default={StaticResource TextColourLight}, Android={AppThemeBinding Light={StaticResource TextColourLight}, Dark={StaticResource TextColourDark}}}">
@@ -110,12 +110,12 @@
-
+
@@ -125,7 +125,7 @@
-
@@ -137,7 +137,7 @@
-
@@ -150,7 +150,7 @@
diff --git a/maui/samples/Gallery/Samples/CircularChart/RadialBar/RadialBarChart.xaml.cs b/maui/samples/Gallery/Samples/CircularChart/RadialBar/RadialBarChart.xaml.cs
index 43804eeb..94f064c1 100644
--- a/maui/samples/Gallery/Samples/CircularChart/RadialBar/RadialBarChart.xaml.cs
+++ b/maui/samples/Gallery/Samples/CircularChart/RadialBar/RadialBarChart.xaml.cs
@@ -14,7 +14,7 @@ private void InitializeProperties()
{
capStyle.SelectedIndex = 1;
trackFill.SelectedIndex = 0;
- trackStroke.SelectedIndex = 1;
+ trackStroke.SelectedIndex = 0;
startAngle.Value = -90;
endAngle.Value = 270;
}
@@ -43,27 +43,27 @@ private void trackStroke_SelectedIndexChanged(object sender, EventArgs e)
var value = (MAUIPicker)sender;
switch (value.SelectedIndex)
{
- case 1:
+ case 0:
{
radialBarSeries.TrackStroke = new SolidColorBrush(Color.FromRgba(0, 0, 0, 0.24));
break;
}
- case 2:
+ case 1:
{
radialBarSeries.TrackStroke = new SolidColorBrush(Color.FromRgba("#CBD5E1"));
break;
}
- case 3:
+ case 2:
{
radialBarSeries.TrackStroke = new SolidColorBrush(Color.FromRgba("#BFDBFE"));
break;
}
- case 4:
+ case 3:
{
radialBarSeries.TrackStroke = new SolidColorBrush(Color.FromRgba("#FED7AA"));
break;
}
- case 5:
+ case 4:
{
radialBarSeries.TrackStroke = new SolidColorBrush(Color.FromRgba("#DDD6FE"));
break;
@@ -76,27 +76,35 @@ private void trackFill_SelectedIndexChanged(object sender, EventArgs e)
var value = (MAUIPicker)sender;
switch (value.SelectedIndex)
{
- case 1:
+ case 0:
{
- radialBarSeries.TrackFill = new SolidColorBrush(Color.FromRgba(0, 0, 0, 0.08));
- break;
+ if (Application.Current?.RequestedTheme == AppTheme.Dark)
+ {
+ radialBarSeries.TrackFill = new SolidColorBrush(Color.FromRgba("#36323B"));
+ break;
+ }
+ else
+ {
+ radialBarSeries.TrackFill = new SolidColorBrush(Color.FromRgba(0, 0, 0, 0.08));
+ break;
+ }
}
- case 2:
+ case 1:
{
radialBarSeries.TrackFill = new SolidColorBrush(Color.FromRgba("#F1F5F9"));
break;
}
- case 3:
+ case 2:
{
radialBarSeries.TrackFill = new SolidColorBrush(Color.FromRgba("#EFF6FF"));
break;
}
- case 4:
+ case 3:
{
radialBarSeries.TrackFill = new SolidColorBrush(Color.FromRgba("#FFF7ED"));
break;
}
- case 5:
+ case 4:
{
radialBarSeries.TrackFill = new SolidColorBrush(Color.FromRgba("#F5F3FF"));
break;
diff --git a/maui/samples/Gallery/Samples/CircularChart/RadialBar/RadialBarSeriesViewModel.cs b/maui/samples/Gallery/Samples/CircularChart/RadialBar/RadialBarSeriesViewModel.cs
index 39e6f72c..09850904 100644
--- a/maui/samples/Gallery/Samples/CircularChart/RadialBar/RadialBarSeriesViewModel.cs
+++ b/maui/samples/Gallery/Samples/CircularChart/RadialBar/RadialBarSeriesViewModel.cs
@@ -55,7 +55,6 @@ public RadialBarSeriesViewModel()
Track = new List()
{
- "Select Color",
"Light Gray",
"Blue Gray",
"Pale Blue",
diff --git a/maui/samples/Gallery/Samples/DatePicker/Customization/View/Customization.xaml b/maui/samples/Gallery/Samples/DatePicker/Customization/View/Customization.xaml
new file mode 100644
index 00000000..8050864e
--- /dev/null
+++ b/maui/samples/Gallery/Samples/DatePicker/Customization/View/Customization.xaml
@@ -0,0 +1,132 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/maui/samples/Gallery/Samples/DatePicker/Customization/View/Customization.xaml.cs b/maui/samples/Gallery/Samples/DatePicker/Customization/View/Customization.xaml.cs
new file mode 100644
index 00000000..e917e783
--- /dev/null
+++ b/maui/samples/Gallery/Samples/DatePicker/Customization/View/Customization.xaml.cs
@@ -0,0 +1,449 @@
+using Syncfusion.Maui.Toolkit.Popup;
+using System.Collections.ObjectModel;
+using System.ComponentModel;
+using System.Globalization;
+using Syncfusion.Maui.Toolkit.TextInputLayout;
+
+namespace Syncfusion.Maui.ControlsGallery.Picker.SfDatePicker;
+
+///
+/// The customization page.
+///
+public partial class Customization : SampleView
+{
+ ///
+ /// The ToDo details.
+ ///
+ ToDoDetails? _toDoDetails;
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ public Customization()
+ {
+ InitializeComponent();
+#if ANDROID || IOS
+ datePicker1.HeaderView.Height = 50;
+ datePicker1.HeaderView.Text = "Select the Date";
+ datePicker1.FooterView.Height = 40;
+#else
+ datePicker.HeaderView.Height = 50;
+ datePicker.HeaderView.Text = "Select the Date";
+ datePicker.FooterView.Height = 40;
+#endif
+ }
+
+ ///
+ /// Invoked tap gesture tapped.
+ ///
+ /// The sender object.
+ /// The event args.
+ void OnTapGestureTapped(object sender, EventArgs e)
+ {
+#if ANDROID || IOS
+ popup1.Reset();
+ popup1.IsOpen = true;
+#else
+ popup.Reset();
+ popup.IsOpen = true;
+#endif
+ }
+
+ ///
+ /// Invoked item tap gesture tapped.
+ ///
+ /// The sender object.
+ /// The event args.
+ void OnItemTapGestureTapped(object sender, EventArgs e)
+ {
+#if ANDROID || IOS
+ if (sender is Grid grid && grid.BindingContext != null && grid.BindingContext is ToDoDetails details)
+ {
+ datePicker1.SelectedDate = details.Date;
+ _toDoDetails = details;
+ }
+
+ datePicker1.IsOpen = true;
+#else
+ if (sender is Grid grid && grid.BindingContext != null && grid.BindingContext is ToDoDetails details)
+ {
+ datePicker.SelectedDate = details.Date;
+ _toDoDetails = details;
+ }
+
+ datePicker.IsOpen = true;
+#endif
+ }
+
+ ///
+ /// Method to handle the date picker ok button clicked event.
+ ///
+ /// The sender object.
+ /// The event args.
+ void OnDatePickerOkButtonClicked(object sender, EventArgs e)
+ {
+ if (sender is Syncfusion.Maui.Toolkit.Picker.SfDatePicker picker && _toDoDetails != null && picker.SelectedDate?.Date != null)
+ {
+ if (_toDoDetails.Date != picker.SelectedDate)
+ {
+ _toDoDetails.Date = picker.SelectedDate.Value.Date;
+ }
+
+ _toDoDetails = null;
+ }
+
+#if ANDROID || IOS
+ datePicker1.IsOpen = false;
+#else
+ datePicker.IsOpen = false;
+#endif
+ }
+
+ ///
+ /// Method to handle the date picker closed event.
+ ///
+ /// The sender object.
+ /// The event args.
+ void OnDatePickerClosed(object sender, EventArgs e)
+ {
+ _toDoDetails = null;
+ }
+
+ ///
+ /// Method to handle the date picker cancel button clicked event.
+ ///
+ /// The sender object.
+ /// The event args.
+ void OnDatePickerCancelButtonClicked(object sender, EventArgs e)
+ {
+ if (sender is Syncfusion.Maui.Toolkit.Picker.SfDatePicker picker)
+ {
+ _toDoDetails = null;
+ picker.IsOpen = false;
+ }
+ }
+
+ ///
+ /// Method to handle the popup item created event.
+ ///
+ /// The sender object.
+ /// The event args.
+ void OnPopupItemCreated(object sender, EventArgs e)
+ {
+ if (BindingContext != null && BindingContext is DatePickerCustomizationViewModel bindingContext && sender is ToDoDetails details)
+ {
+ bindingContext.DataSource.Add(details);
+ }
+ }
+}
+
+///
+/// The DatePickerCustomizationViewModel class.
+///
+public class DatePickerCustomizationViewModel : INotifyPropertyChanged
+{
+ ///
+ /// The data source.
+ ///
+ ObservableCollection _dataSource;
+
+ ///
+ /// Gets or sets the data source.
+ ///
+ public ObservableCollection DataSource
+ {
+ get
+ {
+ return _dataSource;
+ }
+ set
+ {
+ _dataSource = value;
+ RaisePropertyChanged("DataSource");
+ }
+ }
+
+ ///
+ /// Method to raise the property changed event.
+ ///
+ /// The property name.
+ void RaisePropertyChanged(string propertyName)
+ {
+ PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
+ }
+
+ ///
+ /// Initialize a new instance of the class.
+ ///
+ public DatePickerCustomizationViewModel()
+ {
+ _dataSource = new ObservableCollection()
+ {
+ new ToDoDetails() {Subject = "Get quote from travel agent", Date= DateTime.Now.Date},
+ new ToDoDetails() {Subject = "Book flight ticket", Date= DateTime.Now.Date.AddDays(2)},
+ new ToDoDetails() {Subject = "Buy travel guide book", Date= DateTime.Now.Date},
+ new ToDoDetails() {Subject = "Register for sky diving", Date= DateTime.Now.Date.AddDays(8)},
+ };
+ }
+
+ ///
+ /// The property changed event.
+ ///
+ public event PropertyChangedEventHandler? PropertyChanged;
+}
+
+///
+/// The ToDoDetails class.
+///
+public class ToDoDetails : INotifyPropertyChanged
+{
+ ///
+ /// The subject.
+ ///
+ string _subject = string.Empty;
+
+ ///
+ /// Gets or sets the subject.
+ ///
+ public string Subject
+ {
+ get
+ {
+ return _subject;
+ }
+ set
+ {
+ _subject = value;
+ RaisePropertyChanged("Subject");
+ }
+ }
+
+ ///
+ /// The date.
+ ///
+ DateTime _date = DateTime.Now.Date;
+
+ ///
+ /// Gets or sets the date.
+ ///
+ public DateTime Date
+ {
+ get
+ {
+ return _date;
+ }
+ set
+ {
+ _date = value;
+ DateString = _date.Date == DateTime.Now.Date ? "Due today" : _date.ToString("dd-MM-yyyy", CultureInfo.InvariantCulture);
+ RaisePropertyChanged("Date");
+ }
+ }
+
+ ///
+ /// The date string.
+ ///
+ string _dateString = "Due today";
+
+ ///
+ /// Gets or sets the date string.
+ ///
+ public string DateString
+ {
+ get
+ {
+ return _dateString;
+ }
+ set
+ {
+ _dateString = value;
+ RaisePropertyChanged("DateString");
+ }
+ }
+
+ ///
+ /// Method to raise the property changed event.
+ ///
+ /// The property name.
+ void RaisePropertyChanged(string propertyName)
+ {
+ PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
+ }
+
+ ///
+ /// The property changed event.
+ ///
+ public event PropertyChangedEventHandler? PropertyChanged;
+}
+
+///
+/// The DateTimeToColorConverter class.
+///
+public class DateTimeToColorConverter : IValueConverter
+{
+ ///
+ /// Check the application theme is light or dark.
+ ///
+ readonly bool _isLightTheme = Application.Current?.RequestedTheme == AppTheme.Light;
+
+ ///
+ /// Method to convert the date time to color.
+ ///
+ /// The value.
+ /// The target type
+ /// The parameter.
+ /// The culture.
+ /// The color based on the date time.
+ public object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture)
+ {
+ if (value != null && value is DateTime date)
+ {
+ if (date.Date == DateTime.Now.Date)
+ {
+ return _isLightTheme ? Color.FromArgb("#B3261E") : Color.FromArgb("#F2B8B5");
+ }
+ else if (date.Date < DateTime.Now.Date)
+ {
+ return _isLightTheme ? Color.FromArgb("#49454F").WithAlpha(0.5f) : Color.FromArgb("#CAC4D0").WithAlpha(0.5f);
+ }
+
+ return _isLightTheme ? Color.FromArgb("#49454F") : Color.FromArgb("#CAC4D0");
+ }
+
+ return _isLightTheme ? Color.FromArgb("#49454F") : Color.FromArgb("#CAC4D0");
+ }
+
+ ///
+ /// Method to convert back the color to date time.
+ ///
+ /// The value.
+ /// The target type
+ /// The parameter.
+ /// The culture.
+ /// Empty string.
+ public object? ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture)
+ {
+ return string.Empty;
+ }
+}
+
+///
+/// The CustomPopUp class.
+///
+public class CustomPopUp : SfPopup
+{
+ ///
+ /// The SfDatePicker instance.
+ ///
+ readonly Syncfusion.Maui.Toolkit.Picker.SfDatePicker _picker;
+
+ ///
+ /// The Entry instance.
+ ///
+ readonly Entry _entry;
+
+ ///
+ /// The SfTextInputLayout instance.
+ ///
+ readonly SfTextInputLayout _textInput;
+
+ ///
+ /// Initialize the new instance of the class.
+ ///
+ public CustomPopUp()
+ {
+ _picker = new Syncfusion.Maui.Toolkit.Picker.SfDatePicker();
+ StackLayout stack = new StackLayout();
+ Label label = new Label();
+ label.Text = "Subject";
+ label.Margin = new Thickness(10, 4);
+ label.FontSize = 12;
+ stack.Add(label);
+ _textInput = new SfTextInputLayout();
+ _textInput.Hint = "Title";
+ _textInput.HelperText = "Enter Title";
+ _entry = new Entry();
+ _entry.HeightRequest = 40;
+ _entry.Margin = new Thickness(5, 0);
+ _textInput.Content = _entry;
+ stack.Add(_textInput);
+ Label label1 = new Label();
+ label1.Text = "Select the date";
+ label1.FontSize = 12;
+ label1.Margin = new Thickness(10, 5);
+ stack.Add(label1);
+ _picker.FooterView.Height = 40;
+ _picker.HeightRequest = 280;
+ _picker.OkButtonClicked += OnPickerOkButtonClicked;
+ _picker.CancelButtonClicked += OnPickerCancelButtonClicked;
+ stack.Add(_picker);
+ stack.VerticalOptions = LayoutOptions.Center;
+ ContentTemplate = new DataTemplate(() =>
+ {
+ return stack;
+ });
+
+ HeaderTemplate = new DataTemplate(() =>
+ {
+ return new Label() { Text = "Add a task", FontSize = 20, HeightRequest = 40, HorizontalOptions = LayoutOptions.Center, VerticalOptions = LayoutOptions.Center, HorizontalTextAlignment = TextAlignment.Center, VerticalTextAlignment = TextAlignment.Center };
+ });
+
+#if ANDROID || IOS || MACCATALYST
+ HeightRequest = 470;
+#else
+ HeightRequest = 450;
+#endif
+ WidthRequest = 300;
+ ShowFooter = false;
+ ShowHeader = true;
+ HeaderHeight = 40;
+ PopupStyle.CornerRadius = new CornerRadius(5);
+ }
+
+ ///
+ /// Invokes when the picker cancel button clicked.
+ ///
+ /// The sender object.
+ /// The event args.
+ void OnPickerCancelButtonClicked(object? sender, EventArgs e)
+ {
+ Reset();
+ IsOpen = false;
+ }
+
+ ///
+ /// Invokes when the picker ok button clicked.
+ ///
+ /// The sender object.
+ /// The event args.
+ void OnPickerOkButtonClicked(object? sender, EventArgs e)
+ {
+ if (_picker.SelectedDate != null)
+ {
+ OnCreated?.Invoke(new ToDoDetails() { Date = _picker.SelectedDate.Value, Subject = _entry.Text == string.Empty ? "No Title" : _entry.Text }, new EventArgs());
+ }
+
+ IsOpen = false;
+ }
+
+ ///
+ /// Method to reset the picker and entry.
+ ///
+ public void Reset()
+ {
+ if (_picker != null)
+ {
+ _picker.SelectedDate = DateTime.Now.Date;
+ }
+
+ if (_entry != null)
+ {
+ _entry.Text = string.Empty;
+ }
+ }
+
+ ///
+ /// The event handler for the created event.
+ ///
+ public event EventHandler? OnCreated;
+}
\ No newline at end of file
diff --git a/maui/samples/Gallery/Samples/DatePicker/GettingStarted/Behavior/GettingStartedBehavior.cs b/maui/samples/Gallery/Samples/DatePicker/GettingStarted/Behavior/GettingStartedBehavior.cs
new file mode 100644
index 00000000..9cb19afe
--- /dev/null
+++ b/maui/samples/Gallery/Samples/DatePicker/GettingStarted/Behavior/GettingStartedBehavior.cs
@@ -0,0 +1,372 @@
+using Syncfusion.Maui.Toolkit.Picker;
+using System.Collections.ObjectModel;
+
+namespace Syncfusion.Maui.ControlsGallery.Picker.SfDatePicker
+{
+ public class GettingStartedBehavior : Behavior
+ {
+ ///
+ /// Picker view.
+ ///
+ Syncfusion.Maui.Toolkit.Picker.SfDatePicker? _datePicker;
+
+ ///
+ /// The show header switch.
+ ///
+ Switch? _showHeaderSwitch, _showColumnHeaderSwitch, _showFooterSwitch, _clearSelectionSwitch;
+
+ ///
+ /// The date format combo box.
+ ///
+ Microsoft.Maui.Controls.Picker? _formatComboBox, _textDisplayComboBox;
+
+ ///
+ /// The date format to set the combo box item source.
+ ///
+ ObservableCollection
diff --git a/maui/samples/Gallery/Samples/OtpInput/Customization/View/Customization.xaml b/maui/samples/Gallery/Samples/OtpInput/Customization/View/Customization.xaml
index 63cc39ed..e352139d 100644
--- a/maui/samples/Gallery/Samples/OtpInput/Customization/View/Customization.xaml
+++ b/maui/samples/Gallery/Samples/OtpInput/Customization/View/Customization.xaml
@@ -65,11 +65,11 @@
-
+
-
-
+
+
diff --git a/maui/samples/Gallery/Samples/OtpInput/SignUpPage/Helper/SignUpBehavior.cs b/maui/samples/Gallery/Samples/OtpInput/SignUpPage/Helper/SignUpBehavior.cs
index 9f7888e1..469457c6 100644
--- a/maui/samples/Gallery/Samples/OtpInput/SignUpPage/Helper/SignUpBehavior.cs
+++ b/maui/samples/Gallery/Samples/OtpInput/SignUpPage/Helper/SignUpBehavior.cs
@@ -91,7 +91,10 @@ void PhoneEntry_TextChanged(object? sender, TextChangedEventArgs e)
_phoneVerification = false;
_phoneInputLayout.HasError = true;
_phoneInputLayout.ErrorText = "Enter a 10-digit mobile number";
- _phoneInputLayout.ErrorLabelStyle.TextColor = Colors.Red;
+ if (_phoneInputLayout.ErrorLabelStyle is not null)
+ {
+ _phoneInputLayout.ErrorLabelStyle.TextColor = Colors.Red;
+ }
}
}
else
@@ -107,7 +110,10 @@ void PhoneEntry_TextChanged(object? sender, TextChangedEventArgs e)
_phoneVerification = false;
_phoneInputLayout.HasError = true;
_phoneInputLayout.ErrorText = "Enter a 10-digit mobile number";
- _phoneInputLayout.ErrorLabelStyle.TextColor = Colors.Red;
+ if (_phoneInputLayout.ErrorLabelStyle is not null)
+ {
+ _phoneInputLayout.ErrorLabelStyle.TextColor = Colors.Red;
+ }
}
}
}
@@ -125,7 +131,10 @@ void NameEntry_TextChanged(object? sender, TextChangedEventArgs e)
_nameVerification = false;
_nameInputLayout.HasError = true;
_nameInputLayout.ErrorText = "Invalid input";
- _nameInputLayout.ErrorLabelStyle.TextColor = Colors.Red;
+ if (_nameInputLayout.ErrorLabelStyle is not null)
+ {
+ _nameInputLayout.ErrorLabelStyle.TextColor = Colors.Red;
+ }
}
}
@@ -194,7 +203,10 @@ void Register_Clicked(object? sender, EventArgs e)
_nameVerification = false;
_nameInputLayout.HasError = true;
_nameInputLayout.ErrorText = "Invalid input";
- _nameInputLayout.ErrorLabelStyle.TextColor = Colors.Red;
+ if (_nameInputLayout.ErrorLabelStyle is not null)
+ {
+ _nameInputLayout.ErrorLabelStyle.TextColor = Colors.Red;
+ }
}
if (_phoneEntry is not null && _phoneInputLayout is not null && string.IsNullOrEmpty(_phoneEntry.Text))
@@ -202,7 +214,10 @@ void Register_Clicked(object? sender, EventArgs e)
_phoneVerification = false;
_phoneInputLayout.HasError = true;
_phoneInputLayout.ErrorText = "Invalid input";
- _phoneInputLayout.ErrorLabelStyle.TextColor = Colors.Red;
+ if (_phoneInputLayout.ErrorLabelStyle is not null)
+ {
+ _phoneInputLayout.ErrorLabelStyle.TextColor = Colors.Red;
+ }
}
}
}
diff --git a/maui/samples/Gallery/Samples/Picker/FlightBooking/FlightBooking.xaml b/maui/samples/Gallery/Samples/Picker/FlightBooking/FlightBooking.xaml
new file mode 100644
index 00000000..34bb4907
--- /dev/null
+++ b/maui/samples/Gallery/Samples/Picker/FlightBooking/FlightBooking.xaml
@@ -0,0 +1,252 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/maui/samples/Gallery/Samples/Picker/FlightBooking/FlightBooking.xaml.cs b/maui/samples/Gallery/Samples/Picker/FlightBooking/FlightBooking.xaml.cs
new file mode 100644
index 00000000..2730e0de
--- /dev/null
+++ b/maui/samples/Gallery/Samples/Picker/FlightBooking/FlightBooking.xaml.cs
@@ -0,0 +1,713 @@
+using System.Globalization;
+using Syncfusion.Maui.Toolkit.Popup;
+using Syncfusion.Maui.Toolkit.Picker;
+
+namespace Syncfusion.Maui.ControlsGallery.Picker.SfPicker
+{
+ ///
+ /// The Flight booking sample.
+ ///
+ public partial class FlightBooking : SampleView
+ {
+ ///
+ /// The departure date.
+ ///
+ DateTime _from;
+
+ ///
+ /// The return date.
+ ///
+ DateTime _to;
+
+ ///
+ /// The list containing departure location details.
+ ///
+ List _fromList;
+
+ ///
+ /// The list containing destination location details.
+ ///
+ List _toList;
+
+ ///
+ /// Check the application theme is light or dark.
+ ///
+ readonly bool _isLightTheme = Application.Current?.RequestedTheme == AppTheme.Light;
+
+ ///
+ /// List of available countries.
+ ///
+ static readonly List Countries = ["UK", "USA", "India", "UAE", "Germany"];
+
+ ///
+ /// List of cities in the UK.
+ ///
+ static readonly List UkCities = ["London", "Manchester", "Cambridge", "Edinburgh", "Glasgow", "Birmingham"];
+
+ ///
+ /// List of cities in the USA.
+ ///
+ static readonly List UsaCities = ["New York", "Seattle", "Washington", "Chicago", "Boston", "Los Angles"];
+
+ ///
+ /// List of cities in India.
+ ///
+ static readonly List IndiaCities = ["Mumbai", "Bengaluru", "Chennai", "Pune", "Jaipur", "Delhi"];
+
+ ///
+ /// List of cities in UAE.
+ ///
+ static readonly List UaeCities = ["Dubai", "Abu Dhabi", "Fujairah", "Sharjah", "Ajman", "AL Ain"];
+
+ ///
+ /// List of cities in Germany.
+ ///
+ static readonly List GermanyCities = ["Berlin", "Munich", "Frankfurt", "Hamburg", "Cologne", "Bonn"];
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ public FlightBooking()
+ {
+ InitializeComponent();
+
+ if (popup != null)
+ {
+ popup.FooterTemplate = GetFooterTemplate(popup);
+ popup.ContentTemplate = GetContentTemplate(popup);
+ }
+
+ _from = DateTime.Now.Date;
+ _to = DateTime.Now.Date;
+ _fromList = ["India", "Chennai"];
+ _toList = ["USA", "Boston"];
+ PickerColumn countyColumn = new PickerColumn() { HeaderText = "Country", SelectedIndex = 2, ItemsSource = Countries, Width = 150 };
+ PickerColumn countyColumn1 = new PickerColumn() { HeaderText = "Country", SelectedIndex = 1, ItemsSource = Countries, Width = 150 };
+ PickerColumn cityColumn = new PickerColumn() { HeaderText = "City", SelectedIndex = 2, ItemsSource = IndiaCities, Width = 150 };
+ PickerColumn cityColumn1 = new PickerColumn() { HeaderText = "City", SelectedIndex = 4, ItemsSource = UsaCities, Width = 150 };
+ //// To initialize the picker based on the platform.
+#if ANDROID || IOS
+ mobileFromPicker.Columns = [countyColumn, cityColumn];
+ mobileToPicker.Columns = [countyColumn1, cityColumn1];
+ mobileGrid.IsVisible = true;
+#else
+ fromPicker.Columns = new System.Collections.ObjectModel.ObservableCollection() { countyColumn, cityColumn };
+ toPicker.Columns = new System.Collections.ObjectModel.ObservableCollection() { countyColumn1, cityColumn1 };
+ frame.IsVisible = true;
+#endif
+ string str = DateTime.Now.Day.ToString();
+ string fromString = str + " " + CultureInfo.CurrentCulture.DateTimeFormat.GetMonthName(DateTime.Now.Month).ToString() + "," + " " + DateTime.Now.Year.ToString();
+
+#if ANDROID || IOS
+
+ mobileDepartureDatePicker.OkButtonClicked += DepartureDatePicker_OkButtonClicked;
+ mobileReturnDatePicker.OkButtonClicked += ReturnDatePicker_OkButtonClicked;
+
+ mobileDepartureDatePicker.CancelButtonClicked += DepartureDatePicker_CancelButtonClicked;
+ mobileReturnDatePicker.CancelButtonClicked += ReturnDatePicker_CancelButtonClicked;
+
+ mobileDepartureDatePicker.Opened += DepartureDatePicker_OnPopUpOpened;
+ mobileReturnDatePicker.Opened += ReturnDatePicker_OnPopUpOpened;
+
+ mobileDepartureDateLabel.Text = fromString;
+ mobileReturnDateLabel.Text = fromString;
+ if (mobileReturnDatePicker.SelectedDate != null)
+ {
+ mobileReturnDatePicker.MinimumDate = mobileReturnDatePicker.SelectedDate.Value.Date;
+ }
+
+ mobileFromPicker.HeaderView.Height = 40;
+ mobileFromPicker.HeaderView.Text = "FROM";
+ mobileFromPicker.FooterView.Height = 40;
+
+ mobileToPicker.HeaderView.Height = 40;
+ mobileToPicker.HeaderView.Text = "TO";
+ mobileToPicker.FooterView.Height = 40;
+
+ mobileDepartureDatePicker.HeaderView.Height = 40;
+ mobileDepartureDatePicker.HeaderView.Text = "Select a date";
+ mobileDepartureDatePicker.FooterView.Height = 40;
+ mobileDepartureDatePicker.MinimumDate = DateTime.Now;
+
+ mobileReturnDatePicker.HeaderView.Height = 40;
+ mobileReturnDatePicker.HeaderView.Text = "Select a date";
+ mobileReturnDatePicker.FooterView.Height = 40;
+#else
+ departureDatePicker.OkButtonClicked += DepartureDatePicker_OkButtonClicked;
+ returnDatePicker.OkButtonClicked += ReturnDatePicker_OkButtonClicked;
+
+ departureDatePicker.CancelButtonClicked += DepartureDatePicker_CancelButtonClicked;
+ returnDatePicker.CancelButtonClicked += ReturnDatePicker_CancelButtonClicked;
+
+ departureDatePicker.Opened += DepartureDatePicker_OnPopUpOpened;
+ returnDatePicker.Opened += ReturnDatePicker_OnPopUpOpened;
+
+ departureDateLabel.Text = fromString;
+ returnDateLabel.Text = fromString;
+ if (returnDatePicker.SelectedDate != null)
+ {
+ returnDatePicker.MinimumDate = returnDatePicker.SelectedDate.Value.Date;
+ }
+
+ fromPicker.HeaderView.Height = 40;
+ fromPicker.HeaderView.Text = "FROM";
+ fromPicker.FooterView.Height = 40;
+
+ toPicker.HeaderView.Height = 40;
+ toPicker.HeaderView.Text = "TO";
+ toPicker.FooterView.Height = 40;
+
+ departureDatePicker.HeaderView.Height = 40;
+ departureDatePicker.HeaderView.Text = "Select a date";
+ departureDatePicker.FooterView.Height = 40;
+ departureDatePicker.MinimumDate = DateTime.Now;
+
+ returnDatePicker.HeaderView.Height = 40;
+ returnDatePicker.HeaderView.Text = "Select a date";
+ returnDatePicker.FooterView.Height = 40;
+#endif
+ }
+
+ ///
+ /// Method to handle the PopUpOpened event of the departure date picker.
+ ///
+ /// The sender object.
+ /// The event args.
+ void DepartureDatePicker_OnPopUpOpened(object? sender, EventArgs e)
+ {
+#if ANDROID || IOS
+ mobileDepartureDatePicker.IsOpen = true;
+ mobileDepartureDatePicker.SelectedDate = _from;
+#else
+ departureDatePicker.IsOpen = true;
+ departureDatePicker.SelectedDate = _from;
+#endif
+ }
+
+ ///
+ /// Method to handle the PopUpOpened event of the return date picker.
+ ///
+ /// The sender object.
+ /// The event args.
+ void ReturnDatePicker_OnPopUpOpened(object? sender, EventArgs e)
+ {
+#if ANDROID || IOS
+ mobileReturnDatePicker.IsOpen = true;
+ mobileReturnDatePicker.MinimumDate = _from;
+ mobileReturnDatePicker.SelectedDate = _to;
+#else
+ returnDatePicker.IsOpen = true;
+ returnDatePicker.MinimumDate = _from;
+ returnDatePicker.SelectedDate = _to;
+#endif
+ }
+
+ ///
+ /// Method to handle the ok button clicked event of the departure date picker.
+ ///
+ /// The sender object.
+ /// The event args.
+ void DepartureDatePicker_OkButtonClicked(object? sender, EventArgs e)
+ {
+#if ANDROID || IOS
+ if (mobileDepartureDatePicker.SelectedDate != null)
+ {
+ _from = mobileDepartureDatePicker.SelectedDate.Value.Date;
+ }
+ mobileDepartureDateLabel.Text = _from.Day.ToString() + " " + CultureInfo.CurrentCulture.DateTimeFormat.GetMonthName(_from.Month).ToString() + "," + " " + _from.Year.ToString();
+ if (_from.Date > _to.Date)
+ {
+ _to = _from;
+ mobileReturnDateLabel.Text = _to.Day.ToString() + " " + CultureInfo.CurrentCulture.DateTimeFormat.GetMonthName(_to.Month).ToString() + "," + " " + _to.Year.ToString();
+ }
+
+ mobileDepartureDatePicker.IsOpen = false;
+#else
+ if (departureDatePicker.SelectedDate != null)
+ {
+ _from = departureDatePicker.SelectedDate.Value.Date;
+ }
+ departureDateLabel.Text = _from.Day.ToString() + " " + CultureInfo.CurrentCulture.DateTimeFormat.GetMonthName(_from.Month).ToString() + "," + " " + _from.Year.ToString();
+ if (_from.Date > _to.Date)
+ {
+ _to = _from;
+ returnDateLabel.Text = _to.Day.ToString() + " " + CultureInfo.CurrentCulture.DateTimeFormat.GetMonthName(_to.Month).ToString() + "," + " " + _to.Year.ToString();
+ }
+
+ departureDatePicker.IsOpen = false;
+#endif
+ }
+
+ ///
+ /// Method to handle the ok button clicked event of the return date picker.
+ ///
+ /// The sender object.
+ /// The event args.
+ void ReturnDatePicker_OkButtonClicked(object? sender, EventArgs e)
+ {
+#if ANDROID || IOS
+ if (mobileReturnDatePicker.SelectedDate != null)
+ {
+ _to = mobileReturnDatePicker.SelectedDate.Value.Date;
+ }
+
+ mobileReturnDateLabel.Text = _to.Day.ToString() + " " + CultureInfo.CurrentCulture.DateTimeFormat.GetMonthName(_to.Month).ToString() + "," + " " + _to.Year.ToString();
+ mobileReturnDatePicker.IsOpen = false;
+#else
+ if (returnDatePicker.SelectedDate != null)
+ {
+ _to = returnDatePicker.SelectedDate.Value.Date;
+ }
+
+ returnDateLabel.Text = _to.Day.ToString() + " " + CultureInfo.CurrentCulture.DateTimeFormat.GetMonthName(_to.Month).ToString() + "," + " " + _to.Year.ToString();
+ returnDatePicker.IsOpen = false;
+#endif
+ }
+
+ ///
+ /// Method to handle the Cancel button clicked event of the departure date picker.
+ ///
+ /// The sender object.
+ /// The event args.
+ void DepartureDatePicker_CancelButtonClicked(object? sender, EventArgs e)
+ {
+#if ANDROID || IOS
+ mobileDepartureDatePicker.IsOpen = false;
+#else
+ departureDatePicker.IsOpen = false;
+#endif
+ }
+
+ ///
+ /// Method to handle the Cancel button clicked event of the return date picker.
+ ///
+ /// The sender object.
+ /// The event args.
+ void ReturnDatePicker_CancelButtonClicked(object? sender, EventArgs e)
+ {
+#if ANDROID || IOS
+ mobileReturnDatePicker.IsOpen = false;
+#else
+ returnDatePicker.IsOpen = false;
+#endif
+ }
+
+ ///
+ /// Method to handle the tap gesture.
+ ///
+ /// The sender object.
+ /// The event args.
+ void TapGestureRecognizer_Tapped(System.Object sender, System.EventArgs e)
+ {
+#if ANDROID || IOS
+ mobileFromPicker.IsOpen = true;
+ if (mobileFromPicker.Columns[0].ItemsSource is List country)
+ {
+ int selectedIndex = country.IndexOf(_fromList[0]);
+ List cities = GetCityList(Countries[selectedIndex]);
+ int citySelectedIndex = cities.IndexOf(_fromList[1]);
+ PickerColumn cityColumn = new PickerColumn() { HeaderText = "City", SelectedIndex = citySelectedIndex, ItemsSource = cities, Width = 150 };
+ if (mobileFromPicker.Columns[1].ItemsSource != cities)
+ {
+ mobileFromPicker.Columns[1] = cityColumn;
+ }
+ else
+ {
+ mobileFromPicker.Columns[1].SelectedIndex = citySelectedIndex;
+ }
+
+ mobileFromPicker.Columns[0].SelectedIndex = selectedIndex;
+ }
+
+#else
+ fromPicker.IsOpen = true;
+ if (fromPicker.Columns[0].ItemsSource is List country)
+ {
+ int selectedIndex = country.IndexOf(_fromList[0]);
+ List cities = GetCityList(Countries[selectedIndex]);
+ int citySelectedIndex = cities.IndexOf(_fromList[1]);
+ PickerColumn cityColumn = new PickerColumn() { HeaderText = "City", SelectedIndex = citySelectedIndex, ItemsSource = cities, Width = 150 };
+ if (fromPicker.Columns[1].ItemsSource != cities)
+ {
+ fromPicker.Columns[1] = cityColumn;
+ }
+ else
+ {
+ fromPicker.Columns[1].SelectedIndex = citySelectedIndex;
+ }
+
+ fromPicker.Columns[0].SelectedIndex = selectedIndex;
+ }
+#endif
+ }
+
+ ///
+ /// Method to handle the tap gesture.
+ ///
+ /// The sender object.
+ /// The event args.
+ void TapGestureRecognizer_Tapped_1(System.Object sender, System.EventArgs e)
+ {
+#if ANDROID || IOS
+ mobileToPicker.IsOpen = true;
+ if (mobileToPicker.Columns[0].ItemsSource is List country)
+ {
+ int selectedIndex = country.IndexOf(_toList[0]);
+ List cities = GetCityList(Countries[selectedIndex]);
+ int citySelectedIndex = cities.IndexOf(_toList[1]);
+ PickerColumn cityColumn = new PickerColumn() { HeaderText = "City", SelectedIndex = citySelectedIndex, ItemsSource = cities, Width = 150 };
+ if (mobileToPicker.Columns[1].ItemsSource != cities)
+ {
+ mobileToPicker.Columns[1] = cityColumn;
+ }
+ else
+ {
+ mobileToPicker.Columns[1].SelectedIndex = citySelectedIndex;
+ }
+
+ mobileToPicker.Columns[0].SelectedIndex = selectedIndex;
+ }
+#else
+ toPicker.IsOpen = true;
+ if (toPicker.Columns[0].ItemsSource is List country)
+ {
+ int selectedIndex = country.IndexOf(_toList[0]);
+ List cities = GetCityList(Countries[selectedIndex]);
+ int citySelectedIndex = cities.IndexOf(_toList[1]);
+ PickerColumn cityColumn = new PickerColumn() { HeaderText = "City", SelectedIndex = citySelectedIndex, ItemsSource = cities, Width = 150 };
+ if (toPicker.Columns[1].ItemsSource != cities)
+ {
+ toPicker.Columns[1] = cityColumn;
+ }
+ else
+ {
+ toPicker.Columns[1].SelectedIndex = citySelectedIndex;
+ }
+
+ toPicker.Columns[0].SelectedIndex = selectedIndex;
+ }
+#endif
+ }
+
+ ///
+ /// Method to handle the selection changed event of the from picker.
+ ///
+ /// The sender object.
+ /// The event args.
+ void FromPicker_SelectionChanged(System.Object sender, PickerSelectionChangedEventArgs e)
+ {
+ if (e.ColumnIndex == 1)
+ {
+ return;
+ }
+
+ string country = Countries[e.NewValue];
+ List cities = GetCityList(country);
+ PickerColumn cityColumn = new PickerColumn() { HeaderText = "City", SelectedIndex = 0, ItemsSource = cities, Width = 150 };
+
+#if ANDROID || IOS
+ if (mobileFromPicker.Columns[1].ItemsSource != cities)
+ {
+ mobileFromPicker.Columns[1] = cityColumn;
+ }
+#else
+ if (fromPicker.Columns[1].ItemsSource != cities)
+ {
+ fromPicker.Columns[1] = cityColumn;
+ }
+#endif
+ }
+
+ ///
+ /// Method to handle the selection changed event of the to picker.
+ ///
+ /// The sender object.
+ /// The event args.
+ void ToPicker_SelectionChanged(System.Object sender, PickerSelectionChangedEventArgs e)
+ {
+ if (e.ColumnIndex == 1)
+ {
+ return;
+ }
+
+ string country = Countries[e.NewValue];
+ List cities = GetCityList(country);
+ PickerColumn cityColumn = new PickerColumn() { HeaderText = "City", SelectedIndex = 0, ItemsSource = cities, Width = 150 };
+
+#if ANDROID || IOS
+ if (mobileToPicker.Columns[1].ItemsSource != cities)
+ {
+ mobileToPicker.Columns[1] = cityColumn;
+ }
+#else
+ if (toPicker.Columns[1].ItemsSource != cities)
+ {
+ toPicker.Columns[1] = cityColumn;
+ }
+#endif
+ }
+
+ ///
+ /// Method to handle the cancel button click event of the from picker.
+ ///
+ /// The sender object.
+ /// The event args.
+ void FromPicker_CancelButtonClicked(System.Object sender, System.EventArgs e)
+ {
+#if ANDROID || IOS
+ mobileFromPicker.IsOpen = false;
+#else
+ fromPicker.IsOpen = false;
+#endif
+ }
+
+ ///
+ /// Method to handle the cancel button click event of the to picker.
+ ///
+ /// The sender object.
+ /// The event args.
+ void ToPicker_CancelButtonClicked(System.Object sender, System.EventArgs e)
+ {
+#if ANDROID || IOS
+ mobileToPicker.IsOpen = false;
+#else
+ toPicker.IsOpen = false;
+#endif
+ }
+
+ ///
+ /// Method to handle the Ok button click event of the from picker.
+ ///
+ /// The sender object.
+ /// The event args.
+ void FromPicker_OkButtonClicked(System.Object sender, System.EventArgs e)
+ {
+ if (sender is Syncfusion.Maui.Toolkit.Picker.SfPicker picker)
+ {
+ int countryColumnIndex = picker.Columns[0].SelectedIndex;
+ int cityColumnIndex = picker.Columns[1].SelectedIndex;
+ string country = Countries[countryColumnIndex];
+ List cities = GetCityList(country);
+ string city = cities[cityColumnIndex];
+ _fromList = [country, city];
+#if ANDROID || IOS
+ mobileFromLabel.Text = $"{city}, {country}";
+ mobileFromPicker.IsOpen = false;
+#else
+ fromLabel.Text = $"{city}, {country}";
+ fromPicker.IsOpen = false;
+#endif
+ }
+ }
+
+ ///
+ /// Method to handle the Ok button click event of the to picker.
+ ///
+ /// The sender object.
+ /// The event args.
+ void ToPicker_OkButtonClicked(System.Object sender, System.EventArgs e)
+ {
+ if (sender is Syncfusion.Maui.Toolkit.Picker.SfPicker picker)
+ {
+ int countryColumnIndex = picker.Columns[0].SelectedIndex;
+ int cityColumnIndex = picker.Columns[1].SelectedIndex;
+ string country = Countries[countryColumnIndex];
+ List cities = GetCityList(country);
+ string city = cities[cityColumnIndex];
+ _toList = [country, city];
+#if ANDROID || IOS
+ mobileToLabel.Text = $"{city}, {country}";
+ mobileToPicker.IsOpen = false;
+#else
+ toLabel.Text = $"{city}, {country}";
+ toPicker.IsOpen = false;
+#endif
+ }
+ }
+
+ ///
+ /// Method to get the city list based on the country.
+ ///
+ /// The country name.
+ /// City list based on the country.
+ List GetCityList(string country)
+ {
+ if (country == "UK")
+ {
+ return UkCities;
+ }
+ else if (country == "USA")
+ {
+ return UsaCities;
+ }
+ else if (country == "India")
+ {
+ return IndiaCities;
+ }
+ else if (country == "UAE")
+ {
+ return UaeCities;
+ }
+ else if (country == "Germany")
+ {
+ return GermanyCities;
+ }
+
+ return [];
+ }
+
+ ///
+ /// Method to get the dynamic color.
+ ///
+ /// The resource name.
+ /// The color.
+ Color GetDynamicColor(string? resourceName = null)
+ {
+ if (resourceName != null && App.Current != null && App.Current.Resources.TryGetValue(resourceName, out var colorValue) && colorValue is Color color)
+ {
+ return color;
+ }
+ else
+ {
+ if (App.Current != null && App.Current.RequestedTheme == AppTheme.Light)
+ {
+ return Color.FromRgb(0xFF, 0xFF, 0xFF);
+ }
+ else if (App.Current != null && App.Current.RequestedTheme == AppTheme.Dark)
+ {
+ return Color.FromRgb(0x38, 0x1E, 0x72);
+ }
+ }
+
+ return Colors.Transparent;
+ }
+
+ ///
+ /// Method to get the Ok button style.
+ ///
+ /// The button style.
+ Style GetOkButtonStyle()
+ {
+ return new Style(typeof(Button))
+ {
+ Setters =
+ {
+ new Setter { Property = Button.CornerRadiusProperty, Value = 20 },
+ new Setter { Property = Button.BorderColorProperty, Value = Color.FromArgb("#6750A4") },
+ new Setter { Property = Button.BorderWidthProperty, Value = 1 },
+ new Setter { Property = Button.BackgroundColorProperty, Value = GetDynamicColor("SfPickerSelectionStroke") },
+ new Setter { Property = Button.TextColorProperty, Value = GetDynamicColor() },
+ new Setter { Property = Button.FontSizeProperty, Value = 14 },
+ }
+ };
+ }
+
+ ///
+ /// Method to get the footer template.
+ ///
+ /// The pop up.
+ /// The data template.
+ DataTemplate GetFooterTemplate(SfPopup popup)
+ {
+ var footerTemplate = new DataTemplate(() =>
+ {
+ var grid = new Grid
+ {
+ ColumnSpacing = 12,
+ Padding = new Thickness(24)
+ };
+
+ grid.ColumnDefinitions.Add(new ColumnDefinition { Width = GridLength.Star });
+ grid.ColumnDefinitions.Add(new ColumnDefinition { Width = GridLength.Star });
+
+ var oKButton = new Button
+ {
+ Text = "OK",
+ Style = GetOkButtonStyle(),
+ WidthRequest = 96,
+ HeightRequest = 40
+ };
+
+ oKButton.Clicked += (sender, args) =>
+ {
+ popup.Dismiss();
+ };
+
+ grid.Children.Add(oKButton);
+ Grid.SetColumn(oKButton, 1);
+
+ return grid;
+ });
+
+ return footerTemplate;
+ }
+
+ ///
+ /// Method to get the content template.
+ ///
+ /// The pop up.
+ /// The data template.
+ DataTemplate GetContentTemplate(SfPopup popup)
+ {
+ var footerTemplate = new DataTemplate(() =>
+ {
+ var grid = new Grid();
+ grid.RowDefinitions.Add(new RowDefinition { Height = GridLength.Star });
+ grid.RowDefinitions.Add(new RowDefinition { Height = new GridLength(0.1, GridUnitType.Star) });
+
+ var label = new Label
+ {
+ FontFamily = "OpenSansRegular",
+ LineBreakMode = LineBreakMode.WordWrap,
+ Padding = new Thickness(24, 24, 0, 0),
+ FontSize = 16,
+ HorizontalOptions = LayoutOptions.Start,
+ HorizontalTextAlignment = TextAlignment.Start
+ };
+
+ label.BindingContext = popup;
+ label.SetBinding(Label.TextProperty, "Message");
+
+ var stackLayout = new StackLayout
+ {
+ Margin = new Thickness(0, 10, 0, 0),
+ HeightRequest = 1,
+ };
+
+ stackLayout.BackgroundColor = _isLightTheme ? Color.FromArgb("#611c1b1f") : Color.FromArgb("#61e6e1e5");
+ grid.Children.Add(label);
+ grid.Children.Add(stackLayout);
+
+ Grid.SetRow(label, 0);
+ Grid.SetRow(stackLayout, 1);
+
+ return grid;
+ });
+
+ return footerTemplate;
+ }
+
+ ///
+ /// Method to handle the search button clicked event.
+ ///
+ /// The sender object.
+ /// The event args.
+ void SearchButton_Clicked(object sender, EventArgs e)
+ {
+ if (popup == null)
+ {
+ return;
+ }
+
+ Random randomNumber = new Random();
+ int index = randomNumber.Next(0, 50);
+
+#if ANDROID || IOS
+ popup.Message = index + " Flights are available on that dates to depart from " + mobileFromLabel.Text.ToString();
+#else
+ popup.Message = index + " Flights are available on that dates to depart from " + fromLabel.Text.ToString();
+#endif
+ popup.Show();
+ }
+ }
+}
diff --git a/maui/samples/Gallery/Samples/Picker/GettingStarted/Behavior/GettingStartedBehavior.cs b/maui/samples/Gallery/Samples/Picker/GettingStarted/Behavior/GettingStartedBehavior.cs
new file mode 100644
index 00000000..2a0dd34c
--- /dev/null
+++ b/maui/samples/Gallery/Samples/Picker/GettingStarted/Behavior/GettingStartedBehavior.cs
@@ -0,0 +1,209 @@
+using Syncfusion.Maui.Toolkit.Picker;
+using System.Collections.ObjectModel;
+
+namespace Syncfusion.Maui.ControlsGallery.Picker.SfPicker
+{
+ public class GettingStartedBehavior : Behavior
+ {
+ ///
+ /// Picker view.
+ ///
+ Syncfusion.Maui.Toolkit.Picker.SfPicker? _picker;
+
+ ///
+ /// The show header switch
+ ///
+ Switch? _showHeaderSwitch, _showFooterSwitch;
+
+ ///
+ /// The text display mode combo box.
+ ///
+ Microsoft.Maui.Controls.Picker? _textDisplayComboBox;
+
+ ///
+ /// The text display to set the combo box item source.
+ ///
+ ObservableCollection? _textDisplay;
+
+ ///
+ /// Check the application theme is light or dark.
+ ///
+ readonly bool _isLightTheme = Application.Current?.RequestedTheme == AppTheme.Light;
+
+ ///
+ /// Begins when the behavior attached to the view
+ ///
+ /// bindable value
+ protected override void OnAttachedTo(SampleView sampleView)
+ {
+ base.OnAttachedTo(sampleView);
+
+#if MACCATALYST
+ Border border = sampleView.Content.FindByName("border");
+ border.IsVisible = true;
+ border.Stroke = Colors.Transparent;
+ _picker = sampleView.Content.FindByName("Picker1");
+#elif IOS
+ Border border = sampleView.Content.FindByName("border1");
+ border.IsVisible = true;
+ border.Stroke = Colors.Transparent;
+ _picker = sampleView.Content.FindByName("Picker3");
+#elif ANDROID
+ Border frame = sampleView.Content.FindByName("frame1");
+ frame.IsVisible = true;
+ frame.Stroke = Colors.Transparent;
+ _picker = sampleView.Content.FindByName("Picker2");
+#else
+ Border frame = sampleView.Content.FindByName("frame");
+ frame.IsVisible = true;
+ frame.Stroke = Colors.Transparent;
+ _picker = sampleView.Content.FindByName("Picker");
+#endif
+ _picker.HeaderView.Height = 50;
+ _picker.HeaderView.Text = "Select a Country";
+ _picker.SelectionView.Padding = 0;
+ _picker.SelectionView.CornerRadius = 0;
+ _picker.SelectedTextStyle.TextColor = _isLightTheme ? Color.FromArgb("#FFFFFF") : Color.FromArgb("#381E72");
+
+ _showHeaderSwitch = sampleView.Content.FindByName("showHeaderSwitch");
+ _showFooterSwitch = sampleView.Content.FindByName("showFooterSwitch");
+
+ _textDisplay = new ObservableCollection()
+ {
+ "Default", "Fade", "Shrink", "FadeAndShrink"
+ };
+
+ _textDisplayComboBox = sampleView.Content.FindByName("textDisplayComboBox");
+ _textDisplayComboBox.ItemsSource = _textDisplay;
+ _textDisplayComboBox.SelectedIndex = 0;
+ _textDisplayComboBox.SelectedIndexChanged += TextdisplayComboBox_SelectionChanged;
+
+ if (_showHeaderSwitch != null)
+ {
+ _showHeaderSwitch.Toggled += ShowHeaderSwitch_Toggled;
+ }
+
+ if (_showFooterSwitch != null)
+ {
+ _showFooterSwitch.Toggled += ShowFooterSwitch_Toggled;
+ }
+ }
+
+ ///
+ /// The text display combo box selection changed event.
+ ///
+ /// Return the object.
+ /// The Event Arguments.
+ void TextdisplayComboBox_SelectionChanged(object? sender, EventArgs e)
+ {
+ if (_picker == null || _textDisplayComboBox == null)
+ {
+ return;
+ }
+
+ if (sender is Microsoft.Maui.Controls.Picker picker)
+ {
+ string? format = picker.SelectedItem.ToString();
+ switch (format)
+ {
+ case "Default":
+ _picker.TextDisplayMode = PickerTextDisplayMode.Default;
+ break;
+
+ case "Fade":
+ _picker.TextDisplayMode = PickerTextDisplayMode.Fade;
+ break;
+
+ case "Shrink":
+ _picker.TextDisplayMode = PickerTextDisplayMode.Shrink;
+ break;
+
+ case "FadeAndShrink":
+ _picker.TextDisplayMode = PickerTextDisplayMode.FadeAndShrink;
+ break;
+ }
+ }
+ }
+
+ ///
+ /// Method for show header switch toggle changed.
+ ///
+ /// Return the object.
+ /// The Event Arguments.
+ void ShowHeaderSwitch_Toggled(object? sender, ToggledEventArgs e)
+ {
+ if (_picker != null)
+ {
+ if (e.Value == true)
+ {
+ _picker.HeaderView.Height = 50;
+ _picker.HeaderView.Text = "Select a Country";
+ }
+ else if (e.Value == false)
+ {
+ _picker.HeaderView.Height = 0;
+ }
+ }
+ }
+
+ ///
+ /// Method for show column header switch toggle changed.
+ ///
+ /// Return the object.
+ /// The Event Arguments.
+ void ShowColumnHeaderSwitch_Toggled(object? sender, ToggledEventArgs e)
+ {
+ if (_picker != null)
+ {
+ _picker.ColumnHeaderView.Height = e.Value == true ? 40 : 0;
+ }
+ }
+
+ ///
+ /// Method for show footer switch toggle changed.
+ ///
+ /// Return the object.
+ /// The Event Arguments.
+ void ShowFooterSwitch_Toggled(object? sender, ToggledEventArgs e)
+ {
+ if (_picker != null)
+ {
+ if (e.Value == true)
+ {
+ _picker.FooterView.Height = 40;
+ }
+ else if (e.Value == false)
+ {
+ _picker.FooterView.Height = 0;
+ }
+ }
+ }
+
+ ///
+ /// Begins when the behavior attached to the view
+ ///
+ /// bindable value
+ protected override void OnDetachingFrom(SampleView sampleView)
+ {
+ base.OnDetachingFrom(sampleView);
+ if (_showHeaderSwitch != null)
+ {
+ _showHeaderSwitch.Toggled -= ShowHeaderSwitch_Toggled;
+ _showHeaderSwitch = null;
+ }
+
+ if (_showFooterSwitch != null)
+ {
+ _showFooterSwitch.Toggled -= ShowFooterSwitch_Toggled;
+ _showFooterSwitch = null;
+ }
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ public GettingStartedBehavior()
+ {
+ }
+ }
+}
\ No newline at end of file
diff --git a/maui/samples/Gallery/Samples/Picker/GettingStarted/View/GettingStarted.xaml b/maui/samples/Gallery/Samples/Picker/GettingStarted/View/GettingStarted.xaml
new file mode 100644
index 00000000..c7a23250
--- /dev/null
+++ b/maui/samples/Gallery/Samples/Picker/GettingStarted/View/GettingStarted.xaml
@@ -0,0 +1,153 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/maui/samples/Gallery/Samples/Picker/GettingStarted/View/GettingStarted.xaml.cs b/maui/samples/Gallery/Samples/Picker/GettingStarted/View/GettingStarted.xaml.cs
new file mode 100644
index 00000000..06173d01
--- /dev/null
+++ b/maui/samples/Gallery/Samples/Picker/GettingStarted/View/GettingStarted.xaml.cs
@@ -0,0 +1,201 @@
+using Syncfusion.Maui.Toolkit.Picker;
+
+namespace Syncfusion.Maui.ControlsGallery.Picker.SfPicker;
+
+public partial class GettingStarted : SampleView
+{
+ ///
+ /// The view model instance.
+ ///
+ readonly ViewModel? _viewModel;
+
+ public GettingStarted()
+ {
+ InitializeComponent();
+ _viewModel = new ViewModel();
+ List list = new List()
+ {
+ "Australia",
+ "China",
+ "Japan",
+ "Germany",
+ "India",
+ "USA",
+ "France",
+ "UK",
+ "UAE",
+ "Egypt",
+ "Argentina",
+ };
+
+#if MACCATALYST
+ Picker1.Columns.Add(new PickerColumn()
+ {
+ ItemsSource = list,
+ SelectedItem = _viewModel.SelectedItem,
+ });
+#elif IOS
+ Picker3.Columns.Add(new PickerColumn()
+ {
+ ItemsSource = list,
+ SelectedItem = _viewModel.SelectedItem,
+ });
+#elif ANDROID
+ Picker2.Columns.Add(new PickerColumn()
+ {
+ ItemsSource = list,
+ SelectedItem = _viewModel.SelectedItem,
+ });
+#else
+ Picker.Columns.Add(new PickerColumn()
+ {
+ ItemsSource = list,
+ SelectedItem = _viewModel.SelectedItem,
+ });
+#endif
+ }
+
+ ///
+ /// Method to update the change log based on the selected index.
+ ///
+ /// The Sender.
+ /// The event args.
+ void OnPickerSelectionChanged(object sender, PickerSelectionChangedEventArgs e)
+ {
+ Syncfusion.Maui.Toolkit.Picker.SfPicker? picker = sender as Syncfusion.Maui.Toolkit.Picker.SfPicker;
+ if (picker == null || picker.Columns == null || picker.Columns.Count == 0)
+ {
+ return;
+ }
+
+#if IOS || MACCATALYST
+ if (picker == Picker || picker == Picker2)
+ {
+ return;
+ }
+#else
+ if (picker == Picker1 || picker == Picker3)
+ {
+ return;
+ }
+#endif
+
+ PickerColumn selectedColumn = picker.Columns[0];
+ string selectedIndex = selectedColumn.SelectedIndex.ToString();
+ string countryName = "Australia";
+
+ switch (selectedIndex)
+ {
+ case "0":
+ countryName = "Australia";
+ break;
+ case "1":
+ countryName = "China";
+ break;
+ case "2":
+ countryName = "Japan";
+ break;
+ case "3":
+ countryName = "Germany";
+ break;
+ case "4":
+ countryName = "India";
+ break;
+ case "5":
+ countryName = "USA";
+ break;
+ case "6":
+ countryName = "France";
+ break;
+ case "7":
+ countryName = "UK";
+ break;
+ case "8":
+ countryName = "UAE";
+ break;
+ case "9":
+ countryName = "Egypt";
+ break;
+ case "10":
+ countryName = "Argentina";
+ break;
+ }
+
+ if (labelStack == null || labelStack1 == null)
+ {
+ return;
+ }
+
+ string labelText = countryName + " has been selected";
+ bool isNeedToAdd = true;
+#if WINDOWS || MACCATALYST
+ if (labelStack.Children.Count > 0)
+ {
+ var view = labelStack.Children[labelStack.Children.Count - 1];
+ if (view is Label label && label.Text == labelText)
+ {
+ isNeedToAdd = false;
+ }
+ }
+
+ if (!isNeedToAdd)
+ {
+ return;
+ }
+
+ labelStack.Add(new Label()
+ {
+ Text = labelText,
+ VerticalOptions = LayoutOptions.Center,
+ });
+
+ if (labelStack.Parent is ScrollView scrollView)
+ {
+ scrollView.PropertyChanged += OnScrollViewPropertyChanged;
+ }
+#else
+ if (labelStack1.Children.Count > 0)
+ {
+ var view = labelStack1.Children[labelStack.Children.Count - 1];
+ if (view is Label label && label.Text == labelText)
+ {
+ isNeedToAdd = false;
+ }
+ }
+
+ if (!isNeedToAdd)
+ {
+ return;
+ }
+
+ labelStack1.Add(new Label()
+ {
+ Text = labelText,
+ VerticalOptions = LayoutOptions.Center,
+ });
+
+ if (labelStack1.Parent is ScrollView scrollView)
+ {
+ scrollView.PropertyChanged += OnScrollViewPropertyChanged;
+ }
+#endif
+ }
+
+ void OnScrollViewPropertyChanged(object? sender, System.ComponentModel.PropertyChangedEventArgs e)
+ {
+ if (e.PropertyName == nameof(ScrollView.ContentSize) && sender is ScrollView scrollView)
+ {
+ scrollView.PropertyChanged -= OnScrollViewPropertyChanged;
+ scrollView.ScrollToAsync(0, scrollView.ContentSize.Height - scrollView.Height, false);
+ }
+ }
+
+ void OnClearButtonClicked(object sender, EventArgs e)
+ {
+#if WINDOWS || MACCATALYST
+ labelStack.Children.Clear();
+#else
+ labelStack1.Children.Clear();
+#endif
+ }
+}
\ No newline at end of file
diff --git a/maui/samples/Gallery/Samples/Picker/GettingStarted/ViewModel.cs b/maui/samples/Gallery/Samples/Picker/GettingStarted/ViewModel.cs
new file mode 100644
index 00000000..511a7ddc
--- /dev/null
+++ b/maui/samples/Gallery/Samples/Picker/GettingStarted/ViewModel.cs
@@ -0,0 +1,49 @@
+using System.Collections.ObjectModel;
+using System.ComponentModel;
+
+namespace Syncfusion.Maui.ControlsGallery.Picker.SfPicker
+{
+ public class ViewModel : INotifyPropertyChanged
+ {
+ private object? selectedItem = "Australia";
+
+ private Brush selectionColor = Colors.Orange;
+
+ public object? SelectedItem
+ {
+ get
+ {
+ return selectedItem;
+ }
+ set
+ {
+ selectedItem = value;
+ RaisePropertyChanged(nameof(SelectedItem));
+ }
+ }
+
+ public Brush SelectionColor
+ {
+ get
+ {
+ return selectionColor;
+ }
+ set
+ {
+ selectionColor = value;
+ RaisePropertyChanged("SelectionColor");
+ }
+ }
+
+ private void RaisePropertyChanged(string propertyName)
+ {
+ PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
+ }
+
+ public ViewModel()
+ {
+ }
+
+ public event PropertyChangedEventHandler? PropertyChanged;
+ }
+}
\ No newline at end of file
diff --git a/maui/samples/Gallery/Samples/PolarChart/PolarRadarDemo/DefaultPolar.xaml b/maui/samples/Gallery/Samples/PolarChart/PolarRadarDemo/DefaultPolar.xaml
index f1d41848..b853771f 100644
--- a/maui/samples/Gallery/Samples/PolarChart/PolarRadarDemo/DefaultPolar.xaml
+++ b/maui/samples/Gallery/Samples/PolarChart/PolarRadarDemo/DefaultPolar.xaml
@@ -73,18 +73,18 @@
+ SelectedIndex="0" SelectedIndexChanged="type_SelectedIndexChanged"
+ Background="{OnPlatform Default={AppThemeBinding Dark={StaticResource TextColour1Dark}, Light={StaticResource BackgroundLight}},Android=Transparent}"
+ TextColor="{OnPlatform Default={StaticResource TextColourLight}, Android={AppThemeBinding Light={StaticResource TextColourLight}, Dark={StaticResource TextColourDark}}}"/>
+ SelectedIndex="4" SelectedIndexChanged="Angle_SelectedIndexChanged"
+ Background="{OnPlatform Default={AppThemeBinding Dark={StaticResource TextColour1Dark}, Light={StaticResource BackgroundLight}},Android=Transparent}"
+ TextColor="{OnPlatform Default={StaticResource TextColourLight}, Android={AppThemeBinding Light={StaticResource TextColourLight}, Dark={StaticResource TextColourDark}}}"/>
diff --git a/maui/samples/Gallery/Samples/PolarChart/PolarRadarDemo/DefaultRadar.xaml b/maui/samples/Gallery/Samples/PolarChart/PolarRadarDemo/DefaultRadar.xaml
index d33dbabf..c7c0b9f5 100644
--- a/maui/samples/Gallery/Samples/PolarChart/PolarRadarDemo/DefaultRadar.xaml
+++ b/maui/samples/Gallery/Samples/PolarChart/PolarRadarDemo/DefaultRadar.xaml
@@ -74,18 +74,18 @@
+ SelectedIndex="0" SelectedIndexChanged="type_SelectedIndexChanged"
+ Background="{OnPlatform Default={AppThemeBinding Dark={StaticResource TextColour1Dark}, Light={StaticResource BackgroundLight}},Android=Transparent}"
+ TextColor="{OnPlatform Default={StaticResource TextColourLight}, Android={AppThemeBinding Light={StaticResource TextColourLight}, Dark={StaticResource TextColourDark}}}"/>
+ SelectedIndex="4" SelectedIndexChanged="Angle_SelectedIndexChanged"
+ Background="{OnPlatform Default={AppThemeBinding Dark={StaticResource TextColour1Dark}, Light={StaticResource BackgroundLight}},Android=Transparent}"
+ TextColor="{OnPlatform Default={StaticResource TextColourLight}, Android={AppThemeBinding Light={StaticResource TextColourLight}, Dark={StaticResource TextColourDark}}}"/>
diff --git a/maui/samples/Gallery/Samples/Popup/ViewModel/GettingStartedViewModel.cs b/maui/samples/Gallery/Samples/Popup/ViewModel/GettingStartedViewModel.cs
index 82b50e3b..46a196b5 100644
--- a/maui/samples/Gallery/Samples/Popup/ViewModel/GettingStartedViewModel.cs
+++ b/maui/samples/Gallery/Samples/Popup/ViewModel/GettingStartedViewModel.cs
@@ -234,8 +234,11 @@ void OnDelete(Syncfusion.Maui.Toolkit.Popup.SfPopup popup)
void OnSignUp(Syncfusion.Maui.Toolkit.Popup.SfPopup popup)
{
- FullScreenModel = new FullScreenModel();
- popup.IsOpen = false;
+ FullScreenModel.UserName = string.Empty;
+ FullScreenModel.Email = string.Empty;
+ FullScreenModel.Password = string.Empty;
+ FullScreenModel.RePassword = string.Empty;
+ popup.IsOpen = false;
}
async void OnFileAction(Syncfusion.Maui.Toolkit.Popup.SfPopup popup)
diff --git a/maui/samples/Gallery/Samples/ProgressBar/CircularProgressBar/CircularProgressBar.xaml b/maui/samples/Gallery/Samples/ProgressBar/CircularProgressBar/CircularProgressBar.xaml
new file mode 100644
index 00000000..04cad73e
--- /dev/null
+++ b/maui/samples/Gallery/Samples/ProgressBar/CircularProgressBar/CircularProgressBar.xaml
@@ -0,0 +1,303 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/maui/samples/Gallery/Samples/ProgressBar/CircularProgressBar/CircularProgressBar.xaml.cs b/maui/samples/Gallery/Samples/ProgressBar/CircularProgressBar/CircularProgressBar.xaml.cs
new file mode 100644
index 00000000..f18c4042
--- /dev/null
+++ b/maui/samples/Gallery/Samples/ProgressBar/CircularProgressBar/CircularProgressBar.xaml.cs
@@ -0,0 +1,407 @@
+using Syncfusion.Maui.Toolkit.ProgressBar;
+
+namespace Syncfusion.Maui.ControlsGallery.ProgressBar.SfCircularProgressBar;
+
+public partial class CircularProgressBar : SampleView
+{
+ #region Fields
+
+ ///
+ /// Indicates whether the progress bars are currently running.
+ ///
+ bool isRunning = true;
+
+ ///
+ /// Indicates whether the view has been disposed.
+ ///
+ bool isDisposed;
+
+ #endregion
+
+ #region Constructor
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ public CircularProgressBar()
+ {
+ InitializeComponent();
+
+ #region Custom Content
+
+ this.SetCustomContentProgress();
+ this.SetVideoPlayerContent();
+
+ #endregion
+
+ #region Circular Radius
+
+ this.SetTrackInsideProgress();
+
+ #endregion
+
+ #region Circular Segment
+
+ this.SetSegmentedFillingStyleProgress();
+
+ #endregion
+ }
+
+ #endregion
+
+ #region Private Methods
+
+ #region Custom Content
+
+ ///
+ /// Initializes a timer to update the progress of the video player progress bar every 50 milliseconds.
+ ///
+ ///
+ /// The timer continues to run as long as the flag is true.
+ ///
+ private void SetVideoPlayerContent()
+ {
+ Dispatcher.StartTimer(TimeSpan.FromMilliseconds(50), () =>
+ {
+ Dispatcher.DispatchAsync(() =>
+ {
+ this.SetProgress();
+ });
+
+ return isRunning;
+ });
+ }
+
+ ///
+ /// Updates the progress of the video player progress bar.
+ ///
+ ///
+ /// Increments the progress by 0.5. When the progress reaches 100, it resets to 0.
+ ///
+ private void SetProgress()
+ {
+ this.VideoPlayerProgressBar.Progress += 0.5;
+ if (this.VideoPlayerProgressBar.Progress == 100)
+ {
+ this.VideoPlayerProgressBar.Progress = 0;
+ }
+ }
+
+ ///
+ /// Initializes a timer to update the custom content circular progress bar and its label every 50 milliseconds.
+ ///
+ ///
+ /// The progress stops updating when it reaches 74.
+ ///
+ private void SetCustomContentProgress()
+ {
+ double progress = 0;
+
+ Dispatcher.StartTimer(TimeSpan.FromMilliseconds(50), () =>
+ {
+ Dispatcher.DispatchAsync(() =>
+ {
+ this.CustomContentCircularProgressBar.Progress = progress += 1;
+ this.CustomContentProgressBarLabel.Text = progress + "%";
+ });
+
+ return progress < 74;
+ });
+ }
+
+ ///
+ /// Handles the click event of the play button to toggle the video player's progress updates.
+ ///
+ /// The source of the event, typically the play button.
+ /// An instance of containing the event data.
+ ///
+ /// Toggles the flag and updates the play button's text.
+ /// If the video player is running, it restarts the progress updates.
+ ///
+ private void PlayButton_Clicked(object sender, EventArgs e)
+ {
+ isRunning = !isRunning;
+ this.PlayButton.Text = isRunning ? "\ue769" : "\ue737";
+ if (isRunning)
+ {
+ this.SetVideoPlayerContent();
+ }
+ }
+
+ #endregion
+
+ #region Circular Radius
+
+ ///
+ /// Handles the ProgressChanged event for the TrackOutsideProgressBar.
+ ///
+ /// The source of the event, typically the TrackOutsideProgressBar.
+ /// An instance of containing the progress value.
+ ///
+ /// This method toggles the progress of the TrackOutsideProgressBar between 0 and 100.
+ /// When the progress reaches 100, it resets to 0. When it reaches 0, it sets the progress back to 100.
+ ///
+ private void TrackOutsideProgressBar_ProgressChanged(object sender, ProgressValueEventArgs e)
+ {
+ Dispatcher.DispatchAsync(() =>
+ {
+ if (e.Progress.Equals(0))
+ {
+ // Set progress to 100 when it reaches 0.
+ this.TrackOutsideProgressBar.Progress = 100;
+ }
+
+ if (e.Progress.Equals(100))
+ {
+ // Reset progress to 0 when it reaches 100.
+ this.TrackOutsideProgressBar.SetProgress(0, 0);
+ }
+ });
+ }
+
+ ///
+ /// Handles the ProgressChanged event for the FilledIndicatorProgressBar.
+ ///
+ /// The source of the event, typically the FilledIndicatorProgressBar.
+ /// An instance of containing the progress value.
+ ///
+ /// This method toggles the progress of the FilledIndicatorProgressBar between 0 and 100.
+ /// When the progress reaches 100, it resets to 0. When it reaches 0, it sets the progress back to 100.
+ ///
+ private void FilledIndicatorProgressBar_ProgressChanged(object sender, ProgressValueEventArgs e)
+ {
+ Dispatcher.DispatchAsync(() =>
+ {
+ if (e.Progress.Equals(0))
+ {
+ // Set progress to 100 when it reaches 0.
+ this.FilledIndicatorProgressBar.Progress = 100;
+ }
+
+ if (e.Progress.Equals(100))
+ {
+ // Reset progress to 0 when it reaches 100.
+ this.FilledIndicatorProgressBar.SetProgress(0, 0);
+ }
+ });
+ }
+
+ ///
+ /// Handles the ProgressChanged event for the ThinTrackStyle progress bar.
+ ///
+ /// The source of the event, typically the ThinTrackStyle progress bar.
+ /// An instance of containing the progress value.
+ ///
+ /// This method toggles the progress of the ThinTrackStyle progress bar between 0 and 100.
+ /// When the progress reaches 100, it resets to 0. When it reaches 0, it sets the progress back to 100.
+ ///
+ private void ThinTrackStyle_ProgressChanged(object sender, ProgressValueEventArgs e)
+ {
+ Dispatcher.DispatchAsync(() =>
+ {
+ if (e.Progress.Equals(0))
+ {
+ // Set progress to 100 when it reaches 0.
+ this.ThinTrackStyle.Progress = 100;
+ }
+
+ if (e.Progress.Equals(100))
+ {
+ // Reset progress to 0 when it reaches 100.
+ this.ThinTrackStyle.SetProgress(0, 0);
+ }
+ });
+ }
+
+ ///
+ /// Sets the progress for the TrackInsideProgressBar and updates the associated label.
+ ///
+ ///
+ /// This method initializes a timer that updates the progress of the TrackInsideProgressBar every 50 milliseconds.
+ /// When the progress reaches 100, it resets to 0 and continues.
+ /// The timer stops when the view is disposed.
+ ///
+ private void SetTrackInsideProgress()
+ {
+ double progress = 0;
+ Dispatcher.StartTimer(TimeSpan.FromMilliseconds(50), () =>
+ {
+ Dispatcher.DispatchAsync(() =>
+ {
+ // Increment progress by 0.25 and update the progress bar and label.
+ this.TrackInsideProgressBar.Progress = progress += 0.25;
+ this.TrackInsideProgressBarProgressLabel.Text = (int)progress + "%";
+
+ // Reset progress to 0 when it reaches 100.
+ if (progress == 100)
+ {
+ this.TrackInsideProgressBar.Progress = progress = 0;
+ }
+ });
+
+ // Continue the timer until the view is disposed.
+ return !isDisposed;
+ });
+ }
+
+ #endregion
+
+ #region Circular Segment
+
+ ///
+ /// Handles the ProgressChanged event for the SegmentedCircularProgressBar.
+ ///
+ /// The source of the event, typically the SegmentedCircularProgressBar.
+ /// An instance of containing the progress value.
+ ///
+ /// This method toggles the progress of the SegmentedCircularProgressBar between 0 and 75.
+ /// When the progress reaches 75, it resets to 0. When it reaches 0, it sets the progress back to 75.
+ ///
+ private void SegmentedCircularProgressBar_ProgressChanged(object sender, ProgressValueEventArgs e)
+ {
+ Dispatcher.DispatchAsync(() =>
+ {
+ if (e.Progress.Equals(75))
+ {
+ // Reset progress to 0 when it reaches 75.
+ this.SegmentedCircularProgressBar.SetProgress(0, 0);
+ }
+
+ if (e.Progress.Equals(0))
+ {
+ // Set progress to 75 when it reaches 0.
+ this.SegmentedCircularProgressBar.Progress = 75;
+ }
+ });
+ }
+
+ ///
+ /// Handles the ProgressChanged event for the SegmentedPaddingCircularProgressBar.
+ ///
+ /// The source of the event, typically the SegmentedPaddingCircularProgressBar.
+ /// An instance of containing the progress value.
+ private void SegmentedPaddingCircularProgressBar_ProgressChanged(object sender, ProgressValueEventArgs e)
+ {
+ Dispatcher.DispatchAsync(() =>
+ {
+ if (e.Progress.Equals(75))
+ {
+ // Reset progress to 0 when it reaches 75.
+ this.SegmentedPaddingCircularProgressBar.SetProgress(0, 0);
+ }
+
+ if (e.Progress.Equals(0))
+ {
+ // Set progress to 75 when it reaches 0.
+ this.SegmentedPaddingCircularProgressBar.Progress = 75;
+ }
+ });
+ }
+
+ ///
+ /// Sets the progress for the SegmentedFillingStyle progress bar.
+ ///
+ ///
+ /// This method initializes a timer that updates the progress of the SegmentedFillingStyle progress bar every 300 milliseconds.
+ /// When the progress reaches 100, it resets to 0 and continues.
+ /// The timer stops when the view is disposed.
+ ///
+ private void SetSegmentedFillingStyleProgress()
+ {
+ double progress = 0;
+
+ Dispatcher.StartTimer(TimeSpan.FromMilliseconds(300), () =>
+ {
+ if (progress == 100)
+ {
+ // Reset progress to 0 when it reaches 100.
+ this.SegmentedFillingStyle.Progress = progress = 0;
+ }
+
+ Dispatcher.DispatchAsync(() =>
+ {
+ // Increment progress by 5.
+ this.SegmentedFillingStyle.Progress = progress += 5;
+ });
+
+ // Continue the timer until the view is disposed.
+ return !isDisposed;
+ });
+ }
+
+ #endregion
+
+ #region Custom Angle
+
+ ///
+ /// Handles the ProgressChanged event for the AngleCustomizationProgressBar.
+ ///
+ /// The source of the event, typically the AngleCustomizationProgressBar.
+ /// An instance of containing the progress value.
+ private void AngleCustomizationProgressBar_ProgressChanged(object sender, ProgressValueEventArgs e)
+ {
+ Dispatcher.DispatchAsync(() =>
+ {
+ if (e.Progress.Equals(0))
+ {
+ // Reset progress to 100 when it reaches 0.
+ this.AngleCustomizationProgressBar.Progress = 100;
+ }
+
+ if (e.Progress.Equals(100))
+ {
+ // Reset progress to 0 when it reaches 100.
+ this.AngleCustomizationProgressBar.SetProgress(0, 0);
+ }
+ });
+ }
+
+ #endregion
+
+ #region Range Colors
+
+ ///
+ /// Handles the ProgressChanged event for the RangeColorProgressBar.
+ ///
+ /// The source of the event, typically the RangeColorProgressBar.
+ /// An instance of containing the progress value.
+ private void RangeColorProgressBar_ProgressChanged(object sender, ProgressValueEventArgs e)
+ {
+ Dispatcher.DispatchAsync(() =>
+ {
+ if (e.Progress.Equals(0))
+ {
+ // Reset progress to 100 when it reaches 0.
+ this.RangeColorProgressBar.Progress = 100;
+ }
+
+ if (e.Progress.Equals(100))
+ {
+ // Reset progress to 0 when it reaches 100.
+ this.RangeColorProgressBar.SetProgress(0, 0);
+ }
+ });
+ }
+
+ #endregion
+
+ #endregion
+
+ #region Override Methods
+
+ ///
+ /// Called when the view is disappearing.
+ ///
+ ///
+ /// This method is overridden to handle cleanup tasks when the view is no longer visible.
+ /// It stops any running timers and marks the view as disposed to prevent further updates.
+ ///
+ public override void OnDisappearing()
+ {
+ base.OnDisappearing();
+ isRunning = false;
+ isDisposed = true;
+ }
+
+ #endregion
+}
\ No newline at end of file
diff --git a/maui/samples/Gallery/Samples/ProgressBar/LinearProgressBar/LinearProgressBar.xaml b/maui/samples/Gallery/Samples/ProgressBar/LinearProgressBar/LinearProgressBar.xaml
new file mode 100644
index 00000000..0647250e
--- /dev/null
+++ b/maui/samples/Gallery/Samples/ProgressBar/LinearProgressBar/LinearProgressBar.xaml
@@ -0,0 +1,179 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/maui/samples/Gallery/Samples/ProgressBar/LinearProgressBar/LinearProgressBar.xaml.cs b/maui/samples/Gallery/Samples/ProgressBar/LinearProgressBar/LinearProgressBar.xaml.cs
new file mode 100644
index 00000000..f9e41f28
--- /dev/null
+++ b/maui/samples/Gallery/Samples/ProgressBar/LinearProgressBar/LinearProgressBar.xaml.cs
@@ -0,0 +1,181 @@
+using Syncfusion.Maui.Toolkit.ProgressBar;
+
+namespace Syncfusion.Maui.ControlsGallery.ProgressBar.SfLinearProgressBar;
+
+public partial class LinearProgressBar : SampleView
+{
+ #region Constructor
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ public LinearProgressBar()
+ {
+ InitializeComponent();
+ }
+
+ #endregion
+
+ #region Private Methods
+
+ #region Corner Radius
+
+ ///
+ /// Handles the progress change event for the CornerRadiusProgressBar.
+ /// Resets the progress to 100 when it reaches 0, and resets it to 0 when it reaches 100.
+ ///
+ /// The source of the event, typically the progress bar.
+ /// The event data containing the current progress value.
+ private void CornerRadiusProgressBar_ProgressChanged(object sender, ProgressValueEventArgs e)
+ {
+ Dispatcher.DispatchAsync(() =>
+ {
+ if (e.Progress.Equals(0))
+ {
+ this.CornerRadiusProgressBar.Progress = 100;
+ }
+
+ if (e.Progress.Equals(100))
+ {
+ this.CornerRadiusProgressBar.SetProgress(0, 0);
+ }
+ });
+ }
+
+ #endregion
+
+ #region Padding Progress Bar
+
+ ///
+ /// Handles the progress change event for the PaddingProgressBar.
+ /// Resets the progress to 100 when it reaches 0, and resets it to 0 when it reaches 100.
+ ///
+ /// The source of the event, typically the progress bar.
+ /// The event data containing the current progress value.
+ private void PaddingProgressBar_ProgressChanged(object sender, ProgressValueEventArgs e)
+ {
+ Dispatcher.DispatchAsync(() =>
+ {
+ if (e.Progress.Equals(0))
+ {
+ this.PaddingProgressBar.Progress = 100;
+ }
+
+ if (e.Progress.Equals(100))
+ {
+ this.PaddingProgressBar.SetProgress(0, 0);
+ }
+ });
+ }
+
+ #endregion
+
+ #region Range Progress Bar
+
+ ///
+ /// Handles the progress change event for the RangeProgressBar.
+ /// Resets the progress to 100 when it reaches 0, and resets it to 0 when it reaches 100.
+ ///
+ /// The source of the event, typically the progress bar.
+ /// The event data containing the current progress value.
+ private void RangeProgressBar_ProgressChanged(object sender, ProgressValueEventArgs e)
+ {
+ Dispatcher.DispatchAsync(() =>
+ {
+ if (e.Progress.Equals(0))
+ {
+ this.RangeProgressBar.Progress = 100;
+ }
+
+ if (e.Progress.Equals(100))
+ {
+ this.RangeProgressBar.SetProgress(0, 0);
+ }
+ });
+ }
+
+ #endregion
+
+ #region Gradient Progress Bar
+
+ ///
+ /// Handles the progress change event for the GradientProgressBar.
+ /// Resets the progress to 100 when it reaches 0, and resets it to 0 when it reaches 100.
+ ///
+ /// The source of the event, typically the progress bar.
+ /// The event data containing the current progress value.
+ private void GradientProgressBar_ProgressChanged(object sender, ProgressValueEventArgs e)
+ {
+ Dispatcher.DispatchAsync(() =>
+ {
+ if (e.Progress.Equals(0))
+ {
+ this.GradientProgressBar.Progress = 100;
+ }
+
+ if (e.Progress.Equals(100))
+ {
+ this.GradientProgressBar.SetProgress(0, 0);
+ }
+ });
+ }
+
+ #endregion
+
+ #region Linear Buffer
+
+ ///
+ /// Handles the progress change event for the SecondaryProgressProgressBar.
+ ///
+ /// The source of the event, typically the progress bar.
+ /// The event data containing the current progress value.
+ private void SecondaryProgressProgressBar_ValueChanged(object sender, ProgressValueEventArgs e)
+ {
+ Dispatcher.DispatchAsync(() =>
+ {
+ if (e.Progress.Equals(0))
+ {
+ this.SecondaryProgressProgressBar.SecondaryAnimationDuration = 1000;
+ this.SecondaryProgressProgressBar.SecondaryProgress = 100;
+ this.SecondaryProgressProgressBar.Progress = 100;
+ }
+
+ if (e.Progress.Equals(100))
+ {
+ this.SecondaryProgressProgressBar.SecondaryAnimationDuration = 0;
+ this.SecondaryProgressProgressBar.SecondaryProgress = 0;
+ this.SecondaryProgressProgressBar.SetProgress(0, 0);
+ }
+ });
+ }
+
+ #endregion
+
+ #region Segmented Corner Radius
+
+ ///
+ /// Handles the progress change event for the SegmentedCornerRadiusProgressBar.
+ /// Resets the progress to 100 when it reaches 0, and resets it to 0 when it reaches 100.
+ ///
+ /// The source of the event, typically the progress bar.
+ /// The event data containing the current progress value.
+ private void SegmentedCornerRadiusProgressBar_ProgressChanged(object sender, ProgressValueEventArgs e)
+ {
+ Dispatcher.DispatchAsync(() =>
+ {
+ if (e.Progress.Equals(0))
+ {
+ this.SegmentedCornerRadiusProgressBar.Progress = 100;
+ }
+
+ if (e.Progress.Equals(100))
+ {
+ this.SegmentedCornerRadiusProgressBar.SetProgress(0, 0);
+ }
+ });
+ }
+
+ #endregion
+
+ #endregion
+}
\ No newline at end of file
diff --git a/maui/samples/Gallery/Samples/PullToRefresh/ListViewPullToRefresh/Helper/ListViewPullToRefreshBehavior.cs b/maui/samples/Gallery/Samples/PullToRefresh/ListViewPullToRefresh/Helper/ListViewPullToRefreshBehavior.cs
index 773075ae..532eacf9 100644
--- a/maui/samples/Gallery/Samples/PullToRefresh/ListViewPullToRefresh/Helper/ListViewPullToRefreshBehavior.cs
+++ b/maui/samples/Gallery/Samples/PullToRefresh/ListViewPullToRefresh/Helper/ListViewPullToRefreshBehavior.cs
@@ -11,7 +11,7 @@ public partial class ListViewPullToRefreshBehavior : Behavior
Syncfusion.Maui.Toolkit.PullToRefresh.SfPullToRefresh? _pullToRefresh;
ListView? _listView;
ListViewInboxInfoViewModel? _viewModel;
- Picker? _transitionType;
+ Microsoft.Maui.Controls.Picker? _transitionType;
///
/// You can override this method to subscribe to AssociatedObject events and initialize properties.
@@ -23,7 +23,7 @@ protected override void OnAttachedTo(SampleView bindable)
bindable.BindingContext = _viewModel;
_pullToRefresh = bindable.FindByName("pullToRefresh");
_listView = bindable.FindByName("listView");
- _transitionType = bindable.FindByName("comboBox");
+ _transitionType = bindable.FindByName("comboBox");
_transitionType.SelectedIndexChanged += OnSelectionChanged;
_pullToRefresh.Refreshing += PullToRefresh_Refreshing;
_pullToRefresh.Refreshed += PullToRefresh_Refreshed;
diff --git a/maui/samples/Gallery/Samples/PullToRefresh/ListViewPullToRefresh/View/ListViewPullToRefresh.xaml.cs b/maui/samples/Gallery/Samples/PullToRefresh/ListViewPullToRefresh/View/ListViewPullToRefresh.xaml.cs
index 26821874..04c0656c 100644
--- a/maui/samples/Gallery/Samples/PullToRefresh/ListViewPullToRefresh/View/ListViewPullToRefresh.xaml.cs
+++ b/maui/samples/Gallery/Samples/PullToRefresh/ListViewPullToRefresh/View/ListViewPullToRefresh.xaml.cs
@@ -4,9 +4,9 @@ public partial class ListViewPullToRefresh : SampleView
{
public ListViewPullToRefresh()
{
- InitializeComponent();
+ InitializeComponent();
}
-
+
public override void OnDisappearing()
{
base.OnDisappearing();
diff --git a/maui/samples/Gallery/Samples/PullToRefresh/PullToRefreshView/Helper/PullToRefreshViewBehavior.cs b/maui/samples/Gallery/Samples/PullToRefresh/PullToRefreshView/Helper/PullToRefreshViewBehavior.cs
index c07c4f21..b682dd1c 100644
--- a/maui/samples/Gallery/Samples/PullToRefresh/PullToRefreshView/Helper/PullToRefreshViewBehavior.cs
+++ b/maui/samples/Gallery/Samples/PullToRefresh/PullToRefreshView/Helper/PullToRefreshViewBehavior.cs
@@ -9,7 +9,7 @@ public partial class PullToRefreshViewBehavior : Behavior
{
CollectionView? _listView;
Syncfusion.Maui.Toolkit.PullToRefresh.SfPullToRefresh? _pullToRefresh;
- Picker? _transitionType;
+ Microsoft.Maui.Controls.Picker? _transitionType;
PullToRefreshViewModel? _viewModel;
///
@@ -31,7 +31,7 @@ protected override void OnAttachedTo(SampleView bindable)
bindable.BindingContext = _viewModel;
_pullToRefresh = bindable.FindByName("pullToRefresh");
_listView = bindable.FindByName("listView");
- _transitionType = bindable.FindByName("comboBox");
+ _transitionType = bindable.FindByName("comboBox");
_pullToRefresh.PullingThreshold = 100;
_listView.BindingContext = _viewModel;
_listView.ItemsSource = _viewModel.SelectedData;
diff --git a/maui/samples/Gallery/Samples/PullToRefresh/PullToRefreshView/View/PullToRefreshView.xaml.cs b/maui/samples/Gallery/Samples/PullToRefresh/PullToRefreshView/View/PullToRefreshView.xaml.cs
index 10753eaa..1f1cc258 100644
--- a/maui/samples/Gallery/Samples/PullToRefresh/PullToRefreshView/View/PullToRefreshView.xaml.cs
+++ b/maui/samples/Gallery/Samples/PullToRefresh/PullToRefreshView/View/PullToRefreshView.xaml.cs
@@ -4,9 +4,9 @@ public partial class PullToRefreshView : SampleView
{
public PullToRefreshView()
{
- InitializeComponent();
+ InitializeComponent();
}
-
+
public override void OnDisappearing()
{
base.OnDisappearing();
diff --git a/maui/samples/Gallery/Samples/TabView/CenterButton/CenterButton.xaml b/maui/samples/Gallery/Samples/TabView/CenterButton/CenterButton.xaml
index 11f62b8e..5be54ec0 100644
--- a/maui/samples/Gallery/Samples/TabView/CenterButton/CenterButton.xaml
+++ b/maui/samples/Gallery/Samples/TabView/CenterButton/CenterButton.xaml
@@ -8,6 +8,7 @@
xmlns:textInputLayout="clr-namespace:Syncfusion.Maui.Toolkit.TextInputLayout;assembly=Syncfusion.Maui.Toolkit"
xmlns:local="clr-namespace:Syncfusion.Maui.ControlsGallery.TabView.SfTabView"
xmlns:imageExtension="clr-namespace:Syncfusion.Maui.ControlsGallery.Converters;assembly=Syncfusion.Maui.ControlsGallery"
+ xmlns:base="clr-namespace:Syncfusion.Maui.ControlsGallery.Samples.TabView.CenterButton"
BackgroundColor="{AppThemeBinding Light={StaticResource BackgroundLight}, Dark={StaticResource BackgroundDark}}"
x:Name="root">
@@ -47,12 +48,12 @@
-
+
@@ -110,12 +111,12 @@
FontFamily="MauiSampleFontIcon"/>
-
+
@@ -171,12 +172,12 @@
FontFamily="MauiSampleFontIcon"/>
-
+
diff --git a/maui/samples/Gallery/Samples/TabView/Customization/Customization.xaml b/maui/samples/Gallery/Samples/TabView/Customization/Customization.xaml
index 3df8cc5a..bb2ef610 100644
--- a/maui/samples/Gallery/Samples/TabView/Customization/Customization.xaml
+++ b/maui/samples/Gallery/Samples/TabView/Customization/Customization.xaml
@@ -37,7 +37,7 @@
-
+
@@ -88,7 +88,7 @@
-
+
@@ -117,7 +117,7 @@
-
+
@@ -153,7 +153,7 @@
-
+
@@ -179,7 +179,7 @@
-
+
diff --git a/maui/samples/Gallery/Samples/TabView/TabViewGettingStarted/TabViewGettingStarted.xaml b/maui/samples/Gallery/Samples/TabView/TabViewGettingStarted/TabViewGettingStarted.xaml
index 3bf6fad7..165ef49c 100644
--- a/maui/samples/Gallery/Samples/TabView/TabViewGettingStarted/TabViewGettingStarted.xaml
+++ b/maui/samples/Gallery/Samples/TabView/TabViewGettingStarted/TabViewGettingStarted.xaml
@@ -59,9 +59,9 @@
-
+
-
+
@@ -69,8 +69,8 @@
-
+
-
+
+ /// The time picker instance.
+ ///
+ readonly Syncfusion.Maui.Toolkit.Picker.SfTimePicker _alarmTimePicker;
+
+ ///
+ /// The alarm message entry instance.
+ ///
+ readonly Entry _alarmMessageEntry;
+
+ ///
+ /// The alarm text input instance.
+ ///
+ readonly SfTextInputLayout _alarmTextInput;
+
+ ///
+ /// The event handler for the created event.
+ ///
+ public event EventHandler? OnCreated;
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ public AlarmPopup()
+ {
+ _alarmTimePicker = new Syncfusion.Maui.Toolkit.Picker.SfTimePicker();
+ StackLayout stack = new StackLayout();
+ Label label = new Label();
+ label.Text = "Alarm Message";
+ label.Margin = new Thickness(10, 4);
+ label.FontSize = 12;
+ stack.Add(label);
+ _alarmTextInput = new SfTextInputLayout();
+ _alarmTextInput.Padding = new Thickness(0);
+ _alarmTextInput.Hint = "Alarm Message";
+ _alarmTextInput.HelperText = "Enter Alarm Message";
+ _alarmMessageEntry = new Entry();
+ _alarmMessageEntry.HeightRequest = 40;
+ _alarmMessageEntry.Text = string.Empty;
+ _alarmMessageEntry.Margin = new Thickness(5, 0);
+ _alarmTextInput.Content = _alarmMessageEntry;
+ stack.Add(_alarmTextInput);
+ Label label1 = new Label();
+ label1.Text = "Alarm Time";
+ label1.FontSize = 12;
+ label1.Margin = new Thickness(10, 5);
+ stack.Add(label1);
+ _alarmTimePicker.FooterView.Height = 40;
+ _alarmTimePicker.HeightRequest = 280;
+ _alarmTimePicker.Format = PickerTimeFormat.h_mm_tt;
+ _alarmTimePicker.OkButtonClicked += AlarmTimePicker_OkButtonClicked;
+ _alarmTimePicker.CancelButtonClicked += AlarmTimePicker_CancelButtonClicked;
+ stack.Add(_alarmTimePicker);
+ stack.VerticalOptions = LayoutOptions.Center;
+ ContentTemplate = new DataTemplate(() =>
+ {
+ return stack;
+ });
+
+ HeaderTemplate = new DataTemplate(() =>
+ {
+ return new Label() { Text = "Set Alarm", FontSize = 20, HeightRequest = 40, HorizontalOptions = LayoutOptions.Center, VerticalOptions = LayoutOptions.Center, HorizontalTextAlignment = TextAlignment.Center, VerticalTextAlignment = TextAlignment.Center };
+ });
+
+#if ANDROID || IOS || MACCATALYST
+ HeightRequest = 470;
+#else
+ HeightRequest = 450;
+#endif
+ WidthRequest = 300;
+ ShowFooter = false;
+ ShowHeader = true;
+ HeaderHeight = 40;
+ PopupStyle.CornerRadius = new CornerRadius(5);
+ }
+
+ ///
+ /// Invoked when the cancel button is clicked.
+ ///
+ /// The sender object.
+ /// The event args.
+ void AlarmTimePicker_CancelButtonClicked(object? sender, EventArgs e)
+ {
+ Reset();
+ IsOpen = false;
+ }
+
+ ///
+ /// Invoked when the ok button is clicked.
+ ///
+ /// The sender object.
+ /// The event args.
+ void AlarmTimePicker_OkButtonClicked(object? sender, EventArgs e)
+ {
+ if (_alarmTimePicker.SelectedTime != null)
+ {
+ OnCreated?.Invoke(new AlarmDetails() { AlarmTime = _alarmTimePicker.SelectedTime.Value, AlarmMessage = _alarmMessageEntry.Text == string.Empty ? "No Alarm Message" : _alarmMessageEntry.Text, IsAlarmEnabled = true }, new EventArgs());
+
+ }
+ IsOpen = false;
+ Reset();
+ }
+
+ ///
+ /// Method to reset the alarm popup.
+ ///
+ public void Reset()
+ {
+ _alarmTimePicker.SelectedTime = DateTime.Now.TimeOfDay;
+ _alarmMessageEntry.Text = string.Empty;
+ _alarmMessageEntry.Placeholder = "Enter Alarm Message";
+ }
+ }
+}
\ No newline at end of file
diff --git a/maui/samples/Gallery/Samples/TimePicker/Customization/AlarmTimer.cs b/maui/samples/Gallery/Samples/TimePicker/Customization/AlarmTimer.cs
new file mode 100644
index 00000000..a927ded2
--- /dev/null
+++ b/maui/samples/Gallery/Samples/TimePicker/Customization/AlarmTimer.cs
@@ -0,0 +1,48 @@
+using System.Globalization;
+
+namespace Syncfusion.Maui.ControlsGallery.Picker.SfTimePicker
+{
+ public class AlarmTimer : IValueConverter
+ {
+ ///
+ /// Method to convert the time span to string.
+ ///
+ /// The value.
+ /// The target type
+ /// The parameter.
+ /// The culture.
+ /// The alarm time.
+ public object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture)
+ {
+ if (value is TimeSpan timeSpan)
+ {
+ TimeSpan currentTime = DateTime.Now.TimeOfDay;
+ timeSpan = currentTime.Hours >= timeSpan.Hours ? timeSpan.Add(new TimeSpan(24, 0, 0)) : timeSpan;
+ var timeDifference = timeSpan.Subtract(currentTime);
+ if (timeDifference.Minutes == 0 && timeDifference.Hours == 0)
+ {
+ return $"Alarm in {timeDifference.Seconds} seconds";
+ }
+ else
+ {
+ return $"Alarm in {timeDifference.Hours} hours {timeDifference.Minutes} minutes";
+ }
+ }
+
+ return string.Empty;
+ }
+
+ ///
+ /// Method to convert the string to time span.
+ ///
+ /// The value.
+ /// The target type
+ /// The parameter.
+ /// The culture.
+ /// Empty string.
+ public object? ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture)
+ {
+ return string.Empty;
+ }
+ }
+}
\ No newline at end of file
diff --git a/maui/samples/Gallery/Samples/TimePicker/Customization/AlarmViewModel.cs b/maui/samples/Gallery/Samples/TimePicker/Customization/AlarmViewModel.cs
new file mode 100644
index 00000000..ef541a9d
--- /dev/null
+++ b/maui/samples/Gallery/Samples/TimePicker/Customization/AlarmViewModel.cs
@@ -0,0 +1,186 @@
+using System.Collections.ObjectModel;
+using System.ComponentModel;
+
+namespace Syncfusion.Maui.ControlsGallery.Picker.SfTimePicker
+{
+ public class AlarmDetails : INotifyPropertyChanged
+ {
+ ///
+ /// The alarm time.
+ ///
+ TimeSpan _alarmTime;
+
+ ///
+ /// The alarm message.
+ ///
+ string _alarmMessage = string.Empty;
+
+ ///
+ /// The is alarm enabled.
+ ///
+ bool _isAlarmEnabled = true;
+
+ ///
+ /// The alarm text color.
+ ///
+ Color _alarmTextColor = Colors.Black;
+
+ ///
+ /// The alarm secondary text color.
+ ///
+ Color _alarmSecondaryTextColor = Color.FromArgb("#49454F");
+
+ ///
+ /// The property changed event.
+ ///
+ public event PropertyChangedEventHandler? PropertyChanged;
+
+ ///
+ /// Gets or sets the alarm time.
+ ///
+ public TimeSpan AlarmTime
+ {
+ get
+ {
+ return _alarmTime;
+ }
+ set
+ {
+ _alarmTime = value;
+ RaisePropertyChanged("AlarmTime");
+ }
+ }
+
+ ///
+ /// Gets or sets the alarm message.
+ ///
+ public string AlarmMessage
+ {
+ get
+ {
+ return _alarmMessage;
+ }
+ set
+ {
+ _alarmMessage = value;
+ RaisePropertyChanged("AlarmMessage");
+ }
+ }
+
+ ///
+ /// Gets or sets a value indicating whether the alarm is enabled.
+ ///
+ public bool IsAlarmEnabled
+ {
+ get
+ {
+ return _isAlarmEnabled;
+ }
+ set
+ {
+ _isAlarmEnabled = value;
+ RaisePropertyChanged("IsAlarmEnabled");
+ }
+ }
+
+ ///
+ /// Gets or sets the alarm text color.
+ ///
+ public Color AlarmTextColor
+ {
+ get
+ {
+ return _alarmTextColor;
+ }
+ set
+ {
+ _alarmTextColor = value;
+ RaisePropertyChanged("AlarmTextColor");
+ }
+ }
+
+ ///
+ /// Gets or sets the alarm secondary text color.
+ ///
+ public Color AlarmSecondaryTextColor
+ {
+ get
+ {
+ return _alarmSecondaryTextColor;
+ }
+ set
+ {
+ _alarmSecondaryTextColor = value;
+ RaisePropertyChanged("AlarmSecondaryTextColor");
+ }
+ }
+
+ ///
+ /// Method to raise the property changed event.
+ ///
+ /// The property name.
+ void RaisePropertyChanged(string propertyName)
+ {
+ PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
+ }
+ }
+
+ ///
+ /// The view model class.
+ ///
+ public class ViewModel : INotifyPropertyChanged
+ {
+ ///
+ /// Check the application theme is light or dark.
+ ///
+ readonly bool _isLightTheme = Application.Current?.RequestedTheme == AppTheme.Light;
+
+ ///
+ /// The alarm collection.
+ ///
+ ObservableCollection _alarmCollection;
+
+ ///
+ /// The property changed event.
+ ///
+ public event PropertyChangedEventHandler? PropertyChanged;
+
+ ///
+ /// Gets or sets the alarm collection.
+ ///
+ public ObservableCollection AlarmCollection
+ {
+ get
+ {
+ return _alarmCollection;
+ }
+ set
+ {
+ _alarmCollection = value;
+ RaisePropertyChanged("AlarmCollection");
+ }
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ public ViewModel()
+ {
+ _alarmCollection = new ObservableCollection()
+ {
+ new AlarmDetails() { AlarmTime = new TimeSpan(4, 0, 0), AlarmMessage = "Wake Up", IsAlarmEnabled = true, AlarmTextColor = _isLightTheme ? Colors.Black : Colors.White, AlarmSecondaryTextColor= _isLightTheme ? Color.FromArgb("#49454F") : Color.FromArgb("#CAC4D0") },
+ new AlarmDetails() { AlarmTime = new TimeSpan(5, 0, 0), AlarmMessage = "Morning Workout", IsAlarmEnabled = true, AlarmTextColor = _isLightTheme ? Colors.Black : Colors.White, AlarmSecondaryTextColor= _isLightTheme ? Color.FromArgb("#49454F") : Color.FromArgb("#CAC4D0") },
+ new AlarmDetails() { AlarmTime = new TimeSpan(13, 0, 0), AlarmMessage = "No Alarm Message", IsAlarmEnabled = false, AlarmTextColor = _isLightTheme ? Colors.Black.WithAlpha(0.5f) : Colors.White.WithAlpha(0.5f), AlarmSecondaryTextColor = _isLightTheme ? Color.FromArgb("#49454F").WithAlpha(0.5f) : Color.FromArgb("#CAC4D0").WithAlpha(0.5f) },
+ };
+ }
+
+ ///
+ /// Method to raise the property changed event.
+ ///
+ /// The property name.
+ void RaisePropertyChanged(string propertyName)
+ {
+ PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
+ }
+ }
+}
\ No newline at end of file
diff --git a/maui/samples/Gallery/Samples/TimePicker/Customization/TimeSpanConverter.cs b/maui/samples/Gallery/Samples/TimePicker/Customization/TimeSpanConverter.cs
new file mode 100644
index 00000000..5834e9e9
--- /dev/null
+++ b/maui/samples/Gallery/Samples/TimePicker/Customization/TimeSpanConverter.cs
@@ -0,0 +1,62 @@
+using System.Globalization;
+
+namespace Syncfusion.Maui.ControlsGallery.Picker.SfTimePicker
+{
+ public class TimeSpanConverter : IValueConverter
+ {
+ ///
+ /// Method to convert the time span to AM/PM.
+ ///
+ /// The value.
+ /// The target type
+ /// The parameter.
+ /// The culture.
+ /// The AM/PM.
+ public object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture)
+ {
+ if (value is TimeSpan timeSpan)
+ {
+ TimeSpan twelveHrsTime = timeSpan.Hours > 12 || timeSpan.Hours == 0 ? timeSpan.Subtract(new TimeSpan(12, 0, 0)) : timeSpan;
+ if (timeSpan.Hours > 12)
+ {
+ twelveHrsTime = timeSpan.Subtract(new TimeSpan(12, 0, 0));
+ }
+ else if (timeSpan.Hours == 0)
+ {
+ twelveHrsTime = new TimeSpan(12, 0, 0);
+ }
+
+ if (parameter is Boolean parameterValue)
+ {
+ if (parameterValue)
+ {
+ return $"{twelveHrsTime.Hours}:{timeSpan.Minutes:D2}";
+ }
+ else
+ {
+ return $"{((timeSpan.Hours < 12) ? " AM" : " PM")}";
+ }
+ }
+ else
+ {
+ return $"{twelveHrsTime.Hours}:{timeSpan.Minutes:D2} {((timeSpan.Hours < 12) ? " AM" : " PM")}";
+ }
+ }
+
+ return string.Empty;
+ }
+
+ ///
+ /// Method to converts a value.
+ ///
+ /// The value.
+ /// The target type
+ /// The parameter.
+ /// The culture.
+ /// Empty string.
+ public object? ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture)
+ {
+ return string.Empty;
+ }
+ }
+}
\ No newline at end of file
diff --git a/maui/samples/Gallery/Samples/TimePicker/Customization/View/Customization.xaml b/maui/samples/Gallery/Samples/TimePicker/Customization/View/Customization.xaml
new file mode 100644
index 00000000..0a244e38
--- /dev/null
+++ b/maui/samples/Gallery/Samples/TimePicker/Customization/View/Customization.xaml
@@ -0,0 +1,152 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/maui/samples/Gallery/Samples/TimePicker/Customization/View/Customization.xaml.cs b/maui/samples/Gallery/Samples/TimePicker/Customization/View/Customization.xaml.cs
new file mode 100644
index 00000000..af5b708c
--- /dev/null
+++ b/maui/samples/Gallery/Samples/TimePicker/Customization/View/Customization.xaml.cs
@@ -0,0 +1,152 @@
+namespace Syncfusion.Maui.ControlsGallery.Picker.SfTimePicker
+{
+ public partial class Customization : SampleView
+ {
+ ///
+ /// Check the application theme is light or dark.
+ ///
+ readonly bool _isLightTheme = Application.Current?.RequestedTheme == AppTheme.Light;
+
+ ///
+ /// The alarm details.
+ ///
+ AlarmDetails? _alarmDetails;
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ public Customization()
+ {
+ InitializeComponent();
+#if ANDROID || IOS
+ alarmEditPicker1.HeaderView.Height = 40;
+ alarmEditPicker1.HeaderView.Text = "Edit Alarm";
+ alarmEditPicker1.FooterView.Height = 40;
+ alarmEditPicker1.FooterView.OkButtonText = "Save";
+#else
+ alarmEditPicker.HeaderView.Height = 40;
+ alarmEditPicker.HeaderView.Text = "Edit Alarm";
+ alarmEditPicker.FooterView.Height = 40;
+ alarmEditPicker.FooterView.OkButtonText = "Save";
+#endif
+ }
+
+ ///
+ /// Invoked when an alarm is tapped.
+ ///
+ /// The sender object.
+ /// The event args.
+ void OnAlarmTapped(object sender, EventArgs e)
+ {
+#if ANDROID || IOS
+ if (sender is Border border && border.BindingContext != null && border.BindingContext is AlarmDetails alarmDetails)
+ {
+ if (alarmDetails.IsAlarmEnabled)
+ {
+ alarmEditPicker1.SelectedTime = alarmDetails.AlarmTime;
+ _alarmDetails = alarmDetails;
+ alarmEditPicker1.IsOpen = true;
+ }
+ }
+#else
+ if (sender is Border border && border.BindingContext != null && border.BindingContext is AlarmDetails alarmDetails)
+ {
+ if (alarmDetails.IsAlarmEnabled)
+ {
+ alarmEditPicker.SelectedTime = alarmDetails.AlarmTime;
+ _alarmDetails = alarmDetails;
+ alarmEditPicker.IsOpen = true;
+ }
+ }
+#endif
+ }
+
+ ///
+ /// Invoked when the Ok button is clicked.
+ ///
+ /// The sender object.
+ /// The event args.
+ void AlarmEditPicker_OkButtonClicked(object? sender, EventArgs e)
+ {
+ if (sender is Syncfusion.Maui.Toolkit.Picker.SfTimePicker picker && _alarmDetails != null)
+ {
+ if (picker.SelectedTime != null && _alarmDetails.AlarmTime != picker.SelectedTime)
+ {
+ _alarmDetails.AlarmTime = picker.SelectedTime.Value;
+ }
+
+ _alarmDetails = null;
+ }
+
+#if ANDROID || IOS
+ alarmEditPicker1.IsOpen = false;
+#else
+ alarmEditPicker.IsOpen = false;
+#endif
+ }
+
+ ///
+ /// Invoked when the Cancel button is clicked.
+ ///
+ /// The sender object.
+ /// The event args.
+ void alarmEditPicker_CancelButtonClicked(object sender, EventArgs e)
+ {
+#if ANDROID || IOS
+ alarmEditPicker1.IsOpen = false;
+#else
+ alarmEditPicker.IsOpen = false;
+#endif
+ }
+
+ ///
+ /// Invoked when the alarm switch is toggled.
+ ///
+ /// The sender object.
+ /// The event args.
+ void alarmSwitch_Toggled(object sender, ToggledEventArgs e)
+ {
+ if (sender is Switch toggleSwitch && toggleSwitch.BindingContext != null && toggleSwitch.BindingContext is AlarmDetails alarmDetails)
+ {
+ if (e.Value)
+ {
+ alarmDetails.AlarmTextColor = _isLightTheme ? Colors.Black : Colors.White;
+ alarmDetails.AlarmSecondaryTextColor = _isLightTheme ? Color.FromArgb("#49454F") : Color.FromArgb("#CAC4D0");
+ }
+ else
+ {
+ alarmDetails.AlarmTextColor = _isLightTheme ? Colors.Black.WithAlpha(0.5f) : Colors.White.WithAlpha(0.5f);
+ alarmDetails.AlarmSecondaryTextColor = _isLightTheme ? Color.FromArgb("#49454F").WithAlpha(0.5f) : Color.FromArgb("#CAC4D0").WithAlpha(0.5f);
+ }
+ }
+
+ }
+
+ ///
+ /// Invoked when the Add Alarm button is clicked.
+ ///
+ /// The sender object.
+ /// The event args.
+ void OnAddAlarmTapped(object sender, EventArgs e)
+ {
+#if ANDROID || IOS
+ alarmPopup1.IsOpen = true;
+#else
+ alarmPopup.IsOpen = true;
+#endif
+ }
+
+ ///
+ /// Invoked when the alarm popup is created.
+ ///
+ /// The sender object.
+ /// The event args.
+ void alarmPopup_OnCreated(object sender, EventArgs e)
+ {
+ if (BindingContext != null && BindingContext is ViewModel bindingContext && sender is AlarmDetails details)
+ {
+ bindingContext.AlarmCollection.Add(details);
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/maui/samples/Gallery/Samples/TimePicker/GettingStarted/Behavior/GettingStartedBehavior.cs b/maui/samples/Gallery/Samples/TimePicker/GettingStarted/Behavior/GettingStartedBehavior.cs
new file mode 100644
index 00000000..5d0c2149
--- /dev/null
+++ b/maui/samples/Gallery/Samples/TimePicker/GettingStarted/Behavior/GettingStartedBehavior.cs
@@ -0,0 +1,425 @@
+using System.Collections.ObjectModel;
+using Syncfusion.Maui.Toolkit.Picker;
+
+namespace Syncfusion.Maui.ControlsGallery.Picker.SfTimePicker
+{
+ public class GettingStartedBehavior : Behavior
+ {
+ ///
+ /// Picker view
+ ///
+ Syncfusion.Maui.Toolkit.Picker.SfTimePicker? _timePicker;
+
+ ///
+ /// The show header switch
+ ///
+ Switch? _showHeaderSwitch, _showColumnHeaderSwitch, _showFooterSwitch, _clearSelectionSwitch;
+
+ ///
+ /// The date format combo box.
+ ///
+ Microsoft.Maui.Controls.Picker? _formatComboBox, _textDisplayComboBox;
+
+ ///
+ /// The time format to set the combo box item source.
+ ///
+ ObservableCollection? _formats;
+
+ ///
+ /// The text display to set the combo box item source.
+ ///
+ ObservableCollection? _textDisplay;
+
+ ///
+ /// The minimum value time picker.
+ ///
+ TimePicker? _minimumTimePicker;
+
+ ///
+ /// The maximum value time picker.
+ ///
+ TimePicker? _maximumTimePicker;
+
+ ///
+ /// Check the application theme is light or dark.
+ ///
+ readonly bool _isLightTheme = Application.Current?.RequestedTheme == AppTheme.Light;
+
+ ///
+ /// Get the previous selected time from time picker.
+ ///
+ TimeSpan? _previousTime;
+
+ ///
+ /// Begins when the behavior attached to the view
+ ///
+ /// bindable value
+ protected override void OnAttachedTo(SampleView bindable)
+ {
+ base.OnAttachedTo(bindable);
+
+#if IOS || MACCATALYST
+ Border border = bindable.Content.FindByName("border");
+ border.IsVisible = true;
+ border.Stroke = _isLightTheme ? Color.FromArgb("#CAC4D0") : Color.FromArgb("#49454F");
+ _timePicker = bindable.Content.FindByName("TimePicker1");
+#else
+ Border frame = bindable.Content.FindByName("frame");
+ frame.IsVisible = true;
+ frame.Stroke = _isLightTheme ? Color.FromArgb("#CAC4D0") : Color.FromArgb("#49454F");
+ _timePicker = bindable.Content.FindByName("TimePicker");
+#endif
+
+ _previousTime = _timePicker.SelectedTime;
+ _timePicker.HeaderView.Height = 50;
+ _timePicker.HeaderView.Text = "Select a Time";
+ _timePicker.BlackoutTimes = new ObservableCollection()
+ {
+ DateTime.Now.AddHours(1).AddMinutes(2).TimeOfDay,
+ DateTime.Now.AddHours(1).AddMinutes(4).TimeOfDay,
+ DateTime.Now.AddHours(1).AddMinutes(5).TimeOfDay,
+ DateTime.Now.AddHours(1).AddMinutes(-1).TimeOfDay,
+ DateTime.Now.AddHours(1).AddMinutes(-3).TimeOfDay,
+ DateTime.Now.AddHours(1).AddMinutes(-9).TimeOfDay,
+ DateTime.Now.AddHours(1).AddMinutes(1).TimeOfDay,
+ DateTime.Now.AddHours(-1).AddMinutes(2).TimeOfDay,
+ DateTime.Now.AddHours(-1).AddMinutes(6).TimeOfDay,
+ DateTime.Now.AddHours(-1).AddMinutes(-2).TimeOfDay,
+ DateTime.Now.AddHours(-1).AddMinutes(-9).TimeOfDay,
+ DateTime.Now.AddHours(-1).AddMinutes(-7).TimeOfDay,
+ DateTime.Now.AddMinutes(1).TimeOfDay,
+ DateTime.Now.AddMinutes(2).TimeOfDay,
+ DateTime.Now.AddMinutes(4).TimeOfDay,
+ DateTime.Now.AddMinutes(-8).TimeOfDay,
+ DateTime.Now.AddMinutes(-4).TimeOfDay,
+ DateTime.Now.AddMinutes(-1).TimeOfDay,
+ };
+
+ _timePicker.SelectionChanged += TimePicker_SelectionChanged;
+ _showHeaderSwitch = bindable.Content.FindByName("showHeaderSwitch");
+ _showColumnHeaderSwitch = bindable.Content.FindByName("showColumnHeaderSwitch");
+ _showFooterSwitch = bindable.Content.FindByName("showFooterSwitch");
+ _clearSelectionSwitch = bindable.Content.FindByName("clearSelectionSwitch");
+ _minimumTimePicker = bindable.Content.FindByName("minimumTime");
+ _maximumTimePicker = bindable.Content.FindByName("maximumTime");
+ _timePicker.SelectedTextStyle.TextColor = _isLightTheme ? Color.FromArgb("#FFFFFF") : Color.FromArgb("#381E72");
+
+ if (_showHeaderSwitch != null)
+ {
+ _showHeaderSwitch.Toggled += ShowHeaderSwitch_Toggled;
+ }
+
+ if (_showColumnHeaderSwitch != null)
+ {
+ _showColumnHeaderSwitch.Toggled += ShowColumnHeaderSwitch_Toggled;
+ }
+
+ if (_showFooterSwitch != null)
+ {
+ _showFooterSwitch.Toggled += ShowFooterSwitch_Toggled;
+ }
+
+ if (_clearSelectionSwitch != null)
+ {
+ _clearSelectionSwitch.Toggled += ClearSelectionSwitch_Toggled;
+ }
+
+ if (_minimumTimePicker != null)
+ {
+ _minimumTimePicker.PropertyChanged += MinimumTimePicker_PropertyChanged;
+ }
+
+ if (_maximumTimePicker != null)
+ {
+ _maximumTimePicker.PropertyChanged += MaximumTimePicker_PropertyChanged;
+ }
+
+ _formats = new ObservableCollection()
+ {
+ "H mm", "H mm ss", "h mm ss tt", "h mm tt", "HH mm", "HH mm ss", "hh mm ss tt", "hh mm tt", "hh tt", "Default"
+ };
+
+ _formatComboBox = bindable.Content.FindByName("formatComboBox");
+ _formatComboBox.ItemsSource = _formats;
+ _formatComboBox.SelectedIndex = 1;
+ _formatComboBox.SelectedIndexChanged += FormatComboBox_SelectionChanged;
+ ;
+
+ _textDisplay = new ObservableCollection()
+ {
+ "Default", "Fade", "Shrink", "FadeAndShrink"
+ };
+
+ _textDisplayComboBox = bindable.Content.FindByName("textDisplayComboBox");
+ _textDisplayComboBox.ItemsSource = _textDisplay;
+ _textDisplayComboBox.SelectedIndex = 0;
+ _textDisplayComboBox.SelectedIndexChanged += TextdisplayComboBox_SelectionChanged;
+ }
+
+ ///
+ /// Method for handle the maximum time picker property changed.
+ ///
+ /// The sender object.
+ /// The event args.
+ void MaximumTimePicker_PropertyChanged(object? sender, System.ComponentModel.PropertyChangedEventArgs e)
+ {
+ if (_timePicker != null && _maximumTimePicker != null && e.PropertyName == "Time")
+ {
+ _timePicker.MaximumTime = _maximumTimePicker.Time;
+ }
+ }
+
+ ///
+ /// Method for handle the minimum time picker property changed.
+ ///
+ /// The sender object.
+ /// The event args.
+ void MinimumTimePicker_PropertyChanged(object? sender, System.ComponentModel.PropertyChangedEventArgs e)
+ {
+ if (_timePicker != null && _minimumTimePicker != null && e.PropertyName == "Time")
+ {
+ _timePicker.MinimumTime = _minimumTimePicker.Time;
+ }
+ }
+
+ ///
+ /// Method for handle the selection changed event of time picker.
+ ///
+ /// The sender object.
+ /// The event args.
+ void TimePicker_SelectionChanged(object? sender, TimePickerSelectionChangedEventArgs e)
+ {
+ if (_clearSelectionSwitch != null && e.NewValue != null)
+ {
+ _previousTime = e.NewValue.Value;
+ _clearSelectionSwitch.IsToggled = false;
+ }
+ }
+
+ ///
+ /// Method for show header switch toggle changed.
+ ///
+ /// Return the object.
+ /// The Event Arguments.
+ void ShowHeaderSwitch_Toggled(object? sender, ToggledEventArgs e)
+ {
+ if (_timePicker != null)
+ {
+ if (e.Value == true)
+ {
+ _timePicker.HeaderView.Height = 50;
+ _timePicker.HeaderView.Text = "Select a time";
+ }
+ else if (e.Value == false)
+ {
+ _timePicker.HeaderView.Height = 0;
+ }
+ }
+ }
+
+ ///
+ /// Method for show column header switch toggle changed.
+ ///
+ /// Return the object.
+ /// The Event Arguments.
+ void ShowColumnHeaderSwitch_Toggled(object? sender, ToggledEventArgs e)
+ {
+ if (_timePicker != null)
+ {
+ if (e.Value == true)
+ {
+ _timePicker.ColumnHeaderView = new TimePickerColumnHeaderView()
+ {
+ Height = 40,
+ };
+ }
+ if (e.Value == false)
+ {
+ _timePicker.ColumnHeaderView = new TimePickerColumnHeaderView()
+ {
+ Height = 0,
+ };
+ }
+ }
+ }
+
+ ///
+ /// Method for show footer switch toggle changed.
+ ///
+ /// Return the object.
+ /// The Event Arguments.
+ void ShowFooterSwitch_Toggled(object? sender, ToggledEventArgs e)
+ {
+ if (_timePicker != null)
+ {
+ if (e.Value == true)
+ {
+ _timePicker.FooterView.Height = 40;
+ }
+ else if (e.Value == false)
+ {
+ _timePicker.FooterView.Height = 0;
+ }
+ }
+ }
+
+ ///
+ /// Method for clear selection switch toggle changed.
+ ///
+ /// Return the object.
+ /// The Event Arguments.
+ void ClearSelectionSwitch_Toggled(object? sender, ToggledEventArgs e)
+ {
+ if (_timePicker != null)
+ {
+ if (e.Value == true)
+ {
+ _timePicker.SelectedTime = null;
+ }
+ else if (e.Value == false)
+ {
+ _timePicker.SelectedTime = _previousTime;
+ }
+ }
+ }
+
+ ///
+ /// The format combo box selection changed event.
+ ///
+ /// Return the object.
+ /// The Event Arguments.
+ void FormatComboBox_SelectionChanged(object? sender, EventArgs e)
+ {
+ if (_timePicker == null || _formatComboBox == null)
+ {
+ return;
+ }
+
+ if (sender is Microsoft.Maui.Controls.Picker picker)
+ {
+ string? format = picker.SelectedItem.ToString();
+ switch (format)
+ {
+ case "H mm":
+ _timePicker.Format = PickerTimeFormat.H_mm;
+ break;
+
+ case "H mm ss":
+ _timePicker.Format = PickerTimeFormat.H_mm_ss;
+ break;
+
+ case "h mm ss tt":
+ _timePicker.Format = PickerTimeFormat.h_mm_ss_tt;
+ break;
+
+ case "h mm tt":
+ _timePicker.Format = PickerTimeFormat.h_mm_tt;
+ break;
+
+ case "HH mm":
+ _timePicker.Format = PickerTimeFormat.HH_mm;
+ break;
+
+ case "HH mm ss":
+ _timePicker.Format = PickerTimeFormat.HH_mm_ss;
+ break;
+
+ case "hh mm ss tt":
+ _timePicker.Format = PickerTimeFormat.hh_mm_ss_tt;
+ break;
+
+ case "hh mm tt":
+ _timePicker.Format = PickerTimeFormat.hh_mm_tt;
+ break;
+
+ case "hh tt":
+ _timePicker.Format = PickerTimeFormat.hh_tt;
+ break;
+ case "Default":
+ _timePicker.Format = PickerTimeFormat.Default;
+ break;
+ }
+ }
+ }
+
+ ///
+ /// The text display combo box selection changed event.
+ ///
+ /// Return the object.
+ /// The Event Arguments.
+ void TextdisplayComboBox_SelectionChanged(object? sender, EventArgs e)
+ {
+ if (_timePicker == null || _textDisplayComboBox == null)
+ {
+ return;
+ }
+
+ if (sender is Microsoft.Maui.Controls.Picker picker)
+ {
+ string? format = picker.SelectedItem.ToString();
+ switch (format)
+ {
+ case "Default":
+ _timePicker.TextDisplayMode = PickerTextDisplayMode.Default;
+ break;
+
+ case "Fade":
+ _timePicker.TextDisplayMode = PickerTextDisplayMode.Fade;
+ break;
+
+ case "Shrink":
+ _timePicker.TextDisplayMode = PickerTextDisplayMode.Shrink;
+ break;
+
+ case "FadeAndShrink":
+ _timePicker.TextDisplayMode = PickerTextDisplayMode.FadeAndShrink;
+ break;
+ }
+ }
+ }
+
+ ///
+ /// Begins when the behavior attached to the view
+ ///
+ /// bindable value
+ protected override void OnDetachingFrom(SampleView bindable)
+ {
+ base.OnDetachingFrom(bindable);
+ if (_showHeaderSwitch != null)
+ {
+ _showHeaderSwitch.Toggled -= ShowHeaderSwitch_Toggled;
+ _showHeaderSwitch = null;
+ }
+
+ if (_showColumnHeaderSwitch != null)
+ {
+ _showColumnHeaderSwitch.Toggled -= ShowColumnHeaderSwitch_Toggled;
+ _showColumnHeaderSwitch = null;
+ }
+
+ if (_showFooterSwitch != null)
+ {
+ _showFooterSwitch.Toggled -= ShowFooterSwitch_Toggled;
+ _showFooterSwitch = null;
+ }
+
+ if (_clearSelectionSwitch != null)
+ {
+ _clearSelectionSwitch.Toggled -= ClearSelectionSwitch_Toggled;
+ _clearSelectionSwitch = null;
+ }
+
+ if (_minimumTimePicker != null)
+ {
+ _minimumTimePicker.PropertyChanged -= MinimumTimePicker_PropertyChanged;
+ _minimumTimePicker = null;
+ }
+
+ if (_maximumTimePicker != null)
+ {
+ _maximumTimePicker.PropertyChanged -= MaximumTimePicker_PropertyChanged;
+ _maximumTimePicker = null;
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/maui/samples/Gallery/Samples/TimePicker/GettingStarted/View/GettingStarted.xaml b/maui/samples/Gallery/Samples/TimePicker/GettingStarted/View/GettingStarted.xaml
new file mode 100644
index 00000000..01b74d3f
--- /dev/null
+++ b/maui/samples/Gallery/Samples/TimePicker/GettingStarted/View/GettingStarted.xaml
@@ -0,0 +1,171 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Grey
+
+ White
+
+
+
+
+
diff --git a/maui/samples/Gallery/Samples/TimePicker/GettingStarted/View/GettingStarted.xaml.cs b/maui/samples/Gallery/Samples/TimePicker/GettingStarted/View/GettingStarted.xaml.cs
new file mode 100644
index 00000000..89ab8836
--- /dev/null
+++ b/maui/samples/Gallery/Samples/TimePicker/GettingStarted/View/GettingStarted.xaml.cs
@@ -0,0 +1,15 @@
+namespace Syncfusion.Maui.ControlsGallery.Picker.SfTimePicker;
+
+///
+/// The Getting started sample.
+///
+public partial class GettingStarted : SampleView
+{
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ public GettingStarted()
+ {
+ InitializeComponent();
+ }
+}
\ No newline at end of file
diff --git a/maui/samples/Gallery/Syncfusion.Maui.ControlsGallery.csproj b/maui/samples/Gallery/Syncfusion.Maui.ControlsGallery.csproj
index 536f84e1..b8601bdc 100644
--- a/maui/samples/Gallery/Syncfusion.Maui.ControlsGallery.csproj
+++ b/maui/samples/Gallery/Syncfusion.Maui.ControlsGallery.csproj
@@ -120,6 +120,7 @@
+
@@ -129,6 +130,7 @@
+
diff --git a/maui/samples/Gallery/View/MainPageAndroid.xaml b/maui/samples/Gallery/View/MainPageAndroid.xaml
index 13ee410f..3b7f96f4 100644
--- a/maui/samples/Gallery/View/MainPageAndroid.xaml
+++ b/maui/samples/Gallery/View/MainPageAndroid.xaml
@@ -15,7 +15,9 @@
-
+
+
+
@@ -27,7 +29,7 @@
-
+
@@ -50,13 +52,13 @@
-
+
-
+
@@ -75,7 +77,29 @@
LineBreakMode="WordWrap"/>
-
+
+
+
+
+
+
+
+
+
@@ -264,7 +288,7 @@
-
+
-
+
@@ -317,9 +341,9 @@
-
+
-
+
@@ -327,9 +351,9 @@
-
+
-
+
@@ -364,9 +388,9 @@
-
+
-
+
@@ -395,9 +419,9 @@
-
+
-
+
@@ -447,30 +471,57 @@
-
+
-
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
+
@@ -482,10 +533,10 @@
-
-
+
+
-
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -664,7 +745,7 @@
-
-
-
-
+
+
@@ -28,7 +29,7 @@
-
-
+
@@ -184,7 +185,7 @@
-
+
@@ -206,7 +207,7 @@
-
+
@@ -241,7 +242,7 @@
-
+
@@ -250,9 +251,9 @@
-
+
-
+
@@ -263,7 +264,7 @@
-
+
@@ -285,7 +286,7 @@
-
+
@@ -312,7 +313,7 @@
-
+
@@ -350,7 +351,7 @@
-
+
@@ -386,9 +387,9 @@
-
-
-
+
+
+
@@ -430,7 +431,7 @@
-
+
@@ -448,13 +449,13 @@
-
+
-
+
@@ -477,6 +478,27 @@
LineBreakMode="CharacterWrap"/>
+
+
+
+
+
+
+
@@ -492,7 +514,7 @@
-
+
@@ -510,13 +532,13 @@
-
+
-
+
@@ -533,7 +555,30 @@
LineBreakMode="CharacterWrap"/>
-
+
+
+
+
+
+
+
+
+
@@ -548,7 +593,7 @@
-
+
@@ -563,16 +608,16 @@
-
+
-
+
-
+
@@ -588,8 +633,28 @@
MaxLines="3"
LineBreakMode="CharacterWrap"/>
-
-
+
+
+
+
+
+
+
+
@@ -624,10 +689,10 @@
-
+
-
+
@@ -695,7 +760,7 @@
Converter={StaticResource BoolToPlatformBoolConverter},ConverterParameter=VerticalStackLayout}"
Margin="0,0,0,5">
-
+
@@ -768,7 +833,7 @@
-
+
@@ -844,10 +909,10 @@
-
+
-
+
@@ -923,7 +988,7 @@
-
+
@@ -1133,14 +1198,14 @@
-
+
-
+
diff --git a/maui/samples/Gallery/View/MainPageWindows.xaml b/maui/samples/Gallery/View/MainPageWindows.xaml
index 83a61b93..b9c596d6 100644
--- a/maui/samples/Gallery/View/MainPageWindows.xaml
+++ b/maui/samples/Gallery/View/MainPageWindows.xaml
@@ -15,7 +15,9 @@
-
+
+
+
@@ -27,7 +29,7 @@
-
-
+
@@ -182,7 +184,7 @@
-
+
@@ -203,7 +205,7 @@
-
+
@@ -239,7 +241,7 @@
-
+
@@ -248,9 +250,9 @@
-
+
-
+
@@ -261,7 +263,7 @@
-
+
@@ -283,7 +285,7 @@
-
+
@@ -310,7 +312,7 @@
-
+
@@ -348,7 +350,7 @@
-
+
@@ -384,9 +386,9 @@
-
+
-
+
@@ -434,7 +436,7 @@
-
+
@@ -454,13 +456,13 @@
-
+
-
-
+
+
@@ -483,7 +485,28 @@
LineBreakMode="CharacterWrap"/>
-
+
+
+
+
+
+
+
+
+
@@ -499,7 +522,7 @@
-
+
@@ -519,13 +542,14 @@
-
+
-
+
+
@@ -542,7 +566,29 @@
LineBreakMode="CharacterWrap"/>
-
+
+
+
+
+
+
+
+
@@ -558,7 +604,7 @@
-
+
@@ -575,34 +621,56 @@
-
+
-
+
-
+
-
+
-
-
-
-
-
-
-
+
+
+
+
+
+
-
-
-
+
+
+
+
+
+
+
+
+
+
@@ -640,10 +708,10 @@
-
+
-
+
@@ -713,7 +781,7 @@
Converter={StaticResource BoolToPlatformBoolConverter},ConverterParameter=VerticalStackLayout}"
Margin="0,0,0,5">
-
+
@@ -786,7 +854,7 @@
-
+
@@ -862,10 +930,10 @@
-
+
-
+
@@ -934,7 +1002,7 @@
-
+
@@ -1134,14 +1202,14 @@
-
+
-
+
diff --git a/maui/samples/Gallery/View/MainPageiOS.xaml b/maui/samples/Gallery/View/MainPageiOS.xaml
index 0ea13be2..205fb9a3 100644
--- a/maui/samples/Gallery/View/MainPageiOS.xaml
+++ b/maui/samples/Gallery/View/MainPageiOS.xaml
@@ -17,7 +17,9 @@
-
+
+
+
@@ -28,7 +30,7 @@
-
+
@@ -45,13 +47,13 @@
-
+
-
+
@@ -68,7 +70,28 @@
LineBreakMode="WordWrap"/>
-
+
+
+
+
+
+
+
+
@@ -217,12 +240,13 @@
-
+
@@ -258,19 +282,19 @@
-
-
+
+
-
+
-
+
-
+
@@ -296,10 +320,10 @@
-
-
+
+
-
+
@@ -323,10 +347,10 @@
-
-
+
+
-
+
@@ -369,37 +393,54 @@
-
+
-
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -407,9 +448,9 @@
-
+
-
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -579,7 +646,7 @@
-
-
- Property old value.
private void RaisePropertyChanged(string propertyName, object? oldValue = null)
{
- this.CalendarPropertyChanged?.Invoke(this, new CalendarPropertyChangedEventArgs(propertyName) { OldValue = oldValue });
+ CalendarPropertyChanged?.Invoke(this, new CalendarPropertyChangedEventArgs(propertyName) { OldValue = oldValue });
}
///
diff --git a/maui/src/Calendar/SfCalendar.cs b/maui/src/Calendar/SfCalendar.cs
index bc04f463..1c182d78 100644
--- a/maui/src/Calendar/SfCalendar.cs
+++ b/maui/src/Calendar/SfCalendar.cs
@@ -123,11 +123,17 @@ public SfCalendar()
{
SfCalendarResources.InitializeDefaultResource("Syncfusion.Maui.Toolkit.Calendar.Resources.SfCalendar", typeof(SfCalendar));
ThemeElement.InitializeThemeResources(this, "SfCalendarTheme");
- _proxy = new(this);
+#if IOS
+ IgnoreSafeArea = true;
+#endif
+ _proxy = new(this);
DrawingOrder = DrawingOrder.AboveContent;
BackgroundColor = CalendarBackground;
_layout = new CalendarVerticalStackLayout(HeaderView.Height, FooterView.ShowActionButtons || FooterView.ShowTodayButton, FooterView.Height);
- Add(_layout);
+#if IOS
+ _layout.IgnoreSafeArea = true;
+#endif
+ Add(_layout);
_displayDate = DisplayDate;
_selectedDates = new ObservableCollection();
_selectedDateRanges = new ObservableCollection();
@@ -2237,7 +2243,7 @@ protected override Size MeasureContent(double widthConstraint, double heightCons
/// The dirty rectangle.
protected override void OnDraw(ICanvas canvas, RectF dirtyRect)
{
- if (this.CornerRadius == 0)
+ if (CornerRadius == 0)
{
return;
}
diff --git a/maui/src/Calendar/SnapLayout/SnapLayoutHandler.Android.cs b/maui/src/Calendar/SnapLayout/SnapLayoutHandler.Android.cs
index ac222609..01f4d3c3 100644
--- a/maui/src/Calendar/SnapLayout/SnapLayoutHandler.Android.cs
+++ b/maui/src/Calendar/SnapLayout/SnapLayoutHandler.Android.cs
@@ -23,7 +23,7 @@ protected override LayoutViewGroup CreatePlatformView()
}
NativeSnapLayout viewGroup = new NativeSnapLayout(Context!);
- SnapLayout? scrollLayout = this.VirtualView as SnapLayout;
+ SnapLayout? scrollLayout = VirtualView as SnapLayout;
if (scrollLayout != null)
{
//// Set the native intercept event property by Maui intercept method because in native we did not decide the whether the child scroll view reaches it end and try to scroll after the end so that we call the Maui method for intercept event.
diff --git a/maui/src/Calendar/Views/CalendarView/MonthView/MonthViewLayout.cs b/maui/src/Calendar/Views/CalendarView/MonthView/MonthViewLayout.cs
index 711c42f8..d7f4a7df 100644
--- a/maui/src/Calendar/Views/CalendarView/MonthView/MonthViewLayout.cs
+++ b/maui/src/Calendar/Views/CalendarView/MonthView/MonthViewLayout.cs
@@ -50,7 +50,10 @@ internal MonthViewLayout(ICalendar calendarView, List visibleDates, Da
_monthView = new MonthView(_calendarViewInfo, visibleDates, selectedDate, disabledDates, specialDates, isCurrentView);
Add(_monthView);
AddOrRemoveViewHeader(isCurrentView);
- }
+#if IOS
+ IgnoreSafeArea = true;
+#endif
+ }
#endregion
diff --git a/maui/src/Calendar/Views/FooterView/FooterLayout.cs b/maui/src/Calendar/Views/FooterView/FooterLayout.cs
index 6cf78a52..30115557 100644
--- a/maui/src/Calendar/Views/FooterView/FooterLayout.cs
+++ b/maui/src/Calendar/Views/FooterView/FooterLayout.cs
@@ -99,7 +99,10 @@ internal FooterLayout(IFooterView footerViewInfo)
Background = _footerViewInfo.FooterView.Background;
BackgroundColor = _footerViewInfo.FooterView.Background.ToColor();
AddOrRemoveFooterButtons();
- }
+#if IOS
+ IgnoreSafeArea = true;
+#endif
+ }
#endregion
diff --git a/maui/src/Calendar/Views/HeaderView/HeaderLayout.cs b/maui/src/Calendar/Views/HeaderView/HeaderLayout.cs
index 4d682dc0..b0ae1947 100644
--- a/maui/src/Calendar/Views/HeaderView/HeaderLayout.cs
+++ b/maui/src/Calendar/Views/HeaderView/HeaderLayout.cs
@@ -112,7 +112,10 @@ internal HeaderLayout(IHeader headerInfo)
Add(_headerTextLabel);
AddOrRemoveNavigationArrows();
}
- }
+#if IOS
+ IgnoreSafeArea = true;
+#endif
+ }
#endregion
diff --git a/maui/src/Cards/SfCardLayout.cs b/maui/src/Cards/SfCardLayout.cs
index 11a274a8..321feb39 100644
--- a/maui/src/Cards/SfCardLayout.cs
+++ b/maui/src/Cards/SfCardLayout.cs
@@ -144,7 +144,10 @@ public SfCardLayout()
Padding = new Thickness(10, 5);
IsClippedToBounds = true;
this.AddGestureListener(this);
- }
+#if IOS
+ IgnoreSafeArea = true;
+#endif
+ }
#endregion
diff --git a/maui/src/Carousel/Handlers/CarouselHandler.Windows.cs b/maui/src/Carousel/Handlers/CarouselHandler.Windows.cs
index 3171449d..a1b5e26d 100644
--- a/maui/src/Carousel/Handlers/CarouselHandler.Windows.cs
+++ b/maui/src/Carousel/Handlers/CarouselHandler.Windows.cs
@@ -235,13 +235,8 @@ private static void MapItemWidth(CarouselHandler handler, ICarousel virtualView)
private static void MapItemHeight(CarouselHandler handler, ICarousel virtualView)
{
handler.PlatformView.ItemHeight = virtualView.ItemHeight;
-#pragma warning disable IDE0031
- SfCarouselLinearPanel? linerPanel = handler.PlatformView.CarouselLinearPanel;
- if (linerPanel != null)
- {
- linerPanel.InvalidateMeasure();
- }
-#pragma warning restore IDE0031
+ SfCarouselLinearPanel? linearPanel = handler.PlatformView.CarouselLinearPanel;
+ linearPanel?.InvalidateMeasure();
}
///
diff --git a/maui/src/Carousel/Platform/Android/PlatformCarousel.Android.cs b/maui/src/Carousel/Platform/Android/PlatformCarousel.Android.cs
index cf8ef456..d1a0d634 100644
--- a/maui/src/Carousel/Platform/Android/PlatformCarousel.Android.cs
+++ b/maui/src/Carousel/Platform/Android/PlatformCarousel.Android.cs
@@ -369,12 +369,10 @@ void Initialize()
{
ClipToOutline = true;
-#pragma warning disable CA1422
-#pragma warning disable CS0618
+#if !ANDROID22_0_OR_GREATER
AnimationCacheEnabled = true;
DrawingCacheEnabled = true;
-#pragma warning restore CS0618
-#pragma warning restore CA1422
+#endif
LayoutParameters = new LayoutParams(ViewGroup.LayoutParams.MatchParent, ViewGroup.LayoutParams.MatchParent);
}
@@ -1973,7 +1971,7 @@ void Selection(bool canStopEvent)
// Preventing the event from being triggered multiple times
if (!canStopEvent)
{
- RefreshSelection(_selectedIndex);
+ RefreshSelection();
}
_prevIndex = _selectedIndex;
}
@@ -2236,10 +2234,7 @@ void FinalizeItemAddition()
///
/// Refresh the selection.
///
- /// Selected index.
-#pragma warning disable IDE0060 // Remove unused parameter
- void RefreshSelection(int selectedIndexArgs)
-#pragma warning restore IDE0060 // Remove unused parameter
+ void RefreshSelection()
{
if (SelectionChangedEventArgs != null)
{
diff --git a/maui/src/Carousel/Platform/Android/PlatformCarouselItem.Android.cs b/maui/src/Carousel/Platform/Android/PlatformCarouselItem.Android.cs
index 44c54ccb..325b3bea 100644
--- a/maui/src/Carousel/Platform/Android/PlatformCarouselItem.Android.cs
+++ b/maui/src/Carousel/Platform/Android/PlatformCarouselItem.Android.cs
@@ -207,9 +207,9 @@ internal bool IsItemSelected
options.InSampleSize = 8;
options.InJustDecodeBounds = false;
options.InScaled = false;
-#pragma warning disable CA1422
+#if !ANDROID24_0_OR_GREATER
options.InDither = false;
-#pragma warning restore CA1422
+#endif
options.InPreferredConfig = Bitmap.Config.Rgb565;
BitmapFactory.DecodeResource(res, resId, options);
options.InSampleSize = CalculateInSampleSize(options, reqWidth, reqHeight);
@@ -373,24 +373,23 @@ void AddImageViewToParent(ImageView imageView, string imageName)
{
return;
}
+ if (_contextCarouselItem != null && Resources != null)
+ {
+ int resID = Resources.GetIdentifier(imageName, "drawable", _contextCarouselItem.PackageName);
+ PlatformCarouselItem.RemoveViewFromParent(imageView);
-#pragma warning disable CS8602
- int resID = Resources.GetIdentifier(imageName, "drawable", _contextCarouselItem.PackageName);
-#pragma warning restore CS8602
-
- PlatformCarouselItem.RemoveViewFromParent(imageView);
+ Bitmap? originalBitmap = DecodeSampledBitmapFromResource(Resources, resID, ParentItem.ItemWidth, ParentItem.ItemHeight);
+ if (originalBitmap != null)
+ {
+ PlatformCarouselItem.SetImageBitmap(imageView, originalBitmap);
+ originalBitmap.Recycle();
+ originalBitmap = null;
+ }
- Bitmap? originalBitmap = DecodeSampledBitmapFromResource(Resources, resID, ParentItem.ItemWidth, ParentItem.ItemHeight);
- if (originalBitmap != null)
- {
- PlatformCarouselItem.SetImageBitmap(imageView, originalBitmap);
- originalBitmap.Recycle();
- originalBitmap = null;
+ _childView = imageView;
+ _childView.LayoutParameters = new LayoutParams(ParentItem.ItemWidth, ParentItem.ItemHeight);
+ AddView(_childView);
}
-
- _childView = imageView;
- _childView.LayoutParameters = new LayoutParams(ParentItem.ItemWidth, ParentItem.ItemHeight);
- AddView(_childView);
}
///
diff --git a/maui/src/Carousel/Platform/Windows/PlatformCarousel.Windows.cs b/maui/src/Carousel/Platform/Windows/PlatformCarousel.Windows.cs
index 342e29c1..bb68608d 100644
--- a/maui/src/Carousel/Platform/Windows/PlatformCarousel.Windows.cs
+++ b/maui/src/Carousel/Platform/Windows/PlatformCarousel.Windows.cs
@@ -706,20 +706,17 @@ void MoveNextVirtualized(bool isForward, SelectionChangedEventArgs args)
{
int newIndex = SelectedIndex + (isForward ? 1 : -1);
var newItem = _virtualItemList[newIndex] as PlatformCarouselItem;
- HandleSelectionChange(tempCollection, args, newIndex, newItem);
+ HandleSelectionChange(args, newIndex, newItem);
}
}
///
/// Handle selection change
///
- ///
///
///
///
-#pragma warning disable IDE0060 // Remove unused parameter
- void HandleSelectionChange(IList tempCollection, SelectionChangedEventArgs args, int newIndex, PlatformCarouselItem? newItem)
-#pragma warning restore IDE0060 // Remove unused parameter
+ void HandleSelectionChange(SelectionChangedEventArgs args, int newIndex, PlatformCarouselItem? newItem)
{
var containerItem = ContainerFromIndex(newIndex) as PlatformCarouselItem;
if (containerItem == null || (containerItem != null && containerItem != newItem))
@@ -831,7 +828,7 @@ public void MovePrevious()
{
int newIndex = SelectedIndex + (isBackward ? 1 : -1);
var newItem = _virtualItemList[newIndex] as PlatformCarouselItem;
- HandleSelectionChange(tempCollection, args, newIndex, newItem);
+ HandleSelectionChange(args, newIndex, newItem);
}
}
}
@@ -2228,7 +2225,7 @@ protected override Size MeasureOverride(Size availableSize)
private static void OnLoadMoreViewChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
PlatformCarousel carousel = (PlatformCarousel)d;
- carousel.OnLoadMoreViewChanged(e);
+ carousel.OnLoadMoreViewChanged();
}
///
@@ -2327,7 +2324,7 @@ private static void OnItemsSourceChanged(DependencyObject d, DependencyPropertyC
private static void OnAllowLoadMoreChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
PlatformCarousel? control = d as PlatformCarousel;
- control?.OnAllowLoadMoreChanged(e);
+ control?.OnAllowLoadMoreChanged();
}
///
@@ -2671,10 +2668,7 @@ private void SfCarousel_Unloaded(object sender, RoutedEventArgs e)
///
/// method for load more view
///
- /// instance contains event data
-#pragma warning disable IDE0060 // Remove unused parameter
- private void OnLoadMoreViewChanged(DependencyPropertyChangedEventArgs e)
-#pragma warning restore IDE0060 // Remove unused parameter
+ private void OnLoadMoreViewChanged()
{
if (LoadMoreItemsCount > 0 && AllowLoadMore)
{
@@ -2796,7 +2790,7 @@ private void OnItemsSourceChanged()
var tempCollection = _tempCollection as IList;
for (int i = 0; i < tempCollection?.Count; i++)
{
- var virtualItem = GetVirtualizedItem(tempCollection[i], i);
+ var virtualItem = GetVirtualizedItem(i);
if (virtualItem != null)
{
_virtualItemList.Add(virtualItem);
@@ -2835,7 +2829,7 @@ void SetQueue()
View? contentView = null;
if (_virtualView != null && _virtualView.ItemTemplate != null)
{
- contentView = GetItemView(_virtualView, this, j);
+ contentView = GetItemView(_virtualView, j);
j++;
}
if (contentView != null && _carouselHandler != null && _virtualView != null)
@@ -2848,10 +2842,7 @@ void SetQueue()
///
/// On Allow Load More Changed method
///
- /// instance containing the event data.
-#pragma warning disable IDE0060 // Remove unused parameter
- private void OnAllowLoadMoreChanged(DependencyPropertyChangedEventArgs e)
-#pragma warning restore IDE0060 // Remove unused parameter
+ private void OnAllowLoadMoreChanged()
{
if (!AllowLoadMore && LoadMoreItemsCount == 0)
{
@@ -2896,17 +2887,14 @@ void CreateItemContent(int position, int index)
///
/// Retrieves the virtualized item as a native view.
///
- ///
///
///
-#pragma warning disable IDE0060 // Remove unused parameter
- private Syncfusion.Maui.Toolkit.Carousel.PlatformCarouselItem? GetVirtualizedItem(object? item, int index)
-#pragma warning restore IDE0060 // Remove unused parameter
+ private Syncfusion.Maui.Toolkit.Carousel.PlatformCarouselItem? GetVirtualizedItem(int index)
{
View? contentView = null;
if (_virtualView != null && _virtualView.ItemTemplate != null)
{
- contentView = GetItemView(_virtualView, this, index);
+ contentView = GetItemView(_virtualView, index);
}
if (contentView != null && _carouselHandler != null && _virtualView != null)
{
@@ -2962,9 +2950,7 @@ void LinearViewModeIndex(Size size)
///
///
///
-#pragma warning disable IDE0060 // Remove unused parameter
void UpdateLinearStartEndPosition(double horizontalCenterPoint, double verticalCenterPoint, bool isUpdateStartPosition, double itemRegion, double startRegion, double endRegion)
-#pragma warning restore IDE0060 // Remove unused parameter
{
if (_tempCollection != null)
{
@@ -3342,7 +3328,7 @@ internal void UpdateItemSource(CarouselHandler handler, ICarousel virtualView)
View? contentView = null;
if (virtualView.ItemTemplate != null)
{
- contentView = GetItemView(virtualView, this, i);
+ contentView = GetItemView(virtualView, i);
i++;
}
if (contentView != null)
@@ -3393,12 +3379,7 @@ private void Source_CollectionChanged(object? sender, NotifyCollectionChangedEve
/// Arguments of selection changed event.
internal virtual void OnSelectionChanged(SelectionChangedEventArgs args)
{
-#pragma warning disable IDE0031
- if (_virtualView != null)
- {
- _virtualView.RaiseSelectionChanged(args);
- }
-#pragma warning restore IDE0031
+ _virtualView?.RaiseSelectionChanged(args);
}
///
@@ -3407,12 +3388,7 @@ internal virtual void OnSelectionChanged(SelectionChangedEventArgs args)
/// Arguments of SwipeStarted event.
internal void OnSwipeStarted(SwipeStartedEventArgs args)
{
-#pragma warning disable IDE0031
- if (_virtualView != null)
- {
- _virtualView.RaiseSwipeStarted(args);
- }
-#pragma warning restore IDE0031
+ _virtualView?.RaiseSwipeStarted(args);
_isSwipeEnded = false;
}
@@ -3423,12 +3399,7 @@ internal void OnSwipeStarted(SwipeStartedEventArgs args)
/// Arguments of SwipeEnded event.
internal virtual void OnSwipeEnded(EventArgs args)
{
-#pragma warning disable IDE0031
- if (_virtualView != null)
- {
- _virtualView.RaiseSwipeEnded(args);
- }
-#pragma warning restore IDE0031
+ _virtualView?.RaiseSwipeEnded(args);
_isSwipeEnded = true;
}
@@ -3450,12 +3421,9 @@ private static void SetParentContent(View templateLayout, ICarousel formsCarouse
/// Retrieves a view for the specified item index in the carousel.
///
///
- ///
///
///
-#pragma warning disable IDE0060 // Remove unused parameter
- internal static View? GetItemView(ICarousel FormsCarousel, PlatformCarousel carousel, int index)
-#pragma warning restore IDE0060 // Remove unused parameter
+ internal static View? GetItemView(ICarousel FormsCarousel, int index)
{
if ((FormsCarousel.ItemsSource is System.Collections.IList itemSource && itemSource.Count > 0))
{
diff --git a/maui/src/Carousel/Platform/Windows/PlatformCarouselItem.Windows.cs b/maui/src/Carousel/Platform/Windows/PlatformCarouselItem.Windows.cs
index 2597fdb8..b280931b 100644
--- a/maui/src/Carousel/Platform/Windows/PlatformCarouselItem.Windows.cs
+++ b/maui/src/Carousel/Platform/Windows/PlatformCarouselItem.Windows.cs
@@ -608,10 +608,10 @@ void InitializeTemplateParts()
void InitializeAnimations()
{
_animation = new Storyboard();
- _rotationKeyFrame = CreateDoubleAnimation("Rotator", "RotationY", 0.9, 0);
- _offsetZKeyFrame = CreateDoubleAnimation("Rotator", "LocalOffsetZ", 0.9, 0);
- _scaleXKeyFrame = CreateDoubleAnimation("ScaleTransform", "ScaleX", 0.9, 1);
- _scaleYKeyFrame = CreateDoubleAnimation("ScaleTransform", "ScaleY", 0.9, 1);
+ _rotationKeyFrame = CreateDoubleAnimation(0.9, 0);
+ _offsetZKeyFrame = CreateDoubleAnimation(0.9, 0);
+ _scaleXKeyFrame = CreateDoubleAnimation(0.9, 1);
+ _scaleYKeyFrame = CreateDoubleAnimation(0.9, 1);
_layoutGrid?.Resources.Add("Animation", _animation);
@@ -666,16 +666,10 @@ void InitializeRotationAnimation()
///
/// Create double animation.
///
- ///
- ///
///
///
///
-#pragma warning disable IDE0060 // Remove unused parameter
-#pragma warning disable IDE0060 // Remove unused parameter
- private static EasingDoubleKeyFrame CreateDoubleAnimation(string targetName, string targetProperty, double keyTimeSeconds, double value)
-#pragma warning restore IDE0060 // Remove unused parameter
-#pragma warning restore IDE0060 // Remove unused parameter
+ private static EasingDoubleKeyFrame CreateDoubleAnimation(double keyTimeSeconds, double value)
{
EasingDoubleKeyFrame keyFrame = new EasingDoubleKeyFrame
{
diff --git a/maui/src/Carousel/Platform/iOS/PlatformCarousel.iOS.cs b/maui/src/Carousel/Platform/iOS/PlatformCarousel.iOS.cs
index e5d8e136..e91d4f2e 100644
--- a/maui/src/Carousel/Platform/iOS/PlatformCarousel.iOS.cs
+++ b/maui/src/Carousel/Platform/iOS/PlatformCarousel.iOS.cs
@@ -15,25 +15,9 @@ namespace Syncfusion.Maui.Toolkit.Carousel
/// Represents a platform-specific handler for connecting a carousel view.
///
///
- public partial class PlatformCarousel : UIView, IComponent
+ public partial class PlatformCarousel : UIView
{
- #region IComponent implementation
-
- ///
- /// The site field
- ///
- ISite? IComponent.Site { get; set; }
-
-#pragma warning disable 0067
- ///
- /// The disposed event
- ///
- public event EventHandler? Disposed;
-
-#pragma warning disable 0067
- #endregion
-
#region Private Variables
private const float Angle45 = 45;
diff --git a/maui/src/Carousel/Platform/iOS/PlatformCarouselItem.iOS.cs b/maui/src/Carousel/Platform/iOS/PlatformCarouselItem.iOS.cs
index 1629fefe..531086ec 100644
--- a/maui/src/Carousel/Platform/iOS/PlatformCarouselItem.iOS.cs
+++ b/maui/src/Carousel/Platform/iOS/PlatformCarouselItem.iOS.cs
@@ -182,9 +182,7 @@ internal string AutomationId
///
/// Touches value.
/// Event parameter.
-#pragma warning disable CS8765
- public override void TouchesEnded(NSSet touches, UIEvent evt)
-#pragma warning restore CS8765
+ public override void TouchesEnded(NSSet touches, UIEvent? evt)
{
UITouch touch = (UITouch)touches.AnyObject;
CGPoint touchPoint = touch.LocationInView(this);
diff --git a/maui/src/Carousel/SfCarousel.cs b/maui/src/Carousel/SfCarousel.cs
index 30d2f119..186ee4a2 100644
--- a/maui/src/Carousel/SfCarousel.cs
+++ b/maui/src/Carousel/SfCarousel.cs
@@ -1121,7 +1121,7 @@ private Size MeasureOverrideForOtherPlatforms(double widthConstraint, double hei
DesiredSize = new Size(widthConstraint, heightConstraint);
if (Parent is HorizontalStackLayout)
{
- DesiredSize = MeasureForHorizontalLayout(widthConstraint, heightConstraint);
+ DesiredSize = MeasureForHorizontalLayout(heightConstraint);
}
else if (Parent is StackLayout stackLayout)
{
@@ -1129,7 +1129,7 @@ private Size MeasureOverrideForOtherPlatforms(double widthConstraint, double hei
}
else if (Parent is VerticalStackLayout)
{
- DesiredSize = MeasureForVerticalLayout(widthConstraint, heightConstraint);
+ DesiredSize = MeasureForVerticalLayout(widthConstraint);
}
else if (Parent is ScrollView scrollView)
{
@@ -1168,23 +1168,16 @@ private void InitializeConstraints(ref double widthConstraint, ref double height
///
/// Calculates the size requirement for an element within a parent.
///
- /// The maximum width available for the element.
/// The maximum height available for the element.
/// A structure representing the calculated size based on the constraints and requests.
-#pragma warning disable IDE0060 // Remove unused parameter
- Size MeasureForHorizontalLayout(double widthConstraint, double heightConstraint) => new(WidthRequest >= 0 ? WidthRequest : ItemWidth, HeightRequest >= 0 && WidthRequest >= 0 ? HeightRequest : heightConstraint);
-#pragma warning restore IDE0060 // Remove unused parameter
-
+ Size MeasureForHorizontalLayout(double heightConstraint) => new(WidthRequest >= 0 ? WidthRequest : ItemWidth, HeightRequest >= 0 && WidthRequest >= 0 ? HeightRequest : heightConstraint);
///
/// Calculates the size of a VerticalStackLayout based on given width and height constraints.
/// Takes into account specified width and height requests, defaulting to constraints or item height as needed.
///
/// The available width for the layout.
- /// The available height for the layout.
/// A Size object representing the dimensions of the layout.
-#pragma warning disable IDE0060 // Remove unused parameter
- Size MeasureForVerticalLayout(double widthConstraint, double heightConstraint)
-#pragma warning restore IDE0060 // Remove unused parameter
+ Size MeasureForVerticalLayout(double widthConstraint)
{
return new Size(HeightRequest >= 0 && WidthRequest >= 0 ? WidthRequest : widthConstraint, HeightRequest >= 0 ? HeightRequest : ItemHeight);
}
diff --git a/maui/src/Charts/Area/Partial/CartesianChartArea.cs b/maui/src/Charts/Area/Partial/CartesianChartArea.cs
index c8ecc5b8..c9f5c783 100644
--- a/maui/src/Charts/Area/Partial/CartesianChartArea.cs
+++ b/maui/src/Charts/Area/Partial/CartesianChartArea.cs
@@ -421,7 +421,6 @@ internal void UpdateStackingSeries()
}
stackingSeries.ValidateYValues();
-
var stackingGroup = stackingSeries.GroupingLabel;
var stackingXAxis = stackingSeries.ActualXAxis;
var stackingYAxis = stackingSeries.ActualYAxis;
diff --git a/maui/src/Charts/Series/BoxAndWhiskerSeries.cs b/maui/src/Charts/Series/BoxAndWhiskerSeries.cs
index 7dff000e..3db4af97 100644
--- a/maui/src/Charts/Series/BoxAndWhiskerSeries.cs
+++ b/maui/src/Charts/Series/BoxAndWhiskerSeries.cs
@@ -609,6 +609,10 @@ protected override ChartSegment CreateSegment()
#region Internal Methods
+ internal override void ValidateYValues()
+ {
+ }
+
internal override void OnDataSourceChanged(object oldValue, object newValue)
{
if (YDataCollection != null)
diff --git a/maui/src/Charts/Series/BubbleSeries.cs b/maui/src/Charts/Series/BubbleSeries.cs
index e0f32868..48012d19 100644
--- a/maui/src/Charts/Series/BubbleSeries.cs
+++ b/maui/src/Charts/Series/BubbleSeries.cs
@@ -567,6 +567,11 @@ internal override void OnDataSourceChanged(object oldValue, object newValue)
ResetAutoScroll();
YValues.Clear();
_sizeValues.Clear();
+ foreach (var item in EmptyPointIndexes)
+ {
+ item?.Clear();
+ }
+
GeneratePoints([YBindingPath, SizeValuePath], YValues, _sizeValues);
}
@@ -599,7 +604,7 @@ internal override void ResetEmptyPointIndexes()
{
foreach (var index in EmptyPointIndexes[0])
{
- if (YValues != null && YValues.Count != 0)
+ if (YValues != null && YValues.Count != 0 && index < YValues.Count)
{
YValues[(int)index] = double.NaN;
}
@@ -610,7 +615,7 @@ internal override void ResetEmptyPointIndexes()
{
foreach (var index in EmptyPointIndexes[1])
{
- if (_sizeValues != null && _sizeValues.Count != 0)
+ if (_sizeValues != null && _sizeValues.Count != 0 && index < _sizeValues.Count)
{
_sizeValues[(int)index] = double.NaN;
}
diff --git a/maui/src/Charts/Series/CartesianSeries.cs b/maui/src/Charts/Series/CartesianSeries.cs
index 4b74a7a0..42d676bd 100644
--- a/maui/src/Charts/Series/CartesianSeries.cs
+++ b/maui/src/Charts/Series/CartesianSeries.cs
@@ -52,6 +52,9 @@ internal bool IsGrouped
internal override ChartDataLabelSettings ChartDataLabelSettings => DataLabelSettings;
+ internal bool RequiredEmptyPointReset { get; set; } = false;
+ internal virtual bool IsFillEmptyPoint { get { return true; } }
+
#endregion
#region Bindable properties
@@ -723,10 +726,7 @@ internal set
_actualYAxis = value;
}
}
- }
-
- internal bool RequiredEmptyPointReset { get; set; } = false;
- internal virtual bool IsFillEmptyPoint { get { return true; } }
+ }
#endregion
@@ -1793,7 +1793,7 @@ internal double[] GetCardinalSpline(List xValues, IList yValues)
internal override void UpdateEmptyPointSettings()
{
- if (!IsFillEmptyPoint && EmptyPointMode == EmptyPointMode.None)
+ if (!IsFillEmptyPoint || EmptyPointMode == EmptyPointMode.None)
{
return;
}
diff --git a/maui/src/Charts/Series/ErrorBarSeries.cs b/maui/src/Charts/Series/ErrorBarSeries.cs
index e108c23d..d2d4ade5 100644
--- a/maui/src/Charts/Series/ErrorBarSeries.cs
+++ b/maui/src/Charts/Series/ErrorBarSeries.cs
@@ -988,6 +988,11 @@ internal override void OnDataSourceChanged(object oldValue, object newValue)
HorizontalErrorValues?.Clear();
VerticalErrorValues?.Clear();
YValues.Clear();
+ foreach (var item in EmptyPointIndexes)
+ {
+ item?.Clear();
+ }
+
GeneratePoints();
ScheduleUpdateChart();
}
@@ -1020,6 +1025,12 @@ internal override bool IsMultipleYPathRequired
internal override void RemoveData(int index, NotifyCollectionChangedEventArgs e)
{
+ ResetEmptyPointIndexes();
+ foreach (var item in EmptyPointIndexes)
+ {
+ item?.Clear();
+ }
+
if (XValues is IList list1)
{
list1.RemoveAt(index);
diff --git a/maui/src/Charts/Series/FastLineSeries.cs b/maui/src/Charts/Series/FastLineSeries.cs
index 72905d72..2a41e56a 100644
--- a/maui/src/Charts/Series/FastLineSeries.cs
+++ b/maui/src/Charts/Series/FastLineSeries.cs
@@ -77,7 +77,7 @@ public partial class FastLineSeries : XYDataSeries, IDrawCustomLegendIcon
#region Internal Properties
internal double ToleranceCoefficient { get; set; }
- internal override bool IsFillEmptyPoint { get { return false; } }
+ internal override bool IsFillEmptyPoint { get { return false; } }
#endregion
diff --git a/maui/src/Charts/Series/FinancialSeriesBase.cs b/maui/src/Charts/Series/FinancialSeriesBase.cs
index 425e7c4e..057d7a7b 100644
--- a/maui/src/Charts/Series/FinancialSeriesBase.cs
+++ b/maui/src/Charts/Series/FinancialSeriesBase.cs
@@ -585,7 +585,7 @@ internal override void ResetEmptyPointIndexes()
{
foreach (var index in EmptyPointIndexes[0])
{
- if (HighValues != null && HighValues.Count != 0)
+ if (HighValues != null && HighValues.Count != 0 && index < HighValues.Count)
{
HighValues[(int)index] = double.NaN;
}
@@ -596,7 +596,7 @@ internal override void ResetEmptyPointIndexes()
{
foreach (var index in EmptyPointIndexes[1])
{
- if (LowValues != null && LowValues.Count != 0)
+ if (LowValues != null && LowValues.Count != 0 && index < LowValues.Count)
{
LowValues[(int)index] = double.NaN;
}
@@ -607,7 +607,7 @@ internal override void ResetEmptyPointIndexes()
{
foreach (var index in EmptyPointIndexes[2])
{
- if (OpenValues != null && OpenValues.Count != 0)
+ if (OpenValues != null && OpenValues.Count != 0 && index < OpenValues.Count)
{
OpenValues[(int)index] = double.NaN;
}
@@ -618,7 +618,7 @@ internal override void ResetEmptyPointIndexes()
{
foreach (var index in EmptyPointIndexes[3])
{
- if (CloseValues != null && CloseValues.Count != 0)
+ if (CloseValues != null && CloseValues.Count != 0 && index < CloseValues.Count)
{
CloseValues[(int)index] = double.NaN;
}
diff --git a/maui/src/Charts/Series/HistogramSeries.cs b/maui/src/Charts/Series/HistogramSeries.cs
index e56c33ed..82e9216c 100644
--- a/maui/src/Charts/Series/HistogramSeries.cs
+++ b/maui/src/Charts/Series/HistogramSeries.cs
@@ -437,6 +437,10 @@ protected internal override void DrawSeries(ICanvas canvas, ReadOnlyObservableCo
#region Internal Methods
+ internal override void ValidateYValues()
+ {
+ }
+
internal override void GenerateSegments(SeriesView seriesView)
{
var xValues = GetXValues();
diff --git a/maui/src/Charts/Series/RangeAreaSeries.cs b/maui/src/Charts/Series/RangeAreaSeries.cs
index 5ed5dd22..0372b7a9 100644
--- a/maui/src/Charts/Series/RangeAreaSeries.cs
+++ b/maui/src/Charts/Series/RangeAreaSeries.cs
@@ -83,17 +83,19 @@ public partial class RangeAreaSeries : RangeSeriesBase, IMarkerDependent, IDrawC
internal override bool IsMultipleYPathRequired => true;
- #endregion
+ internal override bool IsFillEmptyPoint { get { return false; } }
- #region Bindable Properties
+ #endregion
- ///
- /// Identifies the bindable property.
- ///
- ///
- /// The property determines whether markers are displayed on the chart points.
- ///
- public static readonly BindableProperty ShowMarkersProperty = ChartMarker.ShowMarkersProperty;
+ #region Bindable Properties
+
+ ///
+ /// Identifies the bindable property.
+ ///
+ ///
+ /// The property determines whether markers are displayed on the chart points.
+ ///
+ public static readonly BindableProperty ShowMarkersProperty = ChartMarker.ShowMarkersProperty;
///
/// Identifies the bindable property.
diff --git a/maui/src/Charts/Series/RangeColumnSeries.cs b/maui/src/Charts/Series/RangeColumnSeries.cs
index dbc9c464..11961b99 100644
--- a/maui/src/Charts/Series/RangeColumnSeries.cs
+++ b/maui/src/Charts/Series/RangeColumnSeries.cs
@@ -417,6 +417,10 @@ internal override void GenerateTrackballPointInfo(List nearestDataPoints
double yValue = yValues[index];
double yValue1 = yValues1[index];
string label = string.Format("{0} : {1:#.##}\n{2} : {3:#.##}", SfCartesianChartResources.High, yValue, SfCartesianChartResources.Low, yValue1);
+ if (yValue == 0 || yValue1 == 0)
+ {
+ label = string.Format("{0} : {1:0.##}\n{2} : {3:0.##}", SfCartesianChartResources.High, yValue, SfCartesianChartResources.Low, yValue1);
+ }
if (IsSideBySide)
{
diff --git a/maui/src/Charts/Series/RangeSeriesBase .cs b/maui/src/Charts/Series/RangeSeriesBase .cs
index d1711ac2..0783b7be 100644
--- a/maui/src/Charts/Series/RangeSeriesBase .cs
+++ b/maui/src/Charts/Series/RangeSeriesBase .cs
@@ -377,7 +377,7 @@ internal override void ResetEmptyPointIndexes()
{
foreach (var index in EmptyPointIndexes[0])
{
- if (HighValues != null && HighValues.Count != 0)
+ if (HighValues != null && HighValues.Count != 0 && index < HighValues.Count)
{
HighValues[(int)index] = double.NaN;
}
@@ -388,7 +388,7 @@ internal override void ResetEmptyPointIndexes()
{
foreach (var index in EmptyPointIndexes[1])
{
- if (LowValues != null && LowValues.Count != 0)
+ if (LowValues != null && LowValues.Count != 0 && index < LowValues.Count)
{
LowValues[(int)index] = double.NaN;
}
diff --git a/maui/src/Charts/Series/SplineRangeAreaSeries.cs b/maui/src/Charts/Series/SplineRangeAreaSeries.cs
index 771f42e9..dcf580c6 100644
--- a/maui/src/Charts/Series/SplineRangeAreaSeries.cs
+++ b/maui/src/Charts/Series/SplineRangeAreaSeries.cs
@@ -76,17 +76,23 @@ public partial class SplineRangeAreaSeries : RangeSeriesBase, IMarkerDependent,
bool _needToAnimateMarker;
- #endregion
+ #endregion
- #region Bindable Properties
+ #region Internal Properties
- ///
- /// Identifies the bindable property.
- ///
- ///
- /// The property determines whether the chart markers are visible on the series.
- ///
- public static readonly BindableProperty ShowMarkersProperty = ChartMarker.ShowMarkersProperty;
+ internal override bool IsFillEmptyPoint { get { return false; } }
+
+ #endregion
+
+ #region Bindable Properties
+
+ ///
+ /// Identifies the bindable property.
+ ///
+ ///
+ /// The property determines whether the chart markers are visible on the series.
+ ///
+ public static readonly BindableProperty ShowMarkersProperty = ChartMarker.ShowMarkersProperty;
///
/// Identifies the bindable property.
@@ -503,6 +509,11 @@ internal override void GenerateTrackballPointInfo(List nearestDataPoints
double highValue = highValues[index];
double lowValue = lowValues[index];
string label = string.Format("{0} : {1:#.##}\n{2} : {3:#.##}", SfCartesianChartResources.High, highValue, SfCartesianChartResources.Low, lowValue);
+ if (highValue == 0 || lowValue == 0)
+ {
+ label = string.Format("{0} : {1:0.##}\n{2} : {3:0.##}", SfCartesianChartResources.High, highValue, SfCartesianChartResources.Low, lowValue);
+ }
+
var xPoint = TransformToVisibleX(xValue, topValue);
var yPoint = TransformToVisibleY(xValue, topValue);
diff --git a/maui/src/Charts/Series/StackingAreaSeries.cs b/maui/src/Charts/Series/StackingAreaSeries.cs
index 4b8c71dc..519423e1 100644
--- a/maui/src/Charts/Series/StackingAreaSeries.cs
+++ b/maui/src/Charts/Series/StackingAreaSeries.cs
@@ -103,6 +103,12 @@ public partial class StackingAreaSeries : StackingSeriesBase, IDrawCustomLegendI
#endregion
+ #region Internal Properties
+
+ internal override bool IsFillEmptyPoint { get { return false; } }
+
+ #endregion
+
#region Bindable Properties
///
diff --git a/maui/src/Charts/Series/XYDataSeries.cs b/maui/src/Charts/Series/XYDataSeries.cs
index cb60a72f..8aea13d2 100644
--- a/maui/src/Charts/Series/XYDataSeries.cs
+++ b/maui/src/Charts/Series/XYDataSeries.cs
@@ -207,7 +207,7 @@ internal override void ResetEmptyPointIndexes()
{
foreach (var index in item)
{
- if (YValues != null && YValues.Count != 0)
+ if (YValues != null && YValues.Count != 0 && index < YValues.Count)
{
YValues[(int)index] = double.NaN;
}
diff --git a/maui/src/Chip/SfChip.cs b/maui/src/Chip/SfChip.cs
index 698b3235..39c15233 100644
--- a/maui/src/Chip/SfChip.cs
+++ b/maui/src/Chip/SfChip.cs
@@ -847,7 +847,7 @@ void UpdateClearIcon(RectF dirtyRect)
if (ShowIcon)
{
- xOffset = _isRTL ? _leftIconPadding + (float)(LinePadding / 0.95) : (float)(Width - DefaultCloseButtonWidth - _rightIconPadding + (float)(LinePadding * 1.75));
+ xOffset = _isRTL ? _leftIconPadding + (float)(LinePadding / 0.95) : (float)(Width - DefaultCloseButtonWidth - _rightIconPadding + (float)(LinePadding /0.95));
}
else
{
diff --git a/maui/src/Core/AppHostBuilder.cs b/maui/src/Core/AppHostBuilder.cs
index 940a3bf9..dd289f13 100644
--- a/maui/src/Core/AppHostBuilder.cs
+++ b/maui/src/Core/AppHostBuilder.cs
@@ -32,6 +32,7 @@ public static MauiAppBuilder ConfigureSyncfusionToolkit(this MauiAppBuilder buil
handlers.AddHandler(typeof(IDrawableLayout), typeof(SfViewHandler));
handlers.AddHandler(typeof(ICarousel), typeof(CarouselHandler));
handlers.AddHandler(typeof(WindowOverlayContainer), typeof(OverlayContainerHandler));
+ handlers.AddHandler(typeof(SfPickerView), typeof(SfPickerScrollViewHandler));
#if __ANDROID__
handlers.AddHandler(typeof(SnapLayout), typeof(SnapLayoutHandler));
#endif
diff --git a/maui/src/Core/BaseView/DrawableLayoutView/SfView.cs b/maui/src/Core/BaseView/DrawableLayoutView/SfView.cs
index a956d58a..3b59f8f1 100644
--- a/maui/src/Core/BaseView/DrawableLayoutView/SfView.cs
+++ b/maui/src/Core/BaseView/DrawableLayoutView/SfView.cs
@@ -23,9 +23,9 @@ public abstract class SfView : View, IDrawableLayout, IVisualTreeElement, ISeman
///
private bool ignoreSafeArea = false;
- private bool clipToBounds = true;
+ private bool clipToBounds = true;
- ///
+ ///
/// The field indicates whether it is layout based control.
///
private bool isLayoutControl = false;
@@ -54,6 +54,18 @@ internal DrawingOrder DrawingOrder
}
}
+#if WINDOWS
+ ///
+ /// If the SfView drawing canvas size exceeds MaximumBitmapSizeInPixels when adding more items,
+ /// an OS limitation with CanvasImageSource size (refer: https://github.com/dotnet/maui/issues/3785)
+ /// requires restricting the draw function when semantics or accessibility are used on SfView. This prevents OS limitation issues on the Windows platform.
+ /// For accessibility, SfView should be enabled with AboveContentWithTouch to add a native user control and override the AutomationPeer.
+ /// Virtualization isn't possible because automation peers must be added initially to access scrollable content.
+ /// Since accessibility highlights are managed by native framework automation peers, SfView canvas drawing is unnecessary.
+ ///
+ internal bool IsCanvasNeeded { get; set; } = true;
+#endif
+
///
/// This property is used to ignore the safe area.
///
@@ -69,7 +81,7 @@ internal bool IgnoreSafeArea
}
}
- ///
+ ///
/// Gets or sets a value indicating whether it is layout based control.
///
internal bool IsLayoutControl
@@ -84,10 +96,10 @@ internal bool IsLayoutControl
}
}
- ///
- /// Gets the collection of child views contained within this view.
- ///
- public IList Children => this;
+ ///
+ /// Gets the collection of child views contained within this view.
+ ///
+ public IList Children => this;
///
/// Gets or sets a value indicating whether the content of this view is clipped to its bounds.
diff --git a/maui/src/Core/BaseView/PlatformView/LayoutPanelExt.Windows.cs b/maui/src/Core/BaseView/PlatformView/LayoutPanelExt.Windows.cs
index 6da62fa6..3dbea48f 100644
--- a/maui/src/Core/BaseView/PlatformView/LayoutPanelExt.Windows.cs
+++ b/maui/src/Core/BaseView/PlatformView/LayoutPanelExt.Windows.cs
@@ -245,8 +245,18 @@ internal void Invalidate()
private void UserControl_Loaded(object sender, RoutedEventArgs e)
{
_canvasControl = new CanvasControl();
- _canvasControl.Draw += OnDraw;
- base.Content = _canvasControl;
+
+ //// If the SfView drawing canvas size exceeds MaximumBitmapSizeInPixels when adding more items,
+ //// an OS limitation with CanvasImageSource size (refer: https://github.com/dotnet/maui/issues/3785)
+ //// requires restricting the draw function when semantics or accessibility are used on SfView. This prevents OS limitation issues on the Windows platform.
+ //// For accessibility, SfView should be enabled with AboveContentWithTouch to add a native user control and override the AutomationPeer.
+ //// Virtualization isn't possible because automation peers must be added initially to access scrollable content.
+ //// Since accessibility highlights are managed by native framework automation peers, SfView canvas drawing is unnecessary.
+ if (MauiView != null && MauiView.IsCanvasNeeded)
+ {
+ _canvasControl.Draw += OnDraw;
+ base.Content = _canvasControl;
+ }
}
private void UserControl_Unloaded(object sender, RoutedEventArgs e)
diff --git a/maui/src/Core/BaseView/PlatformView/LayoutViewExt.ios.cs b/maui/src/Core/BaseView/PlatformView/LayoutViewExt.ios.cs
index fec191de..9921c97e 100644
--- a/maui/src/Core/BaseView/PlatformView/LayoutViewExt.ios.cs
+++ b/maui/src/Core/BaseView/PlatformView/LayoutViewExt.ios.cs
@@ -201,7 +201,7 @@ public override void LayoutSubviews()
var bounds = AdjustForSafeArea(Bounds).ToRectangle();
var widthConstraint = bounds.Width;
- var heightConstraint = bounds.Height;
+ var heightConstraint = bounds.Height;
if (!IsMeasureValid(widthConstraint, heightConstraint) && Superview is not Microsoft.Maui.Platform.MauiView && View is SfView sfView && !sfView.IsLayoutControl)
{
diff --git a/maui/src/Core/BaseView/Semantics/AutomationPeer.Windows.cs b/maui/src/Core/BaseView/Semantics/AutomationPeer.Windows.cs
index 827e50b2..2e1131e7 100644
--- a/maui/src/Core/BaseView/Semantics/AutomationPeer.Windows.cs
+++ b/maui/src/Core/BaseView/Semantics/AutomationPeer.Windows.cs
@@ -360,6 +360,19 @@ protected override Windows.Foundation.Rect GetBoundingRectangleCore()
height = 0;
}
+#if WINDOWS
+ //// If the SfView drawing canvas size exceeds MaximumBitmapSizeInPixels when adding more items,
+ //// an OS limitation with CanvasImageSource size (refer: https://github.com/dotnet/maui/issues/3785)
+ //// requires restricting the draw function when semantics or accessibility are used on SfView. This prevents OS limitation issues on the Windows platform.
+ //// For accessibility, SfView should be enabled with AboveContentWithTouch to add a native user control and override the AutomationPeer.
+ //// Virtualization isn't possible because automation peers must be added initially to access scrollable content.
+ //// Since accessibility highlights are managed by native framework automation peers, SfView canvas drawing is unnecessary.
+ if (!MauiSfView.IsCanvasNeeded)
+ {
+ return new Windows.Foundation.Rect(xPosition, yPosition, semanticNodeBounds.Width, semanticNodeBounds.Height);
+ }
+#endif
+
return new Windows.Foundation.Rect(xPosition, yPosition, width, height);
}
}
diff --git a/maui/src/Core/ButtonBase/ButtonBase.cs b/maui/src/Core/ButtonBase/ButtonBase.cs
index 5b2e37c0..8af2fce8 100644
--- a/maui/src/Core/ButtonBase/ButtonBase.cs
+++ b/maui/src/Core/ButtonBase/ButtonBase.cs
@@ -279,6 +279,11 @@ public abstract class ButtonBase : SfView, ITouchListener, ITextElement, ITapGes
private TextAlignment _horizontalTextAlignment;
+#if MACCATALYST || IOS
+ private Point _pressedPoint;
+ private const double _scrollThreshold = 10;
+#endif
+
#if WINDOWS
// The native view element for the button control.
private FrameworkElement? _buttonNativeView;
@@ -1621,6 +1626,11 @@ public virtual void OnTouch(PointerEventArgs e)
{
if (e.Action == PointerActions.Pressed)
{
+
+#if IOS || MACCATALYST
+ _pressedPoint = e.TouchPoint;
+#endif
+
if (_background != HighlightColor && !IsEditorControl)
{
_background = HighlightColor;
@@ -1651,15 +1661,32 @@ public virtual void OnTouch(PointerEventArgs e)
chip = Children as SfChip;
}
+#if IOS || MACCATALYST
+ var releasedPoint = e.TouchPoint;
+ double diffX = Math.Abs(this._pressedPoint.X - releasedPoint.X);
+ double diffY = Math.Abs(this._pressedPoint.Y - releasedPoint.Y);
+ bool isScrolled = false;
+ if (diffX >= _scrollThreshold || diffY >= _scrollThreshold)
+ {
+ isScrolled = true;
+ }
+#endif
+
if (chip is not null)
{
if (!chip.IsTouchInsideCloseButton(e.TouchPoint) && IsEnabled)
{
+#if MACCATALYST || IOS
+ if(!isScrolled)
+#endif
RaiseClicked(EventArgs.Empty);
}
}
else
{
+#if MACCATALYST || IOS
+ if(!isScrolled)
+#endif
RaiseClicked(EventArgs.Empty);
}
#if ANDROID || IOS
diff --git a/maui/src/Core/Extensions/CanvasExtensions.Windows.cs b/maui/src/Core/Extensions/CanvasExtensions.Windows.cs
index 172679fd..da68db35 100644
--- a/maui/src/Core/Extensions/CanvasExtensions.Windows.cs
+++ b/maui/src/Core/Extensions/CanvasExtensions.Windows.cs
@@ -130,9 +130,7 @@ public static void DrawText(this ICanvas canvas, string value, Rect rect, Horizo
format.HorizontalAlignment = canvasHorizontalAlignment;
format.Options = CanvasDrawTextOptions.Clip;
if(rect.Width >= 0 && rect.Height >= 0)
- {
w2DCanvas.Session.DrawText(value, new Windows.Foundation.Rect(rect.X, rect.Y, rect.Width, rect.Height), textElement.TextColor.AsColor(), format);
- }
}
}
}
diff --git a/maui/src/Core/Helper/IconButton/SfIconButton.cs b/maui/src/Core/Helper/IconButton/SfIconButton.cs
new file mode 100644
index 00000000..41bc152f
--- /dev/null
+++ b/maui/src/Core/Helper/IconButton/SfIconButton.cs
@@ -0,0 +1,346 @@
+using Microsoft.Maui.Controls.Shapes;
+using Syncfusion.Maui.Toolkit.EffectsView;
+using Syncfusion.Maui.Toolkit.Internals;
+using Syncfusion.Maui.Toolkit.Graphics.Internals;
+using PointerEventArgs = Syncfusion.Maui.Toolkit.Internals.PointerEventArgs;
+
+namespace Syncfusion.Maui.Toolkit
+{
+ ///
+ /// Represents a class which contains information of button icons.
+ ///
+ internal class SfIconButton : Grid, ITouchListener
+ {
+ #region Fields
+
+ ///
+ /// Used to trigger whenever the tap gesture tap event triggered.
+ ///
+ internal Action? Clicked;
+
+ ///
+ /// The show touch effect.
+ ///
+ bool _showTouchEffect;
+
+ ///
+ /// Holds that the view is visible or not.
+ ///
+ bool _visibility;
+
+ ///
+ /// Holds the effect view ripple selection shape.
+ ///
+ bool _isSquareSelection;
+
+ ///
+ /// Holds the icon view.
+ ///
+ readonly SfIconView? _iconView;
+
+ ///
+ /// Used to identify the button need to hover while released the press.
+ ///
+ readonly bool _isHoveringOnReleased;
+
+#if __MACCATALYST__ || (!__ANDROID__ && !__IOS__)
+ ///
+ /// Holds the value to denotes the mouse cursor exited.
+ ///
+ bool _isExited = false;
+
+#endif
+ #endregion
+
+ #region Constructor
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The child view.
+ /// The show touch effect.
+ /// The square selection.
+ /// Used to identify the button need to hover while released the press
+ internal SfIconButton(View child, bool showTouchEffect = true, bool isSquareSelection = true, bool isHoveringOnReleased = true)
+ {
+ _showTouchEffect = showTouchEffect;
+ _isSquareSelection = isSquareSelection;
+ _isHoveringOnReleased = isHoveringOnReleased;
+ EffectsView = new SfEffectsView();
+#if __IOS__
+ IgnoreSafeArea = true;
+ EffectsView.IgnoreSafeArea = true;
+#endif
+ Add(EffectsView);
+ EffectsView.Content = child;
+ EffectsView.ShouldIgnoreTouches = true;
+ this.AddTouchListener(this);
+ _visibility = true;
+ IsClippedToBounds = true;
+ if (child is SfIconView icon)
+ {
+ _iconView = icon;
+ }
+ }
+
+ #endregion
+
+ #region Properties
+
+ ///
+ /// Gets or sets a value indicating whether is show touch effect or not.
+ ///
+ internal bool ShowTouchEffect
+ {
+ get
+ {
+ return _showTouchEffect;
+ }
+
+ set
+ {
+ if (value == _showTouchEffect)
+ {
+ return;
+ }
+
+ _showTouchEffect = value;
+ if (!_showTouchEffect)
+ {
+ //// Continuous tapping triggers pressed and released events. if the view is disabled on async method of clicked event, then the continuous tapping triggers the pressed but the released event will skipped due to view disable, so the effect view effects does not cleared on pressed UI.
+ EffectsView.Reset();
+ EffectsView.Background = Brush.Transparent;
+ }
+ }
+ }
+
+ ///
+ /// Gets or sets a value indicating whether the ripple effect is square or not.
+ ///
+ internal bool IsSquareSelection
+ {
+ get
+ {
+ return _isSquareSelection;
+ }
+
+ set
+ {
+ if (value == _isSquareSelection)
+ {
+ return;
+ }
+
+ _isSquareSelection = value;
+ }
+ }
+
+ ///
+ /// Gets or sets the effective view.
+ ///
+ internal SfEffectsView EffectsView { get; set; }
+
+ ///
+ /// Gets or sets a value indicating whether the view is visible or not.
+ /// TODO: IsVisible property breaks in 6.0.400 release.
+ /// Issue link -https://github.com/dotnet/maui/issues/7507
+ /// -https://github.com/dotnet/maui/issues/8044
+ /// -https://github.com/dotnet/maui/issues/7482
+ ///
+ internal bool Visibility
+ {
+ get
+ {
+ return _visibility;
+ }
+
+ set
+ {
+#if !__MACCATALYST__
+ IsVisible = value;
+#endif
+ _visibility = value;
+ }
+ }
+
+ #endregion
+
+ #region Internal Methods
+
+ ///
+ /// Method to update icon style.
+ ///
+ /// The text style value.
+ internal void UpdateStyle(ITextElement textStyle)
+ {
+ var iconView = EffectsView.Content as SfIconView;
+ iconView?.UpdateStyle(textStyle);
+ }
+
+ ///
+ /// Method to update icon color.
+ ///
+ /// The icon color.
+ internal void UpdateIconColor(Color iconColor)
+ {
+ var iconView = EffectsView.Content as SfIconView;
+ iconView?.UpdateIconColor(iconColor);
+ }
+
+ #endregion
+
+ #region Private Methods
+
+ ///
+ /// Method to update the clip while the ripple selection is circle.
+ ///
+ /// The width value.
+ /// The height value.
+ void UpdateClip(double width, double height)
+ {
+ if (_isSquareSelection || width < 0 || height < 0)
+ {
+ EffectsView.Clip = null;
+ return;
+ }
+
+ double centerX = Math.Min(width, height) / 2;
+ EllipseGeometry currentClip = new EllipseGeometry() { Center = new Point(width / 2, height / 2), RadiusX = centerX, RadiusY = centerX };
+ EllipseGeometry? previousClip = null;
+ if (EffectsView.Clip != null && EffectsView.Clip is EllipseGeometry)
+ {
+ previousClip = (EllipseGeometry)EffectsView.Clip;
+ }
+
+ //// If the previous and current clip values are same, then no need to update the effects view clip.
+ if (previousClip != null && previousClip.Center == currentClip.Center && previousClip.RadiusX == currentClip.RadiusX && previousClip.RadiusY == currentClip.RadiusY)
+ {
+ return;
+ }
+
+ EffectsView.Clip = currentClip;
+ }
+
+#if __MACCATALYST__ || (!__ANDROID__ && !__IOS__)
+ ///
+ /// Method sets clip for icons.
+ ///
+ void ApplyCornerClip()
+ {
+ //// Handles clip for rounded rectangle icons. This clip reset to null in Mac and iOS if we set in measure content.
+ if (_iconView != null && _iconView.SelectionCornerRadius > 0)
+ {
+ RoundRectangleGeometry currentClip = new RoundRectangleGeometry()
+ {
+ Rect = new Rect(0, 0, Width, Height),
+ CornerRadius = _iconView.SelectionCornerRadius,
+ };
+
+ RoundRectangleGeometry? previousClip = null;
+ if (EffectsView.Clip != null && EffectsView.Clip is RoundRectangleGeometry previous)
+ {
+ previousClip = previous;
+ }
+
+ //// If the previous and current clip values are same, then no need to update the effects view clip.
+ if (previousClip != null && previousClip.CornerRadius == currentClip.CornerRadius && previousClip.Rect.Width == currentClip.Rect.Width && previousClip.Rect.Height == currentClip.Rect.Height)
+ {
+ return;
+ }
+
+ EffectsView.Clip = currentClip;
+ }
+ }
+#endif
+
+ #endregion
+
+ #region Override Methods
+
+ protected override Size MeasureOverride(double widthConstraint, double heightConstraint)
+ {
+ //// Method to update the clip based on the isSquareSelection property
+ //// If the isSquareSelection is false, circle effect is performed.
+ //// If the isSquareSelection is true, square effect is performed.
+ var size = base.MeasureOverride(widthConstraint, heightConstraint);
+#if WINDOWS
+ Dispatcher.Dispatch(() =>
+ {
+ UpdateClip(widthConstraint, heightConstraint);
+ });
+#else
+ UpdateClip(widthConstraint, heightConstraint);
+#endif
+ return size;
+ }
+
+ #endregion
+
+ #region Interface Implementation
+
+ ///
+ /// Method invokes on touch interaction.
+ ///
+ /// The touch event args.
+ void ITouchListener.OnTouch(PointerEventArgs e)
+ {
+ if (!_showTouchEffect)
+ {
+ return;
+ }
+
+ if (e.Action == PointerActions.Pressed)
+ {
+#if __MACCATALYST__ || (!__ANDROID__ && !__IOS__)
+ _isExited = false;
+#endif
+ EffectsView.ApplyEffects();
+ }
+ else if (e.Action == PointerActions.Released)
+ {
+ EffectsView.Reset();
+ var sfIconView = EffectsView.Content as SfIconView;
+ if (sfIconView != null)
+ {
+ Clicked?.Invoke(sfIconView.Text);
+ }
+#if __MACCATALYST__ || (!__ANDROID__ && !__IOS__)
+ //// Show effect will false when we reach min or max date view and
+ //// the view is enabled because it was disabled when loading busy indicator.
+ //// is exited bool used to identify the touch exited while long press,
+ //// so did not need to maintain the hovering for the button.
+ if (_showTouchEffect && IsEnabled && !_isExited && _isHoveringOnReleased)
+ {
+ //// The hovering color is not maintained when you press and release the mouse pointer in navigation arrows.
+ EffectsView.Background = new SolidColorBrush(Colors.Black.WithAlpha(0.04f));
+ }
+ else
+ {
+ EffectsView.Background = Brush.Transparent;
+ }
+#endif
+ }
+#if __MACCATALYST__ || (!__ANDROID__ && !__IOS__)
+ else if (e.Action == PointerActions.Entered)
+ {
+ _isExited = false;
+ ApplyCornerClip();
+ EffectsView.ApplyEffects(SfEffects.Highlight);
+ }
+ else if (e.Action == PointerActions.Exited)
+ {
+ _isExited = true;
+ EffectsView.Reset();
+ EffectsView.Background = Brush.Transparent;
+ }
+#endif
+ else if (e.Action == PointerActions.Cancelled)
+ {
+ EffectsView.Reset();
+ EffectsView.Background = Brush.Transparent;
+ }
+ }
+
+ #endregion
+ }
+}
\ No newline at end of file
diff --git a/maui/src/Core/Helper/IconButton/SfIconView.cs b/maui/src/Core/Helper/IconButton/SfIconView.cs
new file mode 100644
index 00000000..8259371c
--- /dev/null
+++ b/maui/src/Core/Helper/IconButton/SfIconView.cs
@@ -0,0 +1,984 @@
+using Syncfusion.Maui.Toolkit.Graphics.Internals;
+
+namespace Syncfusion.Maui.Toolkit
+{
+ ///
+ /// Specifies the type of sficons.
+ ///
+ internal enum SfIcon
+ {
+ ///
+ /// Specifies the forward icon.
+ ///
+ Forward,
+
+ ///
+ /// Specifies the backward icon.
+ ///
+ Backward,
+
+ ///
+ /// Specifies the downward icon.
+ ///
+ Downward,
+
+ ///
+ /// Specifies the upward icon.
+ ///
+ Upward,
+
+ ///
+ /// Specifies the today icon.
+ ///
+ Today,
+
+ ///
+ /// Specifies the option icon.
+ ///
+ Option,
+
+ ///
+ /// Specifies the view button.
+ ///
+ Button,
+
+ ///
+ /// Specifies the combobox view.
+ ///
+ ComboBox,
+
+ ///
+ /// Specifies the today button.
+ ///
+ TodayButton,
+
+ ///
+ /// Specifies the divider view.
+ ///
+ Divider,
+
+ ///
+ /// Specifies the week number text.
+ ///
+ WeekNumber,
+
+ ///
+ /// Specifies the cancel button.
+ ///
+ Cancel,
+
+ ///
+ /// Specifies the more icon.
+ ///
+ More,
+ }
+
+ ///
+ /// Represents a class which contains information of icons view.
+ ///
+ internal class SfIconView : SfView
+ {
+ #region Fields
+
+ ///
+ /// This bool is used to identify whether the combobox dropdown is open or not.
+ ///
+ bool _isOpen;
+
+ ///
+ /// The icon size.
+ ///
+ double _iconSize => _textStyle.FontSize / 1.5;
+
+ ///
+ /// The border color. While the border color is null, then the border color is considered as transparent. The border color is transperent then the border color considered as light gray
+ ///
+ Color? _borderColor;
+
+ ///
+ /// The today highlight color.
+ ///
+ Color _highlightColor;
+
+ ///
+ /// The selection highlight text color.
+ ///
+ Color? _highlightTextColor;
+
+ ///
+ /// The icon Text.
+ ///
+ string _text;
+
+ ///
+ /// Holds that the view is visible or not.
+ ///
+ bool _visibility;
+
+ ///
+ /// Icon text style value.
+ ///
+ TextStyle _textStyle;
+
+ ///
+ /// Defines the icon text is selected or not.
+ ///
+ bool _isSelected;
+
+ ///
+ /// Defines the icon text need to be highlight like tab.
+ ///
+ bool _isTabHighlight;
+
+ ///
+ /// Defines the flow direction is RTL or not.
+ ///
+ bool _isRTL;
+
+ ///
+ /// Used to trim week number text while SfIcon type is Week number.
+ /// It is applicable for SfScheduler.
+ ///
+ readonly Func? _getTrimWeekNumberText;
+
+ ///
+ /// Defines the semantic node.
+ ///
+ readonly SemanticsNode? _semanticsNode;
+
+ #endregion
+
+ #region Constructor
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The sficon.
+ /// The header text style.
+ /// The icon text.
+ /// Border color for the button.
+ /// Highlight color to highlight the selected icon.
+ /// Defines the icon flow direction is RTL.
+ /// Defined the icon is selected or not.
+ /// Used to trim the week number text based on available width and height.
+ /// Used to trim the week number text based on available width and height.
+ /// Semantics information for the button.
+ /// The highlight text color.
+ internal SfIconView(SfIcon icon, ITextElement textStyle, string text, Color? borderColor, Color highlightColor, bool isSelected = false, bool isRTL = false, Func? getTrimWeekNumberText = null, bool isTabHighlight = false, SemanticsNode? semanticsNode = null, Color? highlightTextColor = null)
+ {
+#if __IOS__
+ IgnoreSafeArea = true;
+#endif
+ Icon = icon;
+ _textStyle = new TextStyle()
+ {
+ TextColor = textStyle.TextColor,
+ FontSize = textStyle.FontSize,
+ FontAttributes = textStyle.FontAttributes,
+ FontFamily = textStyle.FontFamily,
+ FontAutoScalingEnabled = textStyle.FontAutoScalingEnabled,
+ };
+ _text = text;
+ _isRTL = isRTL;
+ _isSelected = isSelected;
+ _borderColor = borderColor;
+ _highlightColor = highlightColor;
+ _highlightTextColor = highlightTextColor;
+ _getTrimWeekNumberText = getTrimWeekNumberText;
+ _isTabHighlight = isTabHighlight;
+ _visibility = true;
+ DrawingOrder = DrawingOrder.BelowContent;
+ _semanticsNode = semanticsNode;
+ }
+
+ #endregion
+
+ #region Properties
+
+ ///
+ /// Gets or sets the textstyle.
+ ///
+ internal TextStyle TextStyle
+ {
+ get
+ {
+ return _textStyle;
+ }
+
+ set
+ {
+ if (_textStyle.FontSize == value.FontSize && _textStyle.TextColor == value.TextColor && _textStyle.FontAttributes == value.FontAttributes && _textStyle.FontFamily == value.FontFamily && _textStyle.FontAutoScalingEnabled == value.FontAutoScalingEnabled)
+ {
+ return;
+ }
+
+ _textStyle = value;
+ InvalidateDrawable();
+ }
+ }
+
+ ///
+ /// Gets or sets the border color.
+ ///
+ internal Color? BorderColor
+ {
+ get
+ {
+ return _borderColor;
+ }
+
+ set
+ {
+ if (_borderColor == value)
+ {
+ return;
+ }
+
+ _borderColor = value;
+ InvalidateDrawable();
+ }
+ }
+
+ ///
+ /// Gets or sets the highlight color.
+ ///
+ internal Color HighlightColor
+ {
+ get
+ {
+ return _highlightColor;
+ }
+
+ set
+ {
+ if (_highlightColor == value)
+ {
+ return;
+ }
+
+ _highlightColor = value;
+ InvalidateDrawable();
+ }
+ }
+
+ ///
+ /// Gets or sets the icon highlight text color.
+ ///
+ internal Color? HighlightTextColor
+ {
+ get
+ {
+ return _highlightTextColor;
+ }
+
+ set
+ {
+ if (_highlightTextColor == value)
+ {
+ return;
+ }
+
+ _highlightTextColor = value;
+ InvalidateDrawable();
+ }
+ }
+
+ ///
+ /// Gets or sets the sficon text.
+ ///
+ internal string Text
+ {
+ get
+ {
+ return _text;
+ }
+
+ set
+ {
+ if (_text == value)
+ {
+ return;
+ }
+
+ _text = value;
+ }
+ }
+
+ ///
+ /// Gets or sets a value indicating whether the combobox dropdown is open or not.
+ ///
+ internal bool IsOpen
+ {
+ get
+ {
+ return _isOpen;
+ }
+
+ set
+ {
+ if (_isOpen == value)
+ {
+ return;
+ }
+
+ _isOpen = value;
+ }
+ }
+
+ ///
+ /// Gets or sets a value whether the is selected or not.
+ ///
+ internal bool IsSelected
+ {
+ get
+ {
+ return _isSelected;
+ }
+
+ set
+ {
+ if (_isSelected == value)
+ {
+ return;
+ }
+
+ _isSelected = value;
+ InvalidateDrawable();
+ }
+ }
+
+ ///
+ /// Gets or sets a value whether the is tab highlight or not.
+ ///
+ internal bool IsTabHighlight
+ {
+ get
+ {
+ return _isTabHighlight;
+ }
+
+ set
+ {
+ if (_isTabHighlight == value)
+ {
+ return;
+ }
+
+ _isTabHighlight = value;
+ InvalidateDrawable();
+ }
+ }
+
+ ///
+ /// Gets or sets a value whether the is RTL or not.
+ ///
+ internal bool IsRTL
+ {
+ get
+ {
+ return _isRTL;
+ }
+
+ set
+ {
+ if (_isRTL == value)
+ {
+ return;
+ }
+
+ _isRTL = value;
+ InvalidateDrawable();
+ }
+ }
+
+ ///
+ /// Gets or sets a value indicating whether the view is visible or not.
+ /// TODO: IsVisible property breaks in 6.0.400 release.
+ /// Issue link -https://github.com/dotnet/maui/issues/7507
+ /// -https://github.com/dotnet/maui/issues/8044
+ /// -https://github.com/dotnet/maui/issues/7482
+ ///
+ internal bool Visibility
+ {
+ get
+ {
+ return _visibility;
+ }
+
+ set
+ {
+#if !__MACCATALYST__
+ IsVisible = value;
+#endif
+ _visibility = value;
+ }
+ }
+
+ ///
+ /// Gets or sets the sficon.
+ ///
+ internal SfIcon Icon { get; set; }
+
+ ///
+ /// Gets or sets the view selection corner radius.
+ ///
+ internal double SelectionCornerRadius { get; set; }
+
+ #endregion
+
+ #region Internal Methods
+
+ ///
+ /// Method to update icon style.
+ ///
+ /// The text style value.
+ internal void UpdateStyle(ITextElement textStyle)
+ {
+ TextStyle = new TextStyle()
+ {
+ TextColor = textStyle.TextColor,
+ FontSize = textStyle.FontSize,
+ FontAttributes = textStyle.FontAttributes,
+ FontFamily = textStyle.FontFamily,
+ FontAutoScalingEnabled = textStyle.FontAutoScalingEnabled,
+ };
+ }
+
+ ///
+ /// Method to update icon color.
+ ///
+ /// The icon color.
+ internal void UpdateIconColor(Color iconColor)
+ {
+ TextStyle = new TextStyle()
+ {
+ TextColor = iconColor,
+ FontSize = _textStyle.FontSize,
+ FontAttributes = _textStyle.FontAttributes,
+ FontFamily = _textStyle.FontFamily,
+ FontAutoScalingEnabled = _textStyle.FontAutoScalingEnabled,
+ };
+ }
+
+ ///
+ /// Method to update semantics.
+ ///
+ /// Text.
+ /// To check istouch enabled or not.
+ internal void UpdateSemantics(string? text = null, bool? isTouchEnabled = null)
+ {
+ if (_semanticsNode == null)
+ {
+ return;
+ }
+
+ if (text != null && _semanticsNode.Text != text)
+ {
+ _semanticsNode.Text = text;
+ InvalidateSemantics();
+ }
+
+ if (isTouchEnabled != null && _semanticsNode.IsTouchEnabled != isTouchEnabled)
+ {
+ _semanticsNode.IsTouchEnabled = (bool)isTouchEnabled;
+ InvalidateSemantics();
+ }
+ }
+
+ #endregion
+
+ #region Private Methods
+
+ ///
+ /// Method to draw the forward icon.
+ ///
+ /// The icon width.
+ /// The icon height.
+ /// The draw canvas.
+ void DrawForward(float width, float height, ICanvas canvas)
+ {
+ float arrowHeight = (float)_iconSize;
+ float arrowWidth = arrowHeight / 2;
+ float centerPosition = height / 2;
+ float leftPosition = (width / 2) - (arrowWidth / 2);
+ float topPosition = (height / 2) - (arrowHeight / 2);
+ float rightPosition = (width / 2) + (arrowWidth / 2);
+ float bottomPosition = (height / 2) + (arrowHeight / 2);
+ PathF path = new PathF();
+ path.MoveTo(leftPosition, topPosition);
+ path.LineTo(rightPosition, centerPosition);
+ path.LineTo(leftPosition, bottomPosition);
+ canvas.DrawPath(path);
+ SelectionCornerRadius = 5;
+ }
+
+ ///
+ /// Method to draw the backward icon.
+ ///
+ /// The icon width.
+ /// The icon height.
+ /// The draw canvas.
+ void DrawBackward(float width, float height, ICanvas canvas)
+ {
+ float arrowHeight = (float)_iconSize;
+ float arrowWidth = arrowHeight / 2;
+ float centerPosition = height / 2;
+ float leftPosition = (width / 2) - (arrowWidth / 2);
+ float topPosition = (height / 2) - (arrowHeight / 2);
+ float rightPosition = (width / 2) + (arrowWidth / 2);
+ float bottomPosition = (height / 2) + (arrowHeight / 2);
+ PathF path = new PathF();
+ path.MoveTo(rightPosition, topPosition);
+ path.LineTo(leftPosition, centerPosition);
+ path.LineTo(rightPosition, bottomPosition);
+ canvas.DrawPath(path);
+ SelectionCornerRadius = 5;
+ }
+
+ ///
+ /// Method to draw the downward icon.
+ ///
+ /// The icon width.
+ /// The icon height.
+ /// The draw canvas.
+ void DrawDownward(float width, float height, ICanvas canvas)
+ {
+ float arrowWidth = (float)_iconSize;
+ float arrowHeight = arrowWidth / 2;
+ float centerPosition = width / 2;
+ float leftPosition = (width / 2) - (arrowWidth / 2);
+ float topPosition = (height / 2) - (arrowHeight / 2);
+ float rightPosition = (width / 2) + (arrowWidth / 2);
+ float bottomPosition = (height / 2) + (arrowHeight / 2);
+ PathF path = new PathF();
+ path.MoveTo(leftPosition, topPosition);
+ path.LineTo(centerPosition, bottomPosition);
+ path.LineTo(rightPosition, topPosition);
+ canvas.DrawPath(path);
+ }
+
+ ///
+ /// Method to draw the upward icon.
+ ///
+ /// The icon width.
+ /// The icon height.
+ /// The draw canvas.
+ void DrawUpward(float width, float height, ICanvas canvas)
+ {
+ float arrowWidth = (float)_iconSize;
+ float arrowHeight = arrowWidth / 2;
+ float centerPosition = width / 2;
+ float leftPosition = (width / 2) - (arrowWidth / 2);
+ float topPosition = (height / 2) - (arrowHeight / 2);
+ float rightPosition = (width / 2) + (arrowWidth / 2);
+ float bottomPosition = (height / 2) + (arrowHeight / 2);
+ PathF path = new PathF();
+ path.MoveTo(leftPosition, bottomPosition);
+ path.LineTo(centerPosition, topPosition);
+ path.LineTo(rightPosition, bottomPosition);
+ canvas.DrawPath(path);
+ }
+
+ ///
+ /// Method to draw the today icon.
+ ///
+ /// The icon width.
+ /// The icon height.
+ /// The draw canvas.
+ void DrawToday(float width, float height, ICanvas canvas)
+ {
+ float iconSize = (float)_iconSize;
+ float secondaryRectSize = iconSize / 5;
+ float leftPosition = (width / 2) - (iconSize / 2);
+ float rightPosition = leftPosition + iconSize;
+ float topPosition = (height / 2) - (iconSize / 2);
+ canvas.DrawRoundedRectangle(leftPosition, topPosition, iconSize, iconSize, 1);
+ canvas.DrawLine(leftPosition, topPosition + secondaryRectSize, rightPosition, topPosition + secondaryRectSize);
+ canvas.DrawLine(leftPosition + secondaryRectSize, topPosition, leftPosition + secondaryRectSize, topPosition - secondaryRectSize);
+ canvas.DrawLine(rightPosition - secondaryRectSize, topPosition, rightPosition - secondaryRectSize, topPosition - secondaryRectSize);
+ canvas.FillRectangle(leftPosition + secondaryRectSize, topPosition + (2 * secondaryRectSize), secondaryRectSize, secondaryRectSize);
+ }
+
+ ///
+ /// Method to draw the option icon.
+ ///
+ /// The icon width.
+ /// The icon height.
+ /// The draw canvas.
+ void DrawOption(float width, float height, ICanvas canvas)
+ {
+ float totalHeight = (float)_iconSize;
+ float radius = totalHeight / 8;
+ float centerYPosition = height / 2;
+ float centerXPosition = width / 2;
+ float topPosition = (height / 2) - (totalHeight / 2);
+ float bottomPosition = (height / 2) + (totalHeight / 2);
+ canvas.FillCircle(centerXPosition, topPosition + radius, radius);
+ canvas.FillCircle(centerXPosition, centerYPosition, radius);
+ canvas.FillCircle(centerXPosition, bottomPosition - radius, radius);
+ }
+
+ ///
+ /// Method to draw the option icon. Used for ToolBar button.
+ ///
+ /// The icon width.
+ /// The icon height.
+ /// The draw canvas.
+ void DrawMore(float width, float height, ICanvas canvas)
+ {
+ float radius = 1.2f;
+ float centerYPosition = height / 2;
+ float centerXPosition = width / 2;
+ canvas.FillCircle(centerXPosition - radius - 3, centerYPosition, radius);
+ canvas.FillCircle(centerXPosition, centerYPosition, radius);
+ canvas.FillCircle(centerXPosition + radius + 3, centerYPosition, radius);
+ }
+
+ ///
+ /// Method to draw the responsive UI today button.
+ ///
+ /// The button width.
+ /// The button height.
+ /// The draw canvas.
+ void DrawTodayButton(float width, float height, ICanvas canvas)
+ {
+ DrawTilesButton(width, height, canvas);
+ }
+
+ ///
+ /// Method to draw the divider view.
+ ///
+ /// The button width.
+ /// The button height.
+ /// The draw canvas.
+ void DrawDivider(float width, float height, ICanvas canvas)
+ {
+ canvas.StrokeSize = 1;
+ canvas.SaveState();
+ Color strokeColor = GetCellBorderColor();
+ canvas.StrokeColor = strokeColor;
+ canvas.FillColor = strokeColor;
+ float startPosition = 2;
+ canvas.DrawLine(width / 2, startPosition, width / 2, height - startPosition);
+ canvas.RestoreState();
+ }
+
+ ///
+ /// Get the stroke color based on cell border color.
+ ///
+ /// Return cell border color value.
+ Color GetCellBorderColor()
+ {
+ if (_borderColor == null)
+ {
+ return Colors.Transparent;
+ }
+ else if (_borderColor != Colors.Transparent)
+ {
+ return _borderColor;
+ }
+
+ return Colors.LightGray;
+ }
+
+ ///
+ /// Method to draw the combobox button.
+ ///
+ /// The button width.
+ /// The button height.
+ /// The draw canvas.
+ void DrawComboBox(float width, float height, ICanvas canvas)
+ {
+ var padding = 5;
+ var margin = 2 * padding;
+ var dropDownIConWidth = 10;
+ canvas.StrokeSize = 1;
+
+ float startPosition = 1;
+ canvas.SaveState();
+ TextStyle iconTextStyle = new TextStyle()
+ {
+ FontSize = _textStyle.FontSize,
+ TextColor = _isSelected ? HighlightColor : _textStyle.TextColor,
+ FontAttributes = _textStyle.FontAttributes,
+ FontFamily = _textStyle.FontFamily,
+ FontAutoScalingEnabled = _textStyle.FontAutoScalingEnabled
+ };
+ //// To show the borderline the start position will be 2, the height and width is adjusted to show the border.
+ DrawText(canvas, Text, iconTextStyle, new RectF(margin, 0, width - 2, height), HorizontalAlignment.Left, VerticalAlignment.Center);
+ canvas.RestoreState();
+
+ canvas.SaveState();
+ canvas.StrokeColor = iconTextStyle.TextColor;
+ canvas.FillColor = iconTextStyle.TextColor;
+ //// To show the borderline the start position will be 2, the width and height is adjusted to show the border. The corner radius is 5
+ SelectionCornerRadius = 5;
+ canvas.DrawRoundedRectangle(startPosition, startPosition, width - 2, height - 2, (float)SelectionCornerRadius);
+ canvas.RestoreState();
+ canvas.StrokeColor = _textStyle.TextColor.WithAlpha(0.5f);
+ canvas.FillColor = _textStyle.TextColor.WithAlpha(0.5f);
+
+ if (!IsOpen)
+ {
+ canvas.DrawInverseTriangle(new RectF(width - (2 * margin), (height - 4) / 2, dropDownIConWidth, dropDownIConWidth / 2), false);
+ }
+ else
+ {
+ canvas.DrawTriangle(new RectF(width - (2 * margin), (height - 4) / 2, dropDownIConWidth, dropDownIConWidth / 2), false);
+ }
+ }
+
+ ///
+ /// Method to draw text.
+ ///
+ /// The draw canvas.
+ /// The draw text.
+ /// The textstyle.
+ /// Rect value.
+ /// The horizontal alignment.
+ /// The vertical alignemnt.
+ void DrawText(ICanvas canvas, string text, ITextElement textStyle, Rect rect, HorizontalAlignment horizontalAlignment = HorizontalAlignment.Left, VerticalAlignment verticalAlignment = VerticalAlignment.Top)
+ {
+ if (rect.Height <= 0 || rect.Width <= 0)
+ {
+ return;
+ }
+
+ canvas.DrawText(text, rect, horizontalAlignment, verticalAlignment, textStyle);
+ }
+
+ ///
+ /// Method to draw the tiles button.
+ ///
+ /// The button width.
+ /// The button height.
+ /// The draw canvas.
+ void DrawTilesButton(float width, float height, ICanvas canvas)
+ {
+ canvas.StrokeSize = 1;
+ Color textColor, strokeColor;
+
+ if (_isSelected)
+ {
+ textColor = _highlightTextColor ?? HighlightColor;
+ strokeColor = HighlightColor;
+ }
+ else
+ {
+ textColor = _textStyle.TextColor;
+ strokeColor = GetCellBorderColor();
+ }
+
+ float startPosition = 1;
+ bool isBorderEnabled = strokeColor != Colors.Transparent;
+ if (_isTabHighlight && _textStyle.TextColor != Colors.Transparent)
+ {
+ float highlightHeight = 3;
+ double highlightCorner = height / 2;
+ float highlightPadding = width * 0.3f;
+ float highlightWidth = width * 0.4f;
+ Rect highlight = new Rect(highlightPadding, height - highlightHeight - (isBorderEnabled ? 1 : 0), highlightWidth, highlightHeight);
+ canvas.FillColor = _textStyle.TextColor;
+ canvas.FillRoundedRectangle(highlight, highlightCorner, highlightCorner, highlightCorner, highlightCorner);
+ }
+
+ if (isBorderEnabled || IsSelected)
+ {
+ canvas.SaveState();
+ canvas.StrokeColor = strokeColor;
+ canvas.FillColor = strokeColor;
+
+ //// To show the borderline the start position will be 2, the width and height is adjusted to show the border. The corner radius is 5
+ SelectionCornerRadius = height / 2;
+ if (IsSelected)
+ {
+ canvas.FillColor = HighlightColor;
+ canvas.FillRoundedRectangle(startPosition, startPosition, width - 2, height - 2, (float)SelectionCornerRadius);
+ }
+ else
+ {
+ canvas.DrawRoundedRectangle(startPosition, startPosition, width - 2, height - 2, (float)SelectionCornerRadius);
+ }
+
+ canvas.RestoreState();
+ }
+
+ canvas.SaveState();
+ TextStyle iconTextStyle = new TextStyle() { FontSize = _textStyle.FontSize, TextColor = textColor, FontAttributes = _textStyle.FontAttributes, FontFamily = _textStyle.FontFamily , FontAutoScalingEnabled = _textStyle.FontAutoScalingEnabled };
+ //// To show the borderline the start position will be 2, the height is adjusted to show the border.
+ DrawText(canvas, Text, iconTextStyle, new RectF(startPosition, startPosition, width - 2, height - 2), HorizontalAlignment.Center, VerticalAlignment.Center);
+ canvas.RestoreState();
+ }
+
+ ///
+ /// Method to draw the week number text.
+ ///
+ /// The button width.
+ /// The button height.
+ /// The draw canvas.
+ void DrawWeekNumber(float width, float height, ICanvas canvas)
+ {
+ string text = Text;
+ if (_getTrimWeekNumberText != null)
+ {
+ text = _getTrimWeekNumberText(width, text);
+ }
+
+#if WINDOWS
+ //// TODO: horizontal and vertical alignments are not center aligned with draw text method. Hence used pading values explicitly.
+ HorizontalAlignment alignment = _isRTL ? HorizontalAlignment.Right : HorizontalAlignment.Left;
+ double startPosition = _isRTL ? 0 : 5;
+ DrawText(canvas, text, _textStyle, new Rect(startPosition, 1, width - 5, height - 1), alignment, VerticalAlignment.Top);
+#else
+ DrawText(canvas, text, _textStyle, new RectF(0, 0, width, height), HorizontalAlignment.Center, VerticalAlignment.Center);
+#endif
+ }
+
+ ///
+ /// Draw clear button method.
+ ///
+ /// The canvas.
+ /// The rect.
+ void DrawClearButton(ICanvas canvas, RectF rectF)
+ {
+ PointF leftline = new Point(0, 0);
+ PointF rightline = new Point(0, 0);
+ float padding = 15f;
+
+ leftline.X = rectF.X + padding;
+ leftline.Y = rectF.Y + padding;
+ rightline.X = rectF.X + rectF.Width - padding;
+ rightline.Y = rectF.Y + rectF.Height - padding;
+ canvas.DrawLine(leftline, rightline);
+
+ leftline.X = rectF.X + padding;
+ leftline.Y = rectF.Y + rectF.Height - padding;
+ rightline.X = rectF.X + rectF.Width - padding;
+ rightline.Y = rectF.Y + padding;
+ canvas.DrawLine(leftline, rightline);
+ }
+
+ #endregion
+
+ #region Override Methods
+
+ ///
+ /// Method to draw the sficon.
+ ///
+ /// The draw canvas.
+ /// The rectangle.
+ protected override void OnDraw(ICanvas canvas, RectF dirtyRect)
+ {
+#if __MACCATALYST__
+ //// TODO: IsVisibility property breaks in 6.0.400 release.
+ if (!Visibility)
+ {
+ return;
+ }
+
+#endif
+ canvas.SaveState();
+
+ canvas.StrokeSize = 1.5f;
+ canvas.FillColor = _textStyle.TextColor;
+ canvas.StrokeColor = _textStyle.TextColor;
+ float width = dirtyRect.Width;
+ float height = dirtyRect.Height;
+ switch (Icon)
+ {
+ case SfIcon.Forward:
+ {
+ DrawForward(width, height, canvas);
+ break;
+ }
+
+ case SfIcon.Backward:
+ {
+ DrawBackward(width, height, canvas);
+ break;
+ }
+
+ case SfIcon.Downward:
+ {
+ DrawDownward(width, height, canvas);
+ break;
+ }
+
+ case SfIcon.Upward:
+ {
+ DrawUpward(width, height, canvas);
+ break;
+ }
+
+ case SfIcon.Today:
+ {
+ DrawToday(width, height, canvas);
+ break;
+ }
+
+ case SfIcon.Option:
+ {
+ DrawOption(width, height, canvas);
+ break;
+ }
+
+#if __MACCATALYST__ || (!__ANDROID__ && !__IOS__)
+ case SfIcon.Button:
+ {
+ DrawTilesButton(width, height, canvas);
+ break;
+ }
+#endif
+ case SfIcon.ComboBox:
+ {
+ DrawComboBox(width, height, canvas);
+ break;
+ }
+
+ case SfIcon.TodayButton:
+ {
+ DrawTodayButton(width, height, canvas);
+ break;
+ }
+
+ case SfIcon.Divider:
+ {
+ DrawDivider(width, height, canvas);
+ break;
+ }
+
+ case SfIcon.WeekNumber:
+ {
+ DrawWeekNumber(width, height, canvas);
+ break;
+ }
+ case SfIcon.Cancel:
+ {
+ DrawClearButton(canvas, dirtyRect);
+ break;
+ }
+ case SfIcon.More:
+ {
+ DrawMore(width, height, canvas);
+ break;
+ }
+ }
+
+ canvas.RestoreState();
+ }
+
+ protected override List? GetSemanticsNodesCore(double width, double height)
+ {
+ if (_semanticsNode == null)
+ {
+ return null;
+ }
+
+ Rect rect = new Rect(0, 0, width, height);
+ _semanticsNode.Bounds = rect;
+ return new List() { _semanticsNode };
+ }
+
+ #endregion
+ }
+}
\ No newline at end of file
diff --git a/maui/src/Core/PickerView/Handler/SfPickerScrollViewHandler.Android.cs b/maui/src/Core/PickerView/Handler/SfPickerScrollViewHandler.Android.cs
new file mode 100644
index 00000000..78a4c20a
--- /dev/null
+++ b/maui/src/Core/PickerView/Handler/SfPickerScrollViewHandler.Android.cs
@@ -0,0 +1,208 @@
+using Android.Content;
+using Android.OS;
+using Android.Views;
+using Java.Lang;
+using Microsoft.Maui.Handlers;
+using Microsoft.Maui.Platform;
+
+namespace Syncfusion.Maui.Toolkit.Internals
+{
+ ///
+ /// The ScrollViewHandler for .
+ ///
+ internal partial class SfPickerScrollViewHandler : ScrollViewHandler
+ {
+ #region Override Method
+
+ ///
+ /// Creates native scroll view.
+ ///
+ /// Instance of native scroll view.
+ protected override MauiScrollView CreatePlatformView()
+ {
+ NativeCustomScrolLayout nativeScrollView = new NativeCustomScrolLayout(Context, VirtualView);
+ // Set to avoid overlapping control issue when scrolling.
+ nativeScrollView.ClipToOutline = true;
+ return nativeScrollView;
+ }
+
+ #endregion
+ }
+
+ ///
+ /// Represents a class which contains the information of native scroll view.
+ ///
+ internal class NativeCustomScrolLayout : MauiScrollView
+ {
+ #region Fields
+
+ ///
+ /// A handler used to send and process message and runnable objects associated with a thread's message queue.
+ ///
+ Handler? _handler;
+
+ ///
+ /// The runnable instance.
+ ///
+ Runnable? _runnable;
+
+ ///
+ /// The pickerView instance.
+ ///
+ readonly SfPickerView? _pickerView;
+
+ ///
+ /// The previous scroll y position default value is 0. This value is used to check whether the current scroll position is end scroll position or not while the motion event status up or cancel.
+ ///
+ float _previousScrollY = 0;
+
+ #endregion
+
+ #region Constructor
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The .
+ /// The parent scroll view.
+ internal NativeCustomScrolLayout(Context context, IScrollView scrollView) : base(context)
+ {
+ SfPickerView? scrollLayout = scrollView as SfPickerView;
+ _pickerView = scrollLayout;
+ }
+
+ #endregion
+
+ #region Private Methods
+
+ ///
+ /// Method to call while runnable is running.
+ ///
+ void Run()
+ {
+ //// Check previous scroll position and the current scroll position is same or not. If it is same then it is end scroll position.
+ if (_previousScrollY - ScrollY == 0)
+ {
+ _pickerView?.OnPickerViewScrollStart();
+ _pickerView?.OnPickerViewScrollEnd(GetEndScrollPosition());
+ }
+ else
+ {
+ _previousScrollY = ScrollY;
+ if (_runnable != null)
+ {
+ _handler?.PostDelayed(_runnable, 150);
+ }
+ }
+ }
+
+ ///
+ /// Method to get the end scroll position based on the pixels.
+ ///
+ ///
+ int GetEndScrollPosition()
+ {
+ Func fromPixels = Android.App.Application.Context.FromPixels;
+ double endScrollPosition = fromPixels(ScrollY);
+ return (int)endScrollPosition;
+ }
+
+ #endregion
+
+ #region Override Methods
+
+ ///
+ /// Method to handle the on touch event.
+ ///
+ /// The motion event.
+ ///
+ public override bool OnTouchEvent(MotionEvent? action)
+ {
+ if (action == null)
+ {
+ return base.OnTouchEvent(action);
+ }
+
+ if (action.Action == MotionEventActions.Move)
+ {
+ _previousScrollY = ScrollY;
+ _pickerView?.OnPickerViewScrollStart();
+ }
+ else if (action.Action == MotionEventActions.Up || action.Action == MotionEventActions.Cancel)
+ {
+ //// Check previous scroll position and the current scroll position is same or not. If it is same then it is end scroll position.
+ if (_previousScrollY == ScrollY)
+ {
+ _pickerView?.OnPickerViewScrollStart();
+ _pickerView?.OnPickerViewScrollEnd(GetEndScrollPosition());
+ if (_handler != null)
+ {
+ if (_runnable != null)
+ {
+ _handler.RemoveCallbacks(_runnable);
+ }
+
+ _handler.Dispose();
+ _handler = null;
+ }
+
+ if (_runnable != null)
+ {
+ _runnable.Dispose();
+ _runnable = null;
+ }
+
+ return base.OnTouchEvent(action);
+ }
+
+ _previousScrollY = ScrollY;
+ _runnable = new Runnable(Run);
+ _handler = new Handler(Looper.MainLooper!);
+ _handler?.PostDelayed(_runnable, 200);
+ }
+
+ return base.OnTouchEvent(action);
+ }
+
+ ///
+ /// Return false to skip the motion events(mouse wheel scroll) for scroll view.
+ ///
+ /// Motion event.
+ /// Return false to skip the mouse wheel scroll.
+ public override bool OnGenericMotionEvent(MotionEvent? e)
+ {
+ return false;
+ }
+
+ ///
+ /// Method to use to dispose the handler and runnable instance.
+ ///
+ ///
+ protected override void Dispose(bool disposing)
+ {
+ if (disposing)
+ {
+ if (_handler != null)
+ {
+ if (_runnable != null)
+ {
+ _handler.RemoveCallbacks(_runnable);
+ }
+
+ _handler.Dispose();
+ _handler = null;
+ }
+
+ if (_runnable != null)
+ {
+ _runnable.Dispose();
+ _runnable = null;
+ }
+ }
+
+ base.Dispose(disposing);
+ }
+
+ #endregion
+ }
+}
\ No newline at end of file
diff --git a/maui/src/Core/PickerView/Handler/SfPickerScrollViewHandler.Standard.cs b/maui/src/Core/PickerView/Handler/SfPickerScrollViewHandler.Standard.cs
new file mode 100644
index 00000000..8d33c079
--- /dev/null
+++ b/maui/src/Core/PickerView/Handler/SfPickerScrollViewHandler.Standard.cs
@@ -0,0 +1,11 @@
+using Microsoft.Maui.Handlers;
+
+namespace Syncfusion.Maui.Toolkit.Internals
+{
+ ///
+ /// The ScrollViewHandler for .
+ ///
+ internal partial class SfPickerScrollViewHandler : ScrollViewHandler
+ {
+ }
+}
\ No newline at end of file
diff --git a/maui/src/Core/PickerView/Handler/SfPickerScrollViewHandler.Windows.cs b/maui/src/Core/PickerView/Handler/SfPickerScrollViewHandler.Windows.cs
new file mode 100644
index 00000000..8a45781e
--- /dev/null
+++ b/maui/src/Core/PickerView/Handler/SfPickerScrollViewHandler.Windows.cs
@@ -0,0 +1,76 @@
+using Microsoft.Maui.Handlers;
+using Microsoft.UI.Xaml.Controls;
+using Microsoft.UI.Xaml.Input;
+
+namespace Syncfusion.Maui.Toolkit.Internals
+{
+ ///
+ /// The ScrollViewHandler for .
+ ///
+ internal partial class SfPickerScrollViewHandler : ScrollViewHandler
+ {
+ #region Private Methods
+
+ ///
+ /// Raised when manipulation such like scroll action.
+ ///
+ /// Instance of nativeView.
+ /// Arguments corresponding to ViewChange.
+ void OnPlatformViewChanged(object? sender, ScrollViewerViewChangedEventArgs e)
+ {
+ //// The IsIntermediate is false then the scroll is ended.
+ if (!e.IsIntermediate)
+ {
+ SfPickerView? scrollLayout = VirtualView as SfPickerView;
+ ScrollViewer? nativeScrollView = sender as ScrollViewer;
+ if (nativeScrollView == null || scrollLayout == null)
+ {
+ return;
+ }
+
+ scrollLayout.OnPickerViewScrollEnd(nativeScrollView.VerticalOffset);
+ }
+ }
+
+ ///
+ /// Prevents the keyboard key press by setting the e.handled value as true.
+ ///
+ /// Instance of nativeView.
+ /// Arguments of key routed event args.
+ void OnScrollViewKeyBoardKeyPressed(object sender, KeyRoutedEventArgs e)
+ {
+ SfPickerView? scrollLayout = VirtualView as SfPickerView;
+ scrollLayout?.HandleScrollViewKeyPress(e);
+ }
+
+ #endregion
+
+ #region Override Methods
+
+ ///
+ /// Connects the handler.
+ ///
+ /// Instance of platformView.
+ protected override void ConnectHandler(ScrollViewer platformView)
+ {
+ //// Using this event to identity the scroll view scroll is completed/end.
+ platformView.ViewChanged += OnPlatformViewChanged;
+ //// Using this event we can prevent the keyboard key press.
+ platformView.PreviewKeyDown += OnScrollViewKeyBoardKeyPressed;
+ base.ConnectHandler(platformView);
+ }
+
+ ///
+ /// Disconnects the handler.
+ ///
+ /// Instance of platform view.
+ protected override void DisconnectHandler(ScrollViewer platformView)
+ {
+ platformView.ViewChanged -= OnPlatformViewChanged;
+ platformView.PreviewKeyDown -= OnScrollViewKeyBoardKeyPressed;
+ base.DisconnectHandler(platformView);
+ }
+
+ #endregion
+ }
+}
\ No newline at end of file
diff --git a/maui/src/Core/PickerView/Handler/SfPickerScrollViewHandler.iOS.cs b/maui/src/Core/PickerView/Handler/SfPickerScrollViewHandler.iOS.cs
new file mode 100644
index 00000000..0e1455e2
--- /dev/null
+++ b/maui/src/Core/PickerView/Handler/SfPickerScrollViewHandler.iOS.cs
@@ -0,0 +1,91 @@
+using Microsoft.Maui.Handlers;
+using UIKit;
+
+namespace Syncfusion.Maui.Toolkit.Internals
+{
+ ///
+ /// The ScrollViewHandler for .
+ ///
+ internal partial class SfPickerScrollViewHandler : ScrollViewHandler
+ {
+ #region Fields
+
+ ///
+ /// The proxy object for handling picker scroll view events.
+ ///
+ PickerScrollViewProxy? _proxy;
+
+ #endregion
+
+ #region Override Methods
+
+ ///
+ /// Connects the handler.
+ ///
+ /// Instance of platformView.
+ protected override void ConnectHandler(UIScrollView platformView)
+ {
+ if (_proxy == null && VirtualView is SfPickerView view)
+ {
+ _proxy = new PickerScrollViewProxy(view);
+ }
+
+ platformView.WillEndDragging += _proxy!.OnPlatformViewEndDragging;
+
+ base.ConnectHandler(platformView);
+ }
+
+ ///
+ /// Disconnects the handler.
+ ///
+ /// Instance of platform view.
+ protected override void DisconnectHandler(UIScrollView platformView)
+ {
+ if (_proxy != null)
+ {
+ platformView.WillEndDragging -= _proxy.OnPlatformViewEndDragging;
+ }
+
+ base.DisconnectHandler(platformView);
+ }
+
+ #endregion
+ }
+
+ ///
+ /// Proxy class to handle scroll view events for the picker.
+ ///
+ internal class PickerScrollViewProxy
+ {
+ #region Fields
+
+ ///
+ /// A weak reference to the associated SfPickerView instance.
+ ///
+ readonly WeakReference _view;
+
+ #endregion
+
+ #region Internal Methods
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The SfPickerView instance to associate with this proxy.
+ internal PickerScrollViewProxy(SfPickerView view) => _view = new(view);
+
+ ///
+ /// Handles the end of dragging event for the platform view.
+ ///
+ /// The object that raised the event.
+ /// The event arguments containing dragging information.
+ internal void OnPlatformViewEndDragging(object? sender, WillEndDraggingEventArgs e)
+ {
+ _view.TryGetTarget(out var view);
+ SfPickerView? scrollLayout = view;
+ scrollLayout?.OnPickerViewScrollEnd((int)e.TargetContentOffset.Y);
+ }
+
+ #endregion
+ }
+}
\ No newline at end of file
diff --git a/maui/src/Core/PickerView/SfPickerView.cs b/maui/src/Core/PickerView/SfPickerView.cs
new file mode 100644
index 00000000..4347182e
--- /dev/null
+++ b/maui/src/Core/PickerView/SfPickerView.cs
@@ -0,0 +1,59 @@
+using Microsoft.Maui.Layouts;
+
+namespace Syncfusion.Maui.Toolkit.Internals
+{
+ ///
+ /// Represents an abstract base class for .
+ ///
+#if NET8_0_OR_GREATER && (IOS || MACCATALYST)
+ internal abstract class SfPickerView : ScrollView, ICrossPlatformLayout
+#else
+ internal abstract class SfPickerView : ScrollView
+#endif
+ {
+ ///
+ /// Called when the picker view scroll ends.
+ ///
+ /// The scroll end position.
+ internal abstract void OnPickerViewScrollEnd(double scrollEndPosition);
+
+ ///
+ /// Called when the picker view scroll starts.
+ ///
+ internal abstract void OnPickerViewScrollStart();
+
+ //// We are virtualized the SfPickerView control, the children is arranged based on the scrolled position. The scroll view content is SfView. The SfView content is SfDrawableView. The SfDrawableView content is SfPickerItems.
+ //// In this implementation, the blank issue is occurred while scrolling the SfPickerView.
+ //// While scrolling, the children is not arranged based on the scrolled position. So that rendering is blank.
+ //// This issue is not reproducible in .net6.0 and .net7.0. So, we have overridden the CrossPlatformArrange method to resolve this issue.
+#if NET8_0_OR_GREATER && (IOS || MACCATALYST)
+ ///
+ /// Overdried this method due to .net8 blank issue.
+ ///
+ /// Size of scroll view.
+ Size ICrossPlatformLayout.CrossPlatformArrange(Rect bounds)
+ {
+ // 855697 - Checked, from where container bounds changes and overdried this method.
+ // from ScrollViewHandler ICrossPlatformLayout.CrossPlatformArrange native bounds used instead of frame.
+ if (this is IScrollView scrollView)
+ {
+ bounds.X = 0;
+ bounds.Y = 0;
+ return scrollView.ArrangeContentUnbounded(bounds);
+ }
+
+ return bounds.Size;
+ }
+#endif
+
+#if WINDOWS
+ ///
+ /// Handles key press events for the scroll view on Windows platforms.
+ ///
+ /// The key routed event arguments.
+ internal virtual void HandleScrollViewKeyPress(Microsoft.UI.Xaml.Input.KeyRoutedEventArgs e)
+ {
+ }
+#endif
+ }
+}
\ No newline at end of file
diff --git a/maui/src/Core/Theme/Resources/DefaultTheme.xaml b/maui/src/Core/Theme/Resources/DefaultTheme.xaml
index f4fbf585..6049f725 100644
--- a/maui/src/Core/Theme/Resources/DefaultTheme.xaml
+++ b/maui/src/Core/Theme/Resources/DefaultTheme.xaml
@@ -18,7 +18,7 @@
-
+
@@ -203,8 +203,11 @@
CommonTheme
-
-
+
+
+
+
+
@@ -540,5 +543,127 @@
+
+
+ CommonTheme
+
+
+
+
+
+
+
+
+ CommonTheme
+
+
+
+
+ 0
+ 0
+ 0
+
+
+ CommonTheme
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 16
+ 14
+ 20
+ 14
+ 16
+ 16
+
+
+ CommonTheme
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 16
+ 14
+ 14
+ 16
+ 16
+ 20
+
+
+ CommonTheme
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 16
+ 14
+ 14
+ 16
+ 16
+ 20
+
+
+ CommonTheme
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 14
+ 14
+ 14
+ 14
+ 16
+ 16
+ 20
diff --git a/maui/src/Core/WindowOverlay/WindowOverlay.Android.cs b/maui/src/Core/WindowOverlay/WindowOverlay.Android.cs
index dee83726..ea3147c5 100644
--- a/maui/src/Core/WindowOverlay/WindowOverlay.Android.cs
+++ b/maui/src/Core/WindowOverlay/WindowOverlay.Android.cs
@@ -19,11 +19,6 @@ internal partial class SfWindowOverlay
PlatformRect? _decorViewFrame;
float _density = 1f;
- ///
- /// Platform view of overlay container.
- ///
- WindowOverlayStack? _overlayStack;
-
///
/// WindowManagerLayoutParams for Window overlay.
///
@@ -36,6 +31,20 @@ internal partial class SfWindowOverlay
#endregion
+ #region Internal Fields
+
+ ///
+ /// List of window overlay stacks.
+ ///
+ internal static List? ViewList;
+
+ ///
+ /// Platform view of overlay container.
+ ///
+ internal WindowOverlayStack? _overlayStack;
+
+ #endregion
+
#region Internal Methods
///
@@ -180,6 +189,11 @@ internal void RemoveFromWindow()
_windowManager.RemoveView(_overlayStack);
}
+ if (ViewList is not null && ViewList.Contains(_overlayStack))
+ {
+ ViewList.Remove(_overlayStack);
+ }
+
_windowManagerLayoutParams = null;
_overlayStack = null;
}
@@ -187,6 +201,12 @@ internal void RemoveFromWindow()
_decorViewFrame?.Dispose();
_decorViewFrame = null;
_hasOverlayStackInRoot = false;
+
+ // Disposing the view list if there is no overlay stack in the collection.
+ if (ViewList is not null && ViewList.Count is 0)
+ {
+ ViewList = null;
+ }
}
///
@@ -368,6 +388,16 @@ void Initialize()
GetWindowManagerLayoutParams();
}
+ // Stacking the popup overlay added in the application into the static collection to handle the blur effect in multiple popup scenarios.
+ if (ViewList is null)
+ {
+ ViewList = [_overlayStack];
+ }
+ else
+ {
+ ViewList.Add(_overlayStack);
+ }
+
var platformWindow = WindowOverlayHelper.GetPlatformWindow();
if (platformWindow is not null && platformWindow.WindowManager is not null)
{
diff --git a/maui/src/EffectsView/RippleEffectLayer.cs b/maui/src/EffectsView/RippleEffectLayer.cs
index dba2928c..d15cbcfe 100644
--- a/maui/src/EffectsView/RippleEffectLayer.cs
+++ b/maui/src/EffectsView/RippleEffectLayer.cs
@@ -99,9 +99,7 @@ internal void DrawRipple(ICanvas canvas, RectF dirtyRect)
/// The rectangle.
/// The color.
/// The clip bounds value.
-#pragma warning disable IDE0060 // Remove unused parameter
internal void DrawRipple(ICanvas canvas, RectF dirtyRect, Brush color, bool clipBounds = false)
-#pragma warning restore IDE0060 // Remove unused parameter
{
if (_rippleColor != null)
{
diff --git a/maui/src/NavigationDrawer/SfNavigationDrawer.cs b/maui/src/NavigationDrawer/SfNavigationDrawer.cs
index f0352eca..070cf36c 100644
--- a/maui/src/NavigationDrawer/SfNavigationDrawer.cs
+++ b/maui/src/NavigationDrawer/SfNavigationDrawer.cs
@@ -27,6 +27,8 @@ public partial class SfNavigationDrawer : SfNavigationDrawerExt, ITouchListener,
SfGrid? _drawerLayout;
+ SfGrid? _mainContentGrid;
+
double _touchRightThreshold;
double _touchBottomThreshold;
@@ -187,6 +189,7 @@ public SfNavigationDrawer()
ThemeElement.InitializeThemeResources(this, "SfNavigationDrawerTheme");
InitializeDrawer();
InitializeGreyOverlayGrid();
+ InitializeContentViewGrid();
UpdateAllChild();
PositionUpdate();
this.AddTouchListener(this);
@@ -459,11 +462,11 @@ public void ToggleDrawer()
_drawerLayout.AbortAnimation("drawerAnimation");
}
- if (ContentView != null)
+ if (_mainContentGrid != null)
{
- if (ContentView.AnimationIsRunning("contentViewTranslatePushAnimation"))
+ if (_mainContentGrid.AnimationIsRunning("contentViewTranslatePushAnimation"))
{
- ContentView.AbortAnimation("contentViewTranslatePushAnimation");
+ _mainContentGrid.AbortAnimation("contentViewTranslatePushAnimation");
}
}
@@ -640,15 +643,21 @@ void UpdateAllChild()
#if !WINDOWS
UpdateDrawerFlowDirection();
#endif
+ if (_mainContentGrid != null)
+ {
+ _mainContentGrid.Children.Clear();
+ _mainContentGrid.Children.Add(ContentView);
+ }
+
if (DrawerSettings.Transition == Transition.Reveal)
{
AddChild(_drawerLayout);
- AddChild(ContentView);
+ AddChild(_mainContentGrid);
AddChild(_greyOverlayGrid);
}
else
{
- AddChild(ContentView);
+ AddChild(_mainContentGrid);
AddChild(_greyOverlayGrid);
AddChild(_drawerLayout);
}
@@ -688,6 +697,16 @@ void InitializeGreyOverlayGrid()
_greyOverlayGrid = new SfGrid() { BackgroundColor = GreyOverlayColor, Opacity = 0, };
}
+ void InitializeContentViewGrid()
+ {
+ _mainContentGrid = new SfGrid()
+ {
+ HorizontalOptions = LayoutOptions.Fill,
+ VerticalOptions = LayoutOptions.Fill,
+ Background = Colors.Transparent
+ };
+ }
+
void OnHandleTouchInteraction(PointerActions action, Point point)
{
if (!DrawerSettings.EnableSwipeGesture)
@@ -987,6 +1006,7 @@ void OnDefaultDrawerSettingsChanged()
DurationPropertyUpdate(DrawerSettings.Duration);
PositionUpdate();
UpdateContentBackground();
+ UpdateTransitionAnimation();
}
}
@@ -1173,24 +1193,24 @@ void PositionUpdate()
{
SetVisibility(true);
_drawerLayout.TranslationX = ScreenWidth - DrawerSettings.DrawerWidth;
- if (DrawerSettings.Transition == Transition.SlideOnTop && ContentView != null)
+ if (DrawerSettings.Transition == Transition.SlideOnTop && _mainContentGrid != null)
{
_greyOverlayGrid.TranslationX = 0;
- ContentView.TranslationX = 0;
+ _mainContentGrid.TranslationX = 0;
}
- if ((DrawerSettings.Transition == Transition.Push || DrawerSettings.Transition == Transition.Reveal) && ContentView != null)
+ if ((DrawerSettings.Transition == Transition.Push || DrawerSettings.Transition == Transition.Reveal) && _mainContentGrid != null)
{
- ContentView.TranslationX = -DrawerSettings.DrawerWidth;
+ _mainContentGrid.TranslationX = -DrawerSettings.DrawerWidth;
_greyOverlayGrid.TranslationX = -DrawerSettings.DrawerWidth;
- ContentView.TranslationY = 0;
+ _mainContentGrid.TranslationY = 0;
_greyOverlayGrid.TranslationY = 0;
}
}
else
{
#if WINDOWS || MACCATALYST
- if ((!_drawerLayout.AnimationIsRunning("drawerAnimation") && !_greyOverlayGrid.AnimationIsRunning("greyOverlayTranslateAnimation")) || (DrawerSettings.Transition == Transition.Reveal && !ContentView.AnimationIsRunning("contentViewTranslatePushAnimation")))
+ if ((!_drawerLayout.AnimationIsRunning("drawerAnimation") && !_greyOverlayGrid.AnimationIsRunning("greyOverlayTranslateAnimation")) || (DrawerSettings.Transition == Transition.Reveal && !_mainContentGrid.AnimationIsRunning("contentViewTranslatePushAnimation")))
#endif
{
SetVisibility(false);
@@ -1198,18 +1218,18 @@ void PositionUpdate()
_greyOverlayGrid.TranslationX = ScreenWidth;
- if (DrawerSettings.Transition == Transition.Reveal && ContentView != null)
+ if (DrawerSettings.Transition == Transition.Reveal && _mainContentGrid != null)
{
_drawerLayout.TranslationX = ScreenWidth - DrawerSettings.DrawerWidth;
- ContentView.TranslationX = 0;
- ContentView.BackgroundColor = ContentView.BackgroundColor ?? ContentBackgroundColor;
+ _mainContentGrid.TranslationX = 0;
+ _mainContentGrid.BackgroundColor = _mainContentGrid.BackgroundColor ?? ContentBackgroundColor;
}
else
{
_drawerLayout.TranslationX = ScreenWidth;
- if (DrawerSettings.Transition == Transition.Push && ContentView != null)
+ if (DrawerSettings.Transition == Transition.Push && _mainContentGrid != null)
{
- ContentView.TranslationX = 0;
+ _mainContentGrid.TranslationX = 0;
}
}
}
@@ -1230,41 +1250,41 @@ void PositionUpdate()
{
SetVisibility(true);
_drawerLayout.TranslationX = 0;
- if (DrawerSettings.Transition == Transition.SlideOnTop && ContentView != null)
+ if (DrawerSettings.Transition == Transition.SlideOnTop && _mainContentGrid != null)
{
_greyOverlayGrid.TranslationX = 0;
- ContentView.TranslationX = 0;
+ _mainContentGrid.TranslationX = 0;
}
- else if ((DrawerSettings.Transition == Transition.Push || DrawerSettings.Transition == Transition.Reveal) && ContentView != null)
+ else if ((DrawerSettings.Transition == Transition.Push || DrawerSettings.Transition == Transition.Reveal) && _mainContentGrid != null)
{
_greyOverlayGrid.TranslationX = DrawerSettings.DrawerWidth;
- ContentView.TranslationX = DrawerSettings.DrawerWidth;
- ContentView.TranslationY = 0;
+ _mainContentGrid.TranslationX = DrawerSettings.DrawerWidth;
+ _mainContentGrid.TranslationY = 0;
_greyOverlayGrid.TranslationY = 0;
}
}
else
{
#if WINDOWS || MACCATALYST
- if ((!_drawerLayout.AnimationIsRunning("drawerAnimation") && !_greyOverlayGrid.AnimationIsRunning("greyOverlayTranslateAnimation")) || (DrawerSettings.Transition == Transition.Reveal && !ContentView.AnimationIsRunning("contentViewTranslatePushAnimation")))
+ if ((!_drawerLayout.AnimationIsRunning("drawerAnimation") && !_greyOverlayGrid.AnimationIsRunning("greyOverlayTranslateAnimation")) || (DrawerSettings.Transition == Transition.Reveal && !_mainContentGrid.AnimationIsRunning("contentViewTranslatePushAnimation")))
#endif
{
SetVisibility(false);
}
_greyOverlayGrid.TranslationX = ScreenWidth;
- if (DrawerSettings.Transition == Transition.Reveal && ContentView != null)
+ if (DrawerSettings.Transition == Transition.Reveal && _mainContentGrid != null)
{
_drawerLayout.TranslationX = 0;
- ContentView.TranslationX = 0;
- ContentView.BackgroundColor = ContentView.BackgroundColor ?? ContentBackgroundColor;
+ _mainContentGrid.TranslationX = 0;
+ _mainContentGrid.BackgroundColor = _mainContentGrid.BackgroundColor ?? ContentBackgroundColor;
}
else
{
_drawerLayout.TranslationX = -DrawerSettings.DrawerWidth;
- if (DrawerSettings.Transition == Transition.Push && ContentView != null)
+ if (DrawerSettings.Transition == Transition.Push && _mainContentGrid != null)
{
- ContentView.TranslationX = 0;
+ _mainContentGrid.TranslationX = 0;
}
}
}
@@ -1285,42 +1305,42 @@ void PositionUpdate()
SetVisibility(true);
_greyOverlayGrid.TranslationX = 0;
_drawerLayout.TranslationY = -((ScreenHeight / 2) - (DrawerSettings.DrawerHeight / 2));
- if (DrawerSettings.Transition == Transition.SlideOnTop && ContentView != null)
+ if (DrawerSettings.Transition == Transition.SlideOnTop && _mainContentGrid != null)
{
- ContentView.TranslationY = 0;
- ContentView.TranslationX = 0;
+ _mainContentGrid.TranslationY = 0;
+ _mainContentGrid.TranslationX = 0;
_greyOverlayGrid.TranslationY = 0;
}
- else if ((DrawerSettings.Transition == Transition.Push || DrawerSettings.Transition == Transition.Reveal) && ContentView != null)
+ else if ((DrawerSettings.Transition == Transition.Push || DrawerSettings.Transition == Transition.Reveal) && _mainContentGrid != null)
{
_drawerLayout.TranslationY -= _drawerMoveTop;
_greyOverlayGrid.TranslationY = DrawerSettings.DrawerHeight;
- ContentView.TranslationY = DrawerSettings.DrawerHeight;
- ContentView.TranslationX = 0;
+ _mainContentGrid.TranslationY = DrawerSettings.DrawerHeight;
+ _mainContentGrid.TranslationX = 0;
}
}
else
{
#if WINDOWS || MACCATALYST
- if ((!_drawerLayout.AnimationIsRunning("drawerAnimation") && !_greyOverlayGrid.AnimationIsRunning("greyOverlayTranslateAnimation")) || (DrawerSettings.Transition == Transition.Reveal && !ContentView.AnimationIsRunning("contentViewTranslatePushAnimation")))
+ if ((!_drawerLayout.AnimationIsRunning("drawerAnimation") && !_greyOverlayGrid.AnimationIsRunning("greyOverlayTranslateAnimation")) || (DrawerSettings.Transition == Transition.Reveal && !_mainContentGrid.AnimationIsRunning("contentViewTranslatePushAnimation")))
#endif
{
SetVisibility(false);
}
_greyOverlayGrid.TranslationX = ScreenWidth;
- if (DrawerSettings.Transition == Transition.Reveal && ContentView != null)
+ if (DrawerSettings.Transition == Transition.Reveal && _mainContentGrid != null)
{
_drawerLayout.TranslationY = -((ScreenHeight / 2) - (DrawerSettings.DrawerHeight / 2)) - _drawerMoveTop;
- ContentView.TranslationY = 0;
- ContentView.BackgroundColor = ContentView.BackgroundColor ?? ContentBackgroundColor;
+ _mainContentGrid.TranslationY = 0;
+ _mainContentGrid.BackgroundColor = _mainContentGrid.BackgroundColor ?? ContentBackgroundColor;
}
else
{
_drawerLayout.TranslationY = -((ScreenHeight / 2) + (DrawerSettings.DrawerHeight / 2) + _toolBarHeight);
- if (DrawerSettings.Transition == Transition.Push && ContentView != null)
+ if (DrawerSettings.Transition == Transition.Push && _mainContentGrid != null)
{
- ContentView.TranslationY = 0;
+ _mainContentGrid.TranslationY = 0;
_drawerLayout.TranslationY -= _drawerMoveTop;
}
}
@@ -1336,22 +1356,22 @@ void PositionUpdate()
SetVisibility(true);
_greyOverlayGrid.TranslationX = 0;
_drawerLayout.TranslationY = (ScreenHeight / 2) - (DrawerSettings.DrawerHeight / 2);
- if (DrawerSettings.Transition == Transition.SlideOnTop && ContentView != null)
+ if (DrawerSettings.Transition == Transition.SlideOnTop && _mainContentGrid != null)
{
- ContentView.TranslationY = 0;
- ContentView.TranslationX = 0;
+ _mainContentGrid.TranslationY = 0;
+ _mainContentGrid.TranslationX = 0;
}
- else if ((DrawerSettings.Transition == Transition.Push || DrawerSettings.Transition == Transition.Reveal) && ContentView != null)
+ else if ((DrawerSettings.Transition == Transition.Push || DrawerSettings.Transition == Transition.Reveal) && _mainContentGrid != null)
{
_greyOverlayGrid.TranslationY = -DrawerSettings.DrawerHeight;
- ContentView.TranslationY = -DrawerSettings.DrawerHeight;
- ContentView.TranslationX = 0;
+ _mainContentGrid.TranslationY = -DrawerSettings.DrawerHeight;
+ _mainContentGrid.TranslationX = 0;
}
}
else
{
#if WINDOWS || MACCATALYST
- if ((!_drawerLayout.AnimationIsRunning("drawerAnimation") && !_greyOverlayGrid.AnimationIsRunning("greyOverlayTranslateAnimation")) || (DrawerSettings.Transition == Transition.Reveal && !ContentView.AnimationIsRunning("contentViewTranslatePushAnimation")))
+ if ((!_drawerLayout.AnimationIsRunning("drawerAnimation") && !_greyOverlayGrid.AnimationIsRunning("greyOverlayTranslateAnimation")) || (DrawerSettings.Transition == Transition.Reveal && !_mainContentGrid.AnimationIsRunning("contentViewTranslatePushAnimation")))
#endif
{
SetVisibility(false);
@@ -1359,18 +1379,18 @@ void PositionUpdate()
_greyOverlayGrid.TranslationX = ScreenWidth;
- if (DrawerSettings.Transition == Transition.Reveal && ContentView != null)
+ if (DrawerSettings.Transition == Transition.Reveal && _mainContentGrid != null)
{
_drawerLayout.TranslationY = (ScreenHeight / 2) - (DrawerSettings.DrawerHeight / 2);
- ContentView.TranslationY = 0;
- ContentView.BackgroundColor = ContentView.BackgroundColor ?? ContentBackgroundColor;
+ _mainContentGrid.TranslationY = 0;
+ _mainContentGrid.BackgroundColor = _mainContentGrid.BackgroundColor ?? ContentBackgroundColor;
}
else
{
_drawerLayout.TranslationY = (ScreenHeight / 2) + (DrawerSettings.DrawerHeight / 2);
- if (DrawerSettings.Transition == Transition.Push && ContentView != null)
+ if (DrawerSettings.Transition == Transition.Push && _mainContentGrid != null)
{
- ContentView.TranslationY = 0;
+ _mainContentGrid.TranslationY = 0;
}
}
}
@@ -1383,15 +1403,15 @@ void PositionUpdate()
#if !WINDOWS
void UpdateDrawerFlowDirection()
{
- if (_drawerLayout != null && ContentView != null)
+ if (_drawerLayout != null && _mainContentGrid != null)
{
if (_isRTL)
{
- _drawerLayout.FlowDirection = ContentView.FlowDirection = FlowDirection.RightToLeft;
+ _drawerLayout.FlowDirection = _mainContentGrid.FlowDirection = FlowDirection.RightToLeft;
}
else
{
- _drawerLayout.FlowDirection = ContentView.FlowDirection = FlowDirection.LeftToRight;
+ _drawerLayout.FlowDirection = _mainContentGrid.FlowDirection = FlowDirection.LeftToRight;
}
}
}
@@ -1687,11 +1707,11 @@ void HandleLeftSwipeCompletion()
void HandleLeftSwipeIn()
{
- if (_drawerLayout != null && _greyOverlayGrid != null && DrawerSettings != null)
+ if (_drawerLayout != null && _greyOverlayGrid != null && DrawerSettings != null && _mainContentGrid != null)
{
if (DrawerSettings.Transition == Transition.Reveal)
{
- if (ContentView.TranslationX == DrawerSettings.DrawerWidth && (!_isDrawerOpen || _isTransitionDifference))
+ if (_mainContentGrid.TranslationX == DrawerSettings.DrawerWidth && (!_isDrawerOpen || _isTransitionDifference))
{
UpdateToggleInEvent();
}
@@ -1718,13 +1738,13 @@ void HandleLeftSwipeIn()
void HandleLeftSwipeOut()
{
- if (_drawerLayout != null && _greyOverlayGrid != null && DrawerSettings != null)
+ if (_drawerLayout != null && _greyOverlayGrid != null && DrawerSettings != null && _mainContentGrid != null)
{
if ((_isDrawerOpen || (_greyOverlayGrid.TranslationX == 0 && !_isDrawerOpen)) && _isTransitionDifference)
{
if (DrawerSettings.Transition == Transition.Reveal)
{
- if (ContentView.TranslationX == 0)
+ if (_mainContentGrid.TranslationX == 0)
{
UpdateToggleOutEvent();
}
@@ -1765,9 +1785,9 @@ void HandleLeftSwipeByPosition()
DrawerLeftIn();
}
}
- else if (ContentView != null && ContentView.TranslationX >= DrawerSettings.DrawerWidth / 2 && _greyOverlayGrid.TranslationX != ScreenWidth && DrawerSettings.Transition == Transition.Reveal)
+ else if (_mainContentGrid != null && _mainContentGrid.TranslationX >= DrawerSettings.DrawerWidth / 2 && _greyOverlayGrid.TranslationX != ScreenWidth && DrawerSettings.Transition == Transition.Reveal)
{
- if (ContentView.TranslationX == DrawerSettings.DrawerWidth && (!_isDrawerOpen || _isTransitionDifference))
+ if (_mainContentGrid.TranslationX == DrawerSettings.DrawerWidth && (!_isDrawerOpen || _isTransitionDifference))
{
UpdateToggleInEvent();
}
@@ -1778,7 +1798,7 @@ void HandleLeftSwipeByPosition()
}
else if (!_cancelOpenEventArgs.Cancel)
{
- if ((_drawerLayout.TranslationX != -DrawerSettings.DrawerWidth && DrawerSettings.Transition != Transition.Reveal) || (DrawerSettings.Transition == Transition.Reveal && ContentView != null && ContentView.TranslationX != 0) || _isDrawerOpen || _isTransitionDifference)
+ if ((_drawerLayout.TranslationX != -DrawerSettings.DrawerWidth && DrawerSettings.Transition != Transition.Reveal) || (DrawerSettings.Transition == Transition.Reveal && _mainContentGrid != null && _mainContentGrid.TranslationX != 0) || _isDrawerOpen || _isTransitionDifference)
{
DrawerLeftOut();
_isTransitionDifference = false;
@@ -1805,11 +1825,11 @@ void HandleRightSwipeCompletion()
void HandleRightSwipeIn()
{
- if (_drawerLayout != null && _greyOverlayGrid != null && DrawerSettings != null)
+ if (_drawerLayout != null && _greyOverlayGrid != null && DrawerSettings != null && _mainContentGrid != null)
{
if (DrawerSettings.Transition == Transition.Reveal)
{
- if (ContentView.TranslationX == -DrawerSettings.DrawerWidth && (!_isDrawerOpen || _isTransitionDifference))
+ if (_mainContentGrid.TranslationX == -DrawerSettings.DrawerWidth && (!_isDrawerOpen || _isTransitionDifference))
{
UpdateToggleInEvent();
}
@@ -1836,13 +1856,13 @@ void HandleRightSwipeIn()
void HandleRightSwipeOut()
{
- if (_drawerLayout != null && _greyOverlayGrid != null && DrawerSettings != null)
+ if (_drawerLayout != null && _greyOverlayGrid != null && DrawerSettings != null && _mainContentGrid != null)
{
if ((_isDrawerOpen || (_greyOverlayGrid.TranslationX == 0 && !_isDrawerOpen)) && _isTransitionDifference)
{
if (DrawerSettings.Transition == Transition.Reveal)
{
- if (ContentView.TranslationX == 0)
+ if (_mainContentGrid.TranslationX == 0)
{
UpdateToggleOutEvent();
}
@@ -1884,9 +1904,9 @@ void HandleRightSwipeByPosition()
_isTransitionDifference = false;
}
}
- else if (ContentView != null && ContentView.TranslationX <= -DrawerSettings.DrawerWidth / 2 && _greyOverlayGrid.TranslationX != ScreenWidth && DrawerSettings.Transition == Transition.Reveal)
+ else if (_mainContentGrid != null && _mainContentGrid.TranslationX <= -DrawerSettings.DrawerWidth / 2 && _greyOverlayGrid.TranslationX != ScreenWidth && DrawerSettings.Transition == Transition.Reveal)
{
- if (ContentView.TranslationX == -DrawerSettings.DrawerWidth && (!_isDrawerOpen || _isTransitionDifference))
+ if (_mainContentGrid.TranslationX == -DrawerSettings.DrawerWidth && (!_isDrawerOpen || _isTransitionDifference))
{
UpdateToggleInEvent();
}
@@ -1898,7 +1918,7 @@ void HandleRightSwipeByPosition()
}
else if (!_cancelOpenEventArgs.Cancel)
{
- if ((_drawerLayout.TranslationX != ScreenWidth && DrawerSettings.Transition != Transition.Reveal) || (DrawerSettings.Transition == Transition.Reveal && ContentView != null && ContentView.TranslationX != 0) || _isDrawerOpen || _isTransitionDifference)
+ if ((_drawerLayout.TranslationX != ScreenWidth && DrawerSettings.Transition != Transition.Reveal) || (DrawerSettings.Transition == Transition.Reveal && _mainContentGrid != null && _mainContentGrid.TranslationX != 0) || _isDrawerOpen || _isTransitionDifference)
{
DrawerRightOut();
_isTransitionDifference = false;
@@ -1925,11 +1945,11 @@ void HandleTopSwipeCompletion()
void HandleTopSwipeIn()
{
- if (_drawerLayout != null && _greyOverlayGrid != null && DrawerSettings != null)
+ if (_drawerLayout != null && _greyOverlayGrid != null && DrawerSettings != null && _mainContentGrid != null)
{
if (DrawerSettings.Transition == Transition.Reveal)
{
- if (ContentView.TranslationY == DrawerSettings.DrawerHeight && (!_isDrawerOpen || _isTransitionDifference))
+ if (_mainContentGrid.TranslationY == DrawerSettings.DrawerHeight && (!_isDrawerOpen || _isTransitionDifference))
{
UpdateToggleInEvent();
}
@@ -1956,13 +1976,13 @@ void HandleTopSwipeIn()
void HandleTopSwipeOut()
{
- if (_drawerLayout != null && _greyOverlayGrid != null && DrawerSettings != null)
+ if (_drawerLayout != null && _greyOverlayGrid != null && DrawerSettings != null && _mainContentGrid != null)
{
if ((_isDrawerOpen || (_greyOverlayGrid.TranslationX == 0 && !_isDrawerOpen)) && _isTransitionDifference)
{
if (DrawerSettings.Transition == Transition.Reveal)
{
- if (ContentView.TranslationY == 0)
+ if (_mainContentGrid.TranslationY == 0)
{
UpdateToggleOutEvent();
}
@@ -2004,9 +2024,9 @@ void HandleTopSwipeByPosition()
}
_isTransitionDifference = false;
}
- else if (ContentView != null && ContentView.TranslationY >= DrawerSettings.DrawerHeight / 2 && _greyOverlayGrid.TranslationX != ScreenWidth && DrawerSettings.Transition == Transition.Reveal)
+ else if (_mainContentGrid != null && _mainContentGrid.TranslationY >= DrawerSettings.DrawerHeight / 2 && _greyOverlayGrid.TranslationX != ScreenWidth && DrawerSettings.Transition == Transition.Reveal)
{
- if (ContentView.TranslationY == DrawerSettings.DrawerHeight && (!_isDrawerOpen || _isTransitionDifference))
+ if (_mainContentGrid.TranslationY == DrawerSettings.DrawerHeight && (!_isDrawerOpen || _isTransitionDifference))
{
UpdateToggleInEvent();
}
@@ -2018,7 +2038,7 @@ void HandleTopSwipeByPosition()
}
else if (!_cancelOpenEventArgs.Cancel)
{
- if ((_drawerLayout.TranslationY != -((ScreenHeight / 2) + (DrawerSettings.DrawerHeight / 2) + _toolBarHeight) && DrawerSettings.Transition != Transition.Reveal) || (DrawerSettings.Transition == Transition.Reveal && ContentView != null && ContentView.TranslationY != 0) || _isDrawerOpen || _isTransitionDifference)
+ if ((_drawerLayout.TranslationY != -((ScreenHeight / 2) + (DrawerSettings.DrawerHeight / 2) + _toolBarHeight) && DrawerSettings.Transition != Transition.Reveal) || (DrawerSettings.Transition == Transition.Reveal && _mainContentGrid != null && _mainContentGrid.TranslationY != 0) || _isDrawerOpen || _isTransitionDifference)
{
DrawerTopOut();
_isTransitionDifference = false;
@@ -2045,11 +2065,11 @@ void HandleBottomSwipeCompletion()
void HandleBottomSwipeIn()
{
- if (_drawerLayout != null && _greyOverlayGrid != null && DrawerSettings != null)
+ if (_drawerLayout != null && _greyOverlayGrid != null && DrawerSettings != null && _mainContentGrid != null)
{
if (DrawerSettings.Transition == Transition.Reveal)
{
- if (ContentView.TranslationY == -DrawerSettings.DrawerHeight && (!_isDrawerOpen || _isTransitionDifference))
+ if (_mainContentGrid.TranslationY == -DrawerSettings.DrawerHeight && (!_isDrawerOpen || _isTransitionDifference))
{
UpdateToggleInEvent();
}
@@ -2076,13 +2096,13 @@ void HandleBottomSwipeIn()
void HandleBottomSwipeOut()
{
- if (_drawerLayout != null && _greyOverlayGrid != null && DrawerSettings != null)
+ if (_drawerLayout != null && _greyOverlayGrid != null && DrawerSettings != null && _mainContentGrid != null)
{
if ((_isDrawerOpen || (_greyOverlayGrid.TranslationX == 0 && !_isDrawerOpen)) && _isTransitionDifference)
{
if (DrawerSettings.Transition == Transition.Reveal)
{
- if (ContentView.TranslationY == 0)
+ if (_mainContentGrid.TranslationY == 0)
{
UpdateToggleOutEvent();
}
@@ -2124,9 +2144,9 @@ void HandleBottomSwipeByPosition()
}
_isTransitionDifference = false;
}
- else if (ContentView != null && ContentView.TranslationY <= -DrawerSettings.DrawerHeight / 2 && _greyOverlayGrid.TranslationX != ScreenWidth && DrawerSettings.Transition == Transition.Reveal)
+ else if (_mainContentGrid != null && _mainContentGrid.TranslationY <= -DrawerSettings.DrawerHeight / 2 && _greyOverlayGrid.TranslationX != ScreenWidth && DrawerSettings.Transition == Transition.Reveal)
{
- if (ContentView.TranslationY == -DrawerSettings.DrawerHeight && (!_isDrawerOpen || _isTransitionDifference))
+ if (_mainContentGrid.TranslationY == -DrawerSettings.DrawerHeight && (!_isDrawerOpen || _isTransitionDifference))
{
UpdateToggleInEvent();
}
@@ -2138,7 +2158,7 @@ void HandleBottomSwipeByPosition()
}
else if (!_cancelOpenEventArgs.Cancel)
{
- if ((_drawerLayout.TranslationY != (ScreenHeight / 2) + (DrawerSettings.DrawerHeight / 2) && DrawerSettings.Transition != Transition.Reveal) || (DrawerSettings.Transition == Transition.Reveal && ContentView != null && ContentView.TranslationY != 0) || _isDrawerOpen || _isTransitionDifference)
+ if ((_drawerLayout.TranslationY != (ScreenHeight / 2) + (DrawerSettings.DrawerHeight / 2) && DrawerSettings.Transition != Transition.Reveal) || (DrawerSettings.Transition == Transition.Reveal && _mainContentGrid != null && _mainContentGrid.TranslationY != 0) || _isDrawerOpen || _isTransitionDifference)
{
DrawerBottomOut();
_isTransitionDifference = false;
@@ -2184,7 +2204,7 @@ void UpdateToggleOutEvent()
/// The distance of the swipe.
void LeftDrawerSwipe(double difference)
{
- if (_greyOverlayGrid != null && _drawerLayout != null)
+ if (_greyOverlayGrid != null && _drawerLayout != null && _mainContentGrid != null)
{
double transitionDifference = 0;
double oldTransition = 0;
@@ -2198,20 +2218,20 @@ void LeftDrawerSwipe(double difference)
}
else
{
- oldTransition = Math.Round(ContentView.TranslationX);
+ oldTransition = Math.Round(_mainContentGrid.TranslationX);
}
- if ((DrawerSettings.Transition == Transition.Push || DrawerSettings.Transition == Transition.Reveal) && ContentView != null)
+ if ((DrawerSettings.Transition == Transition.Push || DrawerSettings.Transition == Transition.Reveal) && _mainContentGrid != null)
{
_greyOverlayGrid.TranslationX = Math.Clamp(_greyOverlayGrid.TranslationX + difference, 0, DrawerSettings.DrawerWidth);
- ContentView.TranslationX = Math.Clamp(ContentView.TranslationX + difference, 0, DrawerSettings.DrawerWidth);
+ _mainContentGrid.TranslationX = Math.Clamp(_mainContentGrid.TranslationX + difference, 0, DrawerSettings.DrawerWidth);
if (DrawerSettings.Transition == Transition.Reveal)
{
- transitionDifference = Math.Abs(Math.Round(ContentView.TranslationX) - oldTransition);
+ transitionDifference = Math.Abs(Math.Round(_mainContentGrid.TranslationX) - oldTransition);
}
#if WINDOWS
- if (ContentView.TranslationX != 0 && _greyOverlayGrid.TranslationX != 0)
+ if (_mainContentGrid.TranslationX != 0 && _greyOverlayGrid.TranslationX != 0)
{
SetVisibility(true);
}
@@ -2236,7 +2256,7 @@ void LeftDrawerSwipe(double difference)
/// The distance of the swipe.
void RightDrawerSwipe(double difference)
{
- if (_greyOverlayGrid != null && _drawerLayout != null)
+ if (_greyOverlayGrid != null && _drawerLayout != null && _mainContentGrid != null)
{
double transitionDifference = 0;
double oldTransition = 0;
@@ -2250,20 +2270,20 @@ void RightDrawerSwipe(double difference)
}
else
{
- oldTransition = Math.Round(ContentView.TranslationX);
+ oldTransition = Math.Round(_mainContentGrid.TranslationX);
}
- if ((DrawerSettings.Transition == Transition.Push || DrawerSettings.Transition == Transition.Reveal) && ContentView != null)
+ if ((DrawerSettings.Transition == Transition.Push || DrawerSettings.Transition == Transition.Reveal) && _mainContentGrid != null)
{
_greyOverlayGrid.TranslationX = Math.Clamp(_greyOverlayGrid.TranslationX + difference, -DrawerSettings.DrawerWidth, 0);
- ContentView.TranslationX = Math.Clamp(ContentView.TranslationX + difference, -DrawerSettings.DrawerWidth, 0);
+ _mainContentGrid.TranslationX = Math.Clamp(_mainContentGrid.TranslationX + difference, -DrawerSettings.DrawerWidth, 0);
if (DrawerSettings.Transition == Transition.Reveal)
{
- transitionDifference = Math.Abs(Math.Round(ContentView.TranslationX) - oldTransition);
+ transitionDifference = Math.Abs(Math.Round(_mainContentGrid.TranslationX) - oldTransition);
}
#if WINDOWS
- if (ContentView.TranslationX != 0 && _greyOverlayGrid.TranslationX != 0)
+ if (_mainContentGrid.TranslationX != 0 && _greyOverlayGrid.TranslationX != 0)
{
SetVisibility(true);
}
@@ -2288,7 +2308,7 @@ void RightDrawerSwipe(double difference)
/// The distance of the swipe.
void TopDrawerSwipe(double difference)
{
- if (_greyOverlayGrid != null && _drawerLayout != null)
+ if (_greyOverlayGrid != null && _drawerLayout != null && _mainContentGrid != null)
{
double transitionDifference = 0;
double oldTransition = 0;
@@ -2302,20 +2322,20 @@ void TopDrawerSwipe(double difference)
}
else
{
- oldTransition = Math.Round(ContentView.TranslationY);
+ oldTransition = Math.Round(_mainContentGrid.TranslationY);
}
- if ((DrawerSettings.Transition == Transition.Push || DrawerSettings.Transition == Transition.Reveal) && ContentView != null)
+ if ((DrawerSettings.Transition == Transition.Push || DrawerSettings.Transition == Transition.Reveal) && _mainContentGrid != null)
{
_greyOverlayGrid.TranslationY = Math.Clamp(_greyOverlayGrid.TranslationY + difference, 0, DrawerSettings.DrawerHeight);
- ContentView.TranslationY = Math.Clamp(ContentView.TranslationY + difference, 0, DrawerSettings.DrawerHeight);
+ _mainContentGrid.TranslationY = Math.Clamp(_mainContentGrid.TranslationY + difference, 0, DrawerSettings.DrawerHeight);
if (DrawerSettings.Transition == Transition.Reveal)
{
- transitionDifference = Math.Abs(Math.Round(ContentView.TranslationY) - oldTransition);
+ transitionDifference = Math.Abs(Math.Round(_mainContentGrid.TranslationY) - oldTransition);
}
#if WINDOWS
- if (ContentView.TranslationY != 0 && _greyOverlayGrid.TranslationY != 0)
+ if (_mainContentGrid.TranslationY != 0 && _greyOverlayGrid.TranslationY != 0)
{
SetVisibility(true);
}
@@ -2352,7 +2372,7 @@ void SetIsTransitionDifference(double transitionDifference)
/// The distance of the swipe.
void BottomDrawerSwipe(double difference)
{
- if (_greyOverlayGrid != null && _drawerLayout != null)
+ if (_greyOverlayGrid != null && _drawerLayout != null && _mainContentGrid != null)
{
double transitionDifference = 0;
double oldTransition = 0;
@@ -2366,20 +2386,20 @@ void BottomDrawerSwipe(double difference)
}
else
{
- oldTransition = Math.Round(ContentView.TranslationY);
+ oldTransition = Math.Round(_mainContentGrid.TranslationY);
}
- if ((DrawerSettings.Transition == Transition.Push || DrawerSettings.Transition == Transition.Reveal) && ContentView != null)
+ if ((DrawerSettings.Transition == Transition.Push || DrawerSettings.Transition == Transition.Reveal) && _mainContentGrid != null)
{
_greyOverlayGrid.TranslationY = Math.Clamp(_greyOverlayGrid.TranslationY + difference, -DrawerSettings.DrawerHeight, 0);
- ContentView.TranslationY = Math.Clamp(ContentView.TranslationY + difference, -DrawerSettings.DrawerHeight, 0);
+ _mainContentGrid.TranslationY = Math.Clamp(_mainContentGrid.TranslationY + difference, -DrawerSettings.DrawerHeight, 0);
if (DrawerSettings.Transition == Transition.Reveal)
{
- transitionDifference = Math.Abs(Math.Round(ContentView.TranslationY) - oldTransition);
+ transitionDifference = Math.Abs(Math.Round(_mainContentGrid.TranslationY) - oldTransition);
}
#if WINDOWS
- if (ContentView.TranslationY != 0 && _greyOverlayGrid.TranslationY != 0)
+ if (_mainContentGrid.TranslationY != 0 && _greyOverlayGrid.TranslationY != 0)
{
SetVisibility(true);
}
@@ -2403,9 +2423,9 @@ void BottomDrawerSwipe(double difference)
///
void DrawerLeftIn()
{
- if (_greyOverlayGrid != null && _drawerLayout != null)
+ if (_greyOverlayGrid != null && _drawerLayout != null && _mainContentGrid != null)
{
- if (_drawerLayout.TranslationX != 0 || (DrawerSettings.Transition == Transition.Reveal && ContentView.TranslationX != DrawerSettings.DrawerWidth))
+ if (_drawerLayout.TranslationX != 0 || (DrawerSettings.Transition == Transition.Reveal && _mainContentGrid.TranslationX != DrawerSettings.DrawerWidth))
{
if (_actionFirstMoveOpen && _actionFirstMoveClose)
{
@@ -2439,13 +2459,13 @@ void DrawerLeftIn()
_greyOverlayGrid.Animate("greyOverlayAnimation", greyOverlayAnimation, length: (uint)(currentDuration / 2), easing: Easing.Linear);
});
}
- else if ((DrawerSettings.Transition == Transition.Push || DrawerSettings.Transition == Transition.Reveal) && ContentView != null)
+ else if ((DrawerSettings.Transition == Transition.Push || DrawerSettings.Transition == Transition.Reveal) && _mainContentGrid != null)
{
Animation greyOverlayTranslatePushAnimation = new Animation(d => _greyOverlayGrid.TranslationX = d, _greyOverlayGrid.TranslationX, DrawerSettings.DrawerWidth);
- Animation contentViewTranslatePushAnimation = new Animation(d => ContentView.TranslationX = d, ContentView.TranslationX, DrawerSettings.DrawerWidth);
+ Animation contentViewTranslatePushAnimation = new Animation(d => _mainContentGrid.TranslationX = d, _mainContentGrid.TranslationX, DrawerSettings.DrawerWidth);
_greyOverlayGrid.Animate("greyOverlayTranslatePushAnimation", greyOverlayTranslatePushAnimation, length: (uint)currentDuration, easing: Easing.Linear);
_greyOverlayGrid.Animate("greyOverlayAnimation", greyOverlayAnimation, length: (uint)(currentDuration / 2), easing: Easing.Linear);
- ContentView.Animate("contentViewTranslatePushAnimation", contentViewTranslatePushAnimation, length: (uint)currentDuration, easing: Easing.Linear, finished: (v, e) =>
+ _mainContentGrid.Animate("contentViewTranslatePushAnimation", contentViewTranslatePushAnimation, length: (uint)currentDuration, easing: Easing.Linear, finished: (v, e) =>
{
OnDrawerOpenedToggledEvent();
});
@@ -2513,19 +2533,19 @@ void DrawerLeftOut()
_greyOverlayGrid.Animate("greyOverlayTranslateAnimation", greyOverlayTranslateAnimation, length: 0, easing: Easing.Linear);
});
}
- else if ((DrawerSettings.Transition == Transition.Push || DrawerSettings.Transition == Transition.Reveal) && ContentView != null)
+ else if ((DrawerSettings.Transition == Transition.Push || DrawerSettings.Transition == Transition.Reveal) && _mainContentGrid != null)
{
Animation greyOverlayTranslatePushAnimation = new Animation(d => _greyOverlayGrid.TranslationX = d, _greyOverlayGrid.TranslationX, 0);
- Animation contentViewTranslatePushAnimation = new Animation(d => ContentView.TranslationX = d, ContentView.TranslationX, 0);
+ Animation contentViewTranslatePushAnimation = new Animation(d => _mainContentGrid.TranslationX = d, _mainContentGrid.TranslationX, 0);
_greyOverlayGrid.Animate("greyOverlayTranslatePushAnimation", greyOverlayTranslatePushAnimation, length: (uint)currentDuration, easing: Easing.Linear, finished: (v, e) =>
{
_greyOverlayGrid.Animate("greyOverlayTranslateAnimation", greyOverlayTranslateAnimation, length: 0, easing: Easing.Linear);
});
_greyOverlayGrid.Animate("greyOverlayAnimation", greyOverlayAnimation, length: (uint)(currentDuration / 2), easing: Easing.Linear);
- ContentView.Animate("contentViewTranslatePushAnimation", contentViewTranslatePushAnimation, length: (uint)currentDuration, easing: Easing.Linear, finished: (v, e) =>
+ _mainContentGrid.Animate("contentViewTranslatePushAnimation", contentViewTranslatePushAnimation, length: (uint)currentDuration, easing: Easing.Linear, finished: (v, e) =>
{
#if WINDOWS
- if (ContentView.TranslationX == 0)
+ if (_mainContentGrid.TranslationX == 0)
#endif
{
OnDrawerClosedToggledEvent();
@@ -2558,9 +2578,9 @@ void OnDrawerClosedToggledEvent()
///
void DrawerRightIn()
{
- if (_greyOverlayGrid != null && _drawerLayout != null)
+ if (_greyOverlayGrid != null && _drawerLayout != null && _mainContentGrid != null)
{
- if (_drawerLayout.TranslationX != ScreenWidth - DrawerSettings.DrawerWidth || (DrawerSettings.Transition == Transition.Reveal && ContentView.TranslationX != -DrawerSettings.DrawerWidth))
+ if (_drawerLayout.TranslationX != ScreenWidth - DrawerSettings.DrawerWidth || (DrawerSettings.Transition == Transition.Reveal && _mainContentGrid.TranslationX != -DrawerSettings.DrawerWidth))
{
if (_actionFirstMoveOpen && _actionFirstMoveClose)
{
@@ -2576,7 +2596,7 @@ void DrawerRightIn()
}
else
{
- currentDuration = (Math.Abs(DrawerSettings.DrawerWidth + ContentView.TranslationX) / DrawerSettings.DrawerWidth) * DrawerSettings.Duration;
+ currentDuration = (Math.Abs(DrawerSettings.DrawerWidth + _mainContentGrid.TranslationX) / DrawerSettings.DrawerWidth) * DrawerSettings.Duration;
}
currentDuration = ValidateCurrentDuration(currentDuration);
@@ -2594,13 +2614,13 @@ void DrawerRightIn()
_greyOverlayGrid.Animate("greyOverlayAnimation", greyOverlayAnimation, length: (uint)(DrawerSettings.Duration / 2), easing: Easing.Linear);
});
}
- else if ((DrawerSettings.Transition == Transition.Push || DrawerSettings.Transition == Transition.Reveal) && ContentView != null)
+ else if ((DrawerSettings.Transition == Transition.Push || DrawerSettings.Transition == Transition.Reveal) && _mainContentGrid != null)
{
Animation greyOverlayTranslatePushAnimation = new Animation(d => _greyOverlayGrid.TranslationX = d, _greyOverlayGrid.TranslationX, -DrawerSettings.DrawerWidth);
- Animation contentViewTranslatePushAnimation = new Animation(d => ContentView.TranslationX = d, ContentView.TranslationX, -DrawerSettings.DrawerWidth);
+ Animation contentViewTranslatePushAnimation = new Animation(d => _mainContentGrid.TranslationX = d, _mainContentGrid.TranslationX, -DrawerSettings.DrawerWidth);
_greyOverlayGrid.Animate("greyOverlayTranslatePushAnimation", greyOverlayTranslatePushAnimation, length: (uint)currentDuration, easing: Easing.Linear);
_greyOverlayGrid.Animate("greyOverlayAnimation", greyOverlayAnimation, length: (uint)(DrawerSettings.Duration / 2), easing: Easing.Linear);
- ContentView.Animate("contentViewTranslatePushAnimation", contentViewTranslatePushAnimation, length: (uint)currentDuration, easing: Easing.Linear, finished: (v, e) =>
+ _mainContentGrid.Animate("contentViewTranslatePushAnimation", contentViewTranslatePushAnimation, length: (uint)currentDuration, easing: Easing.Linear, finished: (v, e) =>
{
OnDrawerOpenedToggledEvent();
});
@@ -2629,7 +2649,7 @@ void DrawerRightIn()
///
void DrawerRightOut()
{
- if (_greyOverlayGrid != null && _drawerLayout != null)
+ if (_greyOverlayGrid != null && _drawerLayout != null && _mainContentGrid != null)
{
if (_actionFirstMoveOpen && _actionFirstMoveClose)
{
@@ -2646,7 +2666,7 @@ void DrawerRightOut()
}
else
{
- currentDuration = (Math.Abs(ContentView.TranslationX) / DrawerSettings.DrawerWidth) * DrawerSettings.Duration;
+ currentDuration = (Math.Abs(_mainContentGrid.TranslationX) / DrawerSettings.DrawerWidth) * DrawerSettings.Duration;
}
currentDuration = ValidateCurrentDuration(currentDuration);
@@ -2669,19 +2689,19 @@ void DrawerRightOut()
_greyOverlayGrid.Animate("greyOverlayTranslateAnimation", greyOverlayTranslateAnimation, length: 0, easing: Easing.Linear);
});
}
- else if ((DrawerSettings.Transition == Transition.Push || DrawerSettings.Transition == Transition.Reveal) && ContentView != null)
+ else if ((DrawerSettings.Transition == Transition.Push || DrawerSettings.Transition == Transition.Reveal) && _mainContentGrid != null)
{
Animation greyOverlayTranslatePushAnimation = new Animation(d => _greyOverlayGrid.TranslationX = d, _greyOverlayGrid.TranslationX, 0);
- Animation contentViewTranslatePushAnimation = new Animation(d => ContentView.TranslationX = d, ContentView.TranslationX, 0);
+ Animation contentViewTranslatePushAnimation = new Animation(d => _mainContentGrid.TranslationX = d, _mainContentGrid.TranslationX, 0);
_greyOverlayGrid.Animate("greyOverlayTranslatePushAnimation", greyOverlayTranslatePushAnimation, length: (uint)currentDuration, easing: Easing.Linear, finished: (v, e) =>
{
_greyOverlayGrid.Animate("greyOverlayTranslateAnimation", greyOverlayTranslateAnimation, length: 0, easing: Easing.Linear);
});
_greyOverlayGrid.Animate("greyOverlayAnimation", greyOverlayAnimation, length: (uint)(currentDuration / 2), easing: Easing.Linear);
- ContentView.Animate("contentViewTranslatePushAnimation", contentViewTranslatePushAnimation, length: (uint)currentDuration, easing: Easing.Linear, finished: (v, e) =>
+ _mainContentGrid.Animate("contentViewTranslatePushAnimation", contentViewTranslatePushAnimation, length: (uint)currentDuration, easing: Easing.Linear, finished: (v, e) =>
{
#if WINDOWS
- if (ContentView.TranslationX == 0)
+ if (_mainContentGrid.TranslationX == 0)
#endif
{
OnDrawerClosedToggledEvent();
@@ -2702,9 +2722,9 @@ void DrawerRightOut()
///
void DrawerTopIn()
{
- if (_greyOverlayGrid != null && _drawerLayout != null)
+ if (_greyOverlayGrid != null && _drawerLayout != null && _mainContentGrid != null)
{
- if (_drawerLayout.TranslationY != -((ScreenHeight / 2) - (DrawerSettings.DrawerHeight / 2)) - _drawerMoveTop || (DrawerSettings.Transition == Transition.Reveal && ContentView.TranslationY != DrawerSettings.DrawerHeight))
+ if (_drawerLayout.TranslationY != -((ScreenHeight / 2) - (DrawerSettings.DrawerHeight / 2)) - _drawerMoveTop || (DrawerSettings.Transition == Transition.Reveal && _mainContentGrid.TranslationY != DrawerSettings.DrawerHeight))
{
if (_actionFirstMoveOpen && _actionFirstMoveClose)
{
@@ -2729,7 +2749,7 @@ void DrawerTopIn()
}
else
{
- currentDuration = Math.Abs(DrawerSettings.DrawerHeight - ContentView.TranslationY) / DrawerSettings.DrawerHeight * DrawerSettings.Duration;
+ currentDuration = Math.Abs(DrawerSettings.DrawerHeight - _mainContentGrid.TranslationY) / DrawerSettings.DrawerHeight * DrawerSettings.Duration;
}
currentDuration = ValidateCurrentDuration(currentDuration);
@@ -2747,13 +2767,13 @@ void DrawerTopIn()
_greyOverlayGrid.Animate("greyOverlayAnimation", greyOverlayAnimation, length: (uint)(currentDuration / 2), easing: Easing.Linear);
});
}
- else if ((DrawerSettings.Transition == Transition.Push || DrawerSettings.Transition == Transition.Reveal) && ContentView != null)
+ else if ((DrawerSettings.Transition == Transition.Push || DrawerSettings.Transition == Transition.Reveal) && _mainContentGrid != null)
{
Animation greyOverlayTranslatePushAnimation = new Animation(d => _greyOverlayGrid.TranslationY = d, _greyOverlayGrid.TranslationY, DrawerSettings.DrawerHeight);
- Animation contentViewTranslatePushAnimation = new Animation(d => ContentView.TranslationY = d, ContentView.TranslationY, DrawerSettings.DrawerHeight);
+ Animation contentViewTranslatePushAnimation = new Animation(d => _mainContentGrid.TranslationY = d, _mainContentGrid.TranslationY, DrawerSettings.DrawerHeight);
_greyOverlayGrid.Animate("greyOverlayTranslatePushAnimation", greyOverlayTranslatePushAnimation, length: (uint)currentDuration, easing: Easing.Linear);
_greyOverlayGrid.Animate("greyOverlayAnimation", greyOverlayAnimation, length: (uint)(currentDuration / 2), easing: Easing.Linear);
- ContentView.Animate("contentViewTranslatePushAnimation", contentViewTranslatePushAnimation, length: (uint)currentDuration, easing: Easing.Linear, finished: (v, e) =>
+ _mainContentGrid.Animate("contentViewTranslatePushAnimation", contentViewTranslatePushAnimation, length: (uint)currentDuration, easing: Easing.Linear, finished: (v, e) =>
{
OnDrawerOpenedToggledEvent();
});
@@ -2785,7 +2805,7 @@ void DrawerTopIn()
///
void DrawerTopOut()
{
- if (_greyOverlayGrid != null && _drawerLayout != null)
+ if (_greyOverlayGrid != null && _drawerLayout != null && _mainContentGrid != null)
{
if (_actionFirstMoveOpen && _actionFirstMoveClose)
{
@@ -2807,7 +2827,7 @@ void DrawerTopOut()
}
else
{
- currentDuration = Math.Abs(ContentView.TranslationY) / DrawerSettings.DrawerHeight * DrawerSettings.Duration;
+ currentDuration = Math.Abs(_mainContentGrid.TranslationY) / DrawerSettings.DrawerHeight * DrawerSettings.Duration;
}
currentDuration = ValidateCurrentDuration(currentDuration);
@@ -2830,19 +2850,19 @@ void DrawerTopOut()
_greyOverlayGrid.Animate("greyOverlayTranslateAnimation", greyOverlayTranslateAnimation, length: 0, easing: Easing.Linear);
});
}
- else if ((DrawerSettings.Transition == Transition.Push || DrawerSettings.Transition == Transition.Reveal) && ContentView != null)
+ else if ((DrawerSettings.Transition == Transition.Push || DrawerSettings.Transition == Transition.Reveal) && _mainContentGrid != null)
{
Animation greyOverlayTranslatePushAnimation = new Animation(d => _greyOverlayGrid.TranslationY = d, _greyOverlayGrid.TranslationY, 0);
- Animation contentViewTranslatePushAnimation = new Animation(d => ContentView.TranslationY = d, ContentView.TranslationY, 0);
+ Animation contentViewTranslatePushAnimation = new Animation(d => _mainContentGrid.TranslationY = d, _mainContentGrid.TranslationY, 0);
_greyOverlayGrid.Animate("greyOverlayTranslatePushAnimation", greyOverlayTranslatePushAnimation, length: (uint)currentDuration, easing: Easing.Linear, finished: (v, e) =>
{
_greyOverlayGrid.Animate("greyOverlayTranslateAnimation", greyOverlayTranslateAnimation, length: 0, easing: Easing.Linear);
});
_greyOverlayGrid.Animate("greyOverlayAnimation", greyOverlayAnimation, length: (uint)(currentDuration / 2), easing: Easing.Linear);
- ContentView.Animate("contentViewTranslatePushAnimation", contentViewTranslatePushAnimation, length: (uint)currentDuration, easing: Easing.Linear, finished: (v, e) =>
+ _mainContentGrid.Animate("contentViewTranslatePushAnimation", contentViewTranslatePushAnimation, length: (uint)currentDuration, easing: Easing.Linear, finished: (v, e) =>
{
#if WINDOWS
- if (ContentView.TranslationY == 0)
+ if (_mainContentGrid.TranslationY == 0)
#endif
{
OnDrawerClosedToggledEvent();
@@ -2866,9 +2886,9 @@ void DrawerTopOut()
///
void DrawerBottomIn()
{
- if (_greyOverlayGrid != null && _drawerLayout != null)
+ if (_greyOverlayGrid != null && _drawerLayout != null && _mainContentGrid != null)
{
- if (_drawerLayout.TranslationY != (ScreenHeight / 2) - (DrawerSettings.DrawerHeight / 2) || (DrawerSettings.Transition == Transition.Reveal && ContentView.TranslationY != -DrawerSettings.DrawerHeight))
+ if (_drawerLayout.TranslationY != (ScreenHeight / 2) - (DrawerSettings.DrawerHeight / 2) || (DrawerSettings.Transition == Transition.Reveal && _mainContentGrid.TranslationY != -DrawerSettings.DrawerHeight))
{
if (_actionFirstMoveOpen && _actionFirstMoveClose)
{
@@ -2884,7 +2904,7 @@ void DrawerBottomIn()
}
else
{
- currentDuration = Math.Abs(DrawerSettings.DrawerHeight + ContentView.TranslationY) / DrawerSettings.DrawerHeight * DrawerSettings.Duration;
+ currentDuration = Math.Abs(DrawerSettings.DrawerHeight + _mainContentGrid.TranslationY) / DrawerSettings.DrawerHeight * DrawerSettings.Duration;
}
currentDuration = ValidateCurrentDuration(currentDuration);
@@ -2902,13 +2922,13 @@ void DrawerBottomIn()
_greyOverlayGrid.Animate("greyOverlayAnimation", greyOverlayAnimation, length: (uint)(currentDuration / 2), easing: Easing.Linear);
});
}
- else if ((DrawerSettings.Transition == Transition.Push || DrawerSettings.Transition == Transition.Reveal) && ContentView != null)
+ else if ((DrawerSettings.Transition == Transition.Push || DrawerSettings.Transition == Transition.Reveal) && _mainContentGrid != null)
{
Animation greyOverlayTranslatePushAnimation = new Animation(d => _greyOverlayGrid.TranslationY = d, _greyOverlayGrid.TranslationY, -DrawerSettings.DrawerHeight);
- Animation contentViewTranslatePushAnimation = new Animation(d => ContentView.TranslationY = d, ContentView.TranslationY, -DrawerSettings.DrawerHeight);
+ Animation contentViewTranslatePushAnimation = new Animation(d => _mainContentGrid.TranslationY = d, _mainContentGrid.TranslationY, -DrawerSettings.DrawerHeight);
_greyOverlayGrid.Animate("greyOverlayTranslatePushAnimation", greyOverlayTranslatePushAnimation, length: (uint)currentDuration, easing: Easing.Linear);
_greyOverlayGrid.Animate("greyOverlayAnimation", greyOverlayAnimation, length: (uint)(currentDuration / 2), easing: Easing.Linear);
- ContentView.Animate("contentViewTranslatePushAnimation", contentViewTranslatePushAnimation, length: (uint)currentDuration, easing: Easing.Linear, finished: (v, e) =>
+ _mainContentGrid.Animate("contentViewTranslatePushAnimation", contentViewTranslatePushAnimation, length: (uint)currentDuration, easing: Easing.Linear, finished: (v, e) =>
{
OnDrawerOpenedToggledEvent();
});
@@ -2937,7 +2957,7 @@ void DrawerBottomIn()
///
void DrawerBottomOut()
{
- if (_greyOverlayGrid != null && _drawerLayout != null)
+ if (_greyOverlayGrid != null && _drawerLayout != null && _mainContentGrid != null)
{
if (_actionFirstMoveOpen && _actionFirstMoveClose)
{
@@ -2954,7 +2974,7 @@ void DrawerBottomOut()
}
else
{
- currentDuration = (Math.Abs(ContentView.TranslationY) / DrawerSettings.DrawerHeight) * DrawerSettings.Duration;
+ currentDuration = (Math.Abs(_mainContentGrid.TranslationY) / DrawerSettings.DrawerHeight) * DrawerSettings.Duration;
}
currentDuration = ValidateCurrentDuration(currentDuration);
@@ -2977,19 +2997,19 @@ void DrawerBottomOut()
_greyOverlayGrid.Animate("greyOverlayTranslateAnimation", greyOverlayTranslateAnimation, length: 0, easing: Easing.Linear);
});
}
- else if ((DrawerSettings.Transition == Transition.Push || DrawerSettings.Transition == Transition.Reveal) && ContentView != null)
+ else if ((DrawerSettings.Transition == Transition.Push || DrawerSettings.Transition == Transition.Reveal) && _mainContentGrid != null)
{
Animation greyOverlayTranslatePushAnimation = new Animation(d => _greyOverlayGrid.TranslationY = d, _greyOverlayGrid.TranslationY, 0);
- Animation contentViewTranslatePushAnimation = new Animation(d => ContentView.TranslationY = d, ContentView.TranslationY, 0);
+ Animation contentViewTranslatePushAnimation = new Animation(d => _mainContentGrid.TranslationY = d, _mainContentGrid.TranslationY, 0);
_greyOverlayGrid.Animate("greyOverlayTranslatePushAnimation", greyOverlayTranslatePushAnimation, length: (uint)currentDuration, easing: Easing.Linear, finished: (v, e) =>
{
_greyOverlayGrid.Animate("greyOverlayTranslateAnimation", greyOverlayTranslateAnimation, length: 0, easing: Easing.Linear);
});
_greyOverlayGrid.Animate("greyOverlayAnimation", greyOverlayAnimation, length: (uint)(currentDuration / 2), easing: Easing.Linear);
- ContentView.Animate("contentViewTranslatePushAnimation", contentViewTranslatePushAnimation, length: (uint)currentDuration, easing: Easing.Linear, finished: (v, e) =>
+ _mainContentGrid.Animate("contentViewTranslatePushAnimation", contentViewTranslatePushAnimation, length: (uint)currentDuration, easing: Easing.Linear, finished: (v, e) =>
{
#if WINDOWS
- if (ContentView.TranslationY == 0)
+ if (_mainContentGrid.TranslationY == 0)
#endif
{
OnDrawerClosedToggledEvent();
@@ -3369,9 +3389,7 @@ static void OnFlowDirectionChanged(BindableObject bindable, object oldValue, obj
/// This method used for handle the touch.
///
/// e.
-#pragma warning disable IDE0060 // Remove unused parameter
public static void OnTouch(PointerEventArgs e)
-#pragma warning restore IDE0060 // Remove unused parameter
{
// throw new NotImplementedException();
}
diff --git a/maui/src/NumericEntry/SfNumericEntry.Methods.cs b/maui/src/NumericEntry/SfNumericEntry.Methods.cs
index 8f15a8df..32aaf8df 100644
--- a/maui/src/NumericEntry/SfNumericEntry.Methods.cs
+++ b/maui/src/NumericEntry/SfNumericEntry.Methods.cs
@@ -166,17 +166,12 @@ void SetTextBoxMargin()
if (_textBox != null)
{
_textBox.ButtonSize = ButtonSize + 4;
- int defaultMargin = IsTextInputLayout ? 0 : 10;
double rightMargin = _buttonSpace;
-
- #if ANDROID || IOS || MACCATALYST
- _textBox.Margin = new Thickness(defaultMargin, 0, rightMargin, 0);
- #else
- _textBox.Margin = new Thickness(0, 0, rightMargin, 0);
- #endif
+ _textBox.Margin = GetMarginBasedOnTextAlignment(0, 0, rightMargin, 0);
}
}
+
///
/// Clears the collection of effect bounds, typically used to reset or remove visual effects on the control.
///
@@ -947,9 +942,11 @@ async void OnTextBoxPaste(object? sender, int caretPosition)
/// The SfNumericEntry control.
/// The previous value.
/// The new value.
- static void RaiseValueChangedEvent(SfNumericEntry numberBox, double? oldValue, double? newValue)
+ static async void RaiseValueChangedEvent(SfNumericEntry numberBox, double? oldValue, double? newValue)
{
var valueChangedEventArgs = new NumericEntryValueChangedEventArgs(newValue, oldValue);
+ //Fix included for Value loop issue.
+ await Task.Yield();
numberBox.ValueChanged?.Invoke(numberBox, valueChangedEventArgs);
}
@@ -2275,6 +2272,30 @@ void UpdateMaximumFractionDigit()
}
}
+ ///
+ /// On iOS and MacCatalyst, it ensures a minimum left or right margin value when the
+ /// text alignment is Start or End respectively.
+ /// On other platforms, it returns the provided thickness without modification.
+ /// The original left margin.
+ /// The original top margin.
+ /// The original right margin.
+ /// The original bottom margin.
+ /// Returns a value adjusted based on the specified text alignment.
+ ///
+ internal Thickness GetMarginBasedOnTextAlignment(double left, double top, double right, double bottom)
+ {
+#if MACCATALYST || IOS
+ return HorizontalTextAlignment switch
+ {
+ TextAlignment.Start => new Thickness(left > 0 ? left : MinimumMargin, top, right, bottom),
+ TextAlignment.End => new Thickness(left, top, right > 0 ? right : MinimumMargin, bottom),
+ _ => new Thickness(left, top, right, bottom)
+ };
+#else
+ return new Thickness(left, top, right, bottom);
+#endif
+ }
+
///
/// Restricts digits in the fractional part of the number in the .
///
diff --git a/maui/src/NumericEntry/SfNumericEntry.cs b/maui/src/NumericEntry/SfNumericEntry.cs
index a1e4838d..e4451e22 100644
--- a/maui/src/NumericEntry/SfNumericEntry.cs
+++ b/maui/src/NumericEntry/SfNumericEntry.cs
@@ -308,6 +308,11 @@ public partial class SfNumericEntry : SfView, ITextElement, ITouchListener, IKey
///
string _previousText = string.Empty;
#endif
+
+#if MACCATALYST || IOS
+ const double MinimumMargin = 7;
+#endif
+
#endregion
#region Constructor
@@ -457,19 +462,18 @@ protected override void OnPropertyChanged([CallerMemberName] string? propertyNam
}
break;
- case nameof(IsVisible):
+ #if WINDOWS
+ case nameof(FlowDirection):
+ SetFlowDirection();
+ break;
+ #endif
+ case nameof(IsVisible):
if (_textBox != null)
{
_textBox.IsVisible = IsVisible;
}
break;
-
- #if WINDOWS
- case nameof(FlowDirection):
- SetFlowDirection();
- break;
- #endif
- }
+ }
// Ensure the base method is always called
base.OnPropertyChanged(propertyName);
@@ -848,12 +852,44 @@ void MinusButton_TouchDown(object? sender, EventArgs e)
}
}
- ///
- /// Searches for the next focusable element in the UI tree starting from the given element.
- ///
- /// The current visual element from which the search starts.
- /// The next focusable if found; otherwise, null.
- static VisualElement? FindNextFocusableElement(VisualElement currentElement)
+ ///
+ /// Retrieves the next focusable element within the visual tree.
+ /// If no next element is found, it searches in the parent hierarchy.
+ ///
+ /// The starting parent element to search from.
+ /// The next focusable VisualElement, or null if none is found.
+ static VisualElement? GetNextFocusableElement(VisualElement parent)
+ {
+ var elements = parent.GetVisualTreeDescendants()
+ .Where(e => (e is Entry || e is InputView || e is Microsoft.Maui.Controls.Picker || e is DatePicker || e is TimePicker || e is SearchBar))
+ .OfType()
+ .Where(ve => ve.IsEnabled && ve.IsVisible)
+ .ToList();
+
+ var focusedElement = elements.FirstOrDefault(e => e.IsFocused);
+
+ if (focusedElement != null)
+ {
+ int currentIndex = elements.IndexOf(focusedElement);
+
+ // Return the next focusable element if available
+ if (currentIndex >= 0 && currentIndex < elements.Count - 1)
+ {
+ return elements[currentIndex + 1];
+ }
+ }
+
+ // If no next focusable element is found, search in the parent
+ return parent.Parent is VisualElement currentParent ? GetNextFocusableElement(currentParent) : null;
+ }
+
+
+ ///
+ /// Searches for the next focusable element in the UI tree starting from the given element.
+ ///
+ /// The current visual element from which the search starts.
+ /// The next focusable if found; otherwise, null.
+ static VisualElement? FindNextFocusableElement(VisualElement currentElement)
{
// Start searching from the parent of the current element
if (currentElement.Parent is not VisualElement rootElement || rootElement == null)
@@ -861,40 +897,7 @@ void MinusButton_TouchDown(object? sender, EventArgs e)
return null;
}
- // Track if we have found the current element in the tree
- bool foundCurrent = false;
-
- // Recursive method to search for the next focusable element
- VisualElement? SearchNextFocusableElement(VisualElement parent)
- {
-#pragma warning disable CS0618 // Type or member is obsolete
- foreach (var child in parent.LogicalChildren)
- {
- if (foundCurrent && child is VisualElement ve && ve.IsEnabled && ve.IsVisible && ve.Focus())
- {
- return ve; // Found the next focusable element
- }
-
- if (child == currentElement)
- {
- foundCurrent = true; // Mark that we found the current element
- }
-
- if (child is VisualElement childVisualElement && childVisualElement.LogicalChildren.Count > 0)
- {
- var result = SearchNextFocusableElement(childVisualElement);
- if (result != null)
- {
- return result;
- }
- }
- }
-#pragma warning restore CS0618 // Type or member is obsolete
-
- return null;
- }
-
- return SearchNextFocusableElement(rootElement);
+ return GetNextFocusableElement(rootElement);
}
///
@@ -909,16 +912,21 @@ void ReturnButton_TouchDown(object? sender, EventArgs e)
if (ReturnType == ReturnType.Next)
{
VisualElement? nextControl = FindNextFocusableElement(this);
- if(nextControl is not null && nextControl is SfNumericEntry numeric && numeric is not null)
+ switch (nextControl)
{
- numeric.Focus();
- }
- else
- {
- nextControl?.Focus();
+ case SfNumericEntry numeric:
+ numeric.Focus();
+ break;
+
+ case SfTextInputLayout textInputLayout:
+ textInputLayout.Focus();
+ break;
+
+ default:
+ nextControl?.Focus();
+ break;
}
-
- }
+ }
else
{
Unfocus();
diff --git a/maui/src/NumericEntry/SfNumericUpDown.cs b/maui/src/NumericEntry/SfNumericUpDown.cs
index 17ca54b9..b36c4000 100644
--- a/maui/src/NumericEntry/SfNumericUpDown.cs
+++ b/maui/src/NumericEntry/SfNumericUpDown.cs
@@ -921,13 +921,7 @@ void SetTextBoxMargin()
if (_textBox != null)
{
_textBox.ButtonSize = ButtonSize + 4;
- int defaultMargin = IsTextInputLayout ? 0 : 10;
-
-#if ANDROID || IOS || MACCATALYST
- _textBox.Margin = new Thickness(_leftMargin + defaultMargin, 0, _rightMargin, 0);
-#else
- _textBox.Margin = new Thickness(_leftMargin, 0, _rightMargin, 0);
-#endif
+ _textBox.Margin = GetMarginBasedOnTextAlignment(_leftMargin, 0, _rightMargin, 0);
}
}
diff --git a/maui/src/OtpInput/OTPEntry.cs b/maui/src/OtpInput/OTPEntry.cs
index 3485bc10..b95929ce 100644
--- a/maui/src/OtpInput/OTPEntry.cs
+++ b/maui/src/OtpInput/OTPEntry.cs
@@ -72,6 +72,16 @@ internal class OTPEntry:Entry,IDrawable,ITouchListener
///
Color _stroke = Color.FromArgb("#49454F");
+ ///
+ /// Specifies the default disabled text color for the control
+ ///
+ Color _disabledTextColor = Color.FromArgb("#611c1b1f");
+
+ ///
+ /// Specifies the default text color for the control
+ ///
+ Color _textColor = Color.FromArgb("#1C1B1F");
+
#endregion
#region Constructor
@@ -167,9 +177,11 @@ public void OnTouch(Toolkit.Internals.PointerEventArgs e)
/// The instance of the OTP input control.
/// Indicates whether the control is enabled.
/// Input state for the OTP input.
- /// Input state for the OTP input.
- /// Input state for the OTP input.
- internal void UpdateParameters(OtpInputStyle StylingMode, double cornerRadius, PointF startPoint, PointF endPoint, SfOtpInput sfotpinput, bool isEnabled, OtpInputState inputState,Color stroke, Color background)
+ /// Background for the OTP input.
+ /// Stroke for the OTP input.
+ /// TextColor for the OTP input.
+ /// Disabled texxt Color for the OTP input.
+ internal void UpdateParameters(OtpInputStyle StylingMode, double cornerRadius, PointF startPoint, PointF endPoint, SfOtpInput sfotpinput, bool isEnabled, OtpInputState inputState,Color stroke, Color background,Color textColor, Color disabledTextColor)
{
_styleMode = StylingMode;
_cornerRadius = cornerRadius;
@@ -180,6 +192,8 @@ internal void UpdateParameters(OtpInputStyle StylingMode, double cornerRadius, P
_inputState = inputState;
_background = background;
_stroke = stroke;
+ _textColor = textColor;
+ _disabledTextColor = disabledTextColor;
GetVisualState();
}
@@ -225,6 +239,8 @@ void GetVisualState()
? _sfOtpInput.InputBackground
: Colors.Transparent;
}
+
+ TextColor = _textColor;
}
else
{
@@ -233,6 +249,7 @@ void GetVisualState()
_background = _styleMode == OtpInputStyle.Filled
? _sfOtpInput.FilledDisableBackground
: Colors.Transparent;
+ TextColor = _disabledTextColor;
}
}
diff --git a/maui/src/OtpInput/SfOtpInput.cs b/maui/src/OtpInput/SfOtpInput.cs
index ad83c50b..f7f70d4e 100644
--- a/maui/src/OtpInput/SfOtpInput.cs
+++ b/maui/src/OtpInput/SfOtpInput.cs
@@ -128,6 +128,11 @@ public class SfOtpInput : SfView, IKeyboardListener, IParentThemeElement
/// Represents the previous value of the OTP input.
///
string? _oldText;
+
+ ///
+ /// Stores the last UITextField to unhook event handlers.
+ ///
+ UIKit.UITextField? _lastPlatformView;
#endif
///
@@ -216,7 +221,7 @@ public class SfOtpInput : SfView, IKeyboardListener, IParentThemeElement
///
/// Identifies the bindable property.
///
- public static readonly BindableProperty TextColorProperty = BindableProperty.Create(nameof(TextColor), typeof(Color), typeof(SfOtpInput), Color.FromArgb("#1C1B1F"), BindingMode.TwoWay);
+ public static readonly BindableProperty TextColorProperty = BindableProperty.Create(nameof(TextColor), typeof(Color), typeof(SfOtpInput), Color.FromArgb("#1C1B1F"), BindingMode.TwoWay,propertyChanged:OnPropertyChanged);
///
/// Identifies the bindable property.
@@ -281,6 +286,12 @@ public class SfOtpInput : SfView, IKeyboardListener, IParentThemeElement
/// Identifies the bindable property.
///
internal static readonly BindableProperty ErrorStrokeProperty = BindableProperty.Create(nameof(ErrorStroke), typeof(Color), typeof(SfOtpInput), Color.FromArgb("#B3261E"), BindingMode.TwoWay, propertyChanged: OnPropertyChanged);
+
+ ///
+ /// Identifies the bindable property.
+ ///
+ internal static readonly BindableProperty DisabledTextColorProperty = BindableProperty.Create(nameof(DisabledTextColor), typeof(Color), typeof(SfOtpInput), Color.FromArgb("#611c1b1f"), BindingMode.TwoWay);
+
#endregion
#region Constructor
@@ -293,6 +304,9 @@ public SfOtpInput()
ThemeElement.InitializeThemeResources(this, "SfOtpInputTheme");
DrawingOrder = DrawingOrder.BelowContent;
InitializeFields();
+#if IOS
+ this.IgnoreSafeArea = true;
+#endif
this.AddKeyboardListener(this);
HookEvents();
this.SetDynamicResource(FilledHoveredBackgroundProperty, "SfOtpInputHoveredBackground");
@@ -303,6 +317,7 @@ public SfOtpInput()
this.SetDynamicResource(SuccessStrokeProperty, "SfOtpInputSuccessStroke");
this.SetDynamicResource(WarningStrokeProperty, "SfOtpInputWarningStroke");
this.SetDynamicResource(ErrorStrokeProperty, "SfOtpInputErrorStroke");
+ this.SetDynamicResource(DisabledTextColorProperty, "SfOtpInputDisabledTextColor");
}
#endregion
@@ -818,6 +833,15 @@ internal Color FilledHoverBackground
set { SetValue(FilledHoveredBackgroundProperty, value); }
}
+ ///
+ /// Gets or sets the disabled text color of the Input.
+ ///
+ internal Color DisabledTextColor
+ {
+ get { return (Color)GetValue(DisabledTextColorProperty); }
+ set { SetValue(DisabledTextColorProperty, value); }
+ }
+
#endregion
#region OnPropertyChanged
@@ -1098,7 +1122,7 @@ public void DrawUI(ICanvas canvas, RectF rectF)
{
UpdateDrawingParameters(i);
canvas.StrokeSize = GetStrokeThickness(i);
- _otpEntries[i].UpdateParameters(StylingMode, _cornerRadius, _startPoint, _endPoint, this, IsEnabled, InputState,Stroke,InputBackground);
+ _otpEntries[i].UpdateParameters(StylingMode, _cornerRadius, _startPoint, _endPoint, this, IsEnabled, InputState,Stroke,InputBackground ,TextColor, DisabledTextColor);
_otpEntries[i].Draw(canvas, _outlineRectF);
}
@@ -1246,6 +1270,9 @@ void InitializeFields()
_separators = new SfLabel[(int)Length - 1];
var layout = new AbsoluteLayout();
+#if IOS
+ layout.IgnoreSafeArea = true;
+#endif
layout.BindingContext = this;
#if WINDOWS || ANDROID
layout.SetBinding(AbsoluteLayout.FlowDirectionProperty, BindingHelper.CreateBinding(nameof(FlowDirection), getter: static (SfOtpInput otpInput) => otpInput.FlowDirection));
@@ -2219,6 +2246,15 @@ void OnHandlerChanged(object? sender, EventArgs e)
{
if (sender is OTPEntry textBox)
{
+#if MACCATALYST || IOS
+ // Unhook from previous handler if exists
+ if (_lastPlatformView is not null)
+ {
+ _lastPlatformView.ShouldChangeCharacters -= ValidateText;
+ _lastPlatformView = null;
+ }
+#endif
+
#if WINDOWS
if ((sender as OTPEntry)?.Handler is not null && (sender as OTPEntry)?.Handler?.PlatformView is Microsoft.UI.Xaml.Controls.TextBox platformView)
{
@@ -2234,10 +2270,11 @@ void OnHandlerChanged(object? sender, EventArgs e)
}
#elif MACCATALYST || IOS
- if ((sender as OTPEntry)?.Handler is not null && (sender as OTPEntry)?.Handler?.PlatformView is UIKit.UITextField platformView)
+ if ((sender as OTPEntry)?.Handler is not null && (sender as OTPEntry)?.Handler?.PlatformView is UIKit.UITextField platformView)
{
platformView.ShouldChangeCharacters += ValidateText;
- }
+ _lastPlatformView = platformView;
+ }
#endif
}
}
diff --git a/maui/src/Picker/Enum/PickerDateFormat.cs b/maui/src/Picker/Enum/PickerDateFormat.cs
new file mode 100644
index 00000000..c9fc5af7
--- /dev/null
+++ b/maui/src/Picker/Enum/PickerDateFormat.cs
@@ -0,0 +1,55 @@
+namespace Syncfusion.Maui.Toolkit.Picker
+{
+ ///
+ /// Defines the picker date format for the SfDatePicker.
+ ///
+ [System.Diagnostics.CodeAnalysis.SuppressMessage("Naming", "CA1707:Identifiers should not contain underscores", Justification = "Used to specify the different date elements")]
+ [System.Diagnostics.CodeAnalysis.SuppressMessage("Naming", "SA1300:ElementMustBeginWithUpperCaseLetter", Justification = "Used to specify the different date formats")]
+ public enum PickerDateFormat
+ {
+ ///
+ /// Represents the day and month in dd MM format.
+ ///
+ dd_MM,
+
+ ///
+ /// Represents the day, month, and year in dd MM yyyy format.
+ ///
+ dd_MM_yyyy,
+
+ ///
+ /// Represents the day, month, and year in dd MMM yyyy format.
+ ///
+ dd_MMM_yyyy,
+
+ ///
+ /// Represents the month, day, and year in M d yyyy format.
+ ///
+ M_d_yyyy,
+
+ ///
+ /// Represents the month, day, and year in MM dd yyyy format.
+ ///
+ MM_dd_yyyy,
+
+ ///
+ /// Represents the month and year in MM yyyy format.
+ ///
+ MM_yyyy,
+
+ ///
+ /// Represents the month and year in MMM yyyy format.
+ ///
+ MMM_yyyy,
+
+ ///
+ /// Represents the year, month, and day in yyyy MM dd format.
+ ///
+ yyyy_MM_dd,
+
+ ///
+ /// Represents default culture based format of the Picker Date format.
+ ///
+ Default,
+ }
+}
\ No newline at end of file
diff --git a/maui/src/Picker/Enum/PickerMode.cs b/maui/src/Picker/Enum/PickerMode.cs
new file mode 100644
index 00000000..7346b070
--- /dev/null
+++ b/maui/src/Picker/Enum/PickerMode.cs
@@ -0,0 +1,23 @@
+namespace Syncfusion.Maui.Toolkit.Picker
+{
+ ///
+ /// Defines the picker mode for the picker.
+ ///
+ public enum PickerMode
+ {
+ ///
+ /// Represents the default mode.
+ ///
+ Default,
+
+ ///
+ /// Represents the dialog mode.
+ ///
+ Dialog,
+
+ ///
+ /// Represents the relative dialog mode.
+ ///
+ RelativeDialog,
+ }
+}
\ No newline at end of file
diff --git a/maui/src/Picker/Enum/PickerRelativePosition.cs b/maui/src/Picker/Enum/PickerRelativePosition.cs
new file mode 100644
index 00000000..4cabd5ad
--- /dev/null
+++ b/maui/src/Picker/Enum/PickerRelativePosition.cs
@@ -0,0 +1,48 @@
+namespace Syncfusion.Maui.Toolkit.Picker
+{
+ ///
+ /// Positions the popup view relative to the given view.
+ ///
+ public enum PickerRelativePosition
+ {
+ ///
+ /// Displays the popup at the top of the given view.
+ ///
+ AlignTop,
+
+ ///
+ /// Displays the popup to the left of the given view.
+ ///
+ AlignToLeftOf,
+
+ ///
+ /// Displays the popup to the right of the given view.
+ ///
+ AlignToRightOf,
+
+ ///
+ /// Displays the popup at the bottom of the given view.
+ ///
+ AlignBottom,
+
+ ///
+ /// Displays the popup at the top left position of the given view.
+ ///
+ AlignTopLeft,
+
+ ///
+ /// Displays the popup at the top right position of the given view.
+ ///
+ AlignTopRight,
+
+ ///
+ /// Displays the popup at the bottom left position of the given view.
+ ///
+ AlignBottomLeft,
+
+ ///
+ /// Displays the popup at the bottom right position of the given view.
+ ///
+ AlignBottomRight,
+ }
+}
\ No newline at end of file
diff --git a/maui/src/Picker/Enum/PickerTextDisplayMode.cs b/maui/src/Picker/Enum/PickerTextDisplayMode.cs
new file mode 100644
index 00000000..e92efd42
--- /dev/null
+++ b/maui/src/Picker/Enum/PickerTextDisplayMode.cs
@@ -0,0 +1,41 @@
+namespace Syncfusion.Maui.Toolkit.Picker
+{
+ ///
+ /// Defines the text display mode for the picker.
+ ///
+ public enum PickerTextDisplayMode
+ {
+ ///
+ /// Represents the picker items in default mode.
+ ///
+ ///
+ /// The default font size for the picker Text is 14, and the text color is white.
+ /// The default text color for unselected items is black.
+ ///
+ Default,
+
+ ///
+ /// Represents the picker items in fade mode.
+ ///
+ ///
+ /// The default text display color will fade from the selected item.
+ ///
+ Fade,
+
+ ///
+ /// Represents the picker items in shrink mode.
+ ///
+ ///
+ /// The text font size will be reduced from the selected item, while the text color remains the same.
+ ///
+ Shrink,
+
+ ///
+ /// Represents the picker items in fade and shrink mode.
+ ///
+ ///
+ /// The text display color will fade, and the text font size will be reduced from the selected item.
+ ///
+ FadeAndShrink,
+ }
+}
\ No newline at end of file
diff --git a/maui/src/Picker/Enum/PickerTimeFormat.cs b/maui/src/Picker/Enum/PickerTimeFormat.cs
new file mode 100644
index 00000000..1ee7173f
--- /dev/null
+++ b/maui/src/Picker/Enum/PickerTimeFormat.cs
@@ -0,0 +1,60 @@
+namespace Syncfusion.Maui.Toolkit.Picker
+{
+ ///
+ /// Defines the picker time format for the SfTimePicker.
+ ///
+ [System.Diagnostics.CodeAnalysis.SuppressMessage("Naming", "CA1707:Identifiers should not contain underscores", Justification = "Used to specify the different time elements")]
+ [System.Diagnostics.CodeAnalysis.SuppressMessage("Naming", "SA1300:ElementMustBeginWithUpperCaseLetter", Justification = "Used to specify the different time formats")]
+ public enum PickerTimeFormat
+ {
+ ///
+ /// Represents the hour and minute in H mm format.
+ ///
+ H_mm,
+
+ ///
+ /// Represents the hour, minute, and second in H mm ss format.
+ ///
+ H_mm_ss,
+
+ ///
+ /// Represents the hour, minute, second, and AM/PM designator in h mm ss tt format.
+ ///
+ h_mm_ss_tt,
+
+ ///
+ /// Represents the hour, minute, and AM/PM designator in h mm tt format.
+ ///
+ h_mm_tt,
+
+ ///
+ /// Represents the hour and minute in HH mm format.
+ ///
+ HH_mm,
+
+ ///
+ /// Represents the hour, minute, and second in HH mm ss format.
+ ///
+ HH_mm_ss,
+
+ ///
+ /// Represents the hour, minute, second, and AM/PM designator in hh mm ss tt format.
+ ///
+ hh_mm_ss_tt,
+
+ ///
+ /// Represents the hour, minute, and AM/PM designator in hh mm tt format.
+ ///
+ hh_mm_tt,
+
+ ///
+ /// Represents the hour and AM/PM designator in hh tt format.
+ ///
+ hh_tt,
+
+ ///
+ /// Represents default culture based format of the Picker Time format.
+ ///
+ Default,
+ }
+}
\ No newline at end of file
diff --git a/maui/src/Picker/EventArgs/DatePickerSelectionChangedEventArgs.cs b/maui/src/Picker/EventArgs/DatePickerSelectionChangedEventArgs.cs
new file mode 100644
index 00000000..30eaa083
--- /dev/null
+++ b/maui/src/Picker/EventArgs/DatePickerSelectionChangedEventArgs.cs
@@ -0,0 +1,18 @@
+namespace Syncfusion.Maui.Toolkit.Picker
+{
+ ///
+ /// Date picker selection changed event arguments.
+ ///
+ public class DatePickerSelectionChangedEventArgs : EventArgs
+ {
+ ///
+ /// Gets the new selected date value.
+ ///
+ public DateTime? NewValue { get; internal set; }
+
+ ///
+ /// Gets the previous selected date value.
+ ///
+ public DateTime? OldValue { get; internal set; }
+ }
+}
\ No newline at end of file
diff --git a/maui/src/Picker/EventArgs/DateTimePickerSelectionChangedEventArgs.cs b/maui/src/Picker/EventArgs/DateTimePickerSelectionChangedEventArgs.cs
new file mode 100644
index 00000000..a3a7df8f
--- /dev/null
+++ b/maui/src/Picker/EventArgs/DateTimePickerSelectionChangedEventArgs.cs
@@ -0,0 +1,9 @@
+namespace Syncfusion.Maui.Toolkit.Picker
+{
+ ///
+ /// Date time picker selection changed event arguments.
+ ///
+ public class DateTimePickerSelectionChangedEventArgs : DatePickerSelectionChangedEventArgs
+ {
+ }
+}
\ No newline at end of file
diff --git a/maui/src/Picker/EventArgs/PickerItemDetails.cs b/maui/src/Picker/EventArgs/PickerItemDetails.cs
new file mode 100644
index 00000000..c4c68e40
--- /dev/null
+++ b/maui/src/Picker/EventArgs/PickerItemDetails.cs
@@ -0,0 +1,22 @@
+namespace Syncfusion.Maui.Toolkit.Picker
+{
+ ///
+ /// Represents a class which contains picker item info.
+ ///
+ public class PickerItemDetails
+ {
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// Picker item details.
+ public PickerItemDetails(object data)
+ {
+ Data = data;
+ }
+
+ ///
+ /// Gets item value.
+ ///
+ public object Data { get; internal set; }
+ }
+}
\ No newline at end of file
diff --git a/maui/src/Picker/EventArgs/PickerPropertyChangedEventArgs.cs b/maui/src/Picker/EventArgs/PickerPropertyChangedEventArgs.cs
new file mode 100644
index 00000000..8b137ad7
--- /dev/null
+++ b/maui/src/Picker/EventArgs/PickerPropertyChangedEventArgs.cs
@@ -0,0 +1,24 @@
+using System.ComponentModel;
+
+namespace Syncfusion.Maui.Toolkit.Picker
+{
+ ///
+ /// Picker property changed event arguments.
+ ///
+ internal class PickerPropertyChangedEventArgs : PropertyChangedEventArgs
+ {
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The property name.
+ internal PickerPropertyChangedEventArgs(string? propertyName)
+ : base(propertyName)
+ {
+ }
+
+ ///
+ /// Gets or sets the old vale of the property which is used to unwire events for nested class properties.
+ ///
+ internal object? OldValue { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/maui/src/Picker/EventArgs/PickerSelectionChangedEventArgs.cs b/maui/src/Picker/EventArgs/PickerSelectionChangedEventArgs.cs
new file mode 100644
index 00000000..b05440e1
--- /dev/null
+++ b/maui/src/Picker/EventArgs/PickerSelectionChangedEventArgs.cs
@@ -0,0 +1,23 @@
+namespace Syncfusion.Maui.Toolkit.Picker
+{
+ ///
+ /// Picker selection changed event arguments.
+ ///
+ public class PickerSelectionChangedEventArgs : EventArgs
+ {
+ ///
+ /// Gets the new selected index.
+ ///
+ public int NewValue { get; internal set; }
+
+ ///
+ /// Gets the previous selected index.
+ ///
+ public int OldValue { get; internal set; }
+
+ ///
+ /// Gets the selected column index.
+ ///
+ public int ColumnIndex { get; internal set; }
+ }
+}
\ No newline at end of file
diff --git a/maui/src/Picker/EventArgs/TimePickerSelectionChangedEventArgs.cs b/maui/src/Picker/EventArgs/TimePickerSelectionChangedEventArgs.cs
new file mode 100644
index 00000000..12bfb738
--- /dev/null
+++ b/maui/src/Picker/EventArgs/TimePickerSelectionChangedEventArgs.cs
@@ -0,0 +1,18 @@
+namespace Syncfusion.Maui.Toolkit.Picker
+{
+ ///
+ /// Time picker selection changed event arguments.
+ ///
+ public class TimePickerSelectionChangedEventArgs : EventArgs
+ {
+ ///
+ /// Gets the new selected time value.
+ ///
+ public TimeSpan? NewValue { get; internal set; }
+
+ ///
+ /// Gets the previous selected time value.
+ ///
+ public TimeSpan? OldValue { get; internal set; }
+ }
+}
\ No newline at end of file
diff --git a/maui/src/Picker/Helper/DatePickerHelper.cs b/maui/src/Picker/Helper/DatePickerHelper.cs
new file mode 100644
index 00000000..97727ae8
--- /dev/null
+++ b/maui/src/Picker/Helper/DatePickerHelper.cs
@@ -0,0 +1,645 @@
+using System.Collections.ObjectModel;
+using System.Globalization;
+
+namespace Syncfusion.Maui.Toolkit.Picker
+{
+ ///
+ /// Represents a class which contains date picker helper methods.
+ ///
+ internal static class DatePickerHelper
+ {
+ #region Internal Methods
+
+ ///
+ /// Method to get the day string for the day value based on format.
+ ///
+ /// The day format.
+ /// The day value.
+ /// Returns the day string for the day value.
+ internal static string GetDayString(string format, int day)
+ {
+ return format == "d" ? $"{day:0}" : $"{day:00}";
+ }
+
+ ///
+ /// Method to get the month string for month value based on format.
+ ///
+ /// The month format.
+ /// The month value.
+ /// Returns month string based on format.
+ internal static string GetMonthString(string format, int month)
+ {
+ bool isAbbreviatedMonth = format == "MMM";
+ bool isSingleDigitMonth = format == "M";
+ if (isAbbreviatedMonth)
+ {
+ return CultureInfo.CurrentCulture.DateTimeFormat.GetAbbreviatedMonthName(month);
+ }
+ else if (isSingleDigitMonth)
+ {
+ return $"{month:0}";
+ }
+
+ return $"{month:00}";
+ }
+
+ ///
+ /// Method to replace the invariant month string with culture based month string.
+ ///
+ /// The invariant month string.
+ /// The date format string.
+ /// Date need to format.
+ /// Returns the culture based month value.
+ internal static string ReplaceCultureMonthString(string value, string format, DateTime selectedDate)
+ {
+ if (format.Contains("MMMM", StringComparison.Ordinal))
+ {
+ string monthName = selectedDate.ToString("MMMM", CultureInfo.InvariantCulture);
+ int index = value.IndexOf(monthName);
+ if (index != -1)
+ {
+ string currentMonthName = selectedDate.ToString("MMMM", CultureInfo.CurrentUICulture);
+ value = value.Replace(monthName, currentMonthName, StringComparison.Ordinal);
+ }
+ }
+ else if (format.Contains("MMM", StringComparison.Ordinal))
+ {
+ string monthName = selectedDate.ToString("MMM", CultureInfo.InvariantCulture);
+ int index = value.IndexOf(monthName);
+ if (index != -1)
+ {
+ string currentMonthName = selectedDate.ToString("MMM", CultureInfo.CurrentUICulture);
+ value = value.Replace(monthName, currentMonthName, StringComparison.Ordinal);
+ }
+ }
+
+ return value;
+ }
+
+ ///
+ /// Method to replace the invariant culture meridiem string to localization based string.
+ ///
+ /// Invariant culture meridiem string value.
+ /// Time format.
+ /// Returns localized meridiem string value.
+ internal static string ReplaceCultureMeridiemString(string value, string format)
+ {
+ if (format.Contains("tt", StringComparison.Ordinal) || format == "t")
+ {
+ int index = value.IndexOf("AM");
+ if (index != -1)
+ {
+ string meridiumString = DateTime.Now.Date.AddHours(6).ToString("tt", CultureInfo.CurrentUICulture);
+ value = value.Replace("AM", meridiumString, StringComparison.Ordinal);
+ }
+
+ index = value.IndexOf("PM");
+ if (index != -1)
+ {
+ string meridiumString = DateTime.Now.Date.AddHours(18).ToString("tt", CultureInfo.CurrentUICulture);
+ value = value.Replace("PM", meridiumString, StringComparison.Ordinal);
+ }
+ }
+ else if (format.Contains("t", StringComparison.Ordinal))
+ {
+ int index = value.IndexOf("A");
+ if (index != -1)
+ {
+ string meridiumString = DateTime.Now.Date.AddHours(6).ToString(" t", CultureInfo.CurrentUICulture);
+ meridiumString = meridiumString.Substring(1, meridiumString.Length - 1);
+ value = value.Replace("A", meridiumString, StringComparison.Ordinal);
+ }
+
+ index = value.IndexOf("P");
+ if (index != -1)
+ {
+ string meridiumString = DateTime.Now.Date.AddHours(18).ToString(" t", CultureInfo.CurrentUICulture);
+ meridiumString = meridiumString.Substring(1, meridiumString.Length - 1);
+ value = value.Replace("P", meridiumString, StringComparison.Ordinal);
+ }
+ }
+
+ return value;
+ }
+
+ ///
+ /// Method to get the years from minimum to maximum date.
+ ///
+ /// The min date value.
+ /// The max date value.
+ /// The year interval value.
+ /// Returns year collection based on min and max date.
+ internal static ObservableCollection GetYears(DateTime minDate, DateTime maxDate, int yearInterval)
+ {
+ ObservableCollection years = new ObservableCollection();
+ int minimumYear = minDate.Year;
+ int maximumYear = maxDate.Year;
+ for (int i = minimumYear; i <= maximumYear; i += yearInterval)
+ {
+ years.Add(i.ToString());
+ }
+
+ return years;
+ }
+
+ ///
+ /// Method to get the months for the year from min to max date.
+ ///
+ /// The month format.
+ /// The needed months for the year.
+ /// The min date value.
+ /// The max date value.
+ /// The month interval value.
+ /// Returns month collection for year.
+ internal static ObservableCollection GetMonths(string format, int year, DateTime minDate, DateTime maxDate, int monthInterval)
+ {
+ ObservableCollection months = new ObservableCollection();
+ if (string.IsNullOrEmpty(format))
+ {
+ return months;
+ }
+
+ int minimumMonth = 1;
+ int maximumMonth = 12;
+
+ //// Check the selected date year and minimum date year are same.
+ if (year == minDate.Year)
+ {
+ minimumMonth = minDate.Month;
+ }
+
+ //// Check the selected date year and maximum date year are same.
+ if (year == maxDate.Year)
+ {
+ maximumMonth = maxDate.Month;
+ }
+
+ bool isAbbreviatedMonth = format == "MMM";
+ bool isSingleDigitMonth = format == "M";
+ for (int i = minimumMonth; i <= maximumMonth; i += monthInterval)
+ {
+ if (isAbbreviatedMonth)
+ {
+ months.Add(CultureInfo.CurrentCulture.DateTimeFormat.GetAbbreviatedMonthName(i));
+ }
+ else
+ {
+ string monthValue = isSingleDigitMonth ? $"{i:0}" : $"{i:00}";
+ months.Add(monthValue);
+ }
+ }
+
+ return months;
+ }
+
+ ///
+ /// Method to get the days for the year and month from min to max date.
+ ///
+ /// The day format.
+ /// The month value.
+ /// The year value.
+ /// The min date value.
+ /// The max date value.
+ /// The day interval value.
+ /// Returns the days collection for the specified month and year.
+ internal static ObservableCollection GetDays(string format, int month, int year, DateTime minDate, DateTime maxDate, int dayInterval)
+ {
+ ObservableCollection days = new ObservableCollection();
+ if (string.IsNullOrEmpty(format))
+ {
+ return days;
+ }
+
+ int minimumDate = 1;
+ int maximumDate = DateTime.DaysInMonth(year, month);
+
+ //// Check the selected date year with minimum date year and month are same.
+ if (year == minDate.Year && month == minDate.Month)
+ {
+ minimumDate = minDate.Day;
+ }
+
+ //// Check the selected date year with maximum date year and month are same.
+ if (year == maxDate.Year && month == maxDate.Month)
+ {
+ maximumDate = maxDate.Day;
+ }
+
+ bool isSingleDigitDay = format == "d";
+ for (int i = minimumDate; i <= maximumDate; i += dayInterval)
+ {
+ string dayValue = isSingleDigitDay ? $"{i:0}" : $"{i:00}";
+ days.Add(dayValue);
+ }
+
+ return days;
+ }
+
+ ///
+ /// Method to return the index value for day value inside the days string collection based on format.
+ ///
+ /// The day format.
+ /// The days collection.
+ /// The day value.
+ /// Returns index of the day value. if the day value not placed inside the days collection then return the nearby value.
+ internal static int GetDayIndex(string format, ObservableCollection days, int day)
+ {
+ if (string.IsNullOrEmpty(format))
+ {
+ return -1;
+ }
+
+ string dayString = GetDayString(format, day);
+ int index = days.IndexOf(dayString);
+ if (index != -1)
+ {
+ return index;
+ }
+
+ for (int i = 0; i < days.Count; i++)
+ {
+ string dayItem = days[i];
+ if (int.Parse(dayItem) > day)
+ {
+ index = i;
+ break;
+ }
+ }
+
+ if (index == -1)
+ {
+ index = days.Count - 1;
+ }
+
+ return index;
+ }
+
+ ///
+ /// Method to return the index value for year value inside the year string collection.
+ ///
+ /// The years collection.
+ /// The year value.
+ /// Returns index of the year value. if the year value not placed inside the years collection then return the nearby value.
+ internal static int GetYearIndex(ObservableCollection years, int year)
+ {
+ if (years == null || years.Count == 0)
+ {
+ return -1;
+ }
+
+ string yearString = year.ToString();
+ int index = years.IndexOf(yearString);
+ if (index != -1)
+ {
+ return index;
+ }
+
+ for (int i = 0; i < years.Count; i++)
+ {
+ string yearValue = years[i];
+ if (int.Parse(yearValue) > year)
+ {
+ index = i;
+ break;
+ }
+ }
+
+ if (index == -1)
+ {
+ index = years.Count - 1;
+ }
+
+ return index;
+ }
+
+ ///
+ /// Method to return the index value for month value inside the months string collection based on format.
+ ///
+ /// The month format.
+ /// The months collection.
+ /// The month value.
+ /// Returns index of the month value. if the month value not placed inside the months collection then return the nearby value.
+ internal static int GetMonthIndex(string format, ObservableCollection months, int month)
+ {
+ string monthString = GetMonthString(format, month);
+ int monthIndex = months.IndexOf(monthString);
+ if (monthIndex != -1)
+ {
+ return monthIndex;
+ }
+
+ List monthStrings = new List();
+ bool isAbbreviatedMonth = format == "MMM";
+ if (isAbbreviatedMonth)
+ {
+ monthStrings = DateTimeFormatInfo.CurrentInfo.AbbreviatedMonthNames.ToList();
+ }
+
+ if (isAbbreviatedMonth)
+ {
+ for (int i = 0; i < months.Count; i++)
+ {
+ string monthItem = months[i];
+ if (monthStrings.IndexOf(monthItem) + 1 > month)
+ {
+ monthIndex = i;
+ break;
+ }
+ }
+ }
+ else
+ {
+ for (int i = 0; i < months.Count; i++)
+ {
+ string monthItem = months[i];
+ if (int.Parse(monthItem) > month)
+ {
+ monthIndex = i;
+ break;
+ }
+ }
+ }
+
+ if (monthIndex == -1)
+ {
+ monthIndex = months.Count - 1;
+ }
+
+ return monthIndex;
+ }
+
+ ///
+ /// Method to return the date order based on picker format. 0 denotes day, 1 denotes month and 2 denotes year.
+ ///
+ /// Holds the day format value based on picker format.
+ /// Holds the month format value based on picker format.
+ /// The date picker date format value.
+ /// Returns date order list.
+ internal static List GetFormatStringOrder(out string dayFormat, out string monthFormat, PickerDateFormat format)
+ {
+ dayFormat = string.Empty;
+ monthFormat = string.Empty;
+ List formatStringOrder = new List();
+
+ // Get the culture-specific date pattern
+ string cultureDateFormat = CultureInfo.CurrentUICulture.DateTimeFormat.ShortDatePattern;
+ string cultureFormatString = format.ToString();
+
+ if (format == PickerDateFormat.Default)
+ {
+ cultureFormatString = string.Empty;
+ }
+
+ // Handle predefined formats first
+ switch (cultureFormatString)
+ {
+ case "dd_MM":
+ dayFormat = "dd";
+ monthFormat = "MM";
+ formatStringOrder = new List() { 0, 1 };
+ break;
+
+ case "dd_MM_yyyy":
+ dayFormat = "dd";
+ monthFormat = "MM";
+ formatStringOrder = new List() { 0, 1, 2 };
+ break;
+
+ case "dd_MMM_yyyy":
+ dayFormat = "dd";
+ monthFormat = "MMM";
+ formatStringOrder = new List() { 0, 1, 2 };
+ break;
+
+ case "M_d_yyyy":
+ dayFormat = "d";
+ monthFormat = "M";
+ formatStringOrder = new List() { 1, 0, 2 };
+ break;
+
+ case "MM_dd_yyyy":
+ dayFormat = "dd";
+ monthFormat = "MM";
+ formatStringOrder = new List() { 1, 0, 2 };
+ break;
+
+ case "MM_yyyy":
+ monthFormat = "MM";
+ formatStringOrder = new List() { 1, 2 };
+ break;
+
+ case "MMM_yyyy":
+ monthFormat = "MMM";
+ formatStringOrder = new List() { 1, 2 };
+ break;
+
+ case "yyyy_MM_dd":
+ dayFormat = "dd";
+ monthFormat = "MM";
+ formatStringOrder = new List() { 2, 1, 0 };
+ break;
+
+ default:
+ // If the format is not predefined, dynamically determine the format order
+ formatStringOrder = GetCustomFormatOrder(CultureInfo.CurrentUICulture.DateTimeFormat.ShortDatePattern, out dayFormat, out monthFormat);
+ break;
+ }
+
+ return formatStringOrder;
+ }
+
+ ///
+ /// Method to check both date value are equal.
+ ///
+ /// Date value.
+ /// The other date value.
+ /// Returns true, while both date values are same.
+ internal static bool IsSameDate(DateTime? date, DateTime? otherDate)
+ {
+ if (date == null || otherDate == null)
+ {
+ return false;
+ }
+
+ return date.Value.Year == otherDate.Value.Year && date.Value.Month == otherDate.Value.Month && date.Value.Date == otherDate.Value.Date;
+ }
+
+ ///
+ /// Method to check both date time value are equal.
+ ///
+ /// Date time value.
+ /// The other date time value.
+ /// Returns true, while both date time values are same.
+ internal static bool IsSameDateTime(DateTime? date, DateTime? otherDate)
+ {
+ if (date == null || otherDate == null)
+ {
+ return date == otherDate;
+ }
+
+ return date.Value.Year == otherDate.Value.Year &&
+ date.Value.Month == otherDate.Value.Month &&
+ date.Value.Day == otherDate.Value.Day &&
+ date.Value.Hour == otherDate.Value.Hour &&
+ date.Value.Minute == otherDate.Value.Minute &&
+ date.Value.Second == otherDate.Value.Second;
+ }
+
+ ///
+ /// Method to get the valid date based on min and max date.
+ ///
+ /// The date value.
+ /// The min date value.
+ /// The max date value.
+ /// Returns the valid date.
+ internal static DateTime? GetValidDate(DateTime? date, DateTime minDate, DateTime maxDate)
+ {
+ if (date != null)
+ {
+ if (date.Value.Date < minDate.Date)
+ {
+ return minDate.Date;
+ }
+ else if (date.Value.Date > maxDate.Date)
+ {
+ return maxDate.Date;
+ }
+ }
+
+ return date;
+ }
+
+ ///
+ /// Method to get the valid date time based on min and max date.
+ ///
+ /// The date time value.
+ /// The min date time value.
+ /// The max date time value.
+ /// Returns the valid date time.
+ internal static DateTime GetValidDateTime(DateTime? date, DateTime minDate, DateTime maxDate)
+ {
+ DateTime validDate = date ?? DateTime.MinValue;
+
+ if (validDate < minDate)
+ {
+ return minDate;
+ }
+ else if (validDate > maxDate)
+ {
+ return maxDate;
+ }
+
+ return validDate;
+ }
+
+ ///
+ /// Method to get the valid max date.
+ ///
+ /// The min date value.
+ /// The max date value.
+ /// Returns the valid max date
+ internal static DateTime GetValidMaxDate(DateTime minDate, DateTime maxDate)
+ {
+ if (maxDate < minDate)
+ {
+ return minDate;
+ }
+
+ return maxDate;
+ }
+
+ ///
+ /// Method to check the current time is black out datetime or not.
+ ///
+ /// An black out date time value.
+ /// Current selected datetime value.
+ /// Checks whether we need to update time header only.
+ /// Returns true or false based on blackout datetime.
+ internal static bool IsBlackoutDateTime(DateTime blackOutDateTime, DateTime? currentDateTime, out bool isTimeSpanAtZero)
+ {
+ isTimeSpanAtZero = false;
+
+ if (currentDateTime != null)
+ {
+ if (blackOutDateTime.Date == currentDateTime.Value.Date)
+ {
+ if (blackOutDateTime.TimeOfDay == TimeSpan.Zero)
+ {
+ isTimeSpanAtZero = true;
+ return true;
+ }
+ else if (blackOutDateTime.Hour == currentDateTime.Value.Hour && blackOutDateTime.Minute == currentDateTime.Value.Minute)
+ {
+ return true;
+ }
+ }
+ }
+
+ return false;
+ }
+
+ ///
+ /// Method to check the current time is black out date or not.
+ ///
+ /// Determines calculation compares date only.
+ /// Current selected value.
+ /// An black out date.
+ /// Selected date value.
+ /// Returns true or false based on blackout date.
+ internal static bool IsBlackoutDate(bool isDateOnlyComparison, string currentValue, DateTime blackOutDate, DateTime selectedDate)
+ {
+ if (isDateOnlyComparison)
+ {
+ return blackOutDate.Date == selectedDate.Date;
+ }
+ else
+ {
+ return blackOutDate.Year == selectedDate.Year && blackOutDate.Month == selectedDate.Month && blackOutDate.Day == int.Parse(currentValue);
+ }
+ }
+
+ #endregion
+
+ #region Private Methods
+
+ ///
+ /// Method to return the date order based on culture. 0 denotes day, 1 denotes month and 2 denotes year.
+ ///
+ /// culture based date format
+ /// culture based day format
+ /// culture based month format
+ /// Returns date order list
+ static List GetCustomFormatOrder(string cultureDateFormat, out string dayFormat, out string monthFormat)
+ {
+ dayFormat = string.Empty;
+ monthFormat = string.Empty;
+ List formatOrder = new List();
+
+ // Extract parts of the format and map them to positions (0, 1, 2)
+ string[] formatParts = cultureDateFormat.Split(new char[] { '/', '-', '.', ' ', ',' }, StringSplitOptions.RemoveEmptyEntries);
+ for (int i = 0; i < formatParts.Length; i++)
+ {
+ if (formatParts[i].CompareTo("d") == 0 || formatParts[i].CompareTo("dd") == 0)
+ {
+ dayFormat = formatParts[i];
+ formatOrder.Add(0);
+ }
+ else if (formatParts[i].CompareTo("M") == 0 || formatParts[i].CompareTo("MM") == 0)
+ {
+ monthFormat = formatParts[i];
+ formatOrder.Add(1);
+ }
+ else if (formatParts[i].CompareTo("yy") == 0 || formatParts[i].CompareTo("yyyy") == 0)
+ {
+ formatOrder.Add(2);
+ }
+ }
+
+ // Distinct Method is used to store only unique values in the list.
+ formatOrder = formatOrder.Distinct().ToList();
+ return formatOrder;
+ }
+
+ #endregion
+ }
+}
\ No newline at end of file
diff --git a/maui/src/Picker/Helper/PickerHelper.cs b/maui/src/Picker/Helper/PickerHelper.cs
new file mode 100644
index 00000000..70d914cd
--- /dev/null
+++ b/maui/src/Picker/Helper/PickerHelper.cs
@@ -0,0 +1,269 @@
+using System.Collections;
+using System.Collections.ObjectModel;
+using Syncfusion.Maui.Toolkit.Graphics.Internals;
+
+namespace Syncfusion.Maui.Toolkit.Picker
+{
+ ///
+ /// Represents a class which contains picker view helper.
+ ///
+ internal static class PickerHelper
+ {
+ #region Internal Methods
+
+ ///
+ /// Method to convert brush to color.
+ ///
+ /// The brush to convert.
+ /// Returns the color value.
+ internal static Color ToColor(this Brush brush)
+ {
+ Paint paint = (Paint)brush;
+ return paint.ToColor() ?? Colors.Transparent;
+ }
+
+ ///
+ /// Method to trim the text based on available width.
+ ///
+ /// The text to trim.
+ /// The available width.
+ /// The text style.
+ /// Returns the text for the available width.
+ internal static string TrimText(string text, double width, PickerTextStyle textStyle)
+ {
+ if (string.IsNullOrEmpty(text))
+ {
+ return text;
+ }
+
+ double value = 0;
+ var textTrim = text;
+ Size textSize = text.Measure(textStyle);
+ var ellipsisWidth = "..".Measure(textStyle).Width;
+
+ do
+ {
+ if (textSize.Width > width && text.Length > 0)
+ {
+ int length = text.Length - 2;
+ text = text.Substring(0, length < 0 ? 0 : length);
+ value = text.Measure(textStyle).Width;
+ }
+ else if (width - ellipsisWidth < 0)
+ {
+ break;
+ }
+ else
+ {
+ continue;
+ }
+ }
+ while (value > width - ellipsisWidth && text.Length > 0);
+
+ if (textTrim != text)
+ {
+ textTrim = text + "..";
+ }
+
+ return textTrim;
+ }
+
+ ///
+ /// Method to return the items count from items source object.
+ ///
+ /// The items source object.
+ /// Returns items count from items source object.
+ internal static int GetItemsCount(object itemssSource)
+ {
+ if (itemssSource != null && itemssSource is ICollection collection)
+ {
+ return collection.Count;
+ }
+
+ return 0;
+ }
+
+ ///
+ /// Method to return the items value count from items source object.
+ ///
+ /// The picker column value.
+ /// Returns items value count from items source object.
+ internal static int GetSelectedItemIndex(PickerColumn column)
+ {
+ if (column.ItemsSource != null && column.ItemsSource is ICollection collection && column.SelectedItem != null)
+ {
+ int index = 0;
+ foreach (object value in collection)
+ {
+ //// At initial, not need to check the selected index value for selected item.
+ if (column._isSelectedItemChanged && column.SelectedItem.Equals(value))
+ {
+ return index;
+ }
+ //// In dynamic, need to check the selected item index based on selected index.
+ else if (!column._isSelectedItemChanged && column.SelectedItem.Equals(value) && index == column.SelectedIndex)
+ {
+ return index;
+ }
+
+ index++;
+ }
+ }
+
+ return column.SelectedItem == null ? -1 : 0;
+ }
+
+ ///
+ /// Method invokes to get the default value for selected item based on selected index.
+ ///
+ /// The bindable value.
+ /// Returns the index value.
+ internal static object? GetSelectedItemDefaultValue(BindableObject bindable)
+ {
+ if (bindable is PickerColumn pickerColumn && pickerColumn != null)
+ {
+ if (pickerColumn.ItemsSource != null && pickerColumn.ItemsSource is ICollection collection && pickerColumn.SelectedIndex > -1)
+ {
+ int index = 0;
+ object firstItem = string.Empty;
+ foreach (object item in collection)
+ {
+ if (index == 0)
+ {
+ firstItem = item;
+ }
+
+ if (index == pickerColumn.SelectedIndex)
+ {
+ return item;
+ }
+
+ index++;
+ }
+
+ return firstItem;
+ }
+ }
+
+ return null;
+ }
+
+ ///
+ /// Method to get the valid selected index.
+ ///
+ /// The selected index.
+ /// The items count.
+ /// Returns the valid selected index.
+ internal static int GetValidSelectedIndex(int selectedIndex, int itemsCount)
+ {
+ //// Considered the selected index as -1 if index value is less than -1. Negative index values less than -1 are not valid.
+ if (selectedIndex < -1)
+ {
+ return -1;
+ }
+
+ //// Considered the index value as the last item index value. Because the index value is not greater than the item source count.
+ if (selectedIndex >= itemsCount)
+ {
+ return itemsCount - 1;
+ }
+
+ return selectedIndex;
+ }
+
+ ///
+ /// Method to get the collection is equal or not.
+ ///
+ /// The collection.
+ /// The new collection.
+ /// Returns collections are equal.
+ internal static bool IsCollectionEquals(ObservableCollection collection, ObservableCollection newCollection)
+ {
+ if (collection == newCollection)
+ {
+ return true;
+ }
+
+ if (collection == null || newCollection == null)
+ {
+ return false;
+ }
+
+ if (collection.Count != newCollection.Count)
+ {
+ return false;
+ }
+
+ for (int index = 0; index < collection.Count; index++)
+ {
+ object item = collection[index];
+ object newItem = newCollection[index];
+ if (!item.Equals(newItem))
+ {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ ///
+ /// Method to create a template view.
+ ///
+ /// The data template.
+ /// The item details.
+ /// Returns the view from the view template
+ internal static View CreateTemplateView(DataTemplate template, PickerItemDetails itemDetail)
+ {
+ View view;
+ var content = template.CreateContent();
+ if (content is ViewCell)
+ {
+ view = ((ViewCell)content).View;
+ }
+ else
+ {
+ view = (View)content;
+ }
+
+ if (view.BindingContext == null)
+ {
+ view.BindingContext = itemDetail;
+ }
+
+ return view;
+ }
+
+ ///
+ /// Method to get the name based on parent.
+ ///
+ /// The parent details.
+ /// Returns the name based on parent.
+ internal static string GetParentName(Element parent)
+ {
+ string name = string.Empty;
+ switch (parent)
+ {
+ case SfPicker:
+ name = "Picker";
+ break;
+ case SfDatePicker:
+ name = "DatePicker";
+ break;
+ case SfTimePicker:
+ name = "TimePicker";
+ break;
+ case SfDateTimePicker:
+ name = "DateTimePicker";
+ break;
+ default:
+ name = string.Empty;
+ break;
+ }
+
+ return name;
+ }
+
+ #endregion
+ }
+}
\ No newline at end of file
diff --git a/maui/src/Picker/Helper/TimePickerHelper.cs b/maui/src/Picker/Helper/TimePickerHelper.cs
new file mode 100644
index 00000000..427e4fe1
--- /dev/null
+++ b/maui/src/Picker/Helper/TimePickerHelper.cs
@@ -0,0 +1,559 @@
+using System.Collections.ObjectModel;
+using System.Globalization;
+
+namespace Syncfusion.Maui.Toolkit.Picker
+{
+ ///
+ /// Represents a class which contains time picker helper methods.
+ ///
+ internal static class TimePickerHelper
+ {
+ #region Internal Methods
+
+ ///
+ /// Method to check the time spans are equal are not. It only compares the hour, minute and second values.
+ ///
+ /// The time span value.
+ /// Other time span value.
+ /// Returns true, if both time span values are same.
+ internal static bool IsSameTimeSpan(TimeSpan? time, TimeSpan? other)
+ {
+ if (other == null || time == null)
+ {
+ return false;
+ }
+
+ return time.Value.Hours == other.Value.Hours && time.Value.Minutes == other.Value.Minutes && time.Value.Seconds == other.Value.Seconds;
+ }
+
+ ///
+ /// Method to get the minutes string collection value.
+ ///
+ /// The interval value.
+ /// The hour value.
+ /// The selected date value used to compare min and max date value.
+ /// The min date value.
+ /// The max date value.
+ /// Returns seconds or minutes string collection.
+ internal static ObservableCollection GetMinutes(int interval, int hour, DateTime? selectedDate, DateTime? minDateValue, DateTime? maxDateValue)
+ {
+ ObservableCollection collection = new ObservableCollection();
+ int startIndex = 0;
+ int endIndex = 59;
+ if (minDateValue != null)
+ {
+ if (selectedDate != null && selectedDate.Value.Date == minDateValue.Value.Date && hour == minDateValue.Value.Hour)
+ {
+ startIndex = minDateValue.Value.Minute;
+ }
+ }
+
+ if (maxDateValue != null)
+ {
+ if (selectedDate != null && selectedDate.Value.Date == maxDateValue.Value.Date && hour == maxDateValue.Value.Hour)
+ {
+ endIndex = maxDateValue.Value.Minute;
+ }
+ }
+
+ for (int i = startIndex; i <= endIndex; i = i + interval)
+ {
+ collection.Add($"{i:00}");
+ }
+
+ return collection;
+ }
+
+ ///
+ /// Method to get the seconds string collection value.
+ ///
+ /// The interval value.
+ /// The hour value.
+ /// The minute value.
+ /// The selected date value used to compare min and max date value.
+ /// The min date value.
+ /// The max date value.
+ /// Returns seconds or minutes string collection.
+ internal static ObservableCollection GetSeconds(int interval, int hour, int minute, DateTime? selectedDate, DateTime? minDateValue, DateTime? maxDateValue)
+ {
+ ObservableCollection collection = new ObservableCollection();
+ int startIndex = 0;
+ int endIndex = 59;
+
+ if (minDateValue != null)
+ {
+ if (selectedDate != null && selectedDate.Value.Date == minDateValue.Value.Date && hour == minDateValue.Value.Hour && minute == minDateValue.Value.Minute)
+ {
+ startIndex = minDateValue.Value.Second;
+ }
+ }
+
+ if (maxDateValue != null)
+ {
+ if (selectedDate != null && selectedDate.Value.Date == maxDateValue.Value.Date && hour == maxDateValue.Value.Hour && minute == maxDateValue.Value.Minute)
+ {
+ endIndex = maxDateValue.Value.Second;
+ }
+ }
+
+ for (int i = startIndex; i <= endIndex; i = i + interval)
+ {
+ collection.Add($"{i:00}");
+ }
+
+ return collection;
+ }
+
+ ///
+ /// Method to get the hours string collection value.
+ ///
+ /// The hour format value.
+ /// The hour interval value.
+ /// The selected date value.
+ /// The min date value.
+ /// The max date value.
+ /// Returns hours string collection.
+ internal static ObservableCollection GetHours(string format, int interval, DateTime? selectedDate, DateTime? minDate, DateTime? maxDate)
+ {
+ ObservableCollection hours = new ObservableCollection();
+ if (format == "h")
+ {
+ int startIndex = 0;
+ int endIndex = 11;
+ if (minDate != null && selectedDate != null && selectedDate.Value.Date == minDate.Value.Date && (int)(selectedDate.Value.Hour / 12) == (int)(minDate.Value.Hour / 12))
+ {
+ startIndex = minDate.Value.Hour % 12;
+ }
+
+ if (maxDate != null && selectedDate != null && selectedDate.Value.Date == maxDate.Value.Date && (int)(selectedDate.Value.Hour / 12) == (int)(maxDate.Value.Hour / 12))
+ {
+ endIndex = maxDate.Value.Hour % 12;
+ }
+
+ for (int i = startIndex; i <= endIndex; i = i + interval)
+ {
+ if (i == 0)
+ {
+ continue;
+ }
+
+ hours.Add(i.ToString());
+ }
+
+ if (startIndex == 0)
+ {
+ hours.Add("12");
+ }
+ }
+ else if (format == "hh")
+ {
+ int startIndex = 0;
+ int endIndex = 11;
+ if (minDate != null && selectedDate != null && selectedDate.Value.Date == minDate.Value.Date && (int)(selectedDate.Value.Hour / 12) == (int)(minDate.Value.Hour / 12))
+ {
+ startIndex = minDate.Value.Hour % 12;
+ }
+
+ if (maxDate != null && selectedDate != null && selectedDate.Value.Date == maxDate.Value.Date && (int)(selectedDate.Value.Hour / 12) == (int)(maxDate.Value.Hour / 12))
+ {
+ endIndex = maxDate.Value.Hour % 12;
+ }
+
+ for (int i = startIndex; i <= endIndex; i = i + interval)
+ {
+ if (i == 0)
+ {
+ continue;
+ }
+
+ hours.Add($"{i:00}");
+ }
+
+ if (startIndex == 0)
+ {
+ hours.Add("12");
+ }
+ }
+ else if (format == "H")
+ {
+ int startIndex = 0;
+ int endIndex = 23;
+ if (minDate != null)
+ {
+ startIndex = minDate.Value.Hour;
+ }
+
+ if (maxDate != null)
+ {
+ endIndex = maxDate.Value.Hour;
+ }
+
+ for (int i = startIndex; i <= endIndex; i = i + interval)
+ {
+ hours.Add(i.ToString());
+ }
+ }
+ else if (format == "HH")
+ {
+ int startIndex = 0;
+ int endIndex = 23;
+ if (minDate != null)
+ {
+ startIndex = minDate.Value.Hour;
+ }
+
+ if (maxDate != null)
+ {
+ endIndex = maxDate.Value.Hour;
+ }
+
+ for (int i = startIndex; i <= endIndex; i = i + interval)
+ {
+ hours.Add($"{i:00}");
+ }
+ }
+
+ return hours;
+ }
+
+ ///
+ /// Method to get the hour text for the hour value based on format.
+ ///
+ /// Hour format.
+ /// Hour value.
+ /// Returns hour string based on format.
+ internal static string? GetHourText(string format, int? hour)
+ {
+ if (format == "h")
+ {
+ int? time = hour % 12;
+ return time == 0 ? "12" : time.ToString();
+ }
+ else if (format == "hh")
+ {
+ int? time = hour % 12;
+ return time == 0 ? "12" : $"{time:00}";
+ }
+ else if (format == "H")
+ {
+ return hour.ToString();
+ }
+
+ return $"{hour:00}";
+ }
+
+ ///
+ /// Method to get the minute text for the minute value based on format.
+ ///
+ /// Minute value.
+ /// Returns minute string based on format.
+ internal static string GetMinuteOrSecondText(int minute)
+ {
+ return $"{minute:00}";
+ }
+
+ ///
+ /// Method to get the meridiem string collection value.
+ ///
+ /// The min date value.
+ /// The max date value.
+ /// The selected date value.
+ /// Returns meridiem string collection.
+ internal static ObservableCollection GetMeridiem(DateTime? minDate, DateTime? maxDate, DateTime? selectedDate)
+ {
+ ObservableCollection meridiems = new ObservableCollection();
+ if (minDate == null || (selectedDate != null && selectedDate.Value.Date == minDate.Value.Date && minDate.Value.Hour < 12))
+ {
+ meridiems.Add(CultureInfo.CurrentUICulture.DateTimeFormat.AMDesignator);
+ }
+
+ if (maxDate == null || (selectedDate != null && selectedDate.Value.Date == maxDate.Value.Date && maxDate.Value.Hour >= 12))
+ {
+ meridiems.Add(CultureInfo.CurrentUICulture.DateTimeFormat.PMDesignator);
+ }
+
+ return meridiems;
+ }
+
+ ///
+ /// Method to get the selected index as AM text value.
+ ///
+ /// The meridiem collection.
+ /// The selected index value.
+ /// Returns true while the selected index have AM text.
+ internal static bool IsAMText(ObservableCollection meridiem, int index)
+ {
+ if (index > meridiem.Count)
+ {
+ index = meridiem.Count - 1;
+ }
+
+ if (index == -1)
+ {
+ return true;
+ }
+
+ return meridiem[index] == CultureInfo.CurrentUICulture.DateTimeFormat.AMDesignator;
+ }
+
+ ///
+ /// Method to get the minute or second index value on collection.
+ ///
+ /// The string collection for minutes or seconds.
+ /// The minute or second value.
+ /// Returns index for the value in collection.
+ internal static int GetMinuteOrSecondIndex(ObservableCollection collection, int value)
+ {
+ string dayString = GetMinuteOrSecondText(value);
+ int index = collection.IndexOf(dayString);
+ if (index != -1)
+ {
+ return index;
+ }
+
+ for (int i = 0; i < collection.Count; i++)
+ {
+ string item = collection[i];
+ if (int.Parse(item) > value)
+ {
+ index = i;
+ break;
+ }
+ }
+
+ if (index == -1)
+ {
+ index = collection.Count - 1;
+ }
+
+ return index;
+ }
+
+ ///
+ /// Method to get the hour index value based on the format.
+ ///
+ /// The hour format.
+ /// The hour collection.
+ /// The hour value.
+ /// Returns the hour index value.
+ internal static int GetHourIndex(string format, ObservableCollection hours, int? hour)
+ {
+ if (string.IsNullOrEmpty(format))
+ {
+ return -1;
+ }
+
+ string? dayString = GetHourText(format, hour);
+ int index;
+ if (dayString != null)
+ {
+ index = hours.IndexOf(dayString);
+ }
+ else
+ {
+ index = 0;
+ }
+
+ if (index != -1)
+ {
+ return index;
+ }
+
+ int? time = hour;
+ if (format == "h" || format == "hh")
+ {
+ time = hour % 12;
+ time = time == 0 ? 12 : time;
+ }
+
+ for (int i = 0; i < hours.Count; i++)
+ {
+ string hourItem = hours[i];
+ if (int.Parse(hourItem) > time)
+ {
+ index = i;
+ break;
+ }
+ }
+
+ if (index == -1)
+ {
+ index = hours.Count - 1;
+ }
+
+ return index;
+ }
+
+ ///
+ /// Method to return the time order based on picker format. 0 denotes hour, 1 denotes minute, 2 denotes second and 3 denotes meridiem.
+ ///
+ /// Holds the hour format value based on picker format.
+ /// The time picker time format.
+ /// Returns time order list.
+ internal static List GetFormatStringOrder(out string hourFormat, PickerTimeFormat format)
+ {
+ hourFormat = string.Empty;
+ List formatStringOrder = new List();
+ string cultureFormatString = format.ToString();
+
+ if (format == PickerTimeFormat.Default)
+ {
+ cultureFormatString = string.Empty;
+ }
+
+ // Handle predefined formats first
+ switch (cultureFormatString)
+ {
+ case "H_mm":
+ hourFormat = "H";
+ formatStringOrder = new List() { 0, 1 };
+ break;
+ case "HH_mm":
+ hourFormat = "HH";
+ formatStringOrder = new List() { 0, 1 };
+ break;
+ case "H_mm_ss":
+ hourFormat = "H";
+ formatStringOrder = new List() { 0, 1, 2 };
+ break;
+ case "HH_mm_ss":
+ hourFormat = "HH";
+ formatStringOrder = new List() { 0, 1, 2 };
+ break;
+ case "h_mm_ss_tt":
+ hourFormat = "h";
+ formatStringOrder = new List() { 0, 1, 2, 3 };
+ break;
+ case "hh_mm_ss_tt":
+ hourFormat = "hh";
+ formatStringOrder = new List() { 0, 1, 2, 3 };
+ break;
+ case "h_mm_tt":
+ hourFormat = "h";
+ formatStringOrder = new List() { 0, 1, 3 };
+ break;
+ case "hh_mm_tt":
+ hourFormat = "hh";
+ formatStringOrder = new List() { 0, 1, 3 };
+ break;
+ case "hh_tt":
+ hourFormat = "hh";
+ formatStringOrder = new List() { 0, 3 };
+ break;
+ default:
+ // If the format is not predefined, dynamically determine the format order
+ formatStringOrder = GetCustomFormatOrder(CultureInfo.CurrentUICulture.DateTimeFormat.ShortTimePattern, out hourFormat);
+ break;
+ }
+
+ return formatStringOrder;
+ }
+
+ ///
+ /// Method to get the valid max time.
+ ///
+ /// The min time value.
+ /// The max time value.
+ /// Returns the valid max time
+ internal static TimeSpan GetValidMaxTime(TimeSpan minTime, TimeSpan maxTime)
+ {
+ if (maxTime < minTime)
+ {
+ return minTime;
+ }
+
+ return maxTime;
+ }
+
+ ///
+ /// Method to get the valid selected time based on min and max time.
+ ///
+ /// The time value.
+ /// The min time value.
+ /// The max time value.
+ /// Returns the valid time.
+ internal static TimeSpan? GetValidSelectedTime(TimeSpan? time, TimeSpan minTime, TimeSpan maxTime)
+ {
+ if (time != null)
+ {
+ if (time < minTime)
+ {
+ return new TimeSpan(minTime.Hours, minTime.Minutes, time.Value.Seconds);
+ }
+ else if (time > maxTime)
+ {
+ return new TimeSpan(maxTime.Hours, maxTime.Minutes, time.Value.Seconds);
+ }
+ }
+
+ return time;
+ }
+
+ ///
+ /// Method to check the current time is black out time or not.
+ ///
+ /// An black out time value.
+ /// Current selected time value.
+ /// Returns true or false based on blackout time.
+ internal static bool IsBlackoutTime(TimeSpan blackOutTime, TimeSpan? currentTime)
+ {
+ if (currentTime != null && blackOutTime.Hours == currentTime.Value.Hours && blackOutTime.Minutes == currentTime.Value.Minutes)
+ {
+ return true;
+ }
+
+ return false;
+ }
+
+ #endregion
+
+ #region Private Methods
+
+ ///
+ /// Method to get custom format order if user sets culture values
+ ///
+ /// culture based time format
+ /// culture based hour format
+ /// Returns time order list
+ static List GetCustomFormatOrder(string cultureTimeFormat, out string hourFormat)
+ {
+ hourFormat = string.Empty;
+ List formatOrder = new List();
+
+ // Extract parts of the format and map them to positions (0, 1, 2)
+ string[] formatParts = cultureTimeFormat.Split(new char[] { ' ', ':', '\u202F' }, StringSplitOptions.RemoveEmptyEntries);
+
+ for (int i = 0; i < formatParts.Length; i++)
+ {
+ switch (formatParts[i])
+ {
+ case "HH":
+ case "H":
+ case "h":
+ case "hh":
+ hourFormat = formatParts[i];
+ formatOrder.Add(0);
+ break;
+ case "mm":
+ formatOrder.Add(1);
+ break;
+ case "ss":
+ formatOrder.Add(2);
+ break;
+ case "tt":
+ formatOrder.Add(3);
+ break;
+ default:
+ // Handle unexpected cases or other format patterns
+ break;
+ }
+ }
+
+ // Distinct method is used to store only unique values in the list
+ formatOrder = formatOrder.Distinct().ToList();
+ return formatOrder;
+ }
+
+ #endregion
+ }
+}
\ No newline at end of file
diff --git a/maui/src/Picker/Interface/IColumnHeaderView.cs b/maui/src/Picker/Interface/IColumnHeaderView.cs
new file mode 100644
index 00000000..b41fdd01
--- /dev/null
+++ b/maui/src/Picker/Interface/IColumnHeaderView.cs
@@ -0,0 +1,13 @@
+namespace Syncfusion.Maui.Toolkit.Picker
+{
+ ///
+ /// Interface that holds the properties of column header view details.
+ ///
+ internal interface IColumnHeaderView
+ {
+ ///
+ /// Gets the picker column header details for the picker view.
+ ///
+ PickerColumnHeaderView ColumnHeaderView { get; }
+ }
+}
\ No newline at end of file
diff --git a/maui/src/Picker/Interface/IFooterView.cs b/maui/src/Picker/Interface/IFooterView.cs
new file mode 100644
index 00000000..f692837e
--- /dev/null
+++ b/maui/src/Picker/Interface/IFooterView.cs
@@ -0,0 +1,23 @@
+namespace Syncfusion.Maui.Toolkit.Picker
+{
+ ///
+ /// Interface that holds the properties of Picker footer view.
+ ///
+ internal interface IFooterView : IPickerCommon
+ {
+ ///
+ /// Gets the settings of the picker footer view.
+ ///
+ PickerFooterView FooterView { get; }
+
+ ///
+ /// Method to update after the confirm button clicked.
+ ///
+ void OnConfirmButtonClicked();
+
+ ///
+ /// Method to update after the cancel button clicked.
+ ///
+ void OnCancelButtonClicked();
+ }
+}
\ No newline at end of file
diff --git a/maui/src/Picker/Interface/IHeaderView.cs b/maui/src/Picker/Interface/IHeaderView.cs
new file mode 100644
index 00000000..998e7ccc
--- /dev/null
+++ b/maui/src/Picker/Interface/IHeaderView.cs
@@ -0,0 +1,23 @@
+namespace Syncfusion.Maui.Toolkit.Picker
+{
+ ///
+ /// Interface that holds the properties of Picker header view.
+ ///
+ internal interface IHeaderView : IPickerCommon
+ {
+ ///
+ /// Gets the settings of the picker footer view.
+ ///
+ PickerHeaderView HeaderView { get; }
+
+ ///
+ /// Method to update after the time button clicked.
+ ///
+ void OnTimeButtonClicked();
+
+ ///
+ /// Method to update after the date button clicked.
+ ///
+ void OnDateButtonClicked();
+ }
+}
\ No newline at end of file
diff --git a/maui/src/Picker/Interface/IPicker.cs b/maui/src/Picker/Interface/IPicker.cs
new file mode 100644
index 00000000..37d27c1d
--- /dev/null
+++ b/maui/src/Picker/Interface/IPicker.cs
@@ -0,0 +1,33 @@
+using System.Collections.ObjectModel;
+
+namespace Syncfusion.Maui.Toolkit.Picker
+{
+ ///
+ /// Interface that holds the properties of picker.
+ ///
+ internal interface IPicker : IPickerView, IColumnHeaderView, IPickerCommon, IFooterView, IHeaderView
+ {
+ ///
+ /// Gets the picker columns details for the picker view.
+ ///
+ ObservableCollection Columns { get; }
+
+ ///
+ /// Gets the selection settings for the picker.
+ ///
+ PickerSelectionView SelectionView { get; }
+
+ ///
+ /// Gets the column divider color.
+ ///
+ Color ColumnDividerColor { get; }
+
+ ///
+ /// Method to update the picker tapped item index.
+ ///
+ /// The tapped index.
+ /// The column child index.
+ /// Check whether is initial loading.
+ void UpdateSelectedIndexValue(int tappedIndex, int childIndex, bool isInitialLoading = false);
+ }
+}
\ No newline at end of file
diff --git a/maui/src/Picker/Interface/IPickerCommon.cs b/maui/src/Picker/Interface/IPickerCommon.cs
new file mode 100644
index 00000000..ef95a5d0
--- /dev/null
+++ b/maui/src/Picker/Interface/IPickerCommon.cs
@@ -0,0 +1,18 @@
+namespace Syncfusion.Maui.Toolkit.Picker
+{
+ ///
+ /// Interface that holds the common properties of picker.
+ ///
+ internal interface IPickerCommon
+ {
+ ///
+ /// Gets a value indicating whether the view is in RTL flow direction or not.
+ ///
+ bool IsRTLLayout { get; }
+
+ ///
+ /// Gets or sets a value indicating whether the picker has to be in open or close.
+ ///
+ bool IsOpen { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/maui/src/Picker/Interface/IPickerLayout.cs b/maui/src/Picker/Interface/IPickerLayout.cs
new file mode 100644
index 00000000..a7a587ff
--- /dev/null
+++ b/maui/src/Picker/Interface/IPickerLayout.cs
@@ -0,0 +1,30 @@
+namespace Syncfusion.Maui.Toolkit.Picker
+{
+ ///
+ /// Interface that holds the picker layout details.
+ ///
+ internal interface IPickerLayout
+ {
+ ///
+ /// Gets the picker view details.
+ ///
+ IPickerView PickerInfo { get; }
+
+ ///
+ /// Gets the current column details.
+ ///
+ PickerColumn Column { get; }
+
+ ///
+ /// Gets the scroll offset value of the scroll view.
+ ///
+ double ScrollOffset { get; }
+
+ ///
+ /// Method to update the picker tapped item index.
+ ///
+ /// The tapped index.
+ /// Check whether is initial loading.
+ void UpdateSelectedIndexValue(int tappedIndex, bool isInitialLoading = false);
+ }
+}
\ No newline at end of file
diff --git a/maui/src/Picker/Interface/IPickerView.cs b/maui/src/Picker/Interface/IPickerView.cs
new file mode 100644
index 00000000..fa7d0c27
--- /dev/null
+++ b/maui/src/Picker/Interface/IPickerView.cs
@@ -0,0 +1,43 @@
+namespace Syncfusion.Maui.Toolkit.Picker
+{
+ ///
+ /// Interface that holds the properties of picker view.
+ ///
+ internal interface IPickerView
+ {
+ ///
+ /// Gets the item height for the picker view.
+ ///
+ double ItemHeight { get; }
+
+ ///
+ /// Gets the selected text style for the picker item.
+ ///
+ PickerTextStyle SelectedTextStyle { get; }
+
+ ///
+ /// Gets the unselected text style for the picker item.
+ ///
+ PickerTextStyle TextStyle { get; }
+
+ ///
+ /// Gets the disabled text style for the picker item.
+ ///
+ PickerTextStyle DisabledTextStyle { get; }
+
+ ///
+ /// Gets the picker items text display mode.
+ ///
+ PickerTextDisplayMode TextDisplayMode { get; }
+
+ ///
+ /// Gets the item template for the picker item.
+ ///
+ DataTemplate ItemTemplate { get; }
+
+ ///
+ /// Gets a value indicating whether the parent value have valid value because dynamic scrolling on dialog opening does not scroll because the picker stack layout does not have a parent value.
+ ///
+ bool IsValidParent { get; }
+ }
+}
\ No newline at end of file
diff --git a/maui/src/Picker/Model/Settings/ColumnHeaderSettings.cs b/maui/src/Picker/Model/Settings/ColumnHeaderSettings.cs
new file mode 100644
index 00000000..3b61dc8f
--- /dev/null
+++ b/maui/src/Picker/Model/Settings/ColumnHeaderSettings.cs
@@ -0,0 +1,353 @@
+using Syncfusion.Maui.Toolkit.Graphics.Internals;
+using Syncfusion.Maui.Toolkit.Themes;
+
+namespace Syncfusion.Maui.Toolkit.Picker
+{
+ ///
+ /// Represents a class which is used to customize all the properties of column header view of the SfPicker.
+ ///
+ public class PickerColumnHeaderView : Element, IThemeElement
+ {
+ #region Bindable Properties
+
+ ///
+ /// Identifies the dependency property.
+ ///
+ ///
+ /// The identifier for dependency property.
+ ///
+ public static readonly BindableProperty HeightProperty =
+ BindableProperty.Create(
+ nameof(Height),
+ typeof(double),
+ typeof(PickerColumnHeaderView),
+ 0d,
+ propertyChanged: OnHeightChanged);
+
+ ///
+ /// Identifies the dependency property.
+ ///
+ ///
+ /// The identifier for dependency property.
+ ///
+ public static readonly BindableProperty TextStyleProperty =
+ BindableProperty.Create(
+ nameof(TextStyle),
+ typeof(PickerTextStyle),
+ typeof(PickerColumnHeaderView),
+ defaultValueCreator: bindable => GetColumnHeaderTextStyle(bindable),
+ propertyChanged: OnTextStyleChanged);
+
+ ///
+ /// Identifies the dependency property.
+ ///
+ ///
+ /// The identifier for dependency property.
+ ///
+ public static readonly BindableProperty BackgroundProperty =
+ BindableProperty.Create(
+ nameof(Background),
+ typeof(Brush),
+ typeof(PickerColumnHeaderView),
+ defaultValueCreator: bindable => new SolidColorBrush(Color.FromArgb("#F7F2FB")),
+ propertyChanged: OnBackgroundChanged);
+
+ ///
+ /// Identifies the dependency property.
+ ///
+ ///
+ /// The identifier for dependency property.
+ ///
+ public static readonly BindableProperty DividerColorProperty =
+ BindableProperty.Create(
+ nameof(DividerColor),
+ typeof(Color),
+ typeof(PickerColumnHeaderView),
+ defaultValueCreator: bindable => Color.FromArgb("#CAC4D0"),
+ propertyChanged: OnDividerColorChanged);
+
+ #endregion
+
+ #region Constructor
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ public PickerColumnHeaderView()
+ {
+ ThemeElement.InitializeThemeResources(this, "SfPickerTheme");
+ }
+
+ #endregion
+
+ #region Properties
+
+ ///
+ /// Gets or sets the value to specify the height of column header view on SfPicker.
+ ///
+ /// The default value of is 0d.
+ ///
+ /// The following example demonstrates how to set the height of the column header view.
+ /// # [XAML](#tab/tabid-1)
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ /// ]]>
+ ///
+ /// # [C#](#tab/tabid-2)
+ ///
+ /// SfPicker picker = new SfPicker();
+ /// picker.ColumnHeaderView = new PickerColumnHeaderView
+ /// {
+ /// Height = 50
+ /// };
+ ///
+ ///
+ public double Height
+ {
+ get { return (double)GetValue(HeightProperty); }
+ set { SetValue(HeightProperty, value); }
+ }
+
+ ///
+ /// Gets or sets the text style of the column header text in SfPicker.
+ ///
+ ///
+ /// The following example demonstrates how to set the text style of the column header view.
+ /// # [XAML](#tab/tabid-3)
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ /// ]]>
+ ///
+ /// # [C#](#tab/tabid-4)
+ ///
+ /// SfPicker picker = new SfPicker();
+ /// picker.ColumnHeaderView = new PickerColumnHeaderView
+ /// {
+ /// TextStyle = new PickerTextStyle
+ /// {
+ /// TextColor = Colors.Blue,
+ /// FontSize = 16
+ /// }
+ /// };
+ ///
+ ///
+ public PickerTextStyle TextStyle
+ {
+ get { return (PickerTextStyle)GetValue(TextStyleProperty); }
+ set { SetValue(TextStyleProperty, value); }
+ }
+
+ ///
+ /// Gets or sets the background of the column header view in SfPicker.
+ ///
+ /// The default value is "#F7F2FB".
+ ///
+ /// The following example demonstrates how to set the background of the column header view.
+ /// # [XAML](#tab/tabid-5)
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ /// ]]>
+ ///
+ /// # [C#](#tab/tabid-6)
+ ///
+ /// SfPicker picker = new SfPicker();
+ /// picker.ColumnHeaderView = new PickerColumnHeaderView
+ /// {
+ /// Background = new SolidColorBrush(Color.FromHex("#E0E0E0"))
+ /// };
+ ///
+ ///
+ public Brush Background
+ {
+ get { return (Brush)GetValue(BackgroundProperty); }
+ set { SetValue(BackgroundProperty, value); }
+ }
+
+ ///
+ /// Gets or sets the background of the column header separator line background in SfPicker.
+ ///
+ /// The default value of is "#CAC4D0".
+ ///
+ /// The following example demonstrates how to set the divider color of the column header view.
+ /// # [XAML](#tab/tabid-7)
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ /// ]]>
+ ///
+ /// # [C#](#tab/tabid-8)
+ ///
+ /// SfPicker picker = new SfPicker();
+ /// picker.ColumnHeaderView = new PickerColumnHeaderView
+ /// {
+ /// DividerColor = Colors.Gray
+ /// };
+ ///
+ ///
+ public Color DividerColor
+ {
+ get { return (Color)GetValue(DividerColorProperty); }
+ set { SetValue(DividerColorProperty, value); }
+ }
+
+ #endregion
+
+ #region Private Methods
+
+ ///
+ /// Need to update the parent for the new value.
+ ///
+ /// The old value.
+ /// The new value.
+ void SetParent(Element? oldValue, Element? newValue)
+ {
+ if (oldValue != null)
+ {
+ oldValue.Parent = null;
+ }
+
+ if (newValue != null)
+ {
+ newValue.Parent = this;
+ }
+ }
+
+ #endregion
+
+ #region Property Changed Methods
+
+ ///
+ /// Method invokes on the picker column header height changed.
+ ///
+ /// The column header settings object.
+ /// Property old value.
+ /// Property new value.
+ static void OnHeightChanged(BindableObject bindable, object oldValue, object newValue)
+ {
+ (bindable as PickerColumnHeaderView)?.RaisePropertyChanged(nameof(Height));
+ }
+
+ ///
+ /// Method invokes on picker column header text style property changed.
+ ///
+ /// The column header settings object.
+ /// Property old value.
+ /// Property new value.
+ static void OnTextStyleChanged(BindableObject bindable, object oldValue, object newValue)
+ {
+ if (bindable is PickerColumnHeaderView columnHeaderView)
+ {
+ columnHeaderView.SetParent(oldValue as Element, newValue as Element);
+ }
+
+ (bindable as PickerColumnHeaderView)?.RaisePropertyChanged(nameof(TextStyle), oldValue);
+ }
+
+ ///
+ /// Method invokes on the picker column header background changed.
+ ///
+ /// The column header settings object.
+ /// Property old value.
+ /// Property new value.
+ static void OnBackgroundChanged(BindableObject bindable, object oldValue, object newValue)
+ {
+ (bindable as PickerColumnHeaderView)?.RaisePropertyChanged(nameof(Background));
+ }
+
+ ///
+ /// Method invokes on the picker column header separator line background changed.
+ ///
+ /// The column header settings object.
+ /// Property old value.
+ /// Property new value.
+ static void OnDividerColorChanged(BindableObject bindable, object oldValue, object newValue)
+ {
+ (bindable as PickerColumnHeaderView)?.RaisePropertyChanged(nameof(DividerColor));
+ }
+
+ ///
+ /// Method to get the default text style for the column header view.
+ ///
+ /// Returns the default column header text style.
+ static ITextElement GetColumnHeaderTextStyle(BindableObject bindable)
+ {
+ var columnHeader = (PickerColumnHeaderView)bindable;
+ var pickerTextStyle = new PickerTextStyle()
+ {
+ FontSize = 14,
+ TextColor = Color.FromArgb("#49454F"),
+ Parent = columnHeader,
+ };
+
+ return pickerTextStyle;
+ }
+
+ ///
+ /// Method to invoke picker property changed event on column header settings properties changed.
+ ///
+ /// Property name.
+ /// Property old value.
+ void RaisePropertyChanged(string propertyName, object? oldValue = null)
+ {
+ PickerPropertyChanged?.Invoke(this, new PickerPropertyChangedEventArgs(propertyName) { OldValue = oldValue });
+ }
+
+ #endregion
+
+ #region Interface Implementation
+
+ ///
+ /// This method will be called when a theme dictionary
+ /// that contains the value for your control key is merged in application.
+ ///
+ /// The old value.
+ /// The new value.
+ void IThemeElement.OnCommonThemeChanged(string oldTheme, string newTheme)
+ {
+ }
+
+ ///
+ /// This method will be called when users merge a theme dictionary
+ /// that contains value for “SyncfusionTheme” dynamic resource key.
+ ///
+ /// Old theme.
+ /// New theme.
+ void IThemeElement.OnControlThemeChanged(string oldTheme, string newTheme)
+ {
+ }
+
+ #endregion
+
+ #region Events
+
+ ///
+ /// Event Invokes on column header settings property changed and this includes old value of the changed property which is used to unwire events for nested classes.
+ ///
+ internal event EventHandler? PickerPropertyChanged;
+
+ #endregion
+ }
+}
\ No newline at end of file
diff --git a/maui/src/Picker/Model/Settings/DatePickerColumnHeaderSettings.cs b/maui/src/Picker/Model/Settings/DatePickerColumnHeaderSettings.cs
new file mode 100644
index 00000000..237b47ef
--- /dev/null
+++ b/maui/src/Picker/Model/Settings/DatePickerColumnHeaderSettings.cs
@@ -0,0 +1,494 @@
+using Syncfusion.Maui.Toolkit.Graphics.Internals;
+using Syncfusion.Maui.Toolkit.Themes;
+
+namespace Syncfusion.Maui.Toolkit.Picker
+{
+ ///
+ /// Represents a class which is used to customize all the properties of column header view of the SfDatePicker.
+ ///
+ public class DatePickerColumnHeaderView : Element, IThemeElement
+ {
+ #region Bindable Properties
+
+ ///
+ /// Identifies the dependency property.
+ ///
+ ///
+ /// The identifier for dependency property.
+ ///
+ public static readonly BindableProperty HeightProperty =
+ BindableProperty.Create(
+ nameof(Height),
+ typeof(double),
+ typeof(DatePickerColumnHeaderView),
+ 40d,
+ propertyChanged: OnHeightChanged);
+
+ ///
+ /// Identifies the dependency property.
+ ///
+ ///
+ /// The identifier for dependency property.
+ ///
+ public static readonly BindableProperty TextStyleProperty =
+ BindableProperty.Create(
+ nameof(TextStyle),
+ typeof(PickerTextStyle),
+ typeof(DatePickerColumnHeaderView),
+ defaultValueCreator: bindable => GetColumnHeaderTextStyle(bindable),
+ propertyChanged: OnTextStyleChanged);
+
+ ///
+ /// Identifies the dependency property.
+ ///
+ ///
+ /// The identifier for dependency property.
+ ///
+ public static readonly BindableProperty BackgroundProperty =
+ BindableProperty.Create(
+ nameof(Background),
+ typeof(Brush),
+ typeof(DatePickerColumnHeaderView),
+ defaultValueCreator: bindable => new SolidColorBrush(Color.FromArgb("#F7F2FB")),
+ propertyChanged: OnBackgroundChanged);
+
+ ///
+ /// Identifies the dependency property.
+ ///
+ ///
+ /// The identifier for dependency property.
+ ///
+ public static readonly BindableProperty DividerColorProperty =
+ BindableProperty.Create(
+ nameof(DividerColor),
+ typeof(Color),
+ typeof(DatePickerColumnHeaderView),
+ defaultValueCreator: bindable => Color.FromArgb("#CAC4D0"),
+ propertyChanged: OnDividerColorChanged);
+
+ ///
+ /// Identifies the dependency property.
+ ///
+ ///
+ /// The identifier for dependency property.
+ ///
+ public static readonly BindableProperty DayHeaderTextProperty =
+ BindableProperty.Create(
+ nameof(DayHeaderText),
+ typeof(string),
+ typeof(DatePickerColumnHeaderView),
+ "Day",
+ propertyChanged: OnDayHeaderTextChanged);
+
+ ///
+ /// Identifies the dependency property.
+ ///
+ ///
+ /// The identifier for dependency property.
+ ///
+ public static readonly BindableProperty MonthHeaderTextProperty =
+ BindableProperty.Create(
+ nameof(MonthHeaderText),
+ typeof(string),
+ typeof(DatePickerColumnHeaderView),
+ "Month",
+ propertyChanged: OnMonthHeaderTextChanged);
+
+ ///
+ /// Identifies the dependency property.
+ ///
+ ///
+ /// The identifier for dependency property.
+ ///
+ public static readonly BindableProperty YearHeaderTextProperty =
+ BindableProperty.Create(
+ nameof(YearHeaderText),
+ typeof(string),
+ typeof(DatePickerColumnHeaderView),
+ "Year",
+ propertyChanged: OnYearHeaderTextChanged);
+
+ #endregion
+
+ #region Constructor
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ public DatePickerColumnHeaderView()
+ {
+ ThemeElement.InitializeThemeResources(this, "SfDatePickerTheme");
+ }
+
+ #endregion
+
+ #region Properties
+
+ ///
+ /// Gets or sets the value to specify the height of column header view on SfDatePicker.
+ ///
+ /// The default value of is 40d.
+ ///
+ /// The following example demonstrates how to set the height of the date picker column header view.
+ /// # [XAML](#tab/tabid-1)
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ /// ]]>
+ ///
+ /// # [C#](#tab/tabid-2)
+ ///
+ /// SfDatePicker datePicker = new SfDatePicker();
+ /// datePicker.ColumnHeaderView = new DatePickerColumnHeaderView
+ /// {
+ /// Height = 50
+ /// };
+ ///
+ ///
+ public double Height
+ {
+ get { return (double)GetValue(HeightProperty); }
+ set { SetValue(HeightProperty, value); }
+ }
+
+ ///
+ /// Gets or sets the text style of the column header text in SfDatePicker.
+ ///
+ ///
+ /// The following example demonstrates how to set the text style of the date picker column header view.
+ /// # [XAML](#tab/tabid-3)
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ /// ]]>
+ ///
+ /// # [C#](#tab/tabid-4)
+ ///
+ /// SfDatePicker datePicker = new SfDatePicker();
+ /// datePicker.ColumnHeaderView = new DatePickerColumnHeaderView
+ /// {
+ /// TextStyle = new PickerTextStyle
+ /// {
+ /// TextColor = Colors.Blue,
+ /// FontSize = 16
+ /// }
+ /// };
+ ///
+ ///
+ public PickerTextStyle TextStyle
+ {
+ get { return (PickerTextStyle)GetValue(TextStyleProperty); }
+ set { SetValue(TextStyleProperty, value); }
+ }
+
+ ///
+ /// Gets or sets the background of the column header view in SfDatePicker.
+ ///
+ /// The default value of is "#F7F2FB".
+ ///
+ /// The following example demonstrates how to set the background of the date picker column header view.
+ /// # [XAML](#tab/tabid-5)
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ /// ]]>
+ ///
+ /// # [C#](#tab/tabid-6)
+ ///
+ /// SfDatePicker datePicker = new SfDatePicker();
+ /// datePicker.ColumnHeaderView = new DatePickerColumnHeaderView
+ /// {
+ /// Background = new SolidColorBrush(Color.FromHex("#E0E0E0"))
+ /// };
+ ///
+ ///
+ public Brush Background
+ {
+ get { return (Brush)GetValue(BackgroundProperty); }
+ set { SetValue(BackgroundProperty, value); }
+ }
+
+ ///
+ /// Gets or sets the background of the column header separator line background in SfDatePicker.
+ ///
+ /// The default value of is "#CAC4D0".
+ ///
+ /// The following example demonstrates how to set the divider color of the date picker column header view.
+ /// # [XAML](#tab/tabid-7)
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ /// ]]>
+ ///
+ /// # [C#](#tab/tabid-8)
+ ///
+ /// SfDatePicker datePicker = new SfDatePicker();
+ /// datePicker.ColumnHeaderView = new DatePickerColumnHeaderView
+ /// {
+ /// DividerColor = Colors.Gray
+ /// };
+ ///
+ ///
+ public Color DividerColor
+ {
+ get { return (Color)GetValue(DividerColorProperty); }
+ set { SetValue(DividerColorProperty, value); }
+ }
+
+ ///
+ /// Gets or sets the value to day header text in SfDatePicker.
+ ///
+ /// The default value of is "Day".
+ ///
+ /// The following example demonstrates how to set the day header text of the date picker column header view.
+ /// # [XAML](#tab/tabid-9)
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ /// ]]>
+ ///
+ /// # [C#](#tab/tabid-10)
+ ///
+ /// SfDatePicker datePicker = new SfDatePicker();
+ /// datePicker.ColumnHeaderView = new DatePickerColumnHeaderView
+ /// {
+ /// DayHeaderText = "Date"
+ /// };
+ ///
+ ///
+ public string DayHeaderText
+ {
+ get { return (string)GetValue(DayHeaderTextProperty); }
+ set { SetValue(DayHeaderTextProperty, value); }
+ }
+
+ ///
+ /// Gets or sets the value to month header text in SfDatePicker.
+ ///
+ /// The default value of is "Month".
+ ///
+ /// The following example demonstrates how to set the month header text of the date picker column header view.
+ /// # [XAML](#tab/tabid-11)
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ /// ]]>
+ ///
+ /// # [C#](#tab/tabid-12)
+ ///
+ /// SfDatePicker datePicker = new SfDatePicker();
+ /// datePicker.ColumnHeaderView = new DatePickerColumnHeaderView
+ /// {
+ /// MonthHeaderText = "Month"
+ /// };
+ ///
+ ///
+ public string MonthHeaderText
+ {
+ get { return (string)GetValue(MonthHeaderTextProperty); }
+ set { SetValue(MonthHeaderTextProperty, value); }
+ }
+
+ ///
+ /// Gets or sets the value to year header text in SfDatePicker.
+ ///
+ /// The default value of is "Year".
+ ///
+ /// The following example demonstrates how to set the year header text of the date picker column header view.
+ /// # [XAML](#tab/tabid-13)
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ /// ]]>
+ ///
+ /// # [C#](#tab/tabid-14)
+ ///
+ /// SfDatePicker datePicker = new SfDatePicker();
+ /// datePicker.ColumnHeaderView = new DatePickerColumnHeaderView
+ /// {
+ /// YearHeaderText = "Year"
+ /// };
+ ///
+ ///
+ public string YearHeaderText
+ {
+ get { return (string)GetValue(YearHeaderTextProperty); }
+ set { SetValue(YearHeaderTextProperty, value); }
+ }
+
+ #endregion
+
+ #region Property Changed Methods
+
+ ///
+ /// Method invokes on the picker column header height changed.
+ ///
+ /// The column header settings object.
+ /// Property old value.
+ /// Property new value.
+ static void OnHeightChanged(BindableObject bindable, object oldValue, object newValue)
+ {
+ (bindable as DatePickerColumnHeaderView)?.RaisePropertyChanged(nameof(Height));
+ }
+
+ ///
+ /// Method invokes on picker column header text style property changed.
+ ///
+ /// The column header settings object.
+ /// Property old value.
+ /// Property new value.
+ static void OnTextStyleChanged(BindableObject bindable, object oldValue, object newValue)
+ {
+ (bindable as DatePickerColumnHeaderView)?.RaisePropertyChanged(nameof(TextStyle), oldValue);
+ }
+
+ ///
+ /// Method invokes on the picker column header background changed.
+ ///
+ /// The column header settings object.
+ /// Property old value.
+ /// Property new value.
+ static void OnBackgroundChanged(BindableObject bindable, object oldValue, object newValue)
+ {
+ (bindable as DatePickerColumnHeaderView)?.RaisePropertyChanged(nameof(Background));
+ }
+
+ ///
+ /// Method invokes on the picker column header separator line background changed.
+ ///
+ /// The column header settings object.
+ /// Property old value.
+ /// Property new value.
+ static void OnDividerColorChanged(BindableObject bindable, object oldValue, object newValue)
+ {
+ (bindable as DatePickerColumnHeaderView)?.RaisePropertyChanged(nameof(DividerColor));
+ }
+
+ ///
+ /// Method invokes on is day header text property changed.
+ ///
+ /// The column header view.
+ /// Property old value
+ /// Property new value
+ static void OnDayHeaderTextChanged(BindableObject bindable, object oldValue, object newValue)
+ {
+ (bindable as DatePickerColumnHeaderView)?.RaisePropertyChanged(nameof(DayHeaderText));
+ }
+
+ ///
+ /// Method invokes on month header text property changed.
+ ///
+ /// The column header view.
+ /// Property old value
+ /// Property new value
+ static void OnMonthHeaderTextChanged(BindableObject bindable, object oldValue, object newValue)
+ {
+ (bindable as DatePickerColumnHeaderView)?.RaisePropertyChanged(nameof(MonthHeaderText));
+ }
+
+ ///
+ /// Method invokes on year header text property changed.
+ ///
+ /// The column header view.
+ /// Property old value
+ /// Property new value
+ static void OnYearHeaderTextChanged(BindableObject bindable, object oldValue, object newValue)
+ {
+ (bindable as DatePickerColumnHeaderView)?.RaisePropertyChanged(nameof(YearHeaderText));
+ }
+
+ ///
+ /// Method to get the default text style for the column header view.
+ ///
+ /// Returns the default column header text style.
+ static ITextElement GetColumnHeaderTextStyle(BindableObject bindable)
+ {
+ DatePickerColumnHeaderView columnHeaderView = (DatePickerColumnHeaderView)bindable;
+ PickerTextStyle pickerTextStyle = new PickerTextStyle()
+ {
+ FontSize = 14,
+ TextColor = Color.FromArgb("#49454F"),
+ Parent = columnHeaderView,
+ };
+
+ return pickerTextStyle;
+ }
+
+ ///
+ /// Method to invoke picker property changed event on column header settings properties changed.
+ ///
+ /// Property name.
+ /// Property old value.
+ void RaisePropertyChanged(string propertyName, object? oldValue = null)
+ {
+ PickerPropertyChanged?.Invoke(this, new PickerPropertyChangedEventArgs(propertyName) { OldValue = oldValue });
+ }
+
+ #endregion
+
+ #region Interface Implementation
+
+ ///
+ /// This method will be called when a theme dictionary
+ /// that contains the value for your control key is merged in application.
+ ///
+ /// The old value.
+ /// The new value.
+ void IThemeElement.OnCommonThemeChanged(string oldTheme, string newTheme)
+ {
+ }
+
+ ///
+ /// This method will be called when users merge a theme dictionary
+ /// that contains value for “SyncfusionTheme” dynamic resource key.
+ ///
+ /// Old theme.
+ /// New theme.
+ void IThemeElement.OnControlThemeChanged(string oldTheme, string newTheme)
+ {
+ }
+
+ #endregion
+
+ #region Events
+
+ ///
+ /// Event Invokes on column header settings property changed and this includes old value of the changed property which is used to unwire events for nested classes.
+ ///
+ internal event EventHandler? PickerPropertyChanged;
+
+ #endregion
+ }
+}
\ No newline at end of file
diff --git a/maui/src/Picker/Model/Settings/DateTimePickerColumnHeaderSettings.cs b/maui/src/Picker/Model/Settings/DateTimePickerColumnHeaderSettings.cs
new file mode 100644
index 00000000..edd61230
--- /dev/null
+++ b/maui/src/Picker/Model/Settings/DateTimePickerColumnHeaderSettings.cs
@@ -0,0 +1,714 @@
+using Syncfusion.Maui.Toolkit.Graphics.Internals;
+using Syncfusion.Maui.Toolkit.Themes;
+
+namespace Syncfusion.Maui.Toolkit.Picker
+{
+ ///
+ /// Represents a class which is used to customize all the properties of column header view of the SfDateTimePicker.
+ ///
+ public class DateTimePickerColumnHeaderView : Element, IThemeElement
+ {
+ #region Bindable Properties
+
+ ///
+ /// Identifies the dependency property.
+ ///
+ ///
+ /// The identifier for dependency property.
+ ///
+ public static readonly BindableProperty HeightProperty =
+ BindableProperty.Create(
+ nameof(Height),
+ typeof(double),
+ typeof(DateTimePickerColumnHeaderView),
+ 40d,
+ propertyChanged: OnHeightChanged);
+
+ ///
+ /// Identifies the dependency property.
+ ///
+ ///
+ /// The identifier for dependency property.
+ ///
+ public static readonly BindableProperty TextStyleProperty =
+ BindableProperty.Create(
+ nameof(TextStyle),
+ typeof(PickerTextStyle),
+ typeof(DateTimePickerColumnHeaderView),
+ defaultValueCreator: bindable => GetColumnHeaderTextStyle(),
+ propertyChanged: OnTextStyleChanged);
+
+ ///
+ /// Identifies the dependency property.
+ ///
+ ///
+ /// The identifier for dependency property.
+ ///
+ public static readonly BindableProperty BackgroundProperty =
+ BindableProperty.Create(
+ nameof(Background),
+ typeof(Brush),
+ typeof(DateTimePickerColumnHeaderView),
+ defaultValueCreator: bindable => new SolidColorBrush(Color.FromArgb("#F7F2FB")),
+ propertyChanged: OnBackgroundChanged);
+
+ ///
+ /// Identifies the dependency property.
+ ///
+ ///
+ /// The identifier for dependency property.
+ ///
+ public static readonly BindableProperty DividerColorProperty =
+ BindableProperty.Create(
+ nameof(DividerColor),
+ typeof(Color),
+ typeof(DateTimePickerColumnHeaderView),
+ defaultValueCreator: bindable => Color.FromArgb("#CAC4D0"),
+ propertyChanged: OnDividerColorChanged);
+
+ ///
+ /// Identifies the dependency property.
+ ///
+ ///
+ /// The identifier for dependency property.
+ ///
+ public static readonly BindableProperty DayHeaderTextProperty =
+ BindableProperty.Create(
+ nameof(DayHeaderText),
+ typeof(string),
+ typeof(DateTimePickerColumnHeaderView),
+ "Day",
+ propertyChanged: OnDayHeaderTextChanged);
+
+ ///
+ /// Identifies the dependency property.
+ ///
+ ///
+ /// The identifier for dependency property.
+ ///
+ public static readonly BindableProperty MonthHeaderTextProperty =
+ BindableProperty.Create(
+ nameof(MonthHeaderText),
+ typeof(string),
+ typeof(DateTimePickerColumnHeaderView),
+ "Month",
+ propertyChanged: OnMonthHeaderTextChanged);
+
+ ///
+ /// Identifies the dependency property.
+ ///
+ ///
+ /// The identifier for dependency property.
+ ///
+ public static readonly BindableProperty YearHeaderTextProperty =
+ BindableProperty.Create(
+ nameof(YearHeaderText),
+ typeof(string),
+ typeof(DateTimePickerColumnHeaderView),
+ "Year",
+ propertyChanged: OnYearHeaderTextChanged);
+
+ ///
+ /// Identifies the dependency property.
+ ///
+ ///
+ /// The identifier for dependency property.
+ ///
+ public static readonly BindableProperty HourHeaderTextProperty =
+ BindableProperty.Create(
+ nameof(HourHeaderText),
+ typeof(string),
+ typeof(DateTimePickerColumnHeaderView),
+ "Hour",
+ propertyChanged: OnHourHeaderTextChanged);
+
+ ///
+ /// Identifies the dependency property.
+ ///
+ ///
+ /// The identifier for dependency property.
+ ///
+ public static readonly BindableProperty MinuteHeaderTextProperty =
+ BindableProperty.Create(
+ nameof(MinuteHeaderText),
+ typeof(string),
+ typeof(DateTimePickerColumnHeaderView),
+ "Minute",
+ propertyChanged: OnMinuteHeaderTextChanged);
+
+ ///
+ /// Identifies the dependency property.
+ ///
+ ///
+ /// The identifier for dependency property.
+ ///
+ public static readonly BindableProperty SecondHeaderTextProperty =
+ BindableProperty.Create(
+ nameof(SecondHeaderText),
+ typeof(string),
+ typeof(DateTimePickerColumnHeaderView),
+ "Second",
+ propertyChanged: OnSecondHeaderTextChanged);
+
+ ///
+ /// Identifies the dependency property.
+ ///
+ ///
+ /// The identifier for dependency property.
+ ///
+ public static readonly BindableProperty MeridiemHeaderTextProperty =
+ BindableProperty.Create(
+ nameof(MeridiemHeaderText),
+ typeof(string),
+ typeof(DateTimePickerColumnHeaderView),
+ string.Empty,
+ propertyChanged: OnMeridiemHeaderTextChanged);
+
+ #endregion
+
+ #region Constructor
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ public DateTimePickerColumnHeaderView()
+ {
+ ThemeElement.InitializeThemeResources(this, "SfDateTimePickerTheme");
+ }
+
+ #endregion
+
+ #region Properties
+
+ ///
+ /// Gets or sets the value to specify the height of column header view on SfDateTimePicker.
+ ///
+ /// The default value of is 40d.
+ ///
+ /// The following example demonstrates how to set the height of the date time picker column header view.
+ /// # [XAML](#tab/tabid-1)
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ /// ]]>
+ ///
+ /// # [C#](#tab/tabid-2)
+ ///
+ /// SfDateTimePicker dateTimePicker = new SfDateTimePicker();
+ /// dateTimePicker.ColumnHeaderView = new DateTimePickerColumnHeaderView
+ /// {
+ /// Height = 50
+ /// };
+ ///
+ ///
+ public double Height
+ {
+ get { return (double)GetValue(HeightProperty); }
+ set { SetValue(HeightProperty, value); }
+ }
+
+ ///
+ /// Gets or sets the text style of the column header text in SfDateTimePicker.
+ ///
+ ///
+ /// The following example demonstrates how to set the text style of the date time picker column header view.
+ /// # [XAML](#tab/tabid-3)
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ /// ]]>
+ ///
+ /// # [C#](#tab/tabid-4)
+ ///
+ /// SfDateTimePicker dateTimePicker = new SfDateTimePicker();
+ /// dateTimePicker.ColumnHeaderView = new DateTimePickerColumnHeaderView
+ /// {
+ /// TextStyle = new PickerTextStyle
+ /// {
+ /// TextColor = Colors.Blue,
+ /// FontSize = 16
+ /// }
+ /// };
+ ///
+ ///
+ public PickerTextStyle TextStyle
+ {
+ get { return (PickerTextStyle)GetValue(TextStyleProperty); }
+ set { SetValue(TextStyleProperty, value); }
+ }
+
+ ///
+ /// Gets or sets the background of the column header view in SfDateTimePicker.
+ ///
+ /// The default value of is "#F7F2FB".
+ ///
+ /// The following example demonstrates how to set the background of the date time picker column header view.
+ /// # [XAML](#tab/tabid-5)
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ /// ]]>
+ ///
+ /// # [C#](#tab/tabid-6)
+ ///
+ /// SfDateTimePicker dateTimePicker = new SfDateTimePicker();
+ /// dateTimePicker.ColumnHeaderView = new DateTimePickerColumnHeaderView
+ /// {
+ /// Background = new SolidColorBrush(Color.FromHex("#E0E0E0"))
+ /// };
+ ///
+ ///
+ public Brush Background
+ {
+ get { return (Brush)GetValue(BackgroundProperty); }
+ set { SetValue(BackgroundProperty, value); }
+ }
+
+ ///
+ /// Gets or sets the background of the column header separator line background in SfDateTimePicker.
+ ///
+ /// The default value of is "#CAC4D0".
+ ///
+ /// The following example demonstrates how to set the divider color of the date time picker column header view.
+ /// # [XAML](#tab/tabid-7)
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ /// ]]>
+ ///
+ /// # [C#](#tab/tabid-8)
+ ///
+ /// SfDateTimePicker dateTimePicker = new SfDateTimePicker();
+ /// dateTimePicker.ColumnHeaderView = new DateTimePickerColumnHeaderView
+ /// {
+ /// DividerColor = Colors.Gray
+ /// };
+ ///
+ ///
+ public Color DividerColor
+ {
+ get { return (Color)GetValue(DividerColorProperty); }
+ set { SetValue(DividerColorProperty, value); }
+ }
+
+ ///
+ /// Gets or sets the value to day header text in SfDateTimePicker.
+ ///
+ /// The default value of is "Day".
+ ///
+ /// The following example demonstrates how to set the day header text of the date time picker column header view.
+ /// # [XAML](#tab/tabid-9)
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ /// ]]>
+ ///
+ /// # [C#](#tab/tabid-10)
+ ///
+ /// SfDateTimePicker dateTimePicker = new SfDateTimePicker();
+ /// dateTimePicker.ColumnHeaderView = new DateTimePickerColumnHeaderView
+ /// {
+ /// DayHeaderText = "Day"
+ /// };
+ ///
+ ///
+ public string DayHeaderText
+ {
+ get { return (string)GetValue(DayHeaderTextProperty); }
+ set { SetValue(DayHeaderTextProperty, value); }
+ }
+
+ ///
+ /// Gets or sets the value to month header text in SfDateTimePicker.
+ ///
+ /// The default value of is "Month".
+ ///
+ /// The following example demonstrates how to set the month header text of the date time picker column header view.
+ /// # [XAML](#tab/tabid-11)
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ /// ]]>
+ ///
+ /// # [C#](#tab/tabid-12)
+ ///
+ /// SfDateTimePicker dateTimePicker = new SfDateTimePicker();
+ /// dateTimePicker.ColumnHeaderView = new DateTimePickerColumnHeaderView
+ /// {
+ /// MonthHeaderText = "Month"
+ /// };
+ ///
+ ///
+ public string MonthHeaderText
+ {
+ get { return (string)GetValue(MonthHeaderTextProperty); }
+ set { SetValue(MonthHeaderTextProperty, value); }
+ }
+
+ ///
+ /// Gets or sets the value to year header text in SfDateTimePicker.
+ ///
+ /// The default value of is "Year".
+ ///
+ /// The following example demonstrates how to set the year header text of the date time picker column header view.
+ /// # [XAML](#tab/tabid-13)
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ /// ]]>
+ ///
+ /// # [C#](#tab/tabid-14)
+ ///
+ /// SfDateTimePicker dateTimePicker = new SfDateTimePicker();
+ /// dateTimePicker.ColumnHeaderView = new DateTimePickerColumnHeaderView
+ /// {
+ /// YearHeaderText = "Year"
+ /// };
+ ///
+ ///
+ public string YearHeaderText
+ {
+ get { return (string)GetValue(YearHeaderTextProperty); }
+ set { SetValue(YearHeaderTextProperty, value); }
+ }
+
+ ///
+ /// Gets or sets the value to hour header text in SfDateTimePicker.
+ ///
+ /// The default value of is "Hour".
+ ///
+ /// The following example demonstrates how to set the hour header text of the date time picker column header view.
+ /// # [XAML](#tab/tabid-15)
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ /// ]]>
+ ///
+ /// # [C#](#tab/tabid-16)
+ ///
+ /// SfDateTimePicker dateTimePicker = new SfDateTimePicker();
+ /// dateTimePicker.ColumnHeaderView = new DateTimePickerColumnHeaderView
+ /// {
+ /// HourHeaderText = "Hour"
+ /// };
+ ///
+ ///
+ public string HourHeaderText
+ {
+ get { return (string)GetValue(HourHeaderTextProperty); }
+ set { SetValue(HourHeaderTextProperty, value); }
+ }
+
+ ///
+ /// Gets or sets the value to minute header text in SfDateTimePicker.
+ ///
+ /// The default value of is "Minute".
+ ///
+ /// The following example demonstrates how to set the minute header text of the date time picker column header view.
+ /// # [XAML](#tab/tabid-17)
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ /// ]]>
+ ///
+ /// # [C#](#tab/tabid-18)
+ ///
+ /// SfDateTimePicker dateTimePicker = new SfDateTimePicker();
+ /// dateTimePicker.ColumnHeaderView = new DateTimePickerColumnHeaderView
+ /// {
+ /// MinuteHeaderText = "Minute"
+ /// };
+ ///
+ ///
+ public string MinuteHeaderText
+ {
+ get { return (string)GetValue(MinuteHeaderTextProperty); }
+ set { SetValue(MinuteHeaderTextProperty, value); }
+ }
+
+ ///
+ /// Gets or sets the value to second header text in SfDateTimePicker.
+ ///
+ /// The default value of is "Second".
+ ///
+ /// The following example demonstrates how to set the second header text of the date time picker column header view.
+ /// # [XAML](#tab/tabid-19)
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ /// ]]>
+ ///
+ /// # [C#](#tab/tabid-20)
+ ///
+ /// SfDateTimePicker dateTimePicker = new SfDateTimePicker();
+ /// dateTimePicker.ColumnHeaderView = new DateTimePickerColumnHeaderView
+ /// {
+ /// SecondHeaderText = "Second"
+ /// };
+ ///
+ ///
+ public string SecondHeaderText
+ {
+ get { return (string)GetValue(SecondHeaderTextProperty); }
+ set { SetValue(SecondHeaderTextProperty, value); }
+ }
+
+ ///
+ /// Gets or sets the value to meridiem header text in SfDateTimePicker.
+ ///
+ /// The default value of is an string.empty.
+ ///
+ /// The following example demonstrates how to set the meridiem header text of the date time picker column header view.
+ /// # [XAML](#tab/tabid-21)
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ /// ]]>
+ ///
+ /// # [C#](#tab/tabid-22)
+ ///
+ /// SfDateTimePicker dateTimePicker = new SfDateTimePicker();
+ /// dateTimePicker.ColumnHeaderView = new DateTimePickerColumnHeaderView
+ /// {
+ /// MeridiemHeaderText = "Meridiem"
+ /// };
+ ///
+ ///
+ public string MeridiemHeaderText
+ {
+ get { return (string)GetValue(MeridiemHeaderTextProperty); }
+ set { SetValue(MeridiemHeaderTextProperty, value); }
+ }
+
+ #endregion
+
+ #region Property Changed Methods
+
+ ///
+ /// Method invokes on the picker column header height changed.
+ ///
+ /// The column header settings object.
+ /// Property old value.
+ /// Property new value.
+ static void OnHeightChanged(BindableObject bindable, object oldValue, object newValue)
+ {
+ (bindable as DateTimePickerColumnHeaderView)?.RaisePropertyChanged(nameof(Height));
+ }
+
+ ///
+ /// Method invokes on picker column header text style property changed.
+ ///
+ /// The column header settings object.
+ /// Property old value.
+ /// Property new value.
+ static void OnTextStyleChanged(BindableObject bindable, object oldValue, object newValue)
+ {
+ (bindable as DateTimePickerColumnHeaderView)?.RaisePropertyChanged(nameof(TextStyle), oldValue);
+ }
+
+ ///
+ /// Method invokes on the picker column header background changed.
+ ///
+ /// The column header settings object.
+ /// Property old value.
+ /// Property new value.
+ static void OnBackgroundChanged(BindableObject bindable, object oldValue, object newValue)
+ {
+ (bindable as DateTimePickerColumnHeaderView)?.RaisePropertyChanged(nameof(Background));
+ }
+
+ ///
+ /// Method invokes on the picker column header separator line background changed.
+ ///
+ /// The column header settings object.
+ /// Property old value.
+ /// Property new value.
+ static void OnDividerColorChanged(BindableObject bindable, object oldValue, object newValue)
+ {
+ (bindable as DateTimePickerColumnHeaderView)?.RaisePropertyChanged(nameof(DividerColor));
+ }
+
+ ///
+ /// Method invokes on the day header text property changed.
+ ///
+ /// The column header view.
+ /// Property old value.
+ /// Property new value.
+ static void OnDayHeaderTextChanged(BindableObject bindable, object oldValue, object newValue)
+ {
+ (bindable as DateTimePickerColumnHeaderView)?.RaisePropertyChanged(nameof(DayHeaderText));
+ }
+
+ ///
+ /// Method invokes on month header text property changed.
+ ///
+ /// The column header view.
+ /// Property old value.
+ /// Property new value.
+ static void OnMonthHeaderTextChanged(BindableObject bindable, object oldValue, object newValue)
+ {
+ (bindable as DateTimePickerColumnHeaderView)?.RaisePropertyChanged(nameof(MonthHeaderText));
+ }
+
+ ///
+ /// Method invokes on year header text property changed.
+ ///
+ /// The column header view.
+ /// Property old value.
+ /// Property new value.
+ static void OnYearHeaderTextChanged(BindableObject bindable, object oldValue, object newValue)
+ {
+ (bindable as DateTimePickerColumnHeaderView)?.RaisePropertyChanged(nameof(YearHeaderText));
+ }
+
+ ///
+ /// Method invokes on hour header text property changed.
+ ///
+ /// The column header view.
+ /// Property old value.
+ /// Property new value.
+ static void OnHourHeaderTextChanged(BindableObject bindable, object oldValue, object newValue)
+ {
+ (bindable as DateTimePickerColumnHeaderView)?.RaisePropertyChanged(nameof(HourHeaderText));
+ }
+
+ ///
+ /// Method invokes on minute header text property changed.
+ ///
+ /// The column header view.
+ /// Property old value.
+ /// Property new value.
+ static void OnMinuteHeaderTextChanged(BindableObject bindable, object oldValue, object newValue)
+ {
+ (bindable as DateTimePickerColumnHeaderView)?.RaisePropertyChanged(nameof(MinuteHeaderText));
+ }
+
+ ///
+ /// Method invokes on second header text property changed.
+ ///
+ /// The column header view.
+ /// Property old value.
+ /// Property new value.
+ static void OnSecondHeaderTextChanged(BindableObject bindable, object oldValue, object newValue)
+ {
+ (bindable as DateTimePickerColumnHeaderView)?.RaisePropertyChanged(nameof(SecondHeaderText));
+ }
+
+ ///
+ /// Method invokes on meridiem header text property changed.
+ ///
+ /// The column header view.
+ /// Property old value.
+ /// Property new value.
+ static void OnMeridiemHeaderTextChanged(BindableObject bindable, object oldValue, object newValue)
+ {
+ (bindable as DateTimePickerColumnHeaderView)?.RaisePropertyChanged(nameof(MeridiemHeaderText));
+ }
+
+ ///
+ /// Method to get the default text style for the column header view.
+ ///
+ /// Returns the default column header text style.
+ static ITextElement GetColumnHeaderTextStyle()
+ {
+ return new PickerTextStyle()
+ {
+ FontSize = 14,
+ TextColor = Color.FromArgb("#49454F"),
+ };
+ }
+
+ ///
+ /// Method to invoke picker property changed event on column header settings properties changed.
+ ///
+ /// Property name.
+ /// Property old value.
+ void RaisePropertyChanged(string propertyName, object? oldValue = null)
+ {
+ PickerPropertyChanged?.Invoke(this, new PickerPropertyChangedEventArgs(propertyName) { OldValue = oldValue });
+ }
+
+ #endregion
+
+ #region Interface Implementation
+
+ ///
+ /// This method will be called when a theme dictionary
+ /// that contains the value for your control key is merged in application.
+ ///
+ /// The old value.
+ /// The new value.
+ void IThemeElement.OnCommonThemeChanged(string oldTheme, string newTheme)
+ {
+ }
+
+ ///
+ /// This method will be called when users merge a theme dictionary
+ /// that contains value for “SyncfusionTheme” dynamic resource key.
+ ///
+ /// Old theme.
+ /// New theme.
+ void IThemeElement.OnControlThemeChanged(string oldTheme, string newTheme)
+ {
+ }
+
+ #endregion
+
+ #region Events
+
+ ///
+ /// Event Invokes on column header settings property changed and this includes old value of the changed property which is used to unwire events for nested classes.
+ ///
+ internal event EventHandler? PickerPropertyChanged;
+
+ #endregion
+ }
+}
\ No newline at end of file
diff --git a/maui/src/Picker/Model/Settings/DateTimePickerHeaderSettings.cs b/maui/src/Picker/Model/Settings/DateTimePickerHeaderSettings.cs
new file mode 100644
index 00000000..568458a4
--- /dev/null
+++ b/maui/src/Picker/Model/Settings/DateTimePickerHeaderSettings.cs
@@ -0,0 +1,551 @@
+using Syncfusion.Maui.Toolkit.Graphics.Internals;
+using Syncfusion.Maui.Toolkit.Themes;
+
+namespace Syncfusion.Maui.Toolkit.Picker
+{
+ ///
+ /// Represents a class which is used to customize all the properties of header view of the SfDateTimePicker.
+ ///
+ public class DateTimePickerHeaderView : Element, IThemeElement
+ {
+ #region Bindable Properties
+
+ ///
+ /// Identifies the dependency property.
+ ///
+ ///
+ /// The identifier for dependency property.
+ ///
+ public static readonly BindableProperty HeightProperty =
+ BindableProperty.Create(
+ nameof(Height),
+ typeof(double),
+ typeof(DateTimePickerHeaderView),
+ 50d,
+ propertyChanged: OnHeightChanged);
+
+ ///
+ /// Identifies the dependency property.
+ ///
+ ///
+ /// The identifier for dependency property.
+ ///
+ public static readonly BindableProperty TextStyleProperty =
+ BindableProperty.Create(
+ nameof(TextStyle),
+ typeof(PickerTextStyle),
+ typeof(DateTimePickerHeaderView),
+ defaultValueCreator: bindable => GetHeaderTextStyle(bindable),
+ propertyChanged: OnTextStyleChanged);
+
+ ///
+ /// Identifies the dependency property.
+ ///
+ ///
+ /// The identifier for dependency property.
+ ///
+ public static readonly BindableProperty SelectionTextStyleProperty =
+ BindableProperty.Create(
+ nameof(SelectionTextStyle),
+ typeof(PickerTextStyle),
+ typeof(DateTimePickerHeaderView),
+ defaultValueCreator: bindable => GetHeaderSelectionTextStyle(bindable),
+ propertyChanged: OnSelectionTextStyleChanged);
+
+ ///
+ /// Identifies the dependency property.
+ ///
+ ///
+ /// The identifier for dependency property.
+ ///
+ public static readonly BindableProperty BackgroundProperty =
+ BindableProperty.Create(
+ nameof(Background),
+ typeof(Brush),
+ typeof(DateTimePickerHeaderView),
+ defaultValueCreator: bindable => new SolidColorBrush(Color.FromArgb("#F7F2FB")),
+ propertyChanged: OnBackgroundChanged);
+
+ ///
+ /// Identifies the dependency property.
+ ///
+ ///
+ /// The identifier for dependency property.
+ ///
+ public static readonly BindableProperty DividerColorProperty =
+ BindableProperty.Create(
+ nameof(DividerColor),
+ typeof(Color),
+ typeof(DateTimePickerHeaderView),
+ defaultValueCreator: bindable => Color.FromArgb("#CAC4D0"),
+ propertyChanged: OnDividerColorChanged);
+
+ ///
+ /// Identifies the dependency property.
+ ///
+ ///
+ /// The identifier for dependency property.
+ ///
+ public static readonly BindableProperty DateFormatProperty =
+ BindableProperty.Create(
+ nameof(DateFormat),
+ typeof(string),
+ typeof(DateTimePickerHeaderView),
+ "dd/MM/yyyy",
+ propertyChanged: OnHeaderDateFormatChanged);
+
+ ///
+ /// Identifies the dependency property.
+ ///
+ ///
+ /// The identifier for dependency property.
+ ///
+ public static readonly BindableProperty TimeFormatProperty =
+ BindableProperty.Create(
+ nameof(TimeFormat),
+ typeof(string),
+ typeof(DateTimePickerHeaderView),
+ "hh:mm:ss tt",
+ propertyChanged: OnHeaderTimeFormatChanged);
+
+ #endregion
+
+ #region Constructor
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ public DateTimePickerHeaderView()
+ {
+ ThemeElement.InitializeThemeResources(this, "SfDateTimePickerTheme");
+ SelectionTextStyle.Parent = this;
+ }
+
+ #endregion
+
+ #region Properties
+
+ ///
+ /// Gets or sets the value to specify the height of header view on SfDateTimePicker.
+ ///
+ /// The default value of is 50d.
+ ///
+ /// The following example demonstrates how to set the height of the header view.
+ /// # [XAML](#tab/tabid-1)
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ /// ]]>
+ ///
+ /// # [C#](#tab/tabid-2)
+ ///
+ /// SfDateTimePicker dateTimePicker = new SfDateTimePicker();
+ /// dateTimePicker.HeaderView = new DateTimePickerHeaderView
+ /// {
+ /// Height = 60
+ /// };
+ ///
+ ///
+ public double Height
+ {
+ get { return (double)GetValue(HeightProperty); }
+ set { SetValue(HeightProperty, value); }
+ }
+
+ ///
+ /// Gets or sets the text style of the header text in SfDateTimePicker.
+ ///
+ ///
+ /// The following example demonstrates how to set the text style of the header view.
+ /// # [XAML](#tab/tabid-3)
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ /// ]]>
+ ///
+ /// # [C#](#tab/tabid-4)
+ ///
+ /// SfDateTimePicker dateTimePicker = new SfDateTimePicker();
+ /// dateTimePicker.HeaderView = new DateTimePickerHeaderView
+ /// {
+ /// TextStyle = new PickerTextStyle
+ /// {
+ /// TextColor = Colors.Blue,
+ /// FontSize = 16
+ /// }
+ /// };
+ ///
+ ///
+ public PickerTextStyle TextStyle
+ {
+ get { return (PickerTextStyle)GetValue(TextStyleProperty); }
+ set { SetValue(TextStyleProperty, value); }
+ }
+
+ ///
+ /// Gets or sets the selection text style of the header text in SfDateTimePicker.
+ ///
+ ///
+ /// The following example demonstrates how to set the selection text style of the header view.
+ /// # [XAML](#tab/tabid-5)
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ /// ]]>
+ ///
+ /// # [C#](#tab/tabid-6)
+ ///
+ /// SfDateTimePicker dateTimePicker = new SfDateTimePicker();
+ /// dateTimePicker.HeaderView = new DateTimePickerHeaderView
+ /// {
+ /// SelectionTextStyle = new PickerTextStyle
+ /// {
+ /// TextColor = Colors.Green,
+ /// FontSize = 18
+ /// }
+ /// };
+ ///
+ ///
+ public PickerTextStyle SelectionTextStyle
+ {
+ get { return (PickerTextStyle)GetValue(SelectionTextStyleProperty); }
+ set { SetValue(SelectionTextStyleProperty, value); }
+ }
+
+ ///
+ /// Gets or sets the background of the header view in SfDateTimePicker.
+ ///
+ /// The default value of is a "#F7F2FB".
+ ///
+ /// The following example demonstrates how to set the background of the header view.
+ /// # [XAML](#tab/tabid-7)
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ /// ]]>
+ ///
+ /// # [C#](#tab/tabid-8)
+ ///
+ /// SfDateTimePicker dateTimePicker = new SfDateTimePicker();
+ /// dateTimePicker.HeaderView = new DateTimePickerHeaderView
+ /// {
+ /// Background = new SolidColorBrush(Color.FromHex("#E0E0E0"))
+ /// };
+ ///
+ ///
+ public Brush Background
+ {
+ get { return (Brush)GetValue(BackgroundProperty); }
+ set { SetValue(BackgroundProperty, value); }
+ }
+
+ ///
+ /// Gets or sets the color of the header separator line in SfDateTimePicker.
+ ///
+ /// The default value of is "#CAC4D0".
+ ///
+ /// The following example demonstrates how to set the divider color of the header view.
+ /// # [XAML](#tab/tabid-9)
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ /// ]]>
+ ///
+ /// # [C#](#tab/tabid-10)
+ ///
+ /// SfDateTimePicker dateTimePicker = new SfDateTimePicker();
+ /// dateTimePicker.HeaderView = new DateTimePickerHeaderView
+ /// {
+ /// DividerColor = Colors.Gray
+ /// };
+ ///
+ ///
+ public Color DividerColor
+ {
+ get { return (Color)GetValue(DividerColorProperty); }
+ set { SetValue(DividerColorProperty, value); }
+ }
+
+ ///
+ /// Gets or sets the value to specify the date format of header view on SfDateTimePicker.
+ ///
+ /// The default value of is "dd/MM/yyyy".
+ ///
+ /// The following example demonstrates how to set the date format of the header view.
+ /// # [XAML](#tab/tabid-11)
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ /// ]]>
+ ///
+ /// # [C#](#tab/tabid-12)
+ ///
+ /// SfDateTimePicker dateTimePicker = new SfDateTimePicker();
+ /// dateTimePicker.HeaderView = new DateTimePickerHeaderView
+ /// {
+ /// DateFormat = "MM-dd-yyyy"
+ /// };
+ ///
+ ///
+ public string DateFormat
+ {
+ get { return (string)GetValue(DateFormatProperty); }
+ set { SetValue(DateFormatProperty, value); }
+ }
+
+ ///
+ /// Gets or sets the value to specify the time format of header view on SfDateTimePicker.
+ ///
+ /// The default value of is "hh:mm:ss tt".
+ ///
+ /// The following example demonstrates how to set the time format of the header view.
+ /// # [XAML](#tab/tabid-13)
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ /// ]]>
+ ///
+ /// # [C#](#tab/tabid-14)
+ ///
+ /// SfDateTimePicker dateTimePicker = new SfDateTimePicker();
+ /// dateTimePicker.HeaderView = new DateTimePickerHeaderView
+ /// {
+ /// TimeFormat = "HH:mm"
+ /// };
+ ///
+ ///
+ public string TimeFormat
+ {
+ get { return (string)GetValue(TimeFormatProperty); }
+ set { SetValue(TimeFormatProperty, value); }
+ }
+
+ #endregion
+
+ #region Private Methods
+
+ ///
+ /// Need to update the parent for the new value.
+ ///
+ /// The old value.
+ /// The new value.
+ void SetParent(Element? oldValue, Element? newValue)
+ {
+ if (oldValue != null)
+ {
+ oldValue.Parent = null;
+ }
+
+ if (newValue != null)
+ {
+ newValue.Parent = this;
+ }
+ }
+
+ #endregion
+
+ #region Property Changed Methods
+
+ ///
+ /// Method invokes on the picker header height changed.
+ ///
+ /// The header settings object.
+ /// Property old value.
+ /// Property new value.
+ static void OnHeightChanged(BindableObject bindable, object oldValue, object newValue)
+ {
+ (bindable as DateTimePickerHeaderView)?.RaisePropertyChanged(nameof(Height));
+ }
+
+ ///
+ /// Method invokes on picker header text style property changed.
+ ///
+ /// The header settings object.
+ /// Property old value.
+ /// Property new value.
+ static void OnTextStyleChanged(BindableObject bindable, object oldValue, object newValue)
+ {
+ if (bindable is DateTimePickerHeaderView pickerHeaderView)
+ {
+ pickerHeaderView.SetParent(oldValue as Element, newValue as Element);
+ }
+
+ (bindable as DateTimePickerHeaderView)?.RaisePropertyChanged(nameof(TextStyle), oldValue);
+ }
+
+ ///
+ /// Method invokes on picker header selection text style property changed.
+ ///
+ /// The header settings object.
+ /// Property old value.
+ /// Property new value.
+ static void OnSelectionTextStyleChanged(BindableObject bindable, object oldValue, object newValue)
+ {
+ if (bindable is DateTimePickerHeaderView pickerHeaderView)
+ {
+ pickerHeaderView.SetParent(oldValue as Element, newValue as Element);
+ }
+
+ (bindable as DateTimePickerHeaderView)?.RaisePropertyChanged(nameof(SelectionTextStyle), oldValue);
+ }
+
+ ///
+ /// Method invokes on the picker header background changed.
+ ///
+ /// The header settings object.
+ /// Property old value.
+ /// Property new value.
+ static void OnBackgroundChanged(BindableObject bindable, object oldValue, object newValue)
+ {
+ (bindable as DateTimePickerHeaderView)?.RaisePropertyChanged(nameof(Background));
+ }
+
+ ///
+ /// Method invokes on the picker header separator line background changed.
+ ///
+ /// The header settings object.
+ /// Property old value.
+ /// Property new value.
+ static void OnDividerColorChanged(BindableObject bindable, object oldValue, object newValue)
+ {
+ (bindable as DateTimePickerHeaderView)?.RaisePropertyChanged(nameof(DividerColor));
+ }
+
+ ///
+ /// Method invokes on the picker header date format changed.
+ ///
+ /// The header settings object.
+ /// Property old value.
+ /// Property new value.
+ static void OnHeaderDateFormatChanged(BindableObject bindable, object oldValue, object newValue)
+ {
+ (bindable as DateTimePickerHeaderView)?.RaisePropertyChanged(nameof(DateFormat));
+ }
+
+ ///
+ /// Method invokes on the picker header time format changed.
+ ///
+ /// The header settings object.
+ /// Property old value.
+ /// Property new value.
+ static void OnHeaderTimeFormatChanged(BindableObject bindable, object oldValue, object newValue)
+ {
+ (bindable as DateTimePickerHeaderView)?.RaisePropertyChanged(nameof(TimeFormat));
+ }
+
+ ///
+ /// Method to get the default text style for the header view.
+ ///
+ /// Returns the default header text style.
+ static ITextElement GetHeaderTextStyle(BindableObject bindable)
+ {
+ var pickerHeaderView = (DateTimePickerHeaderView)bindable;
+ PickerTextStyle textStyle = new PickerTextStyle()
+ {
+ FontSize = 14,
+ TextColor = Color.FromArgb("#49454F"),
+ Parent = pickerHeaderView,
+ };
+
+ return textStyle;
+ }
+
+ ///
+ /// Method to get the default selection text style for the header view.
+ ///
+ /// Returns the default header selection text style.
+ static ITextElement GetHeaderSelectionTextStyle(BindableObject bindable)
+ {
+ var pickerHeaderView = (DateTimePickerHeaderView)bindable;
+ PickerTextStyle textStyle = new PickerTextStyle()
+ {
+ FontSize = 14,
+ TextColor = Color.FromArgb("#6750A4"),
+ Parent = pickerHeaderView,
+ };
+
+ return textStyle;
+ }
+
+ ///
+ /// Method to invoke picker property changed event on header settings properties changed.
+ ///
+ /// Property name.
+ /// Property old value.
+ void RaisePropertyChanged(string propertyName, object? oldValue = null)
+ {
+ PickerPropertyChanged?.Invoke(this, new PickerPropertyChangedEventArgs(propertyName) { OldValue = oldValue });
+ }
+
+ #endregion
+
+ #region Interface Implementation
+
+ ///
+ /// This method will be called when a theme dictionary
+ /// that contains the value for your control key is merged in application.
+ ///
+ /// The old value.
+ /// The new value.
+ void IThemeElement.OnCommonThemeChanged(string oldTheme, string newTheme)
+ {
+ }
+
+ ///
+ /// This method will be called when users merge a theme dictionary
+ /// that contains value for “SyncfusionTheme” dynamic resource key.
+ ///
+ /// Old theme.
+ /// New theme.
+ void IThemeElement.OnControlThemeChanged(string oldTheme, string newTheme)
+ {
+ }
+
+ #endregion
+
+ #region Events
+
+ ///
+ /// Event Invokes on header settings property changed and this includes old value of the changed property which is used to unwire events for nested classes.
+ ///
+ internal event EventHandler? PickerPropertyChanged;
+
+ #endregion
+ }
+}
\ No newline at end of file
diff --git a/maui/src/Picker/Model/Settings/FooterSettings.cs b/maui/src/Picker/Model/Settings/FooterSettings.cs
new file mode 100644
index 00000000..fe51d65c
--- /dev/null
+++ b/maui/src/Picker/Model/Settings/FooterSettings.cs
@@ -0,0 +1,457 @@
+using Syncfusion.Maui.Toolkit.Graphics.Internals;
+
+namespace Syncfusion.Maui.Toolkit.Picker
+{
+ ///
+ /// Represents a class which is used to customize all the properties of footer view of the SfPicker.
+ ///
+ public class PickerFooterView : Element
+ {
+ #region Bindable Properties
+
+ ///
+ /// Identifies the dependency property.
+ ///
+ ///
+ /// The identifier for dependency property.
+ ///
+ public static readonly BindableProperty HeightProperty =
+ BindableProperty.Create(
+ nameof(Height),
+ typeof(double),
+ typeof(PickerFooterView),
+ 0d,
+ propertyChanged: OnHeightChanged);
+
+ ///
+ /// Identifies the dependency property.
+ ///
+ ///
+ /// The identifier for dependency property.
+ ///
+ public static readonly BindableProperty BackgroundProperty =
+ BindableProperty.Create(
+ nameof(Background),
+ typeof(Brush),
+ typeof(PickerFooterView),
+ defaultValueCreator: bindable => Brush.Transparent,
+ propertyChanged: OnBackgroundChanged);
+
+ ///
+ /// Identifies the dependency property.
+ ///
+ ///
+ /// The identifier for dependency property.
+ ///
+ public static readonly BindableProperty DividerColorProperty =
+ BindableProperty.Create(
+ nameof(DividerColor),
+ typeof(Color),
+ typeof(PickerFooterView),
+ defaultValueCreator: bindable => Color.FromArgb("#CAC4D0"),
+ propertyChanged: OnDividerColorChanged);
+
+ ///
+ /// Identifies the dependency property.
+ ///
+ ///
+ /// The identifier for dependency property.
+ ///
+ public static readonly BindableProperty OkButtonTextProperty =
+ BindableProperty.Create(
+ nameof(OkButtonText),
+ typeof(string),
+ typeof(PickerFooterView),
+ "OK",
+ propertyChanged: OnOkButtonTextChanged);
+
+ ///
+ /// Identifies the dependency property.
+ ///
+ ///
+ /// The identifier for dependency property.
+ ///
+ public static readonly BindableProperty CancelButtonTextProperty =
+ BindableProperty.Create(
+ nameof(CancelButtonText),
+ typeof(string),
+ typeof(PickerFooterView),
+ "Cancel",
+ propertyChanged: OnCancelButtonTextChanged);
+
+ ///
+ /// Identifies the dependency property.
+ ///
+ ///
+ /// The identifier for dependency property.
+ ///
+ public static readonly BindableProperty ShowOkButtonProperty =
+ BindableProperty.Create(
+ nameof(ShowOkButton),
+ typeof(bool),
+ typeof(PickerFooterView),
+ true,
+ propertyChanged: OnShowOkButtonChanged);
+
+ ///
+ /// Identifies the dependency property.
+ ///
+ ///
+ /// The identifier for dependency property.
+ ///
+ public static readonly BindableProperty TextStyleProperty =
+ BindableProperty.Create(
+ nameof(TextStyle),
+ typeof(PickerTextStyle),
+ typeof(PickerFooterView),
+ defaultValueCreator: bindable => GetFooterTextStyle(bindable),
+ propertyChanged: OnTextStyleChanged);
+
+ #endregion
+
+ #region Properties
+
+ ///
+ /// Gets or sets the value to specify the height of footer view on SfPicker.
+ ///
+ /// The default value of is 0d.
+ ///
+ /// The following example demonstrates how to set the height of the footer view.
+ /// # [XAML](#tab/tabid-1)
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ /// ]]>
+ ///
+ /// # [C#](#tab/tabid-2)
+ ///
+ /// SfPicker picker = new SfPicker();
+ /// picker.FooterView = new PickerFooterView
+ /// {
+ /// Height = 50
+ /// };
+ ///
+ ///
+ public double Height
+ {
+ get { return (double)GetValue(HeightProperty); }
+ set { SetValue(HeightProperty, value); }
+ }
+
+ ///
+ /// Gets or sets the background of the footer view in SfPicker.
+ ///
+ /// The default value of is Brush.Transparent.
+ ///
+ /// The following example demonstrates how to set the background of the footer view.
+ /// # [XAML](#tab/tabid-3)
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ /// ]]>
+ ///
+ /// # [C#](#tab/tabid-4)
+ ///
+ /// SfPicker picker = new SfPicker();
+ /// picker.FooterView = new PickerFooterView
+ /// {
+ /// Background = Brush.Blue
+ /// };
+ ///
+ ///
+ public Brush Background
+ {
+ get { return (Brush)GetValue(BackgroundProperty); }
+ set { SetValue(BackgroundProperty, value); }
+ }
+
+ ///
+ /// Gets or sets the background of the footer separator line background in SfPicker.
+ ///
+ /// The default value of is "#CAC4D0".
+ ///
+ /// The following example demonstrates how to set the divider color of the footer view.
+ /// # [XAML](#tab/tabid-5)
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ /// ]]>
+ ///
+ /// # [C#](#tab/tabid-6)
+ ///
+ /// SfPicker picker = new SfPicker();
+ /// picker.FooterView = new PickerFooterView
+ /// {
+ /// DividerColor = Colors.Gray
+ /// };
+ ///
+ ///
+ public Color DividerColor
+ {
+ get { return (Color)GetValue(DividerColorProperty); }
+ set { SetValue(DividerColorProperty, value); }
+ }
+
+ ///
+ /// Gets or sets the ok button text in the footer view of SfPicker.
+ ///
+ /// The default value of is "OK".
+ ///
+ /// The following example demonstrates how to set the ok button text of the footer view.
+ /// # [XAML](#tab/tabid-7)
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ /// ]]>
+ ///
+ /// # [C#](#tab/tabid-8)
+ ///
+ /// SfPicker picker = new SfPicker();
+ /// picker.FooterView = new PickerFooterView
+ /// {
+ /// OkButtonText = "Save"
+ /// };
+ ///
+ ///
+ public string OkButtonText
+ {
+ get { return (string)GetValue(OkButtonTextProperty); }
+ set { SetValue(OkButtonTextProperty, value); }
+ }
+
+ ///
+ /// Gets or sets the cancel button text in the footer view of SfPicker.
+ ///
+ /// The default value of is "Cancel".
+ ///
+ /// The following example demonstrates how to set the cancel button text of the footer view.
+ /// # [XAML](#tab/tabid-9)
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ /// ]]>
+ ///
+ /// # [C#](#tab/tabid-10)
+ ///
+ /// SfPicker picker = new SfPicker();
+ /// picker.FooterView = new PickerFooterView
+ /// {
+ /// CancelButtonText = "Exit"
+ /// };
+ ///
+ ///
+ public string CancelButtonText
+ {
+ get { return (string)GetValue(CancelButtonTextProperty); }
+ set { SetValue(CancelButtonTextProperty, value); }
+ }
+
+ ///
+ /// Gets or sets a value indicating whether to show the cancel button in the footer view of SfPicker.
+ ///
+ /// The default value of is true.
+ ///
+ /// The following example demonstrates how to hide the ok button in the footer view.
+ /// # [XAML](#tab/tabid-11)
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ /// ]]>
+ ///
+ /// # [C#](#tab/tabid-12)
+ ///
+ /// SfPicker picker = new SfPicker();
+ /// picker.FooterView = new PickerFooterView
+ /// {
+ /// ShowOkButton = false
+ /// };
+ ///
+ ///
+ public bool ShowOkButton
+ {
+ get { return (bool)GetValue(ShowOkButtonProperty); }
+ set { SetValue(ShowOkButtonProperty, value); }
+ }
+
+ ///
+ /// Gets or sets the ok and cancel button text style in the footer view of SfPicker.
+ ///
+ ///
+ /// The following example demonstrates how to set the text style of the footer view buttons.
+ /// # [XAML](#tab/tabid-13)
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ /// ]]>
+ ///
+ /// # [C#](#tab/tabid-14)
+ ///
+ /// SfPicker picker = new SfPicker();
+ /// picker.FooterView = new PickerFooterView
+ /// {
+ /// TextStyle = new PickerTextStyle
+ /// {
+ /// TextColor = Colors.Blue,
+ /// FontSize = 16
+ /// }
+ /// };
+ ///
+ ///
+ public PickerTextStyle TextStyle
+ {
+ get { return (PickerTextStyle)GetValue(TextStyleProperty); }
+ set { SetValue(TextStyleProperty, value); }
+ }
+
+ #endregion
+
+ #region Property Changed Methods
+
+ ///
+ /// Method invokes on the picker footer height changed.
+ ///
+ /// The footer settings object.
+ /// Property old value.
+ /// Property new value.
+ static void OnHeightChanged(BindableObject bindable, object oldValue, object newValue)
+ {
+ (bindable as PickerFooterView)?.RaisePropertyChanged(nameof(Height));
+ }
+
+ ///
+ /// Method invokes on the picker footer background changed.
+ ///
+ /// The footer settings object.
+ /// Property old value.
+ /// Property new value.
+ static void OnBackgroundChanged(BindableObject bindable, object oldValue, object newValue)
+ {
+ (bindable as PickerFooterView)?.RaisePropertyChanged(nameof(Background));
+ }
+
+ ///
+ /// Method invokes on the picker footer separator line background changed.
+ ///
+ /// The footer settings object.
+ /// Property old value.
+ /// Property new value.
+ static void OnDividerColorChanged(BindableObject bindable, object oldValue, object newValue)
+ {
+ (bindable as PickerFooterView)?.RaisePropertyChanged(nameof(DividerColor));
+ }
+
+ ///
+ /// Method invokes to get the default text style of the footer view.
+ ///
+ /// Returns the footer view text style.
+ static ITextElement GetFooterTextStyle(BindableObject bindable)
+ {
+ var pickerFooterView = (PickerFooterView)bindable;
+ PickerTextStyle pickerTextStyle = new PickerTextStyle()
+ {
+ FontSize = 14,
+ TextColor = Color.FromArgb("#6750A4"),
+ Parent = pickerFooterView,
+ };
+
+ return pickerTextStyle;
+ }
+
+ ///
+ /// Method invokes on the picker footer ok button text changed.
+ ///
+ /// The footer settings object.
+ /// Property old value.
+ /// Property new value.
+ static void OnOkButtonTextChanged(BindableObject bindable, object oldValue, object newValue)
+ {
+ (bindable as PickerFooterView)?.RaisePropertyChanged(nameof(OkButtonText));
+ }
+
+ ///
+ /// Method invokes on the picker footer cancel button text changed.
+ ///
+ /// The footer settings object.
+ /// Property old value.
+ /// Property new value.
+ static void OnCancelButtonTextChanged(BindableObject bindable, object oldValue, object newValue)
+ {
+ (bindable as PickerFooterView)?.RaisePropertyChanged(nameof(CancelButtonText));
+ }
+
+ ///
+ /// Method invokes on the picker footer show ok button changed.
+ ///
+ /// The footer settings object.
+ /// Property old value.
+ /// Property new value.
+ static void OnShowOkButtonChanged(BindableObject bindable, object oldValue, object newValue)
+ {
+ (bindable as PickerFooterView)?.RaisePropertyChanged(nameof(ShowOkButton));
+ }
+
+ ///
+ /// Method invokes on the picker footer ok and cancel button text style changed.
+ ///
+ /// The footer settings object.
+ /// Property old value.
+ /// Property new value.
+ static void OnTextStyleChanged(BindableObject bindable, object oldValue, object newValue)
+ {
+ (bindable as PickerFooterView)?.RaisePropertyChanged(nameof(TextStyle));
+ }
+
+ ///
+ /// Method invokes on the picker property changed event on footer settings properties changed.
+ ///
+ /// Property name.
+ /// Property old value.
+ void RaisePropertyChanged(string propertyName, object? oldValue = null)
+ {
+ PickerPropertyChanged?.Invoke(this, new PickerPropertyChangedEventArgs(propertyName) { OldValue = oldValue });
+ }
+
+ #endregion
+
+ #region Events
+
+ ///
+ /// Event Invokes on footer settings property changed and this includes old value of the changed property which is used to unwire events for nested classes.
+ ///
+ internal event EventHandler? PickerPropertyChanged;
+
+ #endregion
+ }
+}
\ No newline at end of file
diff --git a/maui/src/Picker/Model/Settings/HeaderSettings.cs b/maui/src/Picker/Model/Settings/HeaderSettings.cs
new file mode 100644
index 00000000..012ee3ed
--- /dev/null
+++ b/maui/src/Picker/Model/Settings/HeaderSettings.cs
@@ -0,0 +1,512 @@
+using Syncfusion.Maui.Toolkit.Graphics.Internals;
+
+namespace Syncfusion.Maui.Toolkit.Picker
+{
+ ///
+ /// Represents a class which is used to customize all the properties of header view of the picker.
+ ///
+ public class PickerHeaderView : Element
+ {
+ #region Bindable Properties
+
+ ///
+ /// Identifies the dependency property.
+ ///
+ ///
+ /// The identifier for dependency property.
+ ///
+ public static readonly BindableProperty HeightProperty =
+ BindableProperty.Create(
+ nameof(Height),
+ typeof(double),
+ typeof(PickerHeaderView),
+ 0d,
+ propertyChanged: OnHeightChanged);
+
+ ///
+ /// Identifies the dependency property.
+ ///
+ ///
+ /// The identifier for dependency property.
+ ///
+ public static readonly BindableProperty TextProperty =
+ BindableProperty.Create(
+ nameof(Text),
+ typeof(string),
+ typeof(PickerHeaderView),
+ string.Empty,
+ propertyChanged: OnHeaderTextChanged);
+
+ ///
+ /// Identifies the dependency property.
+ ///
+ ///
+ /// The identifier for dependency property.
+ ///
+ public static readonly BindableProperty TextStyleProperty =
+ BindableProperty.Create(
+ nameof(TextStyle),
+ typeof(PickerTextStyle),
+ typeof(PickerHeaderView),
+ defaultValueCreator: bindable => GetHeaderTextStyle(bindable),
+ propertyChanged: OnTextStyleChanged);
+
+ ///
+ /// Identifies the dependency property.
+ ///
+ ///
+ /// The identifier for dependency property.
+ ///
+ public static readonly BindableProperty BackgroundProperty =
+ BindableProperty.Create(
+ nameof(Background),
+ typeof(Brush),
+ typeof(PickerHeaderView),
+ defaultValueCreator: bindable => Brush.Transparent,
+ propertyChanged: OnBackgroundChanged);
+
+ ///
+ /// Identifies the dependency property.
+ ///
+ ///
+ /// The identifier for dependency property.
+ ///
+ public static readonly BindableProperty DividerColorProperty =
+ BindableProperty.Create(
+ nameof(DividerColor),
+ typeof(Color),
+ typeof(PickerHeaderView),
+ defaultValueCreator: bindable => Color.FromArgb("#CAC4D0"),
+ propertyChanged: OnDividerColorChanged);
+
+ #endregion
+
+ #region Internal Bindable Properties
+
+ ///
+ /// Identifies the dependency property.
+ ///
+ ///
+ /// The identifier for dependency property.
+ ///
+ internal static readonly BindableProperty DateTextProperty =
+ BindableProperty.Create(
+ nameof(DateText),
+ typeof(string),
+ typeof(PickerHeaderView),
+ string.Empty,
+ propertyChanged: OnHeaderDateTextChanged);
+
+ ///
+ /// Identifies the dependency property.
+ ///
+ ///
+ /// The identifier for dependency property.
+ ///
+ internal static readonly BindableProperty TimeTextProperty =
+ BindableProperty.Create(
+ nameof(TimeText),
+ typeof(string),
+ typeof(PickerHeaderView),
+ string.Empty,
+ propertyChanged: OnHeaderTimeTextChanged);
+
+ ///
+ /// Identifies the dependency property.
+ ///
+ ///
+ /// The identifier for dependency property.
+ ///
+ internal static readonly BindableProperty SelectionTextStyleProperty =
+ BindableProperty.Create(
+ nameof(SelectionTextStyle),
+ typeof(PickerTextStyle),
+ typeof(PickerHeaderView),
+ defaultValueCreator: bindable => GetHeaderSelectionTextStyle(bindable),
+ propertyChanged: OnSelectionTextStyleChanged);
+
+ #endregion
+
+ #region Constructor
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ public PickerHeaderView()
+ {
+ SelectionTextStyle.Parent = this;
+ TextStyle.Parent = this;
+ }
+
+ #endregion
+
+ #region Properties
+
+ ///
+ /// Gets or sets the value to specify the height of header view on picker.
+ ///
+ /// The default value of is 0d.
+ ///
+ /// The following example demonstrates how to set the height of the header view.
+ /// # [XAML](#tab/tabid-1)
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ /// ]]>
+ ///
+ /// # [C#](#tab/tabid-2)
+ ///
+ /// SfPicker picker = new SfPicker();
+ /// picker.HeaderView = new PickerHeaderView
+ /// {
+ /// Height = 50
+ /// };
+ ///
+ ///
+ public double Height
+ {
+ get { return (double)GetValue(HeightProperty); }
+ set { SetValue(HeightProperty, value); }
+ }
+
+ ///
+ /// Gets or sets the value to specify the text of header view on picker.
+ ///
+ /// The default value of is string.Empty.
+ ///
+ /// The following example demonstrates how to set the text of the header view.
+ /// # [XAML](#tab/tabid-3)
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ /// ]]>
+ ///
+ /// # [C#](#tab/tabid-4)
+ ///
+ /// SfPicker picker = new SfPicker();
+ /// picker.HeaderView = new PickerHeaderView
+ /// {
+ /// Text = "Select an item"
+ /// };
+ ///
+ ///
+ public string Text
+ {
+ get { return (string)GetValue(TextProperty); }
+ set { SetValue(TextProperty, value); }
+ }
+
+ ///
+ /// Gets or sets the text style of the header text in picker.
+ ///
+ ///
+ /// The following example demonstrates how to set the text style of the header view.
+ /// # [XAML](#tab/tabid-5)
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ /// ]]>
+ ///
+ /// # [C#](#tab/tabid-6)
+ ///
+ /// SfPicker picker = new SfPicker();
+ /// picker.HeaderView = new PickerHeaderView
+ /// {
+ /// TextStyle = new PickerTextStyle
+ /// {
+ /// TextColor = Colors.Blue,
+ /// FontSize = 16
+ /// }
+ /// };
+ ///
+ ///
+ public PickerTextStyle TextStyle
+ {
+ get { return (PickerTextStyle)GetValue(TextStyleProperty); }
+ set { SetValue(TextStyleProperty, value); }
+ }
+
+ ///
+ /// Gets or sets the background of the header view in picker.
+ ///
+ /// The default value of is Brush.Transparent.
+ ///
+ /// The following example demonstrates how to set the background of the header view.
+ /// # [XAML](#tab/tabid-7)
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ /// ]]>
+ ///
+ /// # [C#](#tab/tabid-8)
+ ///
+ /// SfPicker picker = new SfPicker();
+ /// picker.HeaderView = new PickerHeaderView
+ /// {
+ /// Background = Brush.Blue
+ /// };
+ ///
+ ///
+ public Brush Background
+ {
+ get { return (Brush)GetValue(BackgroundProperty); }
+ set { SetValue(BackgroundProperty, value); }
+ }
+
+ ///
+ /// Gets or sets the color of the header separator line in picker.
+ ///
+ /// The default value of is "#CAC4D0".
+ ///
+ /// The following example demonstrates how to set the divider color of the header view.
+ /// # [XAML](#tab/tabid-9)
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ /// ]]>
+ ///
+ /// # [C#](#tab/tabid-10)
+ ///
+ /// SfPicker picker = new SfPicker();
+ /// picker.HeaderView = new PickerHeaderView
+ /// {
+ /// DividerColor = Colors.Gray
+ /// };
+ ///
+ ///
+ public Color DividerColor
+ {
+ get { return (Color)GetValue(DividerColorProperty); }
+ set { SetValue(DividerColorProperty, value); }
+ }
+
+ #endregion
+
+ #region Internal Properties
+
+ ///
+ /// Gets or sets the value to specify the date format of header view on Picker.
+ ///
+ internal string DateText
+ {
+ get { return (string)GetValue(DateTextProperty); }
+ set { SetValue(DateTextProperty, value); }
+ }
+
+ ///
+ /// Gets or sets the value to specify the time format of header view on picker.
+ ///
+ internal string TimeText
+ {
+ get { return (string)GetValue(TimeTextProperty); }
+ set { SetValue(TimeTextProperty, value); }
+ }
+
+ ///
+ /// Gets or sets the selection text style of the header text in picker.
+ ///
+ internal PickerTextStyle SelectionTextStyle
+ {
+ get { return (PickerTextStyle)GetValue(SelectionTextStyleProperty); }
+ set { SetValue(SelectionTextStyleProperty, value); }
+ }
+
+ #endregion
+
+ #region Private Methods
+
+ ///
+ /// Need to update the parent for the new value.
+ ///
+ /// The old value.
+ /// The new value.
+ void SetParent(Element? oldValue, Element? newValue)
+ {
+ if (oldValue != null)
+ {
+ oldValue.Parent = null;
+ }
+
+ if (newValue != null)
+ {
+ newValue.Parent = this;
+ }
+ }
+
+ #endregion
+
+ #region Property Changed Methods
+
+ ///
+ /// Method invokes on the picker header height changed.
+ ///
+ /// The header settings object.
+ /// Property old value.
+ /// Property new value.
+ static void OnHeightChanged(BindableObject bindable, object oldValue, object newValue)
+ {
+ (bindable as PickerHeaderView)?.RaisePropertyChanged(nameof(Height));
+ }
+
+ ///
+ /// Method invokes on the picker header text changed.
+ ///
+ /// The header settings object.
+ /// Property old value.
+ /// Property new value.
+ static void OnHeaderTextChanged(BindableObject bindable, object oldValue, object newValue)
+ {
+ (bindable as PickerHeaderView)?.RaisePropertyChanged(nameof(Text));
+ }
+
+ ///
+ /// Method invokes on picker header text style property changed.
+ ///
+ /// The header settings object.
+ /// Property old value.
+ /// Property new value.
+ static void OnTextStyleChanged(BindableObject bindable, object oldValue, object newValue)
+ {
+ (bindable as PickerHeaderView)?.RaisePropertyChanged(nameof(TextStyle), oldValue);
+ }
+
+ ///
+ /// Method invokes on the picker header background changed.
+ ///
+ /// The header settings object.
+ /// Property old value.
+ /// Property new value.
+ static void OnBackgroundChanged(BindableObject bindable, object oldValue, object newValue)
+ {
+ (bindable as PickerHeaderView)?.RaisePropertyChanged(nameof(Background));
+ }
+
+ ///
+ /// Method invokes on the picker header separator line background changed.
+ ///
+ /// The header settings object.
+ /// Property old value.
+ /// Property new value.
+ static void OnDividerColorChanged(BindableObject bindable, object oldValue, object newValue)
+ {
+ (bindable as PickerHeaderView)?.RaisePropertyChanged(nameof(DividerColor));
+ }
+
+ ///
+ /// Method invokes on the picker header date format changed.
+ ///
+ /// The header settings object.
+ /// Property old value.
+ /// Property new value.
+ static void OnHeaderDateTextChanged(BindableObject bindable, object oldValue, object newValue)
+ {
+ (bindable as PickerHeaderView)?.RaisePropertyChanged(nameof(DateText));
+ }
+
+ ///
+ /// Method invokes on the picker header time format changed.
+ ///
+ /// The header settings object.
+ /// Property old value.
+ /// Property new value.
+ static void OnHeaderTimeTextChanged(BindableObject bindable, object oldValue, object newValue)
+ {
+ (bindable as PickerHeaderView)?.RaisePropertyChanged(nameof(TimeText));
+ }
+
+ ///
+ /// Method invokes on picker header selection text style property changed.
+ ///
+ /// The header settings object.
+ /// Property old value.
+ /// Property new value.
+ static void OnSelectionTextStyleChanged(BindableObject bindable, object oldValue, object newValue)
+ {
+ if (bindable is PickerHeaderView pickerHeaderView)
+ {
+ pickerHeaderView.SetParent(oldValue as Element, newValue as Element);
+ }
+
+ (bindable as PickerHeaderView)?.RaisePropertyChanged(nameof(SelectionTextStyle), oldValue);
+ }
+
+ ///
+ /// Method to get the default text style for the header view.
+ ///
+ /// Returns the default header text style.
+ static ITextElement GetHeaderTextStyle(BindableObject bindable)
+ {
+ var pickerHeaderView = (PickerHeaderView)bindable;
+ PickerTextStyle textStyle = new PickerTextStyle()
+ {
+ FontSize = 16,
+ TextColor = Color.FromArgb("#1C1B1F"),
+ Parent = pickerHeaderView,
+ };
+
+ return textStyle;
+ }
+
+ ///
+ /// Method to get the default selection text style for the header view.
+ ///
+ /// Returns the default header selection text style.
+ static ITextElement GetHeaderSelectionTextStyle(BindableObject bindable)
+ {
+ var pickerHeaderView = (PickerHeaderView)bindable;
+ PickerTextStyle textStyle = new PickerTextStyle()
+ {
+ FontSize = 14,
+ TextColor = Color.FromArgb("#6750A4"),
+ Parent = pickerHeaderView,
+ };
+
+ return textStyle;
+ }
+
+ ///
+ /// Method to invoke picker property changed event on header settings properties changed.
+ ///
+ /// Property name.
+ /// Property old value.
+ void RaisePropertyChanged(string propertyName, object? oldValue = null)
+ {
+ PickerPropertyChanged?.Invoke(this, new PickerPropertyChangedEventArgs(propertyName) { OldValue = oldValue });
+ }
+
+ #endregion
+
+ #region Events
+
+ ///
+ /// Event Invokes on header settings property changed and this includes old value of the changed property which is used to unwire events for nested classes.
+ ///
+ internal event EventHandler? PickerPropertyChanged;
+
+ #endregion
+ }
+}
\ No newline at end of file
diff --git a/maui/src/Picker/Model/Settings/PickerBase.cs b/maui/src/Picker/Model/Settings/PickerBase.cs
new file mode 100644
index 00000000..1537c732
--- /dev/null
+++ b/maui/src/Picker/Model/Settings/PickerBase.cs
@@ -0,0 +1,2311 @@
+using System.Collections.ObjectModel;
+using System.Collections.Specialized;
+using System.ComponentModel;
+using System.Diagnostics.CodeAnalysis;
+using System.Windows.Input;
+using Syncfusion.Maui.Toolkit.Graphics.Internals;
+
+namespace Syncfusion.Maui.Toolkit.Picker
+{
+ ///
+ /// Class that represents and render the picker control,
+ ///
+ public abstract partial class PickerBase
+ {
+ #region Bindable Properties
+
+ ///
+ /// Identifies the dependency property.
+ ///
+ ///
+ /// The identifier for dependency property.
+ ///
+ public static readonly BindableProperty FooterViewProperty =
+ BindableProperty.Create(
+ nameof(FooterView),
+ typeof(PickerFooterView),
+ typeof(PickerBase),
+ defaultValueCreator: bindable => new PickerFooterView(),
+ propertyChanged: OnFooterViewChanged);
+
+ ///
+ /// Identifies the dependency property.
+ ///
+ ///
+ /// The identifier for dependency property.
+ ///
+ public static readonly BindableProperty ColumnDividerColorProperty =
+ BindableProperty.Create(
+ nameof(ColumnDividerColor),
+ typeof(Color),
+ typeof(PickerBase),
+ defaultValueCreator: bindable => Color.FromArgb("#CAC4D0"),
+ propertyChanged: OnColumnDividerColorChanged);
+
+ ///
+ /// Identifies the dependency property.
+ ///
+ ///
+ /// The identifier for dependency property.
+ ///
+ public static readonly BindableProperty ItemHeightProperty =
+ BindableProperty.Create(
+ nameof(ItemHeight),
+ typeof(double),
+ typeof(PickerBase),
+ 40d,
+ propertyChanged: OnItemHeightChanged);
+
+ ///
+ /// Identifies the dependency property.
+ ///
+ ///
+ /// The identifier for dependency property.
+ ///
+ public static readonly BindableProperty SelectedTextStyleProperty =
+ BindableProperty.Create(
+ nameof(SelectedTextStyle),
+ typeof(PickerTextStyle),
+ typeof(PickerBase),
+ defaultValueCreator: bindable => GetPickerSelectionTextStyle(bindable),
+ propertyChanged: OnSelectedTextStyleChanged);
+
+ ///
+ /// Identifies the dependency property.
+ ///
+ ///
+ /// The identifier for dependency property.
+ ///
+ public static readonly BindableProperty TextStyleProperty =
+ BindableProperty.Create(
+ nameof(TextStyle),
+ typeof(PickerTextStyle),
+ typeof(PickerBase),
+ defaultValueCreator: bindable => GetPickerTextStyle(bindable),
+ propertyChanged: OnTextStyleChanged);
+
+ ///
+ /// Identifies the dependency property.
+ ///
+ ///
+ /// The identifier for dependency property.
+ ///
+ public static readonly BindableProperty SelectionViewProperty =
+ BindableProperty.Create(
+ nameof(SelectionView),
+ typeof(PickerSelectionView),
+ typeof(PickerBase),
+ defaultValueCreator: bindable => new PickerSelectionView(),
+ propertyChanged: OnSelectionViewChanged);
+
+ ///
+ /// Identifies the dependency property.
+ ///
+ ///
+ /// The identifier for dependency property.
+ ///
+ public static readonly BindableProperty IsOpenProperty =
+ BindableProperty.Create(
+ nameof(IsOpen),
+ typeof(bool),
+ typeof(PickerBase),
+ false,
+ propertyChanged: OnIsOpenChanged);
+
+ ///
+ /// Identifies the dependency property.
+ ///
+ ///
+ /// The identifier for dependency property.
+ ///
+ public static readonly BindableProperty ModeProperty =
+ BindableProperty.Create(
+ nameof(Mode),
+ typeof(PickerMode),
+ typeof(PickerBase),
+ PickerMode.Default,
+ propertyChanged: OnModeChanged);
+
+ ///
+ /// Identifies the dependency property.
+ ///
+ ///
+ /// The identifier for dependency property.
+ ///
+ public static readonly BindableProperty TextDisplayModeProperty =
+ BindableProperty.Create(
+ nameof(TextDisplayMode),
+ typeof(PickerTextDisplayMode),
+ typeof(PickerBase),
+ PickerTextDisplayMode.Default,
+ propertyChanged: OnTextDisplayModeChanged);
+
+ ///
+ /// Identifies the dependency property.
+ ///
+ ///
+ /// The identifier for dependency property.
+ ///
+ public static readonly BindableProperty RelativePositionProperty =
+ BindableProperty.Create(
+ nameof(RelativePosition),
+ typeof(PickerRelativePosition),
+ typeof(PickerBase),
+ PickerRelativePosition.AlignTop,
+ propertyChanged: OnRelativePositionChanged);
+
+ ///
+ /// Identifies the dependency property.
+ ///
+ ///
+ /// The identifier for dependency property.
+ ///
+ public static readonly BindableProperty RelativeViewProperty =
+ BindableProperty.Create(
+ nameof(RelativeView),
+ typeof(View),
+ typeof(PickerBase),
+ null,
+ propertyChanged: OnRelativeViewChanged);
+
+ ///
+ /// Identifies the dependency property.
+ ///
+ ///
+ /// The identifier for dependency property.
+ ///
+ public static readonly BindableProperty AcceptCommandProperty =
+ BindableProperty.Create(
+ nameof(AcceptCommand),
+ typeof(ICommand),
+ typeof(PickerBase),
+ null);
+
+ ///
+ /// Identifies the dependency property.
+ ///
+ ///
+ /// The identifier for dependency property.
+ ///
+ public static readonly BindableProperty DeclineCommandProperty =
+ BindableProperty.Create(
+ nameof(DeclineCommand),
+ typeof(ICommand),
+ typeof(PickerBase),
+ null);
+
+ #endregion
+
+ #region Internal Bindable Properties
+
+ ///
+ /// Identifies the dependency property.
+ ///
+ ///
+ /// The identifier for dependency property.
+ ///
+ internal static readonly BindableProperty BaseHeaderViewProperty =
+ BindableProperty.Create(
+ nameof(BaseHeaderView),
+ typeof(PickerHeaderView),
+ typeof(PickerBase),
+ defaultValueCreator: bindable => new PickerHeaderView(),
+ propertyChanged: OnHeaderViewChanged);
+
+ ///
+ /// Identifies the dependency property.
+ ///
+ ///
+ /// The identifier for dependency property.
+ ///
+ internal static readonly BindableProperty BaseColumnHeaderViewProperty =
+ BindableProperty.Create(
+ nameof(BaseColumnHeaderView),
+ typeof(PickerColumnHeaderView),
+ typeof(PickerBase),
+ defaultValueCreator: bindable => new PickerColumnHeaderView(),
+ propertyChanged: OnColumnHeaderViewChanged);
+
+ ///
+ /// Identifies the dependency property.
+ ///
+ ///
+ /// The identifier for dependency property.
+ ///
+ internal static readonly BindableProperty BaseColumnsProperty =
+ BindableProperty.Create(
+ nameof(BaseColumns),
+ typeof(ObservableCollection),
+ typeof(PickerBase),
+ defaultValueCreator: bindable => new ObservableCollection(),
+ propertyChanged: OnColumnsChanged);
+
+ ///
+ /// Identifies the dependency property.
+ ///
+ ///
+ /// The identifier for dependency property.
+ ///
+ internal static readonly BindableProperty BaseItemTemplateProperty =
+ BindableProperty.Create(
+ nameof(BaseItemTemplate),
+ typeof(DataTemplate),
+ typeof(PickerBase),
+ null,
+ propertyChanged: OnItemTemplateChanged);
+
+ ///
+ /// Identifies the dependency property.
+ ///
+ ///
+ /// The identifier for dependency property.
+ ///
+ internal static readonly BindableProperty DisabledTextStyleProperty =
+ BindableProperty.Create(
+ nameof(DisabledTextStyle),
+ typeof(PickerTextStyle),
+ typeof(PickerBase),
+ defaultValueCreator: bindable => GetPickerDisabledTextStyle(bindable),
+ propertyChanged: OnDisabledTextStyleChanged);
+
+ #endregion
+
+ #region Properties
+
+ ///
+ /// Gets or sets the value to specify the item height of picker view on Picker.
+ ///
+ /// The default value of is 40d.
+ ///
+ /// The following example demonstrates how to set the item height of the picker view.
+ /// # [XAML](#tab/tabid-1)
+ ///
+ ///
+ /// ]]>
+ ///
+ /// # [C#](#tab/tabid-2)
+ ///
+ /// SfPicker picker = new SfPicker
+ /// {
+ /// ItemHeight = 50
+ /// };
+ ///
+ ///
+ public double ItemHeight
+ {
+ get { return (double)GetValue(ItemHeightProperty); }
+ set { SetValue(ItemHeightProperty, value); }
+ }
+
+ ///
+ /// Gets or sets the picker selected text style in Picker.
+ ///
+ ///
+ /// The following example demonstrates how to set the selected text style of the picker.
+ /// # [XAML](#tab/tabid-3)
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ /// ]]>
+ ///
+ /// # [C#](#tab/tabid-4)
+ ///
+ /// SfPicker picker = new SfPicker
+ /// {
+ /// SelectedTextStyle = new PickerTextStyle
+ /// {
+ /// TextColor = Colors.Blue,
+ /// FontSize = 16
+ /// }
+ /// };
+ ///
+ ///
+ public PickerTextStyle SelectedTextStyle
+ {
+ get { return (PickerTextStyle)GetValue(SelectedTextStyleProperty); }
+ set { SetValue(SelectedTextStyleProperty, value); }
+ }
+
+ ///
+ /// Gets or sets the picker item text style in Picker.
+ ///
+ ///
+ /// The following example demonstrates how to set the text style of the picker items.
+ /// # [XAML](#tab/tabid-5)
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ /// ]]>
+ ///
+ /// # [C#](#tab/tabid-6)
+ ///
+ /// SfPicker picker = new SfPicker
+ /// {
+ /// TextStyle = new PickerTextStyle
+ /// {
+ /// TextColor = Colors.Gray,
+ /// FontSize = 14
+ /// }
+ /// };
+ ///
+ ///
+ public PickerTextStyle TextStyle
+ {
+ get { return (PickerTextStyle)GetValue(TextStyleProperty); }
+ set { SetValue(TextStyleProperty, value); }
+ }
+
+ ///
+ /// Gets or sets the value of selection view. This property can be used to customize the selection in Picker.
+ ///
+ ///
+ /// The following example demonstrates how to set the selection view of the picker.
+ /// # [XAML](#tab/tabid-7)
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ /// ]]>
+ ///
+ /// # [C#](#tab/tabid-8)
+ ///
+ /// SfPicker picker = new SfPicker
+ /// {
+ /// SelectionView = new PickerSelectionView
+ /// {
+ /// BackgroundColor = Colors.LightBlue
+ /// }
+ /// };
+ ///
+ ///
+ public PickerSelectionView SelectionView
+ {
+ get { return (PickerSelectionView)GetValue(SelectionViewProperty); }
+ set { SetValue(SelectionViewProperty, value); }
+ }
+
+ ///
+ /// Gets or sets the value of footer view. This property can be used to customize the Footer in Picker.
+ ///
+ ///
+ /// The following example demonstrates how to set the footer view of the picker.
+ /// # [XAML](#tab/tabid-9)
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ /// ]]>
+ ///
+ /// # [C#](#tab/tabid-10)
+ ///
+ /// SfPicker picker = new SfPicker
+ /// {
+ /// FooterView = new PickerFooterView
+ /// {
+ /// OkButtonText = "Save",
+ /// CancelButtonText = "Exit"
+ /// }
+ /// };
+ ///
+ ///
+ public PickerFooterView FooterView
+ {
+ get { return (PickerFooterView)GetValue(FooterViewProperty); }
+ set { SetValue(FooterViewProperty, value); }
+ }
+
+ ///
+ /// Gets or sets the value of column divider color in SfPicker. This property can be used to customize the column divider color in Picker.
+ ///
+ /// The default value of is "#CAC4D0".
+ ///
+ /// The following example demonstrates how to set the column divider color of the picker.
+ /// # [XAML](#tab/tabid-11)
+ ///
+ ///
+ /// ]]>
+ ///
+ /// # [C#](#tab/tabid-12)
+ ///
+ /// SfPicker picker = new SfPicker
+ /// {
+ /// ColumnDividerColor = Colors.Gray
+ /// };
+ ///
+ ///
+ public Color ColumnDividerColor
+ {
+ get { return (Color)GetValue(ColumnDividerColorProperty); }
+ set { SetValue(ColumnDividerColorProperty, value); }
+ }
+
+ ///
+ /// Gets or sets a value indicating whether the picker is open or not.
+ ///
+ /// The default value of is "False".
+ ///
+ /// It will be applicable to set the or .
+ ///
+ ///
+ /// The following example demonstrates how to set the IsOpen property of the picker.
+ /// # [XAML](#tab/tabid-13)
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ /// ]]>
+ ///
+ /// # [C#](#tab/tabid-14)
+ ///
+ ///
+ ///
+ ///
+ public bool IsOpen
+ {
+ get { return (bool)GetValue(IsOpenProperty); }
+ set { SetValue(IsOpenProperty, value); }
+ }
+
+ ///
+ /// Gets or sets the mode of the picker.
+ ///
+ /// The default value of is .
+ ///
+ /// The and only visible to set the is "True".
+ ///
+ ///
+ /// The following code demonstrates, how to use the Mode property in the picker.
+ /// # [XAML](#tab/tabid-15)
+ ///
+ ///
+ /// ]]>
+ /// # [C#](#tab/tabid-16)
+ ///
+ ///
+ public PickerMode Mode
+ {
+ get { return (PickerMode)GetValue(ModeProperty); }
+ set { SetValue(ModeProperty, value); }
+ }
+
+ ///
+ /// Gets or sets the text display mode of the picker.
+ ///
+ /// The default value of is .
+ ///
+ /// The following example demonstrates how to set the TextDisplayMode property of the picker.
+ /// # [XAML](#tab/tabid-17)
+ ///
+ ///
+ /// ]]>
+ ///
+ /// # [C#](#tab/tabid-18)
+ ///
+ /// SfPicker picker = new SfPicker
+ /// {
+ /// TextDisplayMode = PickerTextDisplayMode.Fade
+ /// };
+ ///
+ ///
+ public PickerTextDisplayMode TextDisplayMode
+ {
+ get { return (PickerTextDisplayMode)GetValue(TextDisplayModeProperty); }
+ set { SetValue(TextDisplayModeProperty, value); }
+ }
+
+ ///
+ /// Gets or sets the relative position of the picker popup.
+ ///
+ /// The default value of is .
+ ///
+ /// It will be applicable to set is .
+ ///
+ ///
+ /// The following code demonstrates, how to use the RelativePosition property in the picker.
+ /// # [XAML](#tab/tabid-19)
+ ///
+ ///
+ /// ]]>
+ /// # [C#](#tab/tabid-20)
+ ///
+ ///
+ public PickerRelativePosition RelativePosition
+ {
+ get { return (PickerRelativePosition)GetValue(RelativePositionProperty); }
+ set { SetValue(RelativePositionProperty, value); }
+ }
+
+ ///
+ /// Gets or sets the view relative to which the picker dialog should be displayed based on the RelativePosition.
+ ///
+ ///
+ ///
+ /// It is only applicable for RelativeDialog mode. If no relative view is given, the picker base will be set as the relative view.
+ ///
+ ///
+ /// The following code demonstrates, how to use the RelativeView property in the picker.
+ /// # [XAML](#tab/tabid-21)
+ ///
+ ///
+ ///
+ ///
+ ///
+ /// ]]>
+ /// # [C#](#tab/tabid-22)
+ ///
+ ///
+ public View RelativeView
+ {
+ get { return (View)GetValue(RelativeViewProperty); }
+ set { SetValue(RelativeViewProperty, value); }
+ }
+
+ ///
+ /// Gets or sets the picker ok button clicked command.
+ ///
+ /// The default value of is null.
+ ///
+ /// The following code demonstrates, how to use the AcceptCommand property in the picker.
+ /// # [XAML](#tab/tabid-23)
+ ///
+ ///
+ ///
+ ///
+ ///
+ /// ]]>
+ /// # [C#](#tab/tabid-24)
+ ///
+ ///
+ public ICommand AcceptCommand
+ {
+ get { return (ICommand)GetValue(AcceptCommandProperty); }
+ set { SetValue(AcceptCommandProperty, value); }
+ }
+
+ ///
+ /// Gets or sets the picker cancel button clicked command.
+ ///
+ /// The default value of is null.
+ ///
+ /// The following code demonstrates, how to use the DeclineCommand property in the picker.
+ /// # [XAML](#tab/tabid-25)
+ ///
+ ///
+ ///
+ ///
+ ///
+ /// ]]>
+ /// # [C#](#tab/tabid-26)
+ ///
+ ///
+ public ICommand DeclineCommand
+ {
+ get { return (ICommand)GetValue(DeclineCommandProperty); }
+ set { SetValue(DeclineCommandProperty, value); }
+ }
+
+ #endregion
+
+ #region Internal Properties
+
+ ///
+ /// Gets the value of column header view settings.
+ ///
+ PickerColumnHeaderView IColumnHeaderView.ColumnHeaderView => BaseColumnHeaderView;
+
+ ///
+ /// Gets the value of picker columns.
+ ///
+ ObservableCollection IPicker.Columns => BaseColumns;
+
+ ///
+ /// Gets the value of header view settings.
+ ///
+ PickerHeaderView IHeaderView.HeaderView => BaseHeaderView;
+
+ ///
+ /// Gets the item template for the picker item.
+ ///
+ DataTemplate IPickerView.ItemTemplate => BaseItemTemplate;
+
+ ///
+ /// Gets the disabled text style for the picker item.
+ ///
+ PickerTextStyle IPickerView.DisabledTextStyle => DisabledTextStyle;
+
+ ///
+ /// Gets a value indicating whether the parent value have valid value because dynamic scrolling on dialog opening does not scroll because the picker stack layout does not have a parent value.
+ ///
+ bool IPickerView.IsValidParent => _pickerStackLayout != null && _pickerStackLayout.Parent != null && Parent != null;
+
+ ///
+ /// Gets or sets the value of header view. This property can be used to customize the of header in Picker.
+ ///
+ internal PickerHeaderView BaseHeaderView
+ {
+ get { return (PickerHeaderView)GetValue(BaseHeaderViewProperty); }
+ set { SetValue(BaseHeaderViewProperty, value); }
+ }
+
+ ///
+ /// Gets or sets the picker item template in Picker.
+ ///
+ internal DataTemplate BaseItemTemplate
+ {
+ get { return (DataTemplate)GetValue(BaseItemTemplateProperty); }
+ set { SetValue(BaseItemTemplateProperty, value); }
+ }
+
+ ///
+ /// Gets or sets the value of column header view. This property can be used to customize the of header column in Picker.
+ ///
+ internal PickerColumnHeaderView BaseColumnHeaderView
+ {
+ get { return (PickerColumnHeaderView)GetValue(BaseColumnHeaderViewProperty); }
+ set { SetValue(BaseColumnHeaderViewProperty, value); }
+ }
+
+ ///
+ /// Gets or sets the value of picker columns. This property can be used to customize the of column in Picker.
+ ///
+ internal ObservableCollection BaseColumns
+ {
+ get { return (ObservableCollection)GetValue(BaseColumnsProperty); }
+ set { SetValue(BaseColumnsProperty, value); }
+ }
+
+ ///
+ /// Gets or sets the picker disabled item text style in Picker.
+ ///
+ internal PickerTextStyle DisabledTextStyle
+ {
+ get { return (PickerTextStyle)GetValue(DisabledTextStyleProperty); }
+ set { SetValue(DisabledTextStyleProperty, value); }
+ }
+
+ #endregion
+
+ #region Private Methods
+
+ ///
+ /// Method to update the selected text style.
+ ///
+ /// Returns the picker text style.
+ PickerTextStyle UpdateSelectedTextStyle()
+ {
+ PickerTextStyle selectedTextStyle;
+ if (TextDisplayMode == PickerTextDisplayMode.Default)
+ {
+ selectedTextStyle = new PickerTextStyle
+ {
+ FontSize = 14,
+ TextColor = GetDefaultModeSelectedTextColor(),
+ };
+
+ return selectedTextStyle;
+ }
+ else
+ {
+ selectedTextStyle = new PickerTextStyle
+ {
+ FontSize = 16,
+ FontAttributes = FontAttributes.Bold,
+ TextColor = GetOpacityModeSelectedTextColor(),
+ };
+
+ return selectedTextStyle;
+ }
+ }
+
+ ///
+ /// Method to get the default mode selected text color.
+ ///
+ /// The selected color.
+ Color GetDefaultModeSelectedTextColor()
+ {
+ Color selectedColor;
+ switch (Children)
+ {
+ case SfPicker picker:
+ selectedColor = picker.SelectedTextColor;
+ break;
+ case SfTimePicker timePicker:
+ selectedColor = timePicker.SelectedTextColor;
+ break;
+ case SfDatePicker datePicker:
+ selectedColor = datePicker.SelectedTextColor;
+ break;
+ case SfDateTimePicker dateTimePicker:
+ selectedColor = dateTimePicker.SelectedTextColor;
+ break;
+ default:
+ selectedColor = Colors.White;
+ break;
+ }
+
+ return selectedColor;
+ }
+
+ ///
+ /// Method to get the special mode selected text color.
+ ///
+ /// The selected color.
+ Color GetOpacityModeSelectedTextColor()
+ {
+ Color selectedColor;
+ switch (Children)
+ {
+ case SfPicker picker:
+ selectedColor = picker.SelectionTextColor;
+ break;
+ case SfTimePicker timePicker:
+ selectedColor = timePicker.SelectionTextColor;
+ break;
+ case SfDatePicker datePicker:
+ selectedColor = datePicker.SelectionTextColor;
+ break;
+ case SfDateTimePicker dateTimePicker:
+ selectedColor = dateTimePicker.SelectionTextColor;
+ break;
+ default:
+ selectedColor = Colors.White;
+ break;
+ }
+
+ return selectedColor;
+ }
+
+ ///
+ /// Need to update the parent for the new value.
+ ///
+ /// The old value.
+ /// The new value.
+ void SetParent(Element? oldValue, Element? newValue)
+ {
+ if (oldValue != null)
+ {
+ oldValue.Parent = null;
+ }
+
+ if (newValue != null)
+ {
+ newValue.Parent = this;
+ }
+ }
+
+ #endregion
+
+ #region Property Changed Methods
+
+ ///
+ /// Method invokes on picker header view changed.
+ ///
+ /// The picker object.
+ /// Old value of the property.
+ /// New value of the property.
+ static void OnHeaderViewChanged(BindableObject bindable, object oldValue, object newValue)
+ {
+ PickerBase? picker = bindable as PickerBase;
+ if (picker == null)
+ {
+ return;
+ }
+
+ PickerHeaderView? oldStyle = oldValue as PickerHeaderView;
+ if (oldStyle != null)
+ {
+ oldStyle.PickerPropertyChanged -= picker.OnHeaderSettingsPropertyChanged;
+ if (oldStyle.TextStyle != null)
+ {
+ oldStyle.TextStyle.PropertyChanged -= picker.OnHeaderTextStylePropertyChanged;
+ oldStyle.TextStyle.BindingContext = null;
+ }
+
+ if (oldStyle.SelectionTextStyle != null)
+ {
+ oldStyle.SelectionTextStyle.PropertyChanged -= picker.OnHeaderSelectionTextStylePropertyChanged;
+ oldStyle.SelectionTextStyle.BindingContext = null;
+ }
+
+ oldStyle.BindingContext = null;
+ }
+
+ PickerHeaderView? newStyle = newValue as PickerHeaderView;
+ if (newStyle != null)
+ {
+ SetInheritedBindingContext(newStyle, picker.BindingContext);
+ newStyle.PickerPropertyChanged += picker.OnHeaderSettingsPropertyChanged;
+ if (newStyle.TextStyle != null)
+ {
+ SetInheritedBindingContext(newStyle.TextStyle, picker.BindingContext);
+ newStyle.TextStyle.PropertyChanged += picker.OnHeaderTextStylePropertyChanged;
+ }
+
+ if (newStyle.SelectionTextStyle != null)
+ {
+ SetInheritedBindingContext(newStyle.SelectionTextStyle, picker.BindingContext);
+ newStyle.SelectionTextStyle.PropertyChanged += picker.OnHeaderSelectionTextStylePropertyChanged;
+ }
+ }
+
+ picker.AddOrRemoveHeaderLayout();
+ picker.UpdatePopupSize();
+ if (picker._headerLayout == null)
+ {
+ return;
+ }
+
+ if (newStyle != null)
+ {
+ picker._headerLayout.InvalidateHeaderView();
+ picker._headerLayout.UpdateHeaderDateText();
+ picker._headerLayout.UpdateHeaderTimeText();
+ picker._headerLayout.UpdateIconButtonTextStyle();
+ }
+
+ if (picker._availableSize == Size.Zero)
+ {
+ return;
+ }
+
+ picker.InvalidatePickerView();
+ }
+
+ ///
+ /// Method invokes on picker footer view changed.
+ ///
+ /// The picker object.
+ /// Old value of the property.
+ /// New value of the property.
+ static void OnFooterViewChanged(BindableObject bindable, object oldValue, object newValue)
+ {
+ PickerBase? picker = bindable as PickerBase;
+ if (picker == null)
+ {
+ return;
+ }
+
+ PickerFooterView? oldStyle = oldValue as PickerFooterView;
+ if (oldStyle != null)
+ {
+ oldStyle.PickerPropertyChanged -= picker.OnFooterSettingsPropertyChanged;
+ if (oldStyle.TextStyle != null)
+ {
+ oldStyle.TextStyle.PropertyChanged -= picker.OnFooterTextStylePropertyChanged;
+ oldStyle.TextStyle.BindingContext = null;
+ }
+
+ oldStyle.Parent = null;
+ oldStyle.BindingContext = null;
+ }
+
+ PickerFooterView? newStyle = newValue as PickerFooterView;
+ if (newStyle != null)
+ {
+ newStyle.Parent = picker;
+ SetInheritedBindingContext(newStyle, picker.BindingContext);
+ newStyle.PickerPropertyChanged += picker.OnFooterSettingsPropertyChanged;
+ if (newStyle.TextStyle != null)
+ {
+ SetInheritedBindingContext(newStyle.TextStyle, picker.BindingContext);
+ newStyle.TextStyle.PropertyChanged += picker.OnFooterTextStylePropertyChanged;
+ }
+ }
+
+ picker.AddOrRemoveFooterLayout();
+ picker.UpdatePopupSize();
+ if (picker._footerLayout == null)
+ {
+ return;
+ }
+
+ picker._footerLayout.Background = picker.FooterView.Background;
+ picker._footerLayout.UpdateFooterStyle();
+ if (picker._availableSize == Size.Zero)
+ {
+ return;
+ }
+
+ picker.InvalidatePickerView();
+ }
+
+ ///
+ /// Method invokes on the picker item height changed.
+ ///
+ /// The picker settings object.
+ /// Property old value.
+ /// Property new value.
+ static void OnItemHeightChanged(BindableObject bindable, object oldValue, object newValue)
+ {
+ PickerBase? picker = bindable as PickerBase;
+ if (picker == null)
+ {
+ return;
+ }
+
+ picker._pickerContainer?.UpdateItemHeight();
+ }
+
+ ///
+ /// Method invokes on the picker item template changed.
+ ///
+ /// The picker settings object.
+ /// Property old value.
+ /// Property new value.
+ static void OnItemTemplateChanged(BindableObject bindable, object oldValue, object newValue)
+ {
+ PickerBase? picker = bindable as PickerBase;
+ if (picker == null)
+ {
+ return;
+ }
+
+ picker._pickerContainer?.UpdateItemTemplate();
+ }
+
+ ///
+ /// Method invokes on picker selected text style property changed.
+ ///
+ /// The picker settings object.
+ /// Property old value.
+ /// Property new value.
+ static void OnSelectedTextStyleChanged(BindableObject bindable, object oldValue, object newValue)
+ {
+ PickerBase? picker = bindable as PickerBase;
+ if (picker == null || picker._isInternalPropertyChange)
+ {
+ return;
+ }
+
+ picker._isExternalStyle = true;
+ PickerTextStyle? oldStyle = oldValue as PickerTextStyle;
+ if (oldStyle != null)
+ {
+ oldStyle.PropertyChanged -= picker.OnSelectedTextStylePropertyChanged;
+ oldStyle.BindingContext = null;
+ oldStyle.Parent = null;
+ }
+
+ PickerTextStyle? newStyle = newValue as PickerTextStyle;
+ if (newStyle != null)
+ {
+ newStyle.Parent = picker;
+ SetInheritedBindingContext(newStyle, picker.BindingContext);
+ newStyle.PropertyChanged += picker.OnSelectedTextStylePropertyChanged;
+ }
+
+ //// No need to update the picker scroll view when the picker size is not defined.
+ if (picker._availableSize == Size.Zero || picker._pickerContainer == null)
+ {
+ return;
+ }
+
+ picker._pickerContainer.UpdateScrollViewDraw();
+ }
+
+ ///
+ /// Method invokes on picker unselected text style property changed.
+ ///
+ /// The picker settings object.
+ /// Property old value.
+ /// Property new value.
+ static void OnTextStyleChanged(BindableObject bindable, object oldValue, object newValue)
+ {
+ PickerBase? picker = bindable as PickerBase;
+ if (picker == null)
+ {
+ return;
+ }
+
+ PickerTextStyle? oldStyle = oldValue as PickerTextStyle;
+ if (oldStyle != null)
+ {
+ oldStyle.PropertyChanged -= picker.OnUnSelectedTextStylePropertyChanged;
+ oldStyle.BindingContext = null;
+ oldStyle.Parent = null;
+ }
+
+ PickerTextStyle? newStyle = newValue as PickerTextStyle;
+ if (newStyle != null)
+ {
+ newStyle.Parent = picker;
+ SetInheritedBindingContext(newStyle, picker.BindingContext);
+ newStyle.PropertyChanged += picker.OnUnSelectedTextStylePropertyChanged;
+ }
+
+ //// No need to update the picker scroll view when the picker size is not defined.
+ if (picker._availableSize == Size.Zero || picker._pickerContainer == null)
+ {
+ return;
+ }
+
+ picker._pickerContainer.UpdateScrollViewDraw();
+ }
+
+ ///
+ /// Method invokes on the picker selection view changed.
+ ///
+ /// The picker settings object.
+ /// Property old value.
+ /// Property new value.
+ static void OnSelectionViewChanged(BindableObject bindable, object oldValue, object newValue)
+ {
+ PickerBase? picker = bindable as PickerBase;
+ if (picker == null)
+ {
+ return;
+ }
+
+ if (oldValue is PickerSelectionView oldStyle)
+ {
+ oldStyle.PropertyChanged -= picker.OnSelectionViewPropertyChanged;
+ oldStyle.Parent = null;
+ }
+
+ if (newValue is PickerSelectionView newStyle)
+ {
+ newStyle.Parent = picker;
+ newStyle.PropertyChanged += picker.OnSelectionViewPropertyChanged;
+ }
+
+ //// No need to update the picker selection view when the picker size is not defined.
+ if (picker._availableSize == Size.Zero)
+ {
+ return;
+ }
+
+ picker.UpdatePickerSelectionView();
+ }
+
+ ///
+ /// Method invokes on the picker columns changed.
+ ///
+ /// The picker settings object.
+ /// Property old value.
+ /// Property new value.
+ [UnconditionalSuppressMessage("Trimming", "IL2026:Members annotated with 'RequiresUnreferencedCodeAttribute' require dynamic access otherwise can break functionality when trimming application code", Justification = "")]
+ static void OnColumnsChanged(BindableObject bindable, object oldValue, object newValue)
+ {
+ PickerBase? picker = bindable as PickerBase;
+ if (picker == null)
+ {
+ return;
+ }
+
+ ObservableCollection? oldColumns = oldValue as ObservableCollection;
+ if (oldColumns != null)
+ {
+ oldColumns.CollectionChanged -= picker.OnColumnsCollectionChanged;
+ foreach (PickerColumn column in oldColumns)
+ {
+ column.BindingContext = null;
+ column.PickerPropertyChanged -= picker.OnColumnPropertyChanged;
+ column.PickerColumnCollectionChanged -= picker.OnItemsSourceCollectionChanged;
+ column.UnWireCollectionChanged(column.ItemsSource);
+ column.Parent = null;
+ }
+ }
+
+ ObservableCollection? newColumns = newValue as ObservableCollection;
+ if (newColumns != null)
+ {
+ newColumns.CollectionChanged += picker.OnColumnsCollectionChanged;
+ for (int index = 0; index < newColumns.Count; index++)
+ {
+ PickerColumn column = newColumns[index];
+ column._columnIndex = index;
+ SetInheritedBindingContext(column, picker.BindingContext);
+ if (column.SelectedIndex != 0 && column.SelectedItem != null)
+ {
+ picker.SelectionIndexChanged?.Invoke(picker, new PickerSelectionChangedEventArgs { NewValue = column.SelectedIndex, OldValue = 0, ColumnIndex = column._columnIndex });
+ }
+
+ column.Parent = picker;
+ column.PickerPropertyChanged += picker.OnColumnPropertyChanged;
+ column.WireCollectionChanged();
+ column.PickerColumnCollectionChanged += picker.OnItemsSourceCollectionChanged;
+ }
+
+ picker.UpdatePopupSize();
+ picker._pickerContainer?.ResetPickerColumns(newColumns);
+ }
+ }
+
+ ///
+ /// Method invokes on the picker column divider color changed.
+ ///
+ /// The picker settings object.
+ /// Property old value.
+ /// Property new value.
+ static void OnColumnDividerColorChanged(BindableObject bindable, object oldValue, object newValue)
+ {
+ PickerBase? picker = bindable as PickerBase;
+ if (picker == null)
+ {
+ return;
+ }
+
+ picker.SetParent(oldValue as Element, newValue as Element);
+ picker._pickerContainer?.UpdateColumnDividerColor();
+ }
+
+ ///
+ /// Method invokes on the picker column header view changed.
+ ///
+ /// The picker settings object.
+ /// Property old value.
+ /// Property new value.
+ static void OnColumnHeaderViewChanged(BindableObject bindable, object oldValue, object newValue)
+ {
+ PickerBase? picker = bindable as PickerBase;
+ if (picker == null)
+ {
+ return;
+ }
+
+ PickerColumnHeaderView? oldStyle = oldValue as PickerColumnHeaderView;
+ if (oldStyle != null)
+ {
+ oldStyle.PickerPropertyChanged -= picker.OnColumnHeaderViewPropertyChanged;
+ if (oldStyle.TextStyle != null)
+ {
+ oldStyle.TextStyle.PropertyChanged -= picker.OnColumnHeaderTextStylePropertyChanged;
+ oldStyle.TextStyle.BindingContext = null;
+ oldStyle.Parent = null;
+ }
+
+ oldStyle.BindingContext = null;
+ }
+
+ PickerColumnHeaderView? newStyle = newValue as PickerColumnHeaderView;
+ if (newStyle != null)
+ {
+ SetInheritedBindingContext(newStyle, picker.BindingContext);
+ newStyle.PickerPropertyChanged += picker.OnColumnHeaderViewPropertyChanged;
+ if (newStyle.TextStyle != null)
+ {
+ newStyle.Parent = picker;
+ SetInheritedBindingContext(newStyle.TextStyle, picker.BindingContext);
+ newStyle.TextStyle.PropertyChanged += picker.OnColumnHeaderTextStylePropertyChanged;
+ }
+ }
+
+ picker._pickerContainer?.UpdateColumnHeaderHeight();
+ picker._pickerContainer?.UpdateColumnHeaderDraw();
+ picker._pickerContainer?.UpdateColumnHeaderDividerColor();
+#if ANDROID || IOS
+ //// While adding the picker item height, the picker selected item not updated properly in android and ios. So, we have updated that.
+ picker._pickerContainer?.UpdateItemHeight();
+#endif
+ }
+
+ ///
+ /// Method invokes on IsOpen property changed.
+ ///
+ /// The picker settings object.
+ /// Property old value.
+ /// Property new value.
+ static void OnIsOpenChanged(BindableObject bindable, object oldValue, object newValue)
+ {
+ PickerBase? picker = bindable as PickerBase;
+ if (picker == null)
+ {
+ return;
+ }
+
+ //// Here we have checked the picker visibility because the picker is not visible, then the picker popup will not be opened.
+ //// Here we just stored the previous value of the picker visibility and then return.
+ //// Storing the previous value is used to define the state of the picker popup while the picker visibility is changing.
+ if (!picker.IsVisible)
+ {
+ if (!(bool)newValue)
+ {
+ picker._isPickerPreviouslyOpened = !(bool)newValue;
+ }
+ else
+ {
+ picker._isPickerPreviouslyOpened = (bool)newValue;
+ }
+
+ return;
+ }
+
+ bool isOpen = false;
+ if (newValue is bool value)
+ {
+ isOpen = value;
+ }
+
+ if (isOpen && (picker.Mode == PickerMode.Dialog || picker.Mode == PickerMode.RelativeDialog))
+ {
+ if (picker.Children.Count != 0 && picker._pickerStackLayout != null && picker._pickerStackLayout.Parent == picker)
+ {
+ picker.Remove(picker._pickerStackLayout);
+ }
+
+ picker.AddPickerToPopup();
+ picker.ShowPopup();
+ }
+ else
+ {
+ picker.ClosePickerPopup();
+ if (picker._pickerStackLayout != null && picker._pickerStackLayout.Parent != null && picker._pickerStackLayout.Parent != picker && picker._pickerStackLayout.Parent is ICollection view)
+ {
+ view.Remove(picker._pickerStackLayout);
+ }
+ }
+ }
+
+ ///
+ /// Method invokes on the picker mode changed.
+ ///
+ /// The picker settings object.
+ /// Property old value.
+ /// Property new value.
+ static void OnModeChanged(BindableObject bindable, object oldValue, object newValue)
+ {
+ PickerBase? picker = bindable as PickerBase;
+ if (picker == null)
+ {
+ return;
+ }
+
+ PickerMode mode = PickerMode.Default;
+ if (newValue is PickerMode value)
+ {
+ mode = value;
+ }
+
+ if (mode == PickerMode.Dialog || mode == PickerMode.RelativeDialog)
+ {
+ if (picker.Children.Count != 0 && picker._pickerStackLayout != null && picker._pickerStackLayout.Parent == picker)
+ {
+ picker.Remove(picker._pickerStackLayout);
+ }
+
+ if (picker.IsOpen)
+ {
+ picker.AddPickerToPopup();
+ picker.ShowPopup();
+ }
+ else
+ {
+ picker.ClosePickerPopup();
+ }
+ }
+ else
+ {
+ picker.ResetPopup();
+ picker.OnPickerLoading();
+ if (picker._pickerStackLayout != null)
+ {
+ if (picker._pickerStackLayout.Parent != null && picker._pickerStackLayout.Parent is ICollection view)
+ {
+ view.Remove(picker._pickerStackLayout);
+ }
+
+ picker.Add(picker._pickerStackLayout);
+#if ANDROID
+ //// While adding the picker to the popup, the picker selected item not updated properly in android. So, we have updated that.
+ picker.Dispatcher.Dispatch(() =>
+ {
+ picker._pickerContainer?.UpdateItemHeight();
+ });
+#endif
+ }
+ }
+
+ picker.InvalidateMeasure();
+ }
+
+ ///
+ /// Method invokes on the picker text display mode changed.
+ ///
+ /// The picker object.
+ /// Property old value.
+ /// Property new value.
+ static void OnTextDisplayModeChanged(BindableObject bindable, object oldValue, object newValue)
+ {
+ PickerBase? picker = bindable as PickerBase;
+ if (picker == null)
+ {
+ return;
+ }
+
+ picker._pickerContainer?.UpdateScrollViewDraw();
+ picker._pickerContainer?.UpdatePickerSelectionView();
+ }
+
+ ///
+ /// Method invokes on the picker popup relative position changed.
+ ///
+ /// The picker settings object.
+ /// Property old value.
+ /// Property new value.
+ static void OnRelativePositionChanged(BindableObject bindable, object oldValue, object newValue)
+ {
+ PickerBase? picker = bindable as PickerBase;
+ if (picker == null)
+ {
+ return;
+ }
+
+ if (picker.IsOpen && picker.Mode == PickerMode.RelativeDialog)
+ {
+ picker.ShowPopup();
+ }
+ }
+
+ ///
+ /// Method invokes on the picker popup relative view changed.
+ ///
+ /// The picker setting object.
+ /// Property old value.
+ /// Property new value.
+ static void OnRelativeViewChanged(BindableObject bindable, object oldvalue, object newvalue)
+ {
+ PickerBase? picker = bindable as PickerBase;
+ if (picker == null)
+ {
+ return;
+ }
+
+ if (picker.IsOpen && picker.Mode == PickerMode.RelativeDialog && picker.RelativeView != null)
+ {
+ picker.ShowPopup();
+ }
+ }
+
+ ///
+ /// Method to invokes while the item source collection changed.
+ ///
+ /// The picker column item source object.
+ /// The collection changed event arguments.
+ [UnconditionalSuppressMessage("Trimming", "IL2026:Members annotated with 'RequiresUnreferencedCodeAttribute' require dynamic access otherwise can break functionality when trimming application code", Justification = "")]
+ void OnItemsSourceCollectionChanged(object? sender, NotifyCollectionChangedEventArgs e)
+ {
+ PickerColumn? pickerColumn = sender as PickerColumn;
+ if (pickerColumn == null || _pickerContainer == null)
+ {
+ return;
+ }
+
+ //// While the items source collection changed, the picker popup size also need to be updated. Because based on the item source collection, the picker popup size will be changed.
+ UpdatePopupSize();
+ _pickerContainer.UpdateItemsSource(pickerColumn._columnIndex);
+ }
+
+ ///
+ /// Method to invokes while the column property changed.
+ ///
+ /// The picker column object.
+ /// The collection changed event arguments.
+ [UnconditionalSuppressMessage("Trimming", "IL2026:Members annotated with 'RequiresUnreferencedCodeAttribute' require dynamic access otherwise can break functionality when trimming application code", Justification = "")]
+ void OnColumnPropertyChanged(object? sender, PickerPropertyChangedEventArgs e)
+ {
+ PickerColumn? column = sender as PickerColumn;
+ if (column == null)
+ {
+ return;
+ }
+
+ if (e.PropertyName == nameof(PickerColumn.SelectedIndex))
+ {
+ // If the selected index is updated dynamically beyond the items count, then the old and new index will be actual index.
+ //// Assume items count = 10.
+ //// Here old index = 9.
+ int oldIndex = -1;
+ int itemsCount = PickerHelper.GetItemsCount(column.ItemsSource);
+ if (e.OldValue != null)
+ {
+ oldIndex = PickerHelper.GetValidSelectedIndex((int)e.OldValue, itemsCount);
+ }
+
+ //// The old index changed from 9 to 10 then the actual new index value is 9.
+ int newIndex = PickerHelper.GetValidSelectedIndex(column.SelectedIndex, itemsCount);
+ SelectionIndexChanged?.Invoke(this, new PickerSelectionChangedEventArgs { NewValue = column.SelectedIndex, OldValue = oldIndex, ColumnIndex = column._columnIndex });
+ column._isSelectedItemChanged = false;
+ column.SelectedItem = PickerHelper.GetSelectedItemDefaultValue(column);
+ if (newIndex == -1)
+ {
+ //// If selected index is -1, then change all column selected item value as null.
+ foreach (var columns in BaseColumns)
+ {
+ columns.SelectedItem = null;
+ columns.SelectedIndex = -1;
+ }
+ }
+ else if (newIndex != -1)
+ {
+ //// If selected index is not equal to -1, then check the remaining column selected index value is -1.
+ //// If any column value as -1, then change the seelcted index value to 0.
+ for (int index = 0; index < BaseColumns.Count; index++)
+ {
+ if (column._columnIndex == index)
+ {
+ continue;
+ }
+ else if (BaseColumns[index].SelectedIndex <= -1)
+ {
+ BaseColumns[index].SelectedIndex = 0;
+ }
+ }
+ }
+
+ //// From the above example, the old index and new index are same. So, no need to update the selection changed event and UI.
+ if (oldIndex == newIndex)
+ {
+ return;
+ }
+
+ UpdateSelectedIndexValue(column._columnIndex);
+ _pickerContainer?.UpdateItemHeight();
+ }
+ else if (e.PropertyName == nameof(PickerColumn.ItemsSource))
+ {
+ if (e.OldValue != null)
+ {
+ column.UnWireCollectionChanged(e.OldValue);
+ column.PickerColumnCollectionChanged -= OnItemsSourceCollectionChanged;
+ }
+
+ if (column.ItemsSource != null)
+ {
+ column.WireCollectionChanged();
+ column.PickerColumnCollectionChanged += OnItemsSourceCollectionChanged;
+ }
+
+ UpdatePopupSize();
+ _pickerContainer?.UpdateItemsSource(column._columnIndex);
+ }
+ else if (e.PropertyName == nameof(PickerColumn.Width))
+ {
+ UpdatePopupSize();
+ _pickerContainer?.UpdatePickerColumnWidth();
+ }
+ else if (e.PropertyName == nameof(PickerColumn.HeaderText))
+ {
+ _pickerContainer?.UpdateHeaderText(column._columnIndex);
+ }
+ else if (e.PropertyName == nameof(PickerColumn.SelectedItem))
+ {
+ if (column.SelectedItem == null)
+ {
+ column.Parent = this;
+ if (column.Parent is SfPicker)
+ {
+ column.SelectedIndex = -1;
+ column._isSelectedItemChanged = true;
+ return;
+ }
+ else
+ {
+ _pickerContainer?.UpdateScrollViewDraw();
+ _pickerContainer?.InvalidateDrawable();
+ return;
+ }
+ }
+
+ int itemsCount = PickerHelper.GetItemsCount(column.ItemsSource);
+ int valueCount = PickerHelper.GetSelectedItemIndex(column);
+ int newIndex = PickerHelper.GetValidSelectedIndex(valueCount, itemsCount);
+ if (column.SelectedIndex == newIndex)
+ {
+ column._isSelectedItemChanged = true;
+ return;
+ }
+
+ column.SelectedIndex = newIndex;
+ column._isSelectedItemChanged = true;
+ }
+ }
+
+ ///
+ /// Method to invokes while the columns collection changed.
+ ///
+ /// The picker column object.
+ /// The collection changed event arguments.
+ [UnconditionalSuppressMessage("Trimming", "IL2026:Members annotated with 'RequiresUnreferencedCodeAttribute' require dynamic access otherwise can break functionality when trimming application code", Justification = "")]
+ void OnColumnsCollectionChanged(object? sender, NotifyCollectionChangedEventArgs e)
+ {
+ if (e.Action == NotifyCollectionChangedAction.Add)
+ {
+ if (e.NewStartingIndex == -1)
+ {
+ return;
+ }
+
+ if (e.NewStartingIndex == BaseColumns.Count - 1)
+ {
+ PickerColumn column = BaseColumns[e.NewStartingIndex];
+ column._columnIndex = e.NewStartingIndex;
+ SetInheritedBindingContext(column, BindingContext);
+ if (column.SelectedIndex != 0)
+ {
+ SelectionIndexChanged?.Invoke(this, new PickerSelectionChangedEventArgs { NewValue = column.SelectedIndex, OldValue = 0, ColumnIndex = column._columnIndex });
+ }
+
+ column.PickerPropertyChanged += OnColumnPropertyChanged;
+ column.WireCollectionChanged();
+ column.PickerColumnCollectionChanged += OnItemsSourceCollectionChanged;
+ }
+ else
+ {
+ PickerColumn column = BaseColumns[e.NewStartingIndex];
+ column._columnIndex = e.NewStartingIndex;
+ SetInheritedBindingContext(column, BindingContext);
+ if (column.SelectedIndex != 0)
+ {
+ SelectionIndexChanged?.Invoke(this, new PickerSelectionChangedEventArgs { NewValue = column.SelectedIndex, OldValue = 0, ColumnIndex = column._columnIndex });
+ }
+
+ column.PickerPropertyChanged += OnColumnPropertyChanged;
+ column.WireCollectionChanged();
+ column.PickerColumnCollectionChanged += OnItemsSourceCollectionChanged;
+ for (int index = e.NewStartingIndex + 1; index < BaseColumns.Count; index++)
+ {
+ PickerColumn pickerColumn = BaseColumns[index];
+ pickerColumn._columnIndex = index;
+ }
+ }
+ }
+ else if (e.Action == NotifyCollectionChangedAction.Remove)
+ {
+ if (e.OldStartingIndex == -1)
+ {
+ return;
+ }
+
+ ObservableCollection? oldColumns = e.OldItems as ObservableCollection;
+ if (oldColumns != null)
+ {
+ for (int index = 0; index < oldColumns.Count; index++)
+ {
+ PickerColumn column = oldColumns[index];
+ column.PickerPropertyChanged -= OnColumnPropertyChanged;
+ column.UnWireCollectionChanged(column.ItemsSource);
+ column.PickerColumnCollectionChanged -= OnItemsSourceCollectionChanged;
+ column.BindingContext = null;
+ }
+ }
+
+ for (int index = e.OldStartingIndex; index < BaseColumns.Count; index++)
+ {
+ PickerColumn pickerColumn = BaseColumns[index];
+ pickerColumn._columnIndex = index;
+ }
+ }
+ else if (e.Action == NotifyCollectionChangedAction.Replace)
+ {
+ if (e.NewStartingIndex == -1)
+ {
+ return;
+ }
+
+ if (e.OldItems != null)
+ {
+ foreach (PickerColumn pickerColumn in e.OldItems)
+ {
+ pickerColumn.PickerPropertyChanged -= OnColumnPropertyChanged;
+ pickerColumn.UnWireCollectionChanged(pickerColumn.ItemsSource);
+ pickerColumn.PickerColumnCollectionChanged -= OnItemsSourceCollectionChanged;
+ pickerColumn.BindingContext = null;
+ }
+ }
+
+ PickerColumn column = BaseColumns[e.NewStartingIndex];
+ column._columnIndex = e.NewStartingIndex;
+ SetInheritedBindingContext(column, BindingContext);
+ if (column.SelectedIndex != 0)
+ {
+ SelectionIndexChanged?.Invoke(this, new PickerSelectionChangedEventArgs { NewValue = column.SelectedIndex, OldValue = 0, ColumnIndex = column._columnIndex });
+ }
+
+ column.PickerPropertyChanged += OnColumnPropertyChanged;
+ column.WireCollectionChanged();
+ column.PickerColumnCollectionChanged += OnItemsSourceCollectionChanged;
+ }
+ else if (e.Action == NotifyCollectionChangedAction.Move)
+ {
+ if (e.NewStartingIndex == -1)
+ {
+ return;
+ }
+
+ for (int index = 0; index < BaseColumns.Count; index++)
+ {
+ PickerColumn pickerColumn = BaseColumns[index];
+ pickerColumn._columnIndex = index;
+ }
+ }
+ else if (e.Action == NotifyCollectionChangedAction.Reset)
+ {
+ if (e.OldItems != null)
+ {
+ foreach (PickerColumn pickerColumn in e.OldItems)
+ {
+ pickerColumn.PickerPropertyChanged -= OnColumnPropertyChanged;
+ pickerColumn.UnWireCollectionChanged(pickerColumn.ItemsSource);
+ pickerColumn.PickerColumnCollectionChanged -= OnItemsSourceCollectionChanged;
+ pickerColumn.BindingContext = null;
+ }
+ }
+
+ for (int index = 0; index < BaseColumns.Count; index++)
+ {
+ PickerColumn pickerColumn = BaseColumns[index];
+ pickerColumn._columnIndex = index;
+ SetInheritedBindingContext(pickerColumn, BindingContext);
+ pickerColumn.PickerPropertyChanged += OnColumnPropertyChanged;
+ pickerColumn.WireCollectionChanged();
+ pickerColumn.PickerColumnCollectionChanged += OnItemsSourceCollectionChanged;
+ }
+ }
+
+ UpdatePopupSize();
+ if (_pickerContainer == null)
+ {
+ return;
+ }
+
+ _pickerContainer.OnColumnsCollectionChanged(e);
+ }
+
+ ///
+ /// Method invokes on selection view settings property changed.
+ ///
+ /// The picker object.
+ /// The property changed argument.
+ void OnSelectionViewPropertyChanged(object? sender, PropertyChangedEventArgs e)
+ {
+ //// No need to update the picker selection view when the picker size is not defined.
+ if (_availableSize == Size.Zero)
+ {
+ return;
+ }
+
+ UpdatePickerSelectionView();
+ }
+
+ ///
+ /// Method invokes on header settings property changed.
+ ///
+ /// The picker object.
+ /// The property changed event arguments.
+ void OnHeaderSettingsPropertyChanged(object? sender, PickerPropertyChangedEventArgs e)
+ {
+ if (e.PropertyName == nameof(PickerHeaderView.Height))
+ {
+ AddOrRemoveHeaderLayout();
+ UpdatePopupSize();
+ if (_availableSize == Size.Zero)
+ {
+ return;
+ }
+
+ InvalidatePickerView();
+#if ANDROID || IOS
+ //// While adding the picker item height, the picker selected item not updated properly in android and ios. So, we have updated that.
+ _pickerContainer?.UpdateItemHeight();
+#endif
+ }
+ else if (e.PropertyName == nameof(PickerHeaderView.TextStyle))
+ {
+ PickerTextStyle? oldStyle = e.OldValue as PickerTextStyle;
+ if (oldStyle != null)
+ {
+ oldStyle.PropertyChanged -= OnHeaderTextStylePropertyChanged;
+ oldStyle.BindingContext = null;
+ }
+
+ if (BaseHeaderView.TextStyle != null)
+ {
+ SetInheritedBindingContext(BaseHeaderView.TextStyle, BindingContext);
+ BaseHeaderView.TextStyle.PropertyChanged += OnHeaderTextStylePropertyChanged;
+ }
+
+ if (_headerLayout == null)
+ {
+ return;
+ }
+
+ _headerLayout.InvalidateHeaderView();
+ _headerLayout.UpdateIconButtonTextStyle();
+ }
+ else if (e.PropertyName == nameof(PickerHeaderView.SelectionTextStyle))
+ {
+ PickerTextStyle? oldStyle = e.OldValue as PickerTextStyle;
+ if (oldStyle != null)
+ {
+ oldStyle.PropertyChanged -= OnHeaderSelectionTextStylePropertyChanged;
+ oldStyle.BindingContext = null;
+ }
+
+ if (BaseHeaderView.TextStyle != null)
+ {
+ SetInheritedBindingContext(BaseHeaderView.SelectionTextStyle, BindingContext);
+ BaseHeaderView.SelectionTextStyle.PropertyChanged += OnHeaderSelectionTextStylePropertyChanged;
+ }
+
+ if (_headerLayout == null)
+ {
+ return;
+ }
+
+ _headerLayout.InvalidateHeaderView();
+ _headerLayout.UpdateIconButtonTextStyle();
+ }
+ else if (e.PropertyName == nameof(PickerHeaderView.Background) || e.PropertyName == nameof(PickerHeaderView.DividerColor) || e.PropertyName == nameof(PickerHeaderView.Text))
+ {
+ if (_headerLayout == null || _availableSize == Size.Zero)
+ {
+ return;
+ }
+
+ _headerLayout.InvalidateHeaderView();
+ }
+ else if (e.PropertyName == nameof(PickerHeaderView.DateText))
+ {
+ if (_headerLayout == null)
+ {
+ return;
+ }
+
+ _headerLayout.UpdateHeaderDateText();
+ }
+ else if (e.PropertyName == nameof(PickerHeaderView.TimeText))
+ {
+ if (_headerLayout == null)
+ {
+ return;
+ }
+
+ _headerLayout.UpdateHeaderTimeText();
+ }
+ }
+
+ ///
+ /// Method invokes on header text style property changed.
+ ///
+ /// The picker object.
+ /// The property changed event arguments.
+ void OnHeaderTextStylePropertyChanged(object? sender, PropertyChangedEventArgs e)
+ {
+ if (_headerLayout == null || _availableSize == Size.Zero)
+ {
+ return;
+ }
+
+ _headerLayout.InvalidateHeaderView();
+ _headerLayout.UpdateIconButtonTextStyle();
+ }
+
+ ///
+ /// Method invokes on header selection text style property changed.
+ ///
+ /// The picker object.
+ /// The property changed event arguments.
+ void OnHeaderSelectionTextStylePropertyChanged(object? sender, PropertyChangedEventArgs e)
+ {
+ if (_headerLayout == null || _availableSize == Size.Zero)
+ {
+ return;
+ }
+
+ _headerLayout.InvalidateHeaderView();
+ _headerLayout.UpdateIconButtonTextStyle();
+ }
+
+ ///
+ /// Method invokes on column header text style property changed.
+ ///
+ /// The picker column object.
+ /// The property changed event arguments.
+ void OnColumnHeaderTextStylePropertyChanged(object? sender, PropertyChangedEventArgs e)
+ {
+ if (_pickerContainer == null || _availableSize == Size.Zero)
+ {
+ return;
+ }
+
+ _pickerContainer.UpdateColumnHeaderDraw();
+ }
+
+ ///
+ /// Method invokes on footer button text style property changed.
+ ///
+ /// The sender.
+ /// The property changed events arguments.
+ void OnFooterTextStylePropertyChanged(object? sender, PropertyChangedEventArgs e)
+ {
+ _footerLayout?.UpdateButtonTextStyle();
+ }
+
+ ///
+ /// Method to update the footer property is changed.
+ ///
+ /// The sender.
+ /// The property changed events args.
+ void OnFooterSettingsPropertyChanged(object? sender, PickerPropertyChangedEventArgs e)
+ {
+ if (e.PropertyName == nameof(PickerFooterView.Background))
+ {
+ if (_footerLayout == null)
+ {
+ return;
+ }
+
+ _footerLayout.Background = FooterView.Background;
+ }
+ else if (e.PropertyName == nameof(PickerFooterView.OkButtonText))
+ {
+ if (_footerLayout == null)
+ {
+ return;
+ }
+
+ _footerLayout.UpdateConfirmButtonText();
+ }
+ else if (e.PropertyName == nameof(PickerFooterView.CancelButtonText))
+ {
+ if (_footerLayout == null)
+ {
+ return;
+ }
+
+ _footerLayout.UpdateCancelButtonText();
+ }
+ else if (e.PropertyName == nameof(PickerFooterView.Height))
+ {
+ AddOrRemoveFooterLayout();
+ UpdatePopupSize();
+ if (_availableSize == Size.Zero)
+ {
+ return;
+ }
+
+ InvalidatePickerView();
+#if ANDROID || IOS
+ //// While adding the picker item height, the picker selected item not updated properly in android and ios. So, we have updated that.
+ _pickerContainer?.UpdateItemHeight();
+#endif
+ }
+ else if (e.PropertyName == nameof(PickerFooterView.DividerColor))
+ {
+ if (_footerLayout == null || _availableSize == Size.Zero)
+ {
+ return;
+ }
+
+ _footerLayout.UpdateSeparatorColor();
+ }
+ else if (e.PropertyName == nameof(PickerFooterView.ShowOkButton))
+ {
+ if (_footerLayout == null)
+ {
+ return;
+ }
+
+ _footerLayout.AddOrRemoveFooterButtons();
+ }
+ else if (e.PropertyName == nameof(PickerFooterView.TextStyle))
+ {
+ PickerTextStyle? oldStyle = e.OldValue as PickerTextStyle;
+ if (oldStyle != null)
+ {
+ oldStyle.PropertyChanged -= OnFooterTextStylePropertyChanged;
+ oldStyle.BindingContext = null;
+ }
+
+ if (FooterView.TextStyle != null)
+ {
+ SetInheritedBindingContext(FooterView.TextStyle, BindingContext);
+ FooterView.TextStyle.PropertyChanged += OnFooterTextStylePropertyChanged;
+ }
+
+ if (_footerLayout == null)
+ {
+ return;
+ }
+
+ _footerLayout.UpdateButtonTextStyle();
+ }
+ }
+
+ ///
+ /// Method to update the selected text style.
+ ///
+ /// The sender.
+ /// The property changed events args.
+ void OnSelectedTextStylePropertyChanged(object? sender, PropertyChangedEventArgs e)
+ {
+ if (_pickerContainer == null || _availableSize == Size.Zero || _isInternalPropertyChange)
+ {
+ return;
+ }
+
+ _isExternalStyle = true;
+ _pickerContainer.UpdateScrollViewDraw();
+ }
+
+ ///
+ /// Method to update the unselected test style.
+ ///
+ /// The sender.
+ /// The property changed events args.
+ void OnUnSelectedTextStylePropertyChanged(object? sender, PropertyChangedEventArgs e)
+ {
+ if (_pickerContainer == null || _availableSize == Size.Zero)
+ {
+ return;
+ }
+
+ _pickerContainer.UpdateScrollViewDraw();
+ }
+
+ ///
+ /// Method to update the column header property is changed.
+ ///
+ /// The sender.
+ /// The property changed events args.
+ void OnColumnHeaderViewPropertyChanged(object? sender, PickerPropertyChangedEventArgs e)
+ {
+ if (e.PropertyName == nameof(PickerColumnHeaderView.Height))
+ {
+ _pickerContainer?.UpdateColumnHeaderHeight();
+#if ANDROID || IOS
+ //// While adding the picker item height, the picker selected item not updated properly in android and ios. So, we have updated that.
+ _pickerContainer?.UpdateItemHeight();
+#endif
+ }
+ else if (e.PropertyName == nameof(PickerColumnHeaderView.TextStyle))
+ {
+ PickerTextStyle? oldStyle = e.OldValue as PickerTextStyle;
+ if (oldStyle != null)
+ {
+ oldStyle.PropertyChanged -= OnColumnHeaderTextStylePropertyChanged;
+ oldStyle.BindingContext = null;
+ }
+
+ if (BaseHeaderView.TextStyle != null)
+ {
+ SetInheritedBindingContext(BaseColumnHeaderView.TextStyle, BindingContext);
+ BaseColumnHeaderView.TextStyle.PropertyChanged += OnColumnHeaderTextStylePropertyChanged;
+ }
+
+ _pickerContainer?.UpdateColumnHeaderDraw();
+ }
+ else if (e.PropertyName == nameof(PickerColumnHeaderView.DividerColor))
+ {
+ _pickerContainer?.UpdateColumnHeaderDividerColor();
+ }
+ else if (e.PropertyName == nameof(PickerColumnHeaderView.Background))
+ {
+ _pickerContainer?.UpdateColumnHeaderDraw();
+ }
+ }
+
+ ///
+ /// Method to update the picker property change.
+ ///
+ /// The sender
+ /// The property changed event args
+ void OnPickerPropertyChanged(object? sender, PropertyChangedEventArgs e)
+ {
+ if (_pickerStackLayout == null)
+ {
+ return;
+ }
+
+ if (e.PropertyName == nameof(BackgroundColor))
+ {
+ _pickerStackLayout.BackgroundColor = BackgroundColor;
+ }
+ else if (e.PropertyName == nameof(Background))
+ {
+ _pickerStackLayout.Background = Background;
+ }
+ else if (e.PropertyName == nameof(TextDisplayMode))
+ {
+ if (_isExternalStyle)
+ {
+ return;
+ }
+
+ _isInternalPropertyChange = true;
+ SelectedTextStyle = UpdateSelectedTextStyle();
+ _isInternalPropertyChange = false;
+ }
+ }
+
+ ///
+ /// Method invokes on picker disabled text style property changed.
+ ///
+ /// The picker settings object.
+ /// Property old value.
+ /// Property new value.
+ static void OnDisabledTextStyleChanged(BindableObject bindable, object oldValue, object newValue)
+ {
+ PickerBase? picker = bindable as PickerBase;
+ if (picker == null)
+ {
+ return;
+ }
+
+ PickerTextStyle? oldStyle = oldValue as PickerTextStyle;
+ if (oldStyle != null)
+ {
+ oldStyle.PropertyChanged -= picker.OnDisabledTextStylePropertyChanged;
+ oldStyle.BindingContext = null;
+ oldStyle.Parent = null;
+ }
+
+ PickerTextStyle? newStyle = newValue as PickerTextStyle;
+ if (newStyle != null)
+ {
+ newStyle.Parent = picker;
+ SetInheritedBindingContext(newStyle, picker.BindingContext);
+ newStyle.PropertyChanged += picker.OnDisabledTextStylePropertyChanged;
+ }
+
+ //// No need to update the picker scroll view when the picker size is not defined.
+ if (picker._availableSize == Size.Zero || picker._pickerContainer == null)
+ {
+ return;
+ }
+
+ picker._pickerContainer.UpdateScrollViewDraw();
+ }
+
+ ///
+ /// Method to update the disabled text style.
+ ///
+ /// The sender.
+ /// The property changed events args.
+ void OnDisabledTextStylePropertyChanged(object? sender, PropertyChangedEventArgs e)
+ {
+ if (_pickerContainer == null || _availableSize == Size.Zero)
+ {
+ return;
+ }
+
+ _pickerContainer.UpdateScrollViewDraw();
+ }
+
+ #endregion
+
+ #region Interface Implementation
+
+ ///
+ /// Method to get the default text style for the picker view.
+ ///
+ /// Returns the default text style.
+ static ITextElement GetPickerTextStyle(BindableObject bindable)
+ {
+ var pickerBase = (PickerBase)bindable;
+ PickerTextStyle pickerTextStyle = new PickerTextStyle()
+ {
+ FontSize = 14,
+ TextColor = Color.FromArgb("#1C1B1F"),
+ Parent = pickerBase,
+ };
+
+ return pickerTextStyle;
+ }
+
+ ///
+ /// Method to get the default selected text style for the picker view.
+ ///
+ /// Returns the default selected text style.
+ static ITextElement GetPickerSelectionTextStyle(BindableObject bindable)
+ {
+ var pickerBase = (PickerBase)bindable;
+ PickerTextStyle pickerTextStyle = new PickerTextStyle()
+ {
+ FontSize = 14,
+ TextColor = Colors.White,
+ Parent = pickerBase,
+ };
+
+ return pickerTextStyle;
+ }
+
+ ///
+ /// Method to get the default text style for the picker view.
+ ///
+ /// Returns the default text style.
+ static ITextElement GetPickerDisabledTextStyle(BindableObject bindable)
+ {
+ var pickerBase = (PickerBase)bindable;
+ PickerTextStyle pickerTextStyle = new PickerTextStyle()
+ {
+ FontSize = 14,
+ TextColor = Color.FromArgb("#1C1B1F61"),
+ Parent = pickerBase,
+ };
+
+ return pickerTextStyle;
+ }
+
+ #endregion
+
+ #region Events
+
+ ///
+ /// Occurs after the ok button clicked on SfPicker. This event is not applicable for while the footer view is not visible and the ok button is not visible.
+ ///
+ public event EventHandler? OkButtonClicked;
+
+ ///
+ /// Occurs after the cancel button clicked on SfPicker. This event is not applicable for while the footer view is not visible.
+ ///
+ public event EventHandler? CancelButtonClicked;
+
+ ///
+ /// Occurs after the picker popup is opened.
+ ///
+ public event EventHandler? Opened;
+
+ ///
+ /// Occurs when the picker popup is closed.
+ ///
+ public event EventHandler? Closed;
+
+ ///
+ /// Occurs when the picker popup is closing.
+ ///
+ public event EventHandler? Closing;
+
+ ///
+ /// Occurs after the selected index changed on SfPicker.
+ ///
+ internal event EventHandler? SelectionIndexChanged;
+
+ #endregion
+ }
+}
\ No newline at end of file
diff --git a/maui/src/Picker/Model/Settings/PickerColumn.cs b/maui/src/Picker/Model/Settings/PickerColumn.cs
new file mode 100644
index 00000000..b7dbbcc7
--- /dev/null
+++ b/maui/src/Picker/Model/Settings/PickerColumn.cs
@@ -0,0 +1,470 @@
+using System.Collections.Specialized;
+using Syncfusion.Maui.Toolkit.Themes;
+
+namespace Syncfusion.Maui.Toolkit.Picker
+{
+ ///
+ /// Represents a class which is used to customize all the properties of picker column of the SfPicker.
+ ///
+ public class PickerColumn : Element, IThemeElement
+ {
+ #region Fields
+
+ ///
+ /// The column index holds the current column index value.
+ ///
+ internal int _columnIndex = -1;
+
+ ///
+ /// Determines whether to check the selected item while the selected index change.
+ /// Set to true to selected item only change.
+ ///
+ internal bool _isSelectedItemChanged = true;
+
+ #endregion
+
+ #region Bindable Properties
+
+ ///
+ /// Identifies the dependency property.
+ ///
+ ///
+ /// The identifier for dependency property.
+ ///
+ public static readonly BindableProperty WidthProperty =
+ BindableProperty.Create(
+ nameof(Width),
+ typeof(double),
+ typeof(PickerColumn),
+ -1d,
+ propertyChanged: OnWidthChanged);
+
+ ///
+ /// Identifies the dependency property.
+ ///
+ ///
+ /// The identifier for dependency property.
+ ///
+ public static readonly BindableProperty ItemsSourceProperty =
+ BindableProperty.Create(
+ nameof(ItemsSource),
+ typeof(object),
+ typeof(PickerColumn),
+ defaultValueCreator: bindable => null,
+ propertyChanged: OnItemsSourceChanged);
+
+ ///
+ /// Identifies the dependency property.
+ ///
+ ///
+ /// The identifier for dependency property.
+ ///
+ public static readonly BindableProperty DisplayMemberPathProperty =
+ BindableProperty.Create(
+ nameof(DisplayMemberPath),
+ typeof(string),
+ typeof(PickerColumn),
+ string.Empty,
+ propertyChanged: OnDisplayMemberPathChanged);
+
+ ///
+ /// Identifies the dependency property.
+ ///
+ ///
+ /// The identifier for dependency property.
+ ///
+ public static readonly BindableProperty SelectedIndexProperty =
+ BindableProperty.Create(
+ nameof(SelectedIndex),
+ typeof(int),
+ typeof(PickerColumn),
+ 0,
+ propertyChanged: OnSelectedIndexChanged);
+
+ ///
+ /// Identifies the dependency property.
+ ///
+ ///
+ /// The identifier for dependency property.
+ ///
+ public static readonly BindableProperty HeaderTextProperty =
+ BindableProperty.Create(
+ nameof(HeaderText),
+ typeof(string),
+ typeof(PickerColumn),
+ string.Empty,
+ propertyChanged: OnHeaderTextChanged);
+
+ ///
+ /// Identifies the dependency property.
+ ///
+ ///
+ /// The identifier for dependency property.
+ ///
+ public static readonly BindableProperty SelectedItemProperty =
+ BindableProperty.Create(
+ nameof(SelectedItem),
+ typeof(object),
+ typeof(PickerColumn),
+ defaultValueCreator: bindable => PickerHelper.GetSelectedItemDefaultValue(bindable),
+ propertyChanged: OnSelectedItemChanged);
+
+ #endregion
+
+ #region Constructor
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ public PickerColumn()
+ {
+ ThemeElement.InitializeThemeResources(this, "SfPickerTheme");
+ }
+
+ #endregion
+
+ #region Properties
+
+ ///
+ /// Gets or sets the value to specify the column width on SfPicker
+ ///
+ /// The default value of is -1d.
+ ///
+ /// The following example demonstrates how to set the width of the picker column.
+ /// # [XAML](#tab/tabid-1)
+ ///
+ ///
+ ///
+ ///
+ /// ]]>
+ ///
+ /// # [C#](#tab/tabid-2)
+ ///
+ /// PickerColumn column = new PickerColumn
+ /// {
+ /// Width = 100
+ /// };
+ ///
+ ///
+ public double Width
+ {
+ get { return (double)GetValue(WidthProperty); }
+ set { SetValue(WidthProperty, value); }
+ }
+
+ ///
+ /// Gets or sets the value to specify the column text on SfPicker.
+ ///
+ ///
+ /// The following example demonstrates how to set the items source of the picker column.
+ /// # [XAML](#tab/tabid-3)
+ ///
+ ///
+ ///
+ ///
+ /// ]]>
+ ///
+ /// # [C#](#tab/tabid-4)
+ ///
+ /// PickerColumn column = new PickerColumn
+ /// {
+ /// ItemsSource = dataDource
+ /// };
+ ///
+ ///
+ public object ItemsSource
+ {
+ get { return (object)GetValue(ItemsSourceProperty); }
+ set { SetValue(ItemsSourceProperty, value); }
+ }
+
+ ///
+ /// Gets or sets the selected index of the columns on SfPicker.
+ ///
+ ///
+ /// In multi-column setups, if any column contains a selected index as -1 or less than -1, the selection UI will not be drawn,
+ /// and each column scrolling will move to the zeroth position.
+ ///
+ /// The default value of is 0.
+ ///
+ /// The following example demonstrates how to set the selected index of the picker column.
+ /// # [XAML](#tab/tabid-5)
+ ///
+ ///
+ ///
+ ///
+ /// ]]>
+ ///
+ /// # [C#](#tab/tabid-6)
+ ///
+ /// PickerColumn column = new PickerColumn
+ /// {
+ /// SelectedIndex = 2
+ /// };
+ ///
+ ///
+ public int SelectedIndex
+ {
+ get { return (int)GetValue(SelectedIndexProperty); }
+ set { SetValue(SelectedIndexProperty, value); }
+ }
+
+ ///
+ /// Gets or sets the value to specify the header text of the columns on SfPicker.
+ ///
+ /// The default value of is an string.Empty.
+ ///
+ /// The following example demonstrates how to set the header text of the picker column.
+ /// # [XAML](#tab/tabid-7)
+ ///
+ ///
+ ///
+ ///
+ /// ]]>
+ ///
+ /// # [C#](#tab/tabid-8)
+ ///
+ /// PickerColumn column = new PickerColumn
+ /// {
+ /// HeaderText = "Colors"
+ /// };
+ ///
+ ///
+ public string HeaderText
+ {
+ get { return (string)GetValue(HeaderTextProperty); }
+ set { SetValue(HeaderTextProperty, value); }
+ }
+
+ ///
+ /// Gets or sets the value to specify the path value for items source.
+ ///
+ /// The default value of is an string.empty.
+ ///
+ /// The following example demonstrates how to set the display member path of the picker column.
+ /// # [XAML](#tab/tabid-9)
+ ///
+ ///
+ ///
+ ///
+ /// ]]>
+ ///
+ /// # [C#](#tab/tabid-10)
+ ///
+ /// PickerColumn column = new PickerColumn
+ /// {
+ /// DisplayMemberPath = "Name"
+ /// };
+ ///
+ ///
+ public string DisplayMemberPath
+ {
+ get { return (string)GetValue(DisplayMemberPathProperty); }
+ set { SetValue(DisplayMemberPathProperty, value); }
+ }
+
+ ///
+ /// Gets or sets the selected item of the columns on SfPicker.
+ ///
+ ///
+ /// In multi-column setups, if any column contains a selected item as null, the selection UI will not be drawn,
+ /// and each column scrolling will move to the zeroth position.
+ /// When selected item changed its trigger selection index changed events.
+ ///
+ ///
+ /// The following example demonstrates how to set the selected item of the picker column.
+ /// # [XAML](#tab/tabid-11)
+ ///
+ ///
+ ///
+ ///
+ /// ]]>
+ ///
+ /// # [C#](#tab/tabid-12)
+ ///
+ /// PickerColumn column = new PickerColumn
+ /// {
+ /// SelectedItem = "Blue"
+ /// };
+ ///
+ ///
+ public object? SelectedItem
+ {
+ get { return (object?)GetValue(SelectedItemProperty); }
+ set { SetValue(SelectedItemProperty, value); }
+ }
+
+ #endregion
+
+ #region Internal Methods
+
+ ///
+ /// Method to wire the items source collection.
+ ///
+ internal void WireCollectionChanged()
+ {
+ if (ItemsSource == null)
+ {
+ return;
+ }
+
+ if (ItemsSource is INotifyCollectionChanged itemsSource)
+ {
+ itemsSource.CollectionChanged += OnItemsSourceCollectionChanged;
+ }
+ }
+
+ ///
+ /// Method to remove the wiring of items source collection.
+ ///
+ /// The items source.
+ internal void UnWireCollectionChanged(object itemsSource)
+ {
+ if (itemsSource == null)
+ {
+ return;
+ }
+
+ if (itemsSource is INotifyCollectionChanged items)
+ {
+ items.CollectionChanged -= OnItemsSourceCollectionChanged;
+ }
+ }
+
+ #endregion
+
+ #region Property Changed Methods
+
+ ///
+ /// Method invokes on the picker columns width changed.
+ ///
+ /// The columns settings object.
+ /// Property old value.
+ /// Property new value.
+ static void OnWidthChanged(BindableObject bindable, object oldValue, object newValue)
+ {
+ (bindable as PickerColumn)?.RaisePropertyChanged(nameof(Width));
+ }
+
+ ///
+ /// Method invokes on the picker columns text changed.
+ ///
+ /// The columns settings object.
+ /// Property old value.
+ /// Property new value.
+ static void OnItemsSourceChanged(BindableObject bindable, object oldValue, object newValue)
+ {
+ (bindable as PickerColumn)?.RaisePropertyChanged(nameof(ItemsSource));
+ }
+
+ ///
+ /// Method invokes on the picker columns selected index changed.
+ ///
+ /// The columns settings object.
+ /// Property old value.
+ /// Property new value.
+ static void OnSelectedIndexChanged(BindableObject bindable, object oldValue, object newValue)
+ {
+ (bindable as PickerColumn)?.RaisePropertyChanged(nameof(SelectedIndex), oldValue);
+ }
+
+ ///
+ /// Method invokes on the picker columns header text changed.
+ ///
+ /// The columns settings object.
+ /// Property old value.
+ /// Property new value.
+ static void OnHeaderTextChanged(BindableObject bindable, object oldValue, object newValue)
+ {
+ (bindable as PickerColumn)?.RaisePropertyChanged(nameof(HeaderText));
+ }
+
+ ///
+ /// Method invokes on the picker columns display member path changed.
+ ///
+ /// The columns settings object.
+ /// Property old value.
+ /// Property new value.
+ static void OnDisplayMemberPathChanged(BindableObject bindable, object oldValue, object newValue)
+ {
+ (bindable as PickerColumn)?.RaisePropertyChanged(nameof(DisplayMemberPath));
+ }
+
+ ///
+ /// Method invokes on the picker columns selected item changed.
+ ///
+ /// The columns settings object.
+ /// Property old value.
+ /// Property new value.
+ static void OnSelectedItemChanged(BindableObject bindable, object oldValue, object newValue)
+ {
+ (bindable as PickerColumn)?.RaisePropertyChanged(nameof(SelectedItem), oldValue);
+ }
+
+ ///
+ /// Method to invoke picker property changed event on columns settings properties changed.
+ ///
+ /// Property name.
+ /// Property old value.
+ void RaisePropertyChanged(string propertyName, object? oldValue = null)
+ {
+ PickerPropertyChanged?.Invoke(this, new PickerPropertyChangedEventArgs(propertyName) { OldValue = oldValue });
+ }
+
+ ///
+ /// Method to invoke items source collection changed.
+ ///
+ /// The column items source object.
+ /// The collection changed event arguments.
+ void OnItemsSourceCollectionChanged(object? sender, NotifyCollectionChangedEventArgs e)
+ {
+ PickerColumnCollectionChanged?.Invoke(this, e);
+ }
+
+ #endregion
+
+ #region Interface Implementation
+
+ ///
+ /// This method will be called when a theme dictionary
+ /// that contains the value for your control key is merged in application.
+ ///
+ /// The old value.
+ /// The new value.
+ void IThemeElement.OnCommonThemeChanged(string oldTheme, string newTheme)
+ {
+ }
+
+ ///
+ /// This method will be called when users merge a theme dictionary
+ /// that contains value for “SyncfusionTheme” dynamic resource key.
+ ///
+ /// Old theme.
+ /// New theme.
+ void IThemeElement.OnControlThemeChanged(string oldTheme, string newTheme)
+ {
+ }
+
+ #endregion
+
+ #region Events
+
+ ///
+ /// Event Invokes on picker columns settings property changed and this includes old value of the changed property which is used to unwire events for nested classes.
+ ///
+ internal event EventHandler? PickerPropertyChanged;
+
+ ///
+ /// Event Invokes on picker column collection changed.
+ ///
+ internal event EventHandler? PickerColumnCollectionChanged;
+
+ #endregion
+ }
+}
\ No newline at end of file
diff --git a/maui/src/Picker/Model/Settings/PickerSelectionView.cs b/maui/src/Picker/Model/Settings/PickerSelectionView.cs
new file mode 100644
index 00000000..f25fb56c
--- /dev/null
+++ b/maui/src/Picker/Model/Settings/PickerSelectionView.cs
@@ -0,0 +1,191 @@
+namespace Syncfusion.Maui.Toolkit.Picker;
+
+///
+/// Represents a class which is used to customize all the properties of selection view of the SfPicker.
+///
+public class PickerSelectionView : Element
+{
+ #region Bindable Properties
+
+ ///
+ /// Identifies the dependency property.
+ ///
+ ///
+ /// The identifier for dependency property.
+ ///
+ public static readonly BindableProperty BackgroundProperty =
+ BindableProperty.Create(
+ nameof(Background),
+ typeof(Brush),
+ typeof(PickerSelectionView),
+ defaultValueCreator: bindable => new SolidColorBrush(Color.FromArgb("#6750A4")));
+
+ ///
+ /// Identifies the dependency property.
+ ///
+ ///
+ /// The identifier for dependency property.
+ ///
+ public static readonly BindableProperty StrokeProperty =
+ BindableProperty.Create(
+ nameof(Stroke),
+ typeof(Color),
+ typeof(PickerSelectionView),
+ defaultValueCreator: bindable => Colors.Transparent);
+
+ ///
+ /// Identifies the dependency property.
+ ///
+ ///
+ /// The identifier for dependency property.
+ ///
+ public static readonly BindableProperty CornerRadiusProperty =
+ BindableProperty.Create(
+ nameof(CornerRadius),
+ typeof(CornerRadius),
+ typeof(PickerSelectionView),
+ defaultValueCreator: bindable => new CornerRadius(20));
+
+ ///
+ /// Identifies the dependency property.
+ ///
+ ///
+ /// The identifier for dependency property.
+ ///
+ public static readonly BindableProperty PaddingProperty =
+ BindableProperty.Create(
+ nameof(Padding),
+ typeof(Thickness),
+ typeof(PickerSelectionView),
+ defaultValueCreator: bindable => new Thickness(5, 2));
+
+ #endregion
+
+ #region Properties
+
+ ///
+ /// Gets or sets the background of the selection view in SfPicker.
+ ///
+ /// The default value of is SolidColorBrush(Color.FromArgb("#6750A4")).
+ ///
+ /// The following example demonstrates how to set the background of the selection view.
+ /// # [XAML](#tab/tabid-1)
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ /// ]]>
+ ///
+ /// # [C#](#tab/tabid-2)
+ ///
+ /// SfPicker picker = new SfPicker();
+ /// picker.SelectionView = new PickerSelectionView
+ /// {
+ /// Background = new SolidColorBrush(Colors.LightBlue)
+ /// };
+ ///
+ ///
+ public Brush Background
+ {
+ get { return (Brush)GetValue(BackgroundProperty); }
+ set { SetValue(BackgroundProperty, value); }
+ }
+
+ ///
+ /// Gets or sets the stroke color of the selection view in SfPicker.
+ ///
+ /// The default value of is Colors.Transparent.
+ ///
+ /// The following example demonstrates how to set the stroke of the selection view.
+ /// # [XAML](#tab/tabid-3)
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ /// ]]>
+ ///
+ /// # [C#](#tab/tabid-4)
+ ///
+ /// SfPicker picker = new SfPicker();
+ /// picker.SelectionView = new PickerSelectionView
+ /// {
+ /// Stroke = Colors.Blue
+ /// };
+ ///
+ ///
+ public Color Stroke
+ {
+ get { return (Color)GetValue(StrokeProperty); }
+ set { SetValue(StrokeProperty, value); }
+ }
+
+ ///
+ /// Gets or sets the corner radius of the selection view in SfPicker.
+ ///
+ /// The default value of is 20.
+ ///
+ /// The following example demonstrates how to set the corner radius of the selection view.
+ /// # [XAML](#tab/tabid-5)
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ /// ]]>
+ ///
+ /// # [C#](#tab/tabid-6)
+ ///
+ /// SfPicker picker = new SfPicker();
+ /// picker.SelectionView = new PickerSelectionView
+ /// {
+ /// CornerRadius = new CornerRadius(10)
+ /// };
+ ///
+ ///
+ public CornerRadius CornerRadius
+ {
+ get { return (CornerRadius)GetValue(CornerRadiusProperty); }
+ set { SetValue(CornerRadiusProperty, value); }
+ }
+
+ ///
+ /// Gets or sets the padding value of the selection view in SfPicker.
+ ///
+ /// The default value of is new Thickness(5, 2).
+ ///
+ /// The following example demonstrates how to set the padding of the selection view.
+ /// # [XAML](#tab/tabid-7)
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ /// ]]>
+ ///
+ /// # [C#](#tab/tabid-8)
+ ///
+ /// SfPicker picker = new SfPicker();
+ /// picker.SelectionView = new PickerSelectionView
+ /// {
+ /// Padding = new Thickness(8, 4)
+ /// };
+ ///
+ ///
+ public Thickness Padding
+ {
+ get { return (Thickness)GetValue(PaddingProperty); }
+ set { SetValue(PaddingProperty, value); }
+ }
+
+ #endregion
+}
\ No newline at end of file
diff --git a/maui/src/Picker/Model/Settings/PickerTextStyle.cs b/maui/src/Picker/Model/Settings/PickerTextStyle.cs
new file mode 100644
index 00000000..5a0b61d3
--- /dev/null
+++ b/maui/src/Picker/Model/Settings/PickerTextStyle.cs
@@ -0,0 +1,215 @@
+using Syncfusion.Maui.Toolkit.Graphics.Internals;
+using Font = Microsoft.Maui.Font;
+
+namespace Syncfusion.Maui.Toolkit.Picker
+{
+ ///
+ /// Represents a class which is used to customize the text style of the picker.
+ ///
+ public class PickerTextStyle : Element, ITextElement
+ {
+ #region Bindable Properties
+
+ ///
+ /// Identifies the dependency property.
+ ///
+ ///
+ /// The identifier for dependency property.
+ ///
+ public static readonly BindableProperty TextColorProperty =
+ BindableProperty.Create(
+ nameof(TextColor),
+ typeof(Color),
+ typeof(PickerTextStyle),
+ defaultValueCreator: bindable => Colors.Black);
+
+ ///
+ /// Identifies the dependency property.
+ ///
+ ///
+ /// The identifier for dependency property.
+ ///
+ public static readonly BindableProperty FontSizeProperty = FontElement.FontSizeProperty;
+
+ ///
+ /// Identifies the dependency property.
+ ///
+ ///
+ /// The identifier for dependency property.
+ ///
+ public static readonly BindableProperty FontFamilyProperty = FontElement.FontFamilyProperty;
+
+ ///
+ /// Identifies the dependency property.
+ ///
+ ///
+ /// The identifier for dependency property.
+ ///
+ public static readonly BindableProperty FontAttributesProperty = FontElement.FontAttributesProperty;
+
+ ///
+ /// Identifies the bindable property.
+ ///
+ ///
+ /// The identifier for bindable property.
+ ///
+ public static readonly BindableProperty FontAutoScalingEnabledProperty = FontElement.FontAutoScalingEnabledProperty;
+
+ #endregion
+
+ #region Properties
+
+ ///
+ /// Gets or sets the text color of the text style.
+ ///
+ /// The default value of the is "#000000"(black).
+ ///
+ /// It will be applicable to all the style related properties of the picker.
+ ///
+ ///
+ ///
+ ///
+ public Color TextColor
+ {
+ get { return (Color)GetValue(TextColorProperty); }
+ set { SetValue(TextColorProperty, value); }
+ }
+
+ ///
+ /// Gets or sets the font size of the text style.
+ ///
+ ///
+ /// It will be applicable to all the style related properties of the picker.
+ ///
+ ///
+ ///
+ ///
+ public double FontSize
+ {
+ get { return (double)GetValue(FontSizeProperty); }
+ set { SetValue(FontSizeProperty, value); }
+ }
+
+ ///
+ /// Gets or sets the font family of the text style.
+ ///
+ ///
+ /// It will be applicable to all the style related properties of the picker.
+ ///
+ ///
+ ///
+ ///
+ public string FontFamily
+ {
+ get { return (string)GetValue(FontFamilyProperty); }
+ set { SetValue(FontFamilyProperty, value); }
+ }
+
+ ///
+ /// Gets or sets the font attributes of the text style.
+ ///
+ /// The default value of the is
+ ///
+ /// It will be applicable to all the style related properties of the picker.
+ ///
+ ///
+ ///
+ ///
+ public FontAttributes FontAttributes
+ {
+ get { return (FontAttributes)GetValue(FontAttributesProperty); }
+ set { SetValue(FontAttributesProperty, value); }
+ }
+
+ ///
+ /// Gets or sets a value indicating whether or not the font of the control should scale automatically according to the operating system settings.
+ ///
+ ///
+ /// It accepts Boolean values, and the default value is false.
+ ///
+ ///
+ /// It will be applicable to all the style related properties of the picker.
+ ///
+ ///
+ ///
+ ///
+ public bool FontAutoScalingEnabled
+ {
+ get { return (bool)GetValue(FontAutoScalingEnabledProperty); }
+ set { SetValue(FontAutoScalingEnabledProperty, value); }
+ }
+
+ ///
+ /// Gets the font of the text style.
+ ///
+ [System.Diagnostics.CodeAnalysis.SuppressMessage("Design", "CA1033:Interface methods should be callable by child types", Justification = "")]
+ Font ITextElement.Font => (Font)GetValue(FontElement.FontProperty);
+
+ #endregion
+
+ #region Private Methods
+
+ ///
+ [System.Diagnostics.CodeAnalysis.SuppressMessage("Design", "CA1033:Interface methods should be callable by child types", Justification = "")]
+ double ITextElement.FontSizeDefaultValueCreator()
+ {
+ return 14d;
+ }
+
+ ///
+ void ITextElement.OnFontAttributesChanged(FontAttributes oldValue, FontAttributes newValue)
+ {
+ }
+
+ ///
+ void ITextElement.OnFontChanged(Font oldValue, Font newValue)
+ {
+ }
+
+ ///
+ void ITextElement.OnFontFamilyChanged(string oldValue, string newValue)
+ {
+ }
+
+ ///
+ void ITextElement.OnFontSizeChanged(double oldValue, double newValue)
+ {
+ }
+
+ ///
+ /// To update the font auto scaling enable state.
+ ///
+ /// old value
+ /// new value
+ void ITextElement.OnFontAutoScalingEnabledChanged(bool oldValue, bool newValue)
+ {
+ }
+
+ #endregion
+ }
+}
\ No newline at end of file
diff --git a/maui/src/Picker/Model/Settings/TimePickerColumnHeaderSettings.cs b/maui/src/Picker/Model/Settings/TimePickerColumnHeaderSettings.cs
new file mode 100644
index 00000000..a8e4dcff
--- /dev/null
+++ b/maui/src/Picker/Model/Settings/TimePickerColumnHeaderSettings.cs
@@ -0,0 +1,550 @@
+using Syncfusion.Maui.Toolkit.Graphics.Internals;
+using Syncfusion.Maui.Toolkit.Themes;
+
+namespace Syncfusion.Maui.Toolkit.Picker
+{
+ ///
+ /// Represents a class which is used to customize all the properties of column header view of the SfTimePicker.
+ ///
+ public class TimePickerColumnHeaderView : Element, IThemeElement
+ {
+ #region Bindable Properties
+
+ ///
+ /// Identifies the dependency property.
+ ///
+ ///
+ /// The identifier for dependency property.
+ ///
+ public static readonly BindableProperty HeightProperty =
+ BindableProperty.Create(
+ nameof(Height),
+ typeof(double),
+ typeof(TimePickerColumnHeaderView),
+ 40d,
+ propertyChanged: OnHeightChanged);
+
+ ///
+ /// Identifies the dependency property.
+ ///
+ ///
+ /// The identifier for dependency property.
+ ///
+ public static readonly BindableProperty TextStyleProperty =
+ BindableProperty.Create(
+ nameof(TextStyle),
+ typeof(PickerTextStyle),
+ typeof(TimePickerColumnHeaderView),
+ defaultValueCreator: bindable => GetColumnHeaderTextStyle(bindable),
+ propertyChanged: OnTextStyleChanged);
+
+ ///
+ /// Identifies the dependency property.
+ ///
+ ///
+ /// The identifier for dependency property.
+ ///
+ public static readonly BindableProperty BackgroundProperty =
+ BindableProperty.Create(
+ nameof(Background),
+ typeof(Brush),
+ typeof(TimePickerColumnHeaderView),
+ defaultValueCreator: bindable => new SolidColorBrush(Color.FromArgb("#F7F2FB")),
+ propertyChanged: OnBackgroundChanged);
+
+ ///
+ /// Identifies the dependency property.
+ ///
+ ///
+ /// The identifier for dependency property.
+ ///
+ public static readonly BindableProperty DividerColorProperty =
+ BindableProperty.Create(
+ nameof(DividerColor),
+ typeof(Color),
+ typeof(TimePickerColumnHeaderView),
+ defaultValueCreator: bindable => Color.FromArgb("#CAC4D0"),
+ propertyChanged: OnDividerColorChanged);
+
+ ///
+ /// Identifies the dependency property.
+ ///
+ ///
+ /// The identifier for dependency property.
+ ///
+ public static readonly BindableProperty HourHeaderTextProperty =
+ BindableProperty.Create(
+ nameof(HourHeaderText),
+ typeof(string),
+ typeof(TimePickerColumnHeaderView),
+ "Hour",
+ propertyChanged: OnHourHeaderTextChanged);
+
+ ///
+ /// Identifies the dependency property.
+ ///
+ ///
+ /// The identifier for dependency property.
+ ///
+ public static readonly BindableProperty MinuteHeaderTextProperty =
+ BindableProperty.Create(
+ nameof(MinuteHeaderText),
+ typeof(string),
+ typeof(TimePickerColumnHeaderView),
+ "Minute",
+ propertyChanged: OnMinuteHeaderTextChanged);
+
+ ///
+ /// Identifies the dependency property.
+ ///
+ ///
+ /// The identifier for dependency property.
+ ///
+ public static readonly BindableProperty SecondHeaderTextProperty =
+ BindableProperty.Create(
+ nameof(SecondHeaderText),
+ typeof(string),
+ typeof(TimePickerColumnHeaderView),
+ "Second",
+ propertyChanged: OnSecondHeaderTextChanged);
+
+ ///
+ /// Identifies the dependency property.
+ ///
+ ///
+ /// The identifier for dependency property.
+ ///
+ public static readonly BindableProperty MeridiemHeaderTextProperty =
+ BindableProperty.Create(
+ nameof(MeridiemHeaderText),
+ typeof(string),
+ typeof(TimePickerColumnHeaderView),
+ string.Empty,
+ propertyChanged: OnMeridiemHeaderTextChanged);
+
+ #endregion
+
+ #region Constructor
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ public TimePickerColumnHeaderView()
+ {
+ ThemeElement.InitializeThemeResources(this, "SfTimePickerTheme");
+ }
+
+ #endregion
+
+ #region Properties
+
+ ///
+ /// Gets or sets the value to specify the height of column header view on SfTimePicker.
+ ///
+ /// The default value of is 40d.
+ ///
+ /// The following examples demonstrate how to set the height of the column header view.
+ /// # [XAML](#tab/tabid-1)
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ /// ]]>
+ ///
+ /// # [C#](#tab/tabid-2)
+ ///
+ /// SfTimePicker timePicker = new SfTimePicker();
+ /// timePicker.ColumnHeaderView = new TimePickerColumnHeaderView
+ /// {
+ /// Height = 50
+ /// };
+ ///
+ ///
+ public double Height
+ {
+ get { return (double)GetValue(HeightProperty); }
+ set { SetValue(HeightProperty, value); }
+ }
+
+ ///
+ /// Gets or sets the text style of the column header text in SfTimePicker.
+ ///
+ ///
+ /// The following examples demonstrate how to set the text style of the column header view.
+ /// # [XAML](#tab/tabid-3)
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ /// ]]>
+ ///
+ /// # [C#](#tab/tabid-4)
+ ///
+ /// SfTimePicker timePicker = new SfTimePicker();
+ /// timePicker.ColumnHeaderView = new TimePickerColumnHeaderView
+ /// {
+ /// TextStyle = new PickerTextStyle
+ /// {
+ /// TextColor = Colors.Blue,
+ /// FontSize = 16
+ /// }
+ /// };
+ ///
+ ///
+ public PickerTextStyle TextStyle
+ {
+ get { return (PickerTextStyle)GetValue(TextStyleProperty); }
+ set { SetValue(TextStyleProperty, value); }
+ }
+
+ ///
+ /// Gets or sets the background of the column header view in SfTimePicker.
+ ///
+ /// The default value of is "SolidColorBrush(Color.FromArgb("#F7F2FB"))".
+ ///
+ /// The following examples demonstrate how to set the background of the column header view.
+ /// # [XAML](#tab/tabid-5)
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ /// ]]>
+ ///
+ /// # [C#](#tab/tabid-6)
+ ///
+ /// SfTimePicker timePicker = new SfTimePicker();
+ /// timePicker.ColumnHeaderView = new TimePickerColumnHeaderView
+ /// {
+ /// Background = new SolidColorBrush(Color.FromHex("#E0E0E0"))
+ /// };
+ ///
+ ///
+ public Brush Background
+ {
+ get { return (Brush)GetValue(BackgroundProperty); }
+ set { SetValue(BackgroundProperty, value); }
+ }
+
+ ///
+ /// Gets or sets the background of the column header separator line background in SfTimePicker.
+ ///
+ /// The default value of is "#CAC4D0".
+ ///
+ /// The following examples demonstrate how to set the divider color of the column header view.
+ /// # [XAML](#tab/tabid-7)
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ /// ]]>
+ ///
+ /// # [C#](#tab/tabid-8)
+ ///
+ /// SfTimePicker timePicker = new SfTimePicker();
+ /// timePicker.ColumnHeaderView = new TimePickerColumnHeaderView
+ /// {
+ /// DividerColor = Colors.Gray
+ /// };
+ ///
+ ///
+ public Color DividerColor
+ {
+ get { return (Color)GetValue(DividerColorProperty); }
+ set { SetValue(DividerColorProperty, value); }
+ }
+
+ ///
+ /// Gets or sets the value to hour header text in SfTimePicker.
+ ///
+ /// The default value of is "Hour".
+ ///
+ /// The following examples demonstrate how to set the hour header text of the column header view.
+ /// # [XAML](#tab/tabid-9)
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ /// ]]>
+ ///
+ /// # [C#](#tab/tabid-10)
+ ///
+ /// SfTimePicker timePicker = new SfTimePicker();
+ /// timePicker.ColumnHeaderView = new TimePickerColumnHeaderView
+ /// {
+ /// HourHeaderText = "hour"
+ /// };
+ ///
+ ///
+ public string HourHeaderText
+ {
+ get { return (string)GetValue(HourHeaderTextProperty); }
+ set { SetValue(HourHeaderTextProperty, value); }
+ }
+
+ ///
+ /// Gets or sets the value to minute header text in SfTimePicker.
+ ///
+ /// The default value of is "Minute".
+ ///
+ /// The following examples demonstrate how to set the minute header text of the column header view.
+ /// # [XAML](#tab/tabid-11)
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ /// ]]>
+ ///
+ /// # [C#](#tab/tabid-12)
+ ///
+ /// SfTimePicker timePicker = new SfTimePicker();
+ /// timePicker.ColumnHeaderView = new TimePickerColumnHeaderView
+ /// {
+ /// MinuteHeaderText = "minute"
+ /// };
+ ///
+ ///
+ public string MinuteHeaderText
+ {
+ get { return (string)GetValue(MinuteHeaderTextProperty); }
+ set { SetValue(MinuteHeaderTextProperty, value); }
+ }
+
+ ///
+ /// Gets or sets the value to second header text in SfTimePicker.
+ ///
+ /// The default value of is "Second".
+ ///
+ /// The following examples demonstrate how to set the second header text of the column header view.
+ /// # [XAML](#tab/tabid-13)
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ /// ]]>
+ ///
+ /// # [C#](#tab/tabid-14)
+ ///
+ /// SfTimePicker timePicker = new SfTimePicker();
+ /// timePicker.ColumnHeaderView = new TimePickerColumnHeaderView
+ /// {
+ /// SecondHeaderText = "second"
+ /// };
+ ///
+ ///
+ public string SecondHeaderText
+ {
+ get { return (string)GetValue(SecondHeaderTextProperty); }
+ set { SetValue(SecondHeaderTextProperty, value); }
+ }
+
+ ///
+ /// Gets or sets the value to meridiem header text in SfTimePicker.
+ ///
+ /// The default value of is string.Empty.
+ ///
+ /// The following examples demonstrate how to set the meridiem header text of the column header view.
+ /// # [XAML](#tab/tabid-15)
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ /// ]]>
+ ///
+ /// # [C#](#tab/tabid-16)
+ ///
+ /// SfTimePicker timePicker = new SfTimePicker();
+ /// timePicker.ColumnHeaderView = new TimePickerColumnHeaderView
+ /// {
+ /// MeridiemHeaderText = "Meridiem"
+ /// };
+ ///
+ ///
+ public string MeridiemHeaderText
+ {
+ get { return (string)GetValue(MeridiemHeaderTextProperty); }
+ set { SetValue(MeridiemHeaderTextProperty, value); }
+ }
+
+ #endregion
+
+ #region Property Changed Methods
+
+ ///
+ /// Method invokes on the picker column header height changed.
+ ///
+ /// The column header settings object.
+ /// Property old value.
+ /// Property new value.
+ static void OnHeightChanged(BindableObject bindable, object oldValue, object newValue)
+ {
+ (bindable as TimePickerColumnHeaderView)?.RaisePropertyChanged(nameof(Height));
+ }
+
+ ///
+ /// Method invokes on picker column header text style property changed.
+ ///
+ /// The column header settings object.
+ /// Property old value.
+ /// Property new value.
+ static void OnTextStyleChanged(BindableObject bindable, object oldValue, object newValue)
+ {
+ (bindable as TimePickerColumnHeaderView)?.RaisePropertyChanged(nameof(TextStyle), oldValue);
+ }
+
+ ///
+ /// Method invokes on the picker column header background changed.
+ ///
+ /// The column header settings object.
+ /// Property old value.
+ /// Property new value.
+ static void OnBackgroundChanged(BindableObject bindable, object oldValue, object newValue)
+ {
+ (bindable as TimePickerColumnHeaderView)?.RaisePropertyChanged(nameof(Background));
+ }
+
+ ///
+ /// Method invokes on the picker column header separator line background changed.
+ ///
+ /// The column header settings object.
+ /// Property old value.
+ /// Property new value.
+ static void OnDividerColorChanged(BindableObject bindable, object oldValue, object newValue)
+ {
+ (bindable as TimePickerColumnHeaderView)?.RaisePropertyChanged(nameof(DividerColor));
+ }
+
+ ///
+ /// Method invokes on hour header text property changed.
+ ///
+ /// The column header view.
+ /// Property old value.
+ /// Property new value.
+ static void OnHourHeaderTextChanged(BindableObject bindable, object oldValue, object newValue)
+ {
+ (bindable as TimePickerColumnHeaderView)?.RaisePropertyChanged(nameof(HourHeaderText));
+ }
+
+ ///
+ /// Method invokes on minute header text property changed.
+ ///
+ /// The column header view.
+ /// Property old value.
+ /// Property new value.
+ static void OnMinuteHeaderTextChanged(BindableObject bindable, object oldValue, object newValue)
+ {
+ (bindable as TimePickerColumnHeaderView)?.RaisePropertyChanged(nameof(MinuteHeaderText));
+ }
+
+ ///
+ /// Method invokes on second header text property changed.
+ ///
+ /// The column header view.
+ /// Property old value.
+ /// Property new value.
+ static void OnSecondHeaderTextChanged(BindableObject bindable, object oldValue, object newValue)
+ {
+ (bindable as TimePickerColumnHeaderView)?.RaisePropertyChanged(nameof(SecondHeaderText));
+ }
+
+ ///
+ /// Method invokes on meridiem header text property changed.
+ ///
+ /// The column header view.
+ /// Property old value.
+ /// Property new value.
+ static void OnMeridiemHeaderTextChanged(BindableObject bindable, object oldValue, object newValue)
+ {
+ (bindable as TimePickerColumnHeaderView)?.RaisePropertyChanged(nameof(MeridiemHeaderText));
+ }
+
+ ///
+ /// Method to get the default text style for the column header view.
+ ///
+ /// Returns the default column header text style.
+ static ITextElement GetColumnHeaderTextStyle(BindableObject bindable)
+ {
+ TimePickerColumnHeaderView columnHeaderView = (TimePickerColumnHeaderView)bindable;
+ PickerTextStyle pickerTextStyle = new PickerTextStyle()
+ {
+ FontSize = 14,
+ TextColor = Color.FromArgb("49454F"),
+ Parent = columnHeaderView,
+ };
+
+ return pickerTextStyle;
+ }
+
+ ///
+ /// Method to invoke picker property changed event on column header settings properties changed.
+ ///
+ /// Property name.
+ /// Property old value.
+ void RaisePropertyChanged(string propertyName, object? oldValue = null)
+ {
+ PickerPropertyChanged?.Invoke(this, new PickerPropertyChangedEventArgs(propertyName) { OldValue = oldValue });
+ }
+
+ #endregion
+
+ #region Interface Implementation
+
+ ///
+ /// This method will be called when a theme dictionary
+ /// that contains the value for your control key is merged in application.
+ ///
+ /// The old value.
+ /// The new value.
+ void IThemeElement.OnCommonThemeChanged(string oldTheme, string newTheme)
+ {
+ }
+
+ ///
+ /// This method will be called when users merge a theme dictionary
+ /// that contains value for “SyncfusionTheme” dynamic resource key.
+ ///
+ /// Old theme.
+ /// New theme.
+ void IThemeElement.OnControlThemeChanged(string oldTheme, string newTheme)
+ {
+ }
+
+ #endregion
+
+ #region Events
+
+ ///
+ /// Event Invokes on column header settings property changed and this includes old value of the changed property which is used to unwire events for nested classes.
+ ///
+ internal event EventHandler? PickerPropertyChanged;
+
+ #endregion
+ }
+}
\ No newline at end of file
diff --git a/maui/src/Picker/PickerBase.cs b/maui/src/Picker/PickerBase.cs
new file mode 100644
index 00000000..fefca5d5
--- /dev/null
+++ b/maui/src/Picker/PickerBase.cs
@@ -0,0 +1,920 @@
+using System.Collections;
+using System.ComponentModel;
+using System.Globalization;
+using Syncfusion.Maui.Toolkit.Popup;
+
+namespace Syncfusion.Maui.Toolkit.Picker
+{
+ ///
+ /// Represents the Picker base class.
+ ///
+ public abstract partial class PickerBase : SfView, IPicker
+ {
+ #region Fields
+
+ ///
+ /// Holds the previouslySelectedDate.
+ ///
+ internal DateTime _previousSelectedDateTime;
+
+ ///
+ /// The minimum height for the picker UI
+ ///
+ readonly double _minHeight = 300;
+
+ ///
+ /// The minimum width for the picker UI.
+ ///
+ readonly double _minWidth = 300;
+
+ ///
+ /// Holds the value of previous UI culture.
+ ///
+ CultureInfo _previousUICulture = CultureInfo.CurrentUICulture;
+
+ ///
+ /// The header layout contains the text of the header.
+ ///
+ HeaderLayout? _headerLayout;
+
+ ///
+ /// The footer layout contains ok and cancel buttons.
+ ///
+ FooterLayout? _footerLayout;
+
+ ///
+ /// The picker container contains picker view layouts.
+ ///
+ PickerContainer? _pickerContainer;
+
+ ///
+ /// Holds the picker measured size.
+ ///
+ Size _availableSize = Size.Zero;
+
+ ///
+ /// The picker stack layout contains header, footer and picker container.
+ ///
+ PickerStackLayout? _pickerStackLayout;
+
+ ///
+ /// The SfPopup view.
+ ///
+ SfPopup? _popup;
+
+ ///
+ /// Boolean to get the previous open state of the picker in the dialog mode.
+ /// This value is used to maintain the previous state of the picker while changing the visibility of the picker in the dialog mode.
+ /// While changing the IsOpen property of the picker dynamically in the Visibility change the property change will not trigger sometimes.
+ /// So that we are updated the previous state of the picker when the IsOpen property changed.
+ ///
+ bool _isPickerPreviouslyOpened = false;
+
+ ///
+ /// Flag indicating whether the text style is not internally.
+ ///
+ bool _isExternalStyle = false;
+
+ ///
+ /// Holds the value of internal property change or not.
+ ///
+ bool _isInternalPropertyChange = false;
+
+#if MACCATALYST || IOS
+
+ ///
+ /// Holds the value of picker view loaded or not.
+ ///
+ bool _isPickerViewLoaded = false;
+
+#endif
+
+ #endregion
+
+ #region Internal Properties
+
+ ///
+ /// Gets a value indicating whether the direction of the layout is RTL.
+ ///
+ bool IPickerCommon.IsRTLLayout => IsRTL(this);
+
+ #endregion
+
+ #region Internal Methods
+
+ ///
+ /// Gets the main page of the application.
+ ///
+ /// Returns page.
+ internal static Page? GetMainWindowPage()
+ {
+ var application = IPlatformApplication.Current?.Application as Application;
+ if (application != null && application.Windows.Count > 0)
+ {
+ return application.Windows[0].Page;
+ }
+
+ return new Page();
+ }
+
+ ///
+ /// Method to reset the header interaction highlight.
+ ///
+ internal void ResetHeaderHighlight()
+ {
+ _headerLayout?.ResetHeaderHighlight();
+ }
+
+ ///
+ /// Method to get the picker container value.
+ ///
+ /// The picker container.
+ internal PickerContainer? GetPickerContainerValue()
+ {
+ return _pickerContainer;
+ }
+
+ ///
+ /// Method to invoke the closed event.
+ ///
+ /// The picker instance.
+ /// The event arguments.
+ internal void InvokeClosedEvent(object sender, EventArgs eventArgs)
+ {
+ Closed?.Invoke(sender, eventArgs);
+ }
+
+ ///
+ /// Method to invoke the closing event.
+ ///
+ /// The picker instance.
+ /// The event arguments.
+ internal void InvokeClosingEvent(object sender, CancelEventArgs eventArgs)
+ {
+ Closing?.Invoke(sender, eventArgs);
+ }
+
+ ///
+ /// Method to invoke the opened event.
+ ///
+ /// The picker instance.
+ /// The event arguments.
+ internal void InvokeOpenedEvent(object sender, EventArgs eventArgs)
+ {
+ Opened?.Invoke(sender, eventArgs);
+ }
+
+ ///
+ /// Method to invoke ok button clicked event.
+ ///
+ /// The picker instance.
+ /// The event arguments.
+ internal void InvokeOkButtonClickedEvent(object sender, EventArgs eventArgs)
+ {
+ OkButtonClicked?.Invoke(sender, eventArgs);
+ }
+
+ ///
+ /// Method to invoke ok button clicked event.
+ ///
+ /// The picker instance.
+ /// The event arguments.
+ internal void InvokeCancelButtonClickedEvent(object sender, EventArgs eventArgs)
+ {
+ CancelButtonClicked?.Invoke(sender, eventArgs);
+ }
+
+ #endregion
+
+ #region Private Methods
+
+ ///
+ /// Gets the RTL value true or false.
+ ///
+ /// The type of view.
+ /// Returns true or false.
+ static bool IsRTL(View view)
+ {
+ //// Flow direction property only added in VisualElement class only, so that we return while the object type is not visual element.
+ //// And Effective flow direction is flag type so that we add & RightToLeft.
+ if (!(view is IVisualElementController))
+ {
+ return false;
+ }
+
+ return ((view as IVisualElementController).EffectiveFlowDirection & EffectiveFlowDirection.RightToLeft) == EffectiveFlowDirection.RightToLeft;
+ }
+
+ ///
+ /// Method to update format based on current culture.
+ ///
+ void UpdateFormatBasedOnCulture()
+ {
+ if (!_previousUICulture.Name.Equals(CultureInfo.CurrentUICulture.Name, StringComparison.Ordinal))
+ {
+ _previousUICulture = CultureInfo.CurrentUICulture;
+
+ // Updated Formats of SfDatePicker,SfDateTimePicker and SfTimePicker to Reinitialize the component
+ if (Children is SfDatePicker datePicker)
+ {
+ datePicker.UpdateFormat();
+ }
+
+ if (Children is SfDateTimePicker dateTimePicker)
+ {
+ dateTimePicker.ResetDateColumns();
+ dateTimePicker.BaseHeaderView.DateText = dateTimePicker.GetDateHeaderText();
+ dateTimePicker.BaseHeaderView.TimeText = dateTimePicker.GetTimeHeaderText();
+ }
+
+ if (Children is SfTimePicker timePicker)
+ {
+ timePicker.UpdateFormat();
+ }
+ }
+ }
+
+ ///
+ /// Method to add the children of the picker control.
+ ///
+ void AddChildren()
+ {
+ _pickerStackLayout = new PickerStackLayout(this);
+ AddOrRemoveHeaderLayout();
+ _pickerContainer = new PickerContainer(this);
+ _pickerStackLayout.Children.Add(_pickerContainer);
+ AddOrRemoveFooterLayout();
+ Children.Add(_pickerStackLayout);
+ }
+
+ ///
+ /// Method to add or remove header layout.
+ ///
+ void AddOrRemoveHeaderLayout()
+ {
+ if (BaseHeaderView.Height > 0 && _headerLayout == null)
+ {
+ _headerLayout = new HeaderLayout(this);
+ _pickerStackLayout?.Children.Add(_headerLayout);
+ }
+ else if (_headerLayout != null && (BaseHeaderView.Height <= 0))
+ {
+ _pickerStackLayout?.Children.Remove(_headerLayout);
+ _headerLayout = null;
+ }
+ }
+
+ ///
+ /// Method to add or remove footer layout.
+ ///
+ void AddOrRemoveFooterLayout()
+ {
+ if (FooterView.Height > 0 && _footerLayout == null)
+ {
+ _footerLayout = new FooterLayout(this);
+ _pickerStackLayout?.Children.Add(_footerLayout);
+ }
+ else if (_footerLayout != null && FooterView.Height <= 0)
+ {
+ _pickerStackLayout?.Children.Remove(_footerLayout);
+ _footerLayout.RemoveFooterButtons();
+ _footerLayout = null;
+ }
+ }
+
+ ///
+ /// Method to update the picker view.
+ ///
+ void InvalidatePickerView()
+ {
+ InvalidateMeasure();
+ _pickerStackLayout?.InvalidateView();
+ }
+
+ ///
+ /// Method to update the selected index changed.
+ ///
+ /// The updated column index.
+ void UpdateSelectedIndexValue(int columnIndex)
+ {
+ if (_pickerContainer == null || _availableSize == Size.Zero)
+ {
+ return;
+ }
+
+ _pickerContainer.UpdateSelectedIndexValue(columnIndex);
+ }
+
+ ///
+ /// Method to update the picker selection view.
+ ///
+ void UpdatePickerSelectionView()
+ {
+ if (_pickerContainer == null || _availableSize == Size.Zero)
+ {
+ return;
+ }
+
+ _pickerContainer.UpdatePickerSelectionView();
+ }
+
+ ///
+ /// Method to show the picker popup based on the mode.
+ ///
+ void ShowPopup()
+ {
+ if (_popup == null)
+ {
+ return;
+ }
+
+ if (Mode == PickerMode.RelativeDialog)
+ {
+ OnPickerLoading();
+ if (RelativeView != null)
+ {
+ ShowRelativeToView(RelativeView, RelativePosition);
+ }
+ else
+ {
+ ShowRelativeToView(this, RelativePosition);
+ }
+ }
+ else
+ {
+ OnPickerLoading();
+
+ var application = IPlatformApplication.Current?.Application as Microsoft.Maui.Controls.Application;
+ var windowPage = GetMainWindowPage();
+ if (application != null && windowPage is Shell shellPage && !shellPage.IsLoaded)
+ {
+ shellPage.Loaded -= ShellPage_Loaded;
+ shellPage.Loaded += ShellPage_Loaded;
+#if MACCATALYST || IOS
+ if (windowPage.Navigation != null && windowPage.Navigation.ModalStack.Count > 0)
+ {
+ _popup.IsOpen = true;
+ }
+#endif
+ }
+ else
+ {
+ _popup.IsOpen = true;
+ }
+ }
+ }
+
+ ///
+ /// Method for shell page loaded event.
+ ///
+ /// Loaded event instance
+ /// Shell page loaded event args
+ void ShellPage_Loaded(object? sender, EventArgs e)
+ {
+ var shellCurrentPage = (sender as Shell)?.CurrentPage;
+ if (shellCurrentPage != null)
+ {
+ shellCurrentPage.Loaded -= OnMainPageLoaded;
+ shellCurrentPage.Loaded += OnMainPageLoaded;
+ }
+ }
+
+ ///
+ /// Method for main page loaded event.
+ ///
+ /// Loaded event instance
+ /// Main page loaded event args
+ void OnMainPageLoaded(object? sender, EventArgs e)
+ {
+ if (_popup != null)
+ {
+ _popup.IsOpen = true;
+ }
+ }
+
+ ///
+ /// Method to dismiss the picker popup view.
+ ///
+ void ClosePickerPopup()
+ {
+ if (_popup == null)
+ {
+ return;
+ }
+
+ _popup.IsOpen = false;
+ _popup.ContentTemplate = new DataTemplate(() =>
+ {
+ return null;
+ });
+
+ ResetPopup();
+ }
+
+ ///
+ /// Method to reset the popup and it was triggered while the mode changed to dialog to default.
+ ///
+ void ResetPopup()
+ {
+ if (_popup == null)
+ {
+ return;
+ }
+
+ _popup.IsOpen = false;
+ _popup.Opened -= OnPopupOpened;
+ _popup.Closed -= OnPopupClosed;
+ _popup.Closing -= OnPopupClosing;
+ Remove(_popup);
+ _popup = null;
+ }
+
+ ///
+ /// Method to show the picker popup based on the relative view and relative position.
+ ///
+ /// The relative view.
+ /// The relative position.
+ void ShowRelativeToView(View relativeView, PickerRelativePosition relativePosition)
+ {
+ if (_popup == null)
+ {
+ return;
+ }
+
+ switch (relativePosition)
+ {
+ case PickerRelativePosition.AlignTop:
+ _popup.ShowRelativeToView(relativeView, PopupRelativePosition.AlignTop);
+ break;
+
+ case PickerRelativePosition.AlignBottom:
+ _popup.ShowRelativeToView(relativeView, PopupRelativePosition.AlignBottom);
+ break;
+
+ case PickerRelativePosition.AlignTopLeft:
+ _popup.ShowRelativeToView(relativeView, PopupRelativePosition.AlignTopLeft);
+ break;
+
+ case PickerRelativePosition.AlignTopRight:
+ _popup.ShowRelativeToView(relativeView, PopupRelativePosition.AlignTopRight);
+ break;
+
+ case PickerRelativePosition.AlignBottomLeft:
+ _popup.ShowRelativeToView(relativeView, PopupRelativePosition.AlignBottomLeft);
+ break;
+
+ case PickerRelativePosition.AlignBottomRight:
+ _popup.ShowRelativeToView(relativeView, PopupRelativePosition.AlignBottomRight);
+ break;
+
+ case PickerRelativePosition.AlignToLeftOf:
+ _popup.ShowRelativeToView(relativeView, PopupRelativePosition.AlignToLeftOf);
+ break;
+
+ case PickerRelativePosition.AlignToRightOf:
+ _popup.ShowRelativeToView(relativeView, PopupRelativePosition.AlignToRightOf);
+ break;
+ }
+ }
+
+ ///
+ /// Method to add the picker to popup.
+ ///
+ void AddPickerToPopup()
+ {
+ if (_popup == null)
+ {
+ _popup = new SfPopup();
+ _popup.ShowHeader = false;
+ _popup.ShowFooter = false;
+ _popup.PopupStyle.CornerRadius = 5;
+ _popup.Opened += OnPopupOpened;
+ _popup.Closed += OnPopupClosed;
+ _popup.Closing += OnPopupClosing;
+ Add(_popup);
+ }
+
+ //// Here we have added the picker stack layout to the popup content if it is not null.
+ if (_pickerStackLayout == null)
+ {
+ return;
+ }
+
+ _pickerStackLayout.Background = Background;
+ _pickerStackLayout.BackgroundColor = BackgroundColor;
+ _popup.ContentTemplate = new DataTemplate(() =>
+ {
+ return _pickerStackLayout;
+ });
+ UpdatePopupSize();
+#if ANDROID
+ //// While adding the picker to the popup, the picker selected item not updated properly in android. So, we have updated that.
+ Dispatcher.Dispatch(() =>
+ {
+ _pickerContainer?.UpdateItemHeight();
+ });
+#endif
+ }
+
+ ///
+ /// Method to update the popup size based on the column values.
+ ///
+ void UpdatePopupSize()
+ {
+ if (_popup == null)
+ {
+ return;
+ }
+
+ double width = 0;
+ int count = 0;
+ int columnCount = BaseColumns.Count;
+ for (int i = 0; i < columnCount; i++)
+ {
+ ICollection itemsSource = (ICollection)BaseColumns[i].ItemsSource;
+ if (itemsSource != null)
+ {
+ count = count < itemsSource.Count ? itemsSource.Count : count;
+ if (BaseColumns[i].SelectedIndex <= -1)
+ {
+ count = count + 1;
+ }
+ }
+
+ width += BaseColumns[i].Width <= 0 ? 100 : BaseColumns[i].Width;
+ }
+
+ _popup.WidthRequest = width < 200 ? 200 : width;
+ _popup.HeightRequest = BaseHeaderView.Height + BaseColumnHeaderView.Height + (ItemHeight * (count >= 5 ? 5 : count)) + FooterView.Height;
+ }
+
+ ///
+ /// Method triggered while the popup closing.
+ ///
+ /// The popup instance.
+ /// Closing event argument.
+ void OnPopupClosing(object? sender, CancelEventArgs e)
+ {
+ e.Cancel = RaisePopupClosingEvent();
+ }
+
+ ///
+ /// Method raises while the popup event closing.
+ ///
+ /// Returns whether to cancel closing of the popup.
+ bool RaisePopupClosingEvent()
+ {
+ if (Closing != null)
+ {
+ CancelEventArgs popupClosingEventArgs = new CancelEventArgs();
+ OnPopupClosing(popupClosingEventArgs);
+ return popupClosingEventArgs.Cancel;
+ }
+ else
+ {
+ return false;
+ }
+ }
+
+ ///
+ /// Method triggered while the popup closed.
+ ///
+ /// The popup instance.
+ /// Closed event argument.
+ void OnPopupClosed(object? sender, EventArgs e)
+ {
+ IsOpen = false;
+ OnPopupClosed(e);
+ }
+
+ ///
+ /// Method triggered while the popup opened.
+ ///
+ /// The popup instance.
+ /// Opened event argument.
+ void OnPopupOpened(object? sender, EventArgs e)
+ {
+ OnPopupOpened(e);
+ }
+
+ #endregion
+
+ #region Override Methods
+
+ ///
+ /// Method used to arrange the children with in the bounds.
+ ///
+ /// The size of the layout.
+ /// Returns layout size.
+ protected override Size ArrangeContent(Rect bounds)
+ {
+ foreach (var child in Children)
+ {
+ child.Arrange(new Rect(bounds.Left, bounds.Top, bounds.Width, bounds.Height));
+ }
+
+ return bounds.Size;
+ }
+
+ ///
+ /// Method used to measure the children based on width and height value.
+ ///
+ /// The maximum width request of the layout.
+ /// The maximum height request of the layout.
+ /// Returns maximum size of the layout.
+ protected override Size MeasureContent(double widthConstraint, double heightConstraint)
+ {
+ UpdateFormatBasedOnCulture();
+ double width = double.IsFinite(widthConstraint) ? widthConstraint : _minWidth;
+ double height = double.IsFinite(heightConstraint) ? heightConstraint : _minHeight;
+
+#if MACCATALYST || IOS
+ if (!_isPickerViewLoaded)
+ {
+ width = DesiredSize.Width <= 0 ? width : DesiredSize.Width;
+ height = DesiredSize.Height <= 0 ? height : DesiredSize.Height;
+ _isPickerViewLoaded = true;
+ }
+#endif
+ if (double.IsInfinity(heightConstraint) && HeightRequest == -1 && Mode == PickerMode.Default)
+ {
+ HeightRequest = height;
+ }
+
+ foreach (var child in Children)
+ {
+ child.Measure(width, height);
+ }
+
+ Size measuredSize = new Size(width, height);
+ if (_availableSize != measuredSize)
+ {
+ _availableSize = measuredSize;
+ }
+
+ if (Mode != PickerMode.Default)
+ {
+ return Size.Zero;
+ }
+
+ return measuredSize;
+ }
+
+ ///
+ /// Method triggers when the time picker property changed
+ ///
+ /// The property name.
+ protected override void OnPropertyChanged(string? propertyName = null)
+ {
+ if (propertyName == nameof(IsVisible) && Mode != PickerMode.Default)
+ {
+ //// If the picker is not visible, we have to close the picker popup only when it is opened previously.
+ if (!IsVisible)
+ {
+ _isPickerPreviouslyOpened = IsOpen;
+ if (_isPickerPreviouslyOpened)
+ {
+ ClosePickerPopup();
+ }
+ }
+ //// If the picker is visible, we have to open the picker popup only when it is opened initally.
+ else if (IsOpen && IsVisible)
+ {
+ AddPickerToPopup();
+ ShowPopup();
+ }
+ //// If the picker is visible, we have to open the picker popup only when it is opened previously.
+ else if (IsVisible && _isPickerPreviouslyOpened)
+ {
+ IsOpen = true;
+ }
+ else
+ {
+ ClosePickerPopup();
+ }
+ }
+
+ base.OnPropertyChanged(propertyName);
+ }
+
+ #endregion
+
+ #region Virtual Methods
+
+ ///
+ /// Method to wire the events.
+ ///
+ protected virtual void Initialize()
+ {
+ // Wire events for header view properties.
+ if (BaseHeaderView != null)
+ {
+ SetInheritedBindingContext(BaseHeaderView, BindingContext);
+ BaseHeaderView.PickerPropertyChanged += OnHeaderSettingsPropertyChanged;
+ if (BaseHeaderView.TextStyle != null)
+ {
+ SetInheritedBindingContext(BaseHeaderView.TextStyle, BindingContext);
+ BaseHeaderView.TextStyle.PropertyChanged += OnHeaderTextStylePropertyChanged;
+ }
+
+ if (BaseHeaderView.SelectionTextStyle != null)
+ {
+ SetInheritedBindingContext(BaseHeaderView.SelectionTextStyle, BindingContext);
+ BaseHeaderView.SelectionTextStyle.PropertyChanged += OnHeaderSelectionTextStylePropertyChanged;
+ }
+ }
+
+ // Wire events for footer view properties.
+ if (FooterView != null)
+ {
+ SetInheritedBindingContext(FooterView, BindingContext);
+ FooterView.PickerPropertyChanged += OnFooterSettingsPropertyChanged;
+ if (FooterView.TextStyle != null)
+ {
+ SetInheritedBindingContext(FooterView.TextStyle, BindingContext);
+ FooterView.TextStyle.PropertyChanged += OnFooterTextStylePropertyChanged;
+ }
+ }
+
+ if (SelectedTextStyle != null)
+ {
+ SetInheritedBindingContext(SelectedTextStyle, BindingContext);
+ SelectedTextStyle.PropertyChanged += OnSelectedTextStylePropertyChanged;
+ }
+
+ if (TextStyle != null)
+ {
+ SetInheritedBindingContext(TextStyle, BindingContext);
+ TextStyle.PropertyChanged += OnUnSelectedTextStylePropertyChanged;
+ }
+
+ if (DisabledTextStyle != null)
+ {
+ SetInheritedBindingContext(DisabledTextStyle, BindingContext);
+ DisabledTextStyle.PropertyChanged += OnDisabledTextStylePropertyChanged;
+ }
+
+ if (BaseColumns != null)
+ {
+ BaseColumns.CollectionChanged += OnColumnsCollectionChanged;
+ }
+
+ if (SelectionView != null)
+ {
+ SelectionView.PropertyChanged += OnSelectionViewPropertyChanged;
+ }
+
+ if (BaseColumnHeaderView != null)
+ {
+ SetInheritedBindingContext(BaseColumnHeaderView, BindingContext);
+ BaseColumnHeaderView.PickerPropertyChanged += OnColumnHeaderViewPropertyChanged;
+ if (BaseColumnHeaderView.TextStyle != null)
+ {
+ SetInheritedBindingContext(BaseColumnHeaderView.TextStyle, BindingContext);
+ BaseColumnHeaderView.TextStyle.PropertyChanged += OnColumnHeaderTextStylePropertyChanged;
+ }
+ }
+
+ PropertyChanged += OnPickerPropertyChanged;
+ AddChildren();
+ }
+
+ ///
+ /// Triggers while the popup opening or switched from popup to default.
+ ///
+ protected virtual void OnPickerLoading()
+ {
+ }
+
+ ///
+ /// Triggers while the header button clicked.
+ ///
+ /// Index of the header button.
+ protected virtual void OnHeaderButtonClicked(int index)
+ {
+ }
+
+ ///
+ /// Triggers when the picker popup closed.
+ ///
+ /// The event arguments.
+ protected virtual void OnPopupClosed(EventArgs e)
+ {
+ }
+
+ ///
+ /// Triggers when the picker popup closing.
+ ///
+ /// The event arguments.
+ protected virtual void OnPopupClosing(CancelEventArgs e)
+ {
+ }
+
+ ///
+ /// Triggers when the picker popup opened.
+ ///
+ /// The event arguments.
+ protected virtual void OnPopupOpened(EventArgs e)
+ {
+ }
+
+ ///
+ /// Triggers when the ok button clicked.
+ ///
+ /// The event arguments.
+ protected virtual void OnOkButtonClicked(EventArgs e)
+ {
+ }
+
+ ///
+ /// Triggers when the cancel button clicked.
+ ///
+ /// The event arguments.
+ protected virtual void OnCancelButtonClicked(EventArgs e)
+ {
+ }
+
+ #endregion
+
+ #region Interface Implementation
+
+ ///
+ /// Method to update the picker tapped item index and scroll view rendering based on the tapped item index.
+ ///
+ /// The tapped index.
+ /// The column child index.
+ /// Check whether is initial loading or not.
+ void IPicker.UpdateSelectedIndexValue(int tappedIndex, int childIndex, bool isInitialLoading)
+ {
+ if (BaseColumns.Count <= childIndex)
+ {
+ return;
+ }
+
+ PickerColumn pickerColumn = BaseColumns[childIndex];
+
+ //// Handles the scenario where the selected index of a picker column matches the tapped index,
+ //// the selected item is null, it's not the initial loading phase, and the parent is an instance of SfDatePicker, SfTimePicker, or SfDateTimePicker.
+ //// In such cases, it resets the selected date/time to the previous selected value
+ //// to avoid triggering UI updates when setting the selected date/time as null during the initial loading phase.
+ if (pickerColumn.SelectedIndex == tappedIndex && pickerColumn.SelectedItem == null && !isInitialLoading && pickerColumn.Parent is SfDatePicker or SfTimePicker or SfDateTimePicker)
+ {
+ if (pickerColumn.Parent is SfDatePicker datePicker)
+ {
+ datePicker.SelectedDate = _previousSelectedDateTime.Date;
+ }
+ else if (pickerColumn.Parent is SfTimePicker timePicker)
+ {
+ timePicker.SelectedTime = _previousSelectedDateTime.TimeOfDay;
+ }
+ else if (pickerColumn.Parent is SfDateTimePicker dateTimePicker)
+ {
+ dateTimePicker.SelectedDate = _previousSelectedDateTime;
+ }
+
+ return;
+ }
+
+ if (pickerColumn.SelectedIndex == tappedIndex)
+ {
+ return;
+ }
+
+ pickerColumn.SelectedIndex = tappedIndex;
+ }
+
+ ///
+ /// Method to invoke the after confirm button clicked and it invokes the confirm button clicked event.
+ ///
+ void IFooterView.OnConfirmButtonClicked()
+ {
+ OnOkButtonClicked(new EventArgs { });
+ }
+
+ ///
+ /// Method to invoke the after cancel button clicked and it invokes the cancel button clicked event.
+ ///
+ void IFooterView.OnCancelButtonClicked()
+ {
+ OnCancelButtonClicked(new EventArgs { });
+ }
+
+ ///
+ /// Method to update the after date button clicked.
+ ///
+ void IHeaderView.OnDateButtonClicked()
+ {
+ OnHeaderButtonClicked(0);
+ }
+
+ ///
+ /// Method to update the after time button clicked.
+ ///
+ void IHeaderView.OnTimeButtonClicked()
+ {
+ OnHeaderButtonClicked(1);
+ }
+
+ #endregion
+ }
+}
\ No newline at end of file
diff --git a/maui/src/Picker/PickerStackLayout.cs b/maui/src/Picker/PickerStackLayout.cs
new file mode 100644
index 00000000..7f4b1a09
--- /dev/null
+++ b/maui/src/Picker/PickerStackLayout.cs
@@ -0,0 +1,132 @@
+namespace Syncfusion.Maui.Toolkit.Picker
+{
+ ///
+ /// Represents the picker stack layout class.
+ ///
+ internal class PickerStackLayout : SfView
+ {
+ #region Fields
+
+ ///
+ /// The picker info.
+ ///
+ readonly IPicker _pickerInfo;
+
+ #endregion
+
+ #region Constructor
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The picker info.
+ internal PickerStackLayout(IPicker pickerInfo)
+ {
+ _pickerInfo = pickerInfo;
+ //// TODO: In windows, child layouts get the parent flow direction hence while arranging child elements the framework automatically reverses the direction.
+ //// In other platforms, child elements' flow direction is not set and always has left flow direction so we have to manually arrange child elements.
+ //// In the Windows platform, the draw view is still needed to configure manually and not take the parent direction.
+ //// Due to this inconsistent behavior in windows, set flow direction to LTR for the inner layout of the calendar, so we manually arrange and draw child elements for all the platforms as common.
+ //// In the Windows platform, the draw view does not arrange based on the flow direction. https://github.com/dotnet/maui/issues/6978
+ FlowDirection = FlowDirection.LeftToRight;
+ }
+
+ #endregion
+
+ #region Internal Methods
+
+ ///
+ /// Trigger to invalidate the view. it was triggered while changing the header or footer view height.
+ ///
+ internal void InvalidateView()
+ {
+ InvalidateMeasure();
+ }
+
+ #endregion
+
+ #region Private Methods
+
+ ///
+ /// Method to get the valid height of header or footer view.
+ ///
+ /// The height of the footer or header view.
+ /// Returns the height.
+ static double GetValidHeight(double height)
+ {
+ if (height > 0)
+ {
+ return height;
+ }
+
+ return 0;
+ }
+
+ #endregion
+
+ #region Override Methods
+
+ ///
+ /// Method used to measure the children based on width and height value.
+ ///
+ /// The maximum width request of the layout.
+ /// The maximum height request of the layout.
+ /// Returns the maximum size of the layout.
+ protected override Size MeasureContent(double widthConstraint, double heightConstraint)
+ {
+ double width = double.IsFinite(widthConstraint) ? widthConstraint : 300;
+ double height = double.IsFinite(heightConstraint) ? heightConstraint : 300;
+ double headerHeight = GetValidHeight(_pickerInfo.HeaderView.Height);
+ double footerHeight = GetValidHeight(_pickerInfo.FooterView.Height);
+ foreach (var child in Children)
+ {
+ if (child is HeaderLayout)
+ {
+ child.Measure(width, headerHeight);
+ }
+ else if (child is PickerContainer)
+ {
+ child.Measure(width, height - footerHeight - headerHeight);
+ }
+ else if (child is FooterLayout)
+ {
+ child.Measure(width, footerHeight);
+ }
+ }
+
+ return new Size(width, height);
+ }
+
+ ///
+ /// Method used to arrange the children with in the bounds.
+ ///
+ /// The size of the layout.
+ /// Returns the layout size.
+ protected override Size ArrangeContent(Rect bounds)
+ {
+ double width = bounds.Width;
+ double topPosition = bounds.Top;
+ double headerHeight = GetValidHeight(_pickerInfo.HeaderView.Height);
+ double footerHeight = GetValidHeight(_pickerInfo.FooterView.Height);
+ foreach (var child in Children)
+ {
+ if (child is HeaderLayout)
+ {
+ child.Arrange(new Rect(bounds.Left, topPosition, width, headerHeight));
+ }
+ else if (child is PickerContainer)
+ {
+ child.Arrange(new Rect(bounds.Left, topPosition + headerHeight, width, bounds.Height - headerHeight - footerHeight));
+ }
+ else if (child is FooterLayout)
+ {
+ child.Arrange(new Rect(bounds.Left, topPosition + bounds.Height - footerHeight, width, footerHeight));
+ }
+ }
+
+ return bounds.Size;
+ }
+
+ #endregion
+ }
+}
\ No newline at end of file
diff --git a/maui/src/Picker/Resources/SfPickerResources.cs b/maui/src/Picker/Resources/SfPickerResources.cs
new file mode 100644
index 00000000..75eadbe9
--- /dev/null
+++ b/maui/src/Picker/Resources/SfPickerResources.cs
@@ -0,0 +1,53 @@
+using System.Globalization;
+using Syncfusion.Maui.Toolkit.Localization;
+
+namespace Syncfusion.Maui.Toolkit.Picker
+{
+ ///
+ /// A localization resource accessor that returns the localized string based on culture for the .
+ ///
+ public class SfPickerResources : LocalizationResourceAccessor
+ {
+ #region Property
+
+ ///
+ /// Gets the culture info.
+ ///
+ /// The culture.
+ internal static CultureInfo CultureInfo
+ {
+ get
+ {
+ return CultureInfo.CurrentUICulture;
+ }
+ }
+
+ #endregion
+
+ #region Internal Method
+
+ ///
+ /// Gets the localized string.
+ ///
+ /// Text type.
+ /// The string.
+ internal static string GetLocalizedString(string text)
+ {
+ string? value = string.Empty;
+ if (ResourceManager != null)
+ {
+ Culture = CultureInfo.CurrentUICulture;
+ value = GetString(text);
+ }
+
+ if (string.IsNullOrEmpty(value))
+ {
+ return text;
+ }
+
+ return value;
+ }
+
+ #endregion
+ }
+}
\ No newline at end of file
diff --git a/maui/src/Picker/SfDatePicker.cs b/maui/src/Picker/SfDatePicker.cs
new file mode 100644
index 00000000..83fd463f
--- /dev/null
+++ b/maui/src/Picker/SfDatePicker.cs
@@ -0,0 +1,2338 @@
+using System.Collections.ObjectModel;
+using System.ComponentModel;
+using System.Globalization;
+using System.Windows.Input;
+using Syncfusion.Maui.Toolkit.Themes;
+
+namespace Syncfusion.Maui.Toolkit.Picker
+{
+ ///
+ /// Initializes a new instance of the class that represents a control, used to select the date with in specified date range.
+ ///
+ public class SfDatePicker : PickerBase, IParentThemeElement, IThemeElement
+ {
+ #region Fields
+
+ ///
+ /// Holds the day column information.
+ ///
+ PickerColumn _dayColumn;
+
+ ///
+ /// Holds the month column information.
+ ///
+ PickerColumn _monthColumn;
+
+ ///
+ /// Holds the year column information.
+ ///
+ PickerColumn _yearColumn;
+
+ ///
+ /// Holds the picker column collection.
+ ///
+ ObservableCollection _columns;
+
+ #endregion
+
+ #region Bindable Properties
+
+ ///
+ /// Identifies the dependency property.
+ ///
+ ///
+ /// The identifier for dependency property.
+ ///
+ public static readonly BindableProperty HeaderViewProperty =
+ BindableProperty.Create(
+ nameof(HeaderView),
+ typeof(PickerHeaderView),
+ typeof(SfDatePicker),
+ defaultValueCreator: bindable => new PickerHeaderView(),
+ propertyChanged: OnHeaderViewChanged);
+
+ ///
+ /// Identifies the dependency property.
+ ///
+ ///
+ /// The identifier for dependency property.
+ ///
+ public static readonly BindableProperty ColumnHeaderViewProperty =
+ BindableProperty.Create(
+ nameof(ColumnHeaderView),
+ typeof(DatePickerColumnHeaderView),
+ typeof(SfDatePicker),
+ defaultValueCreator: bindable => new DatePickerColumnHeaderView(),
+ propertyChanged: OnColumnHeaderViewChanged);
+
+ ///
+ /// Identifies the dependency property.
+ ///
+ ///
+ /// The identifier for dependency property.
+ ///
+ public static readonly BindableProperty SelectedDateProperty =
+ BindableProperty.Create(
+ nameof(SelectedDate),
+ typeof(DateTime?),
+ typeof(SfDatePicker),
+ defaultValueCreator: bindable => DateTime.Now.Date,
+ propertyChanged: OnSelectedDatePropertyChanged);
+
+ ///
+ /// Identifies the dependency property.
+ ///
+ ///
+ /// The identifier for dependency property.
+ ///
+ public static readonly BindableProperty DayIntervalProperty =
+ BindableProperty.Create(
+ nameof(DayInterval),
+ typeof(int),
+ typeof(SfDatePicker),
+ 1,
+ propertyChanged: OnDayIntervalPropertyChanged);
+
+ ///
+ /// Identifies the dependency property.
+ ///
+ ///
+ /// The identifier for dependency property.
+ ///
+ public static readonly BindableProperty MonthIntervalProperty =
+ BindableProperty.Create(
+ nameof(MonthInterval),
+ typeof(int),
+ typeof(SfDatePicker),
+ 1,
+ propertyChanged: OnMonthIntervalPropertyChanged);
+
+ ///
+ /// Identifies the dependency property.
+ ///
+ ///
+ /// The identifier for dependency property.
+ ///
+ public static readonly BindableProperty YearIntervalProperty =
+ BindableProperty.Create(
+ nameof(YearInterval),
+ typeof(int),
+ typeof(SfDatePicker),
+ 1,
+ propertyChanged: OnYearIntervalPropertyChanged);
+
+ ///
+ /// Identifies the dependency property.
+ ///
+ ///
+ /// The identifier for dependency property.
+ ///
+ public static readonly BindableProperty FormatProperty =
+ BindableProperty.Create(
+ nameof(Format),
+ typeof(PickerDateFormat),
+ typeof(SfDatePicker),
+ PickerDateFormat.yyyy_MM_dd,
+ propertyChanged: OnFormatPropertyChanged);
+
+ ///
+ /// Identifies the dependency property.
+ ///
+ ///
+ /// The identifier for dependency property.
+ ///
+ public static readonly BindableProperty MinimumDateProperty =
+ BindableProperty.Create(
+ nameof(MinimumDate),
+ typeof(DateTime),
+ typeof(SfDatePicker),
+ defaultValueCreator: bindable => new DateTime(1900, 01, 01),
+ propertyChanged: OnMinimumDatePropertyChanged);
+
+ ///
+ /// Identifies the dependency property.
+ ///
+ ///
+ /// The identifier for dependency property.
+ ///
+ public static readonly BindableProperty MaximumDateProperty =
+ BindableProperty.Create(
+ nameof(MaximumDate),
+ typeof(DateTime),
+ typeof(SfDatePicker),
+ defaultValueCreator: bindable => new DateTime(2100, 12, 31),
+ propertyChanged: OnMaximumDatePropertyChanged);
+
+ ///
+ /// Identifies the dependency property.
+ ///
+ ///
+ /// The identifier for dependency property.
+ ///
+ public static readonly BindableProperty SelectionChangedCommandProperty =
+ BindableProperty.Create(
+ nameof(SelectionChangedCommand),
+ typeof(ICommand),
+ typeof(SfDatePicker),
+ null);
+
+ ///
+ /// Identifies the dependency property.
+ ///
+ ///
+ /// The identifier for dependency property.
+ ///
+ public static readonly BindableProperty BlackoutDatesProperty =
+ BindableProperty.Create(
+ nameof(BlackoutDates),
+ typeof(ObservableCollection),
+ typeof(SfDatePicker),
+ defaultValueCreator: bindable => new ObservableCollection(),
+ propertyChanged: OnBlackOutDatesPropertyChanged);
+
+ #endregion
+
+ #region Internal Bindable Properties
+
+ ///
+ /// Identifies the bindable property.
+ ///
+ ///
+ /// The identifier for bindable property.
+ ///
+ internal static readonly BindableProperty DatePickerBackgroundProperty =
+ BindableProperty.Create(
+ nameof(DatePickerBackground),
+ typeof(Color),
+ typeof(SfDatePicker),
+ defaultValueCreator: bindable => Color.FromArgb("#EEE8F4"),
+ propertyChanged: OnDatePickerBackgroundChanged);
+
+ ///
+ /// Identifies the dependency property.
+ ///
+ ///
+ /// Identifies the bindable property.
+ ///
+ internal static readonly BindableProperty HeaderBackgroundProperty =
+ BindableProperty.Create(
+ nameof(HeaderBackground),
+ typeof(Brush),
+ typeof(SfDatePicker),
+ defaultValueCreator: bindable => new SolidColorBrush(Color.FromArgb("#F7F2FB")),
+ propertyChanged: OnHeaderBackgroundChanged);
+
+ ///
+ /// Identifies the dependency property.
+ ///
+ ///
+ /// Identifies the bindable property.
+ ///
+ internal static readonly BindableProperty FooterBackgroundProperty =
+ BindableProperty.Create(
+ nameof(FooterBackground),
+ typeof(Brush),
+ typeof(SfDatePicker),
+ defaultValueCreator: bindable => Brush.Transparent,
+ propertyChanged: OnFooterBackgroundChanged);
+
+ ///
+ /// Identifies the dependency property.
+ ///
+ ///
+ /// Identifies the bindable property.
+ ///
+ internal static readonly BindableProperty SelectionBackgroundProperty =
+ BindableProperty.Create(
+ nameof(SelectionBackground),
+ typeof(Brush),
+ typeof(SfDatePicker),
+ defaultValueCreator: bindable => new SolidColorBrush(Color.FromArgb("#6750A4")),
+ propertyChanged: OnSelectionBackgroundChanged);
+
+ ///
+ /// Identifies the dependency property.
+ ///
+ ///
+ /// Identifies the bindable property.
+ ///
+ internal static readonly BindableProperty SelectionStrokeColorProperty =
+ BindableProperty.Create(
+ nameof(SelectionStrokeColor),
+ typeof(Color),
+ typeof(SfDatePicker),
+ defaultValueCreator: bindable => Colors.Transparent,
+ propertyChanged: OnSelectionStrokeColorChanged);
+
+ ///
+ /// Identifies the dependency property.
+ ///
+ ///
+ /// The identifier for dependency property.
+ ///
+ internal static readonly BindableProperty SelectionCornerRadiusProperty =
+ BindableProperty.Create(
+ nameof(SelectionCornerRadius),
+ typeof(CornerRadius),
+ typeof(SfDatePicker),
+ defaultValueCreator: bindable => new CornerRadius(20),
+ propertyChanged: OnSelectionCornerRadiusChanged);
+
+ ///
+ /// Identifies the dependency property.
+ ///
+ ///
+ /// Identifies the bindable property.
+ ///
+ internal static readonly BindableProperty HeaderDividerColorProperty =
+ BindableProperty.Create(
+ nameof(HeaderDividerColor),
+ typeof(Color),
+ typeof(SfDatePicker),
+ defaultValueCreator: bindable => Color.FromArgb("#CAC4D0"),
+ propertyChanged: OnHeaderDividerColorChanged);
+
+ ///
+ /// Identifies the dependency property.
+ ///
+ ///
+ /// Identifies the bindable property.
+ ///
+ internal static readonly BindableProperty FooterDividerColorProperty =
+ BindableProperty.Create(
+ nameof(FooterDividerColor),
+ typeof(Color),
+ typeof(SfDatePicker),
+ defaultValueCreator: bindable => Color.FromArgb("#CAC4D0"),
+ propertyChanged: OnFooterDividerColorChanged);
+
+ ///
+ /// Identifies the dependency property.
+ ///
+ ///
+ /// The identifier for dependency property.
+ ///
+ internal static readonly BindableProperty HeaderTextColorProperty =
+ BindableProperty.Create(
+ nameof(HeaderTextColor),
+ typeof(Color),
+ typeof(SfDatePicker),
+ defaultValueCreator: bindable => Color.FromArgb("#49454F"),
+ propertyChanged: OnHeaderTextColorChanged);
+
+ ///
+ /// Identifies the dependency property.
+ ///
+ ///
+ /// The identifier for dependency property.
+ ///
+ internal static readonly BindableProperty HeaderFontSizeProperty =
+ BindableProperty.Create(
+ nameof(HeaderFontSize),
+ typeof(double),
+ typeof(SfDatePicker),
+ defaultValueCreator: bindable => 16d,
+ propertyChanged: OnHeaderFontSizeChanged);
+
+ ///
+ /// Identifies the dependency property.
+ ///
+ ///
+ /// The identifier for dependency property.
+ ///
+ internal static readonly BindableProperty FooterTextColorProperty =
+ BindableProperty.Create(
+ nameof(FooterTextColor),
+ typeof(Color),
+ typeof(SfDatePicker),
+ defaultValueCreator: bindable => Color.FromArgb("#6750A4"),
+ propertyChanged: OnFooterTextColorChanged);
+
+ ///
+ /// Identifies the dependency property.
+ ///
+ ///
+ /// The identifier for dependency property.
+ ///
+ internal static readonly BindableProperty FooterFontSizeProperty =
+ BindableProperty.Create(
+ nameof(FooterFontSize),
+ typeof(double),
+ typeof(SfDatePicker),
+ defaultValueCreator: bindable => 14d,
+ propertyChanged: OnFooterFontSizeChanged);
+
+ ///
+ /// Identifies the dependency property.
+ ///
+ ///
+ /// The identifier for dependency property.
+ ///
+ internal static readonly BindableProperty SelectedTextColorProperty =
+ BindableProperty.Create(
+ nameof(SelectedTextColor),
+ typeof(Color),
+ typeof(SfDatePicker),
+ defaultValueCreator: bindable => Colors.White,
+ propertyChanged: OnSelectedTextColorChanged);
+
+ ///
+ /// Identifies the dependency property.
+ ///
+ ///
+ /// The identifier for dependency property.
+ ///
+ internal static readonly BindableProperty SelectionTextColorProperty =
+ BindableProperty.Create(
+ nameof(SelectionTextColor),
+ typeof(Color),
+ typeof(SfPicker),
+ defaultValueCreator: bindable => Color.FromArgb("#6750A4"),
+ propertyChanged: OnSelectedTextColorChanged);
+
+ ///
+ /// Identifies the dependency property.
+ ///
+ ///
+ /// The identifier for dependency property.
+ ///
+ internal static readonly BindableProperty SelectedFontSizeProperty =
+ BindableProperty.Create(
+ nameof(SelectedFontSize),
+ typeof(double),
+ typeof(SfDatePicker),
+ defaultValueCreator: bindable => 16d,
+ propertyChanged: OnSelectedFontSizeChanged);
+
+ ///
+ /// Identifies the dependency property.
+ ///
+ ///
+ /// The identifier for dependency property.
+ ///
+ internal static readonly BindableProperty NormalTextColorProperty =
+ BindableProperty.Create(
+ nameof(NormalTextColor),
+ typeof(Color),
+ typeof(SfDatePicker),
+ defaultValueCreator: bindable => Color.FromArgb("#1C1B1F"),
+ propertyChanged: OnNormalTextColorChanged);
+
+ ///
+ /// Identifies the dependency property.
+ ///
+ ///
+ /// The identifier for dependency property.
+ ///
+ internal static readonly BindableProperty NormalFontSizeProperty =
+ BindableProperty.Create(
+ nameof(NormalFontSize),
+ typeof(double),
+ typeof(SfDatePicker),
+ defaultValueCreator: bindable => 16d,
+ propertyChanged: OnNormalFontSizeChanged);
+
+ ///
+ /// Identifies the dependency property.
+ ///
+ ///
+ /// The identifier for dependency property.
+ ///
+ internal static readonly BindableProperty DisabledTextColorProperty =
+ BindableProperty.Create(
+ nameof(DisabledTextColor),
+ typeof(Color),
+ typeof(SfDatePicker),
+ defaultValueCreator: bindable => Color.FromArgb("#611C1B1F"),
+ propertyChanged: OnDisabledTextColorChanged);
+
+ #endregion
+
+ #region Constructor
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ public SfDatePicker()
+ {
+ _dayColumn = new PickerColumn();
+ _monthColumn = new PickerColumn();
+ _yearColumn = new PickerColumn();
+ _columns = new ObservableCollection();
+ Initialize();
+ GeneratePickerColumns();
+ BaseColumns = _columns;
+ SelectionIndexChanged += OnPickerSelectionIndexChanged;
+ BlackoutDates.CollectionChanged += OnBlackoutDates_CollectionChanged;
+ BackgroundColor = DatePickerBackground;
+ Dispatcher.Dispatch(() =>
+ {
+ InitializeTheme();
+ });
+ ColumnHeaderView.Parent = this;
+ }
+
+ #endregion
+
+ #region Properties
+
+ ///
+ /// Gets or sets the value of header view. This property can be used to customize the header in SfDatePicker.
+ ///
+ ///
+ /// The following example demonstrates how to customize the header view of SfDatePicker.
+ ///
+ ///
+ ///
+ ///
+ public PickerHeaderView HeaderView
+ {
+ get { return (PickerHeaderView)GetValue(HeaderViewProperty); }
+ set { SetValue(HeaderViewProperty, value); }
+ }
+
+ ///
+ /// Gets or sets the value of column header view. This property can be used to customize the header column in SfDatePicker.
+ ///
+ /// ///
+ /// The following example demonstrates how to customize the column header view of SfDatePicker.
+ ///
+ ///
+ ///
+ ///
+ public DatePickerColumnHeaderView ColumnHeaderView
+ {
+ get { return (DatePickerColumnHeaderView)GetValue(ColumnHeaderViewProperty); }
+ set { SetValue(ColumnHeaderViewProperty, value); }
+ }
+
+ ///
+ /// Gets or sets the date picker selection date in SfDatePicker.
+ ///
+ /// The default value of is .
+ ///
+ /// The following examples demonstrate how to set the selected date in SfDatePicker.
+ /// # [XAML](#tab/tabid-1)
+ ///
+ ///
+ /// ]]>
+ ///
+ /// # [C#](#tab/tabid-2)
+ ///
+ /// SfDatePicker datePicker = new SfDatePicker();
+ /// datePicker.SelectedDate = new DateTime(2023, 6, 15);
+ ///
+ ///
+ public DateTime? SelectedDate
+ {
+ get { return (DateTime?)GetValue(SelectedDateProperty); }
+ set { SetValue(SelectedDateProperty, value); }
+ }
+
+ ///
+ /// Gets or sets the day interval in SfDatePicker.
+ ///
+ /// The default value of is 1.
+ ///
+ /// The following examples demonstrate how to set the day interval in SfDatePicker.
+ /// # [XAML](#tab/tabid-3)
+ ///
+ ///
+ /// ]]>
+ ///
+ /// # [C#](#tab/tabid-4)
+ ///
+ /// SfDatePicker datePicker = new SfDatePicker();
+ /// datePicker.DayInterval = 2;
+ ///
+ ///
+ public int DayInterval
+ {
+ get { return (int)GetValue(DayIntervalProperty); }
+ set { SetValue(DayIntervalProperty, value); }
+ }
+
+ ///
+ /// Gets or sets the month interval in SfDatePicker.
+ ///
+ /// The default value of is 1.
+ ///
+ /// The following examples demonstrate how to set the month interval in SfDatePicker.
+ /// # [XAML](#tab/tabid-5)
+ ///
+ ///
+ /// ]]>
+ ///
+ /// # [C#](#tab/tabid-6)
+ ///
+ /// SfDatePicker datePicker = new SfDatePicker();
+ /// datePicker.MonthInterval = 2;
+ ///
+ ///
+ public int MonthInterval
+ {
+ get { return (int)GetValue(MonthIntervalProperty); }
+ set { SetValue(MonthIntervalProperty, value); }
+ }
+
+ ///
+ /// Gets or sets the year interval in SfDatePicker.
+ ///
+ /// The default value of is 1.
+ ///
+ /// The following examples demonstrate how to set the year interval in SfDatePicker.
+ /// # [XAML](#tab/tabid-7)
+ ///
+ ///
+ /// ]]>
+ ///
+ /// # [C#](#tab/tabid-8)
+ ///
+ /// SfDatePicker datePicker = new SfDatePicker();
+ /// datePicker.YearInterval = 2;
+ ///
+ ///
+ public int YearInterval
+ {
+ get { return (int)GetValue(YearIntervalProperty); }
+ set { SetValue(YearIntervalProperty, value); }
+ }
+
+ ///
+ /// Gets or sets the picker date format in SfDatePicker.
+ ///
+ /// The default value of is .
+ ///
+ /// The following examples demonstrate how to set the format in SfDatePicker.
+ /// # [XAML](#tab/tabid-9)
+ ///
+ ///
+ /// ]]>
+ ///
+ /// # [C#](#tab/tabid-10)
+ ///
+ /// SfDatePicker datePicker = new SfDatePicker();
+ /// datePicker.Format = PickerDateFormat.dd_MM_yyyy;
+ ///
+ ///
+ public PickerDateFormat Format
+ {
+ get { return (PickerDateFormat)GetValue(FormatProperty); }
+ set { SetValue(FormatProperty, value); }
+ }
+
+ ///
+ /// Gets or sets the minimum date in SfDatePicker.
+ ///
+ /// The default value of is "DateTime(1900, 01, 01)".
+ ///
+ /// The following examples demonstrate how to set the minimum date in SfDatePicker.
+ /// # [XAML](#tab/tabid-11)
+ ///
+ ///
+ /// ]]>
+ ///
+ /// # [C#](#tab/tabid-12)
+ ///
+ /// SfDatePicker datePicker = new SfDatePicker();
+ /// datePicker.MinimumDate = new DateTime(2023, 1, 1);
+ ///
+ ///
+ public DateTime MinimumDate
+ {
+ get { return (DateTime)GetValue(MinimumDateProperty); }
+ set { SetValue(MinimumDateProperty, value); }
+ }
+
+ ///
+ /// Gets or sets the maximum date in SfDatePicker.
+ ///
+ /// The default value of is "DateTime(2100, 12, 31)".
+ ///
+ /// The following examples demonstrate how to set the maximum date in SfDatePicker.
+ /// # [XAML](#tab/tabid-13)
+ ///
+ ///
+ /// ]]>
+ ///
+ /// # [C#](#tab/tabid-14)
+ ///
+ /// SfDatePicker datePicker = new SfDatePicker();
+ /// datePicker.MaximumDate = new DateTime(2023, 12, 31);
+ ///
+ ///
+ public DateTime MaximumDate
+ {
+ get { return (DateTime)GetValue(MaximumDateProperty); }
+ set { SetValue(MaximumDateProperty, value); }
+ }
+
+ ///
+ /// Gets or sets the selection changed command in SfDatePicker.
+ ///
+ /// The default value of is null.
+ ///
+ /// The following example demonstrates how to set the selection changed command in SfDatePicker.
+ /// # [XAML](#tab/tabid-15)
+ ///
+ ///
+ ///
+ ///
+ ///
+ /// ]]>
+ /// # [C#](#tab/tabid-16)
+ ///
+ ///
+ public ICommand SelectionChangedCommand
+ {
+ get { return (ICommand)GetValue(SelectionChangedCommandProperty); }
+ set { SetValue(SelectionChangedCommandProperty, value); }
+ }
+
+ ///
+ /// Gets or sets the BlackoutDates in SfDatePicker.
+ ///
+ /// The selection view will not be applicable when setting blackout dates.
+ ///
+ /// The following examples demonstrate how to set the blackout dates in SfDatePicker.
+ /// # [XAML](#tab/tabid-17)
+ ///
+ ///
+ ///
+ /// 2001-08-10
+ /// 2001-08-12
+ /// 2001-08-14
+ /// 2001-08-17
+ /// 2001-08-18
+ /// 2001-08-20
+ ///
+ ///
+ /// ]]>
+ ///
+ /// # [C#](#tab/tabid-18)
+ ///
+ /// SfDatePicker picker = new SfDatePicker();
+ /// picker.BlackoutDates.Add(new DateTime(2001, 8, 10));
+ /// picker.BlackoutDates.Add(new DateTime(2001, 8, 12));
+ /// picker.BlackoutDates.Add(new DateTime(2001, 8, 14));
+ /// picker.BlackoutDates.Add(new DateTime(2001, 8, 17));
+ /// picker.BlackoutDates.Add(new DateTime(2001, 8, 18));
+ /// picker.BlackoutDates.Add(new DateTime(2001, 8, 20));
+ ///
+ ///
+ public ObservableCollection BlackoutDates
+ {
+ get { return (ObservableCollection)GetValue(BlackoutDatesProperty); }
+ set { SetValue(BlackoutDatesProperty, value); }
+ }
+
+ #endregion
+
+ #region Internal Properties
+
+ ///
+ /// Gets or sets the background color of the date picker.
+ ///
+ internal Color DatePickerBackground
+ {
+ get { return (Color)GetValue(DatePickerBackgroundProperty); }
+ set { SetValue(DatePickerBackgroundProperty, value); }
+ }
+
+ ///
+ /// Gets or sets the background of the header view in date picker.
+ ///
+ internal Brush HeaderBackground
+ {
+ get { return (Brush)GetValue(HeaderBackgroundProperty); }
+ set { SetValue(HeaderBackgroundProperty, value); }
+ }
+
+ ///
+ /// Gets or sets the background of the footer view in SfDatePicker.
+ ///
+ internal Brush FooterBackground
+ {
+ get { return (Brush)GetValue(FooterBackgroundProperty); }
+ set { SetValue(FooterBackgroundProperty, value); }
+ }
+
+ ///
+ /// Gets or sets the background of the selection view in SfDatePicker.
+ ///
+ internal Brush SelectionBackground
+ {
+ get { return (Brush)GetValue(SelectionBackgroundProperty); }
+ set { SetValue(SelectionBackgroundProperty, value); }
+ }
+
+ ///
+ /// Gets or sets the stroke color of the selection view in SfDatePicker.
+ ///
+ internal Color SelectionStrokeColor
+ {
+ get { return (Color)GetValue(SelectionStrokeColorProperty); }
+ set { SetValue(SelectionStrokeColorProperty, value); }
+ }
+
+ ///
+ /// Gets or sets the corner radius of the selection view in SfDatePicker.
+ ///
+ internal CornerRadius SelectionCornerRadius
+ {
+ get { return (CornerRadius)GetValue(SelectionCornerRadiusProperty); }
+ set { SetValue(SelectionCornerRadiusProperty, value); }
+ }
+
+ ///
+ /// Gets or sets the color of the header separator line in date picker.
+ ///
+ internal Color HeaderDividerColor
+ {
+ get { return (Color)GetValue(HeaderDividerColorProperty); }
+ set { SetValue(HeaderDividerColorProperty, value); }
+ }
+
+ ///
+ /// Gets or sets the background of the footer separator line background in SfDatePicker.
+ ///
+ internal Color FooterDividerColor
+ {
+ get { return (Color)GetValue(FooterDividerColorProperty); }
+ set { SetValue(FooterDividerColorProperty, value); }
+ }
+
+ ///
+ /// Gets or sets the header text color of the text style.
+ ///
+ internal Color HeaderTextColor
+ {
+ get { return (Color)GetValue(HeaderTextColorProperty); }
+ set { SetValue(HeaderTextColorProperty, value); }
+ }
+
+ ///
+ /// Gets or sets the header font size of the text style.
+ ///
+ internal double HeaderFontSize
+ {
+ get { return (double)GetValue(HeaderFontSizeProperty); }
+ set { SetValue(HeaderFontSizeProperty, value); }
+ }
+
+ ///
+ /// Gets or sets the footer text color of the text style.
+ ///
+ internal Color FooterTextColor
+ {
+ get { return (Color)GetValue(FooterTextColorProperty); }
+ set { SetValue(FooterTextColorProperty, value); }
+ }
+
+ ///
+ /// Gets or sets the footer font size of the text style.
+ ///
+ internal double FooterFontSize
+ {
+ get { return (double)GetValue(FooterFontSizeProperty); }
+ set { SetValue(FooterFontSizeProperty, value); }
+ }
+
+ ///
+ /// Gets or sets the selection text color of the text style.
+ ///
+ ///
+ /// This color applicable for default text display mode.
+ ///
+ internal Color SelectedTextColor
+ {
+ get { return (Color)GetValue(SelectedTextColorProperty); }
+ set { SetValue(SelectedTextColorProperty, value); }
+ }
+
+ ///
+ /// Gets or sets the selection text color of the text style.
+ ///
+ ///
+ /// This color is used for Fade, Shrink and FadeAndShrink mode.
+ ///
+ internal Color SelectionTextColor
+ {
+ get { return (Color)GetValue(SelectionTextColorProperty); }
+ set { SetValue(SelectionTextColorProperty, value); }
+ }
+
+ ///
+ /// Gets or sets the selection font size of the text style.
+ ///
+ internal double SelectedFontSize
+ {
+ get { return (double)GetValue(SelectedFontSizeProperty); }
+ set { SetValue(SelectedFontSizeProperty, value); }
+ }
+
+ ///
+ /// Gets or sets the normal text color of the text style.
+ ///
+ internal Color NormalTextColor
+ {
+ get { return (Color)GetValue(NormalTextColorProperty); }
+ set { SetValue(NormalTextColorProperty, value); }
+ }
+
+ ///
+ /// Gets or sets the normal font size of the text style.
+ ///
+ internal double NormalFontSize
+ {
+ get { return (double)GetValue(NormalFontSizeProperty); }
+ set { SetValue(NormalFontSizeProperty, value); }
+ }
+
+ ///
+ /// Gets or sets the disabled text color of the text style.
+ ///
+ internal Color DisabledTextColor
+ {
+ get { return (Color)GetValue(DisabledTextColorProperty); }
+ set { SetValue(DisabledTextColorProperty, value); }
+ }
+
+ #endregion
+
+ #region Internal Methods
+
+ ///
+ /// Method to update the date picker format.
+ ///
+ internal void UpdateFormat()
+ {
+ _dayColumn = new PickerColumn();
+ _monthColumn = new PickerColumn();
+ _yearColumn = new PickerColumn();
+ GeneratePickerColumns();
+ BaseColumns = _columns;
+ }
+
+ #endregion
+
+ #region Private Methods
+
+ ///
+ /// Method trigged whenever the base panel selection is changed.
+ ///
+ /// Base picker instance value.
+ /// Selection changed event arguments.
+ void OnPickerSelectionIndexChanged(object? sender, PickerSelectionChangedEventArgs e)
+ {
+ string dayFormat;
+ string monthFormat;
+ List formatStringOrder = DatePickerHelper.GetFormatStringOrder(out dayFormat, out monthFormat, Format);
+ int changedColumnValue = formatStringOrder[e.ColumnIndex];
+ DateTime maxDate = DatePickerHelper.GetValidMaxDate(MinimumDate, MaximumDate);
+ DateTime date = SelectedDate ?? _previousSelectedDateTime.Date;
+ DateTime? previousSelectedDate = DatePickerHelper.GetValidDate(date, MinimumDate, maxDate);
+ if (previousSelectedDate == null)
+ {
+ return;
+ }
+
+ switch (changedColumnValue)
+ {
+ //// Need to handle the day selection changes.
+ case 0:
+ {
+ int day = GetDayFromCollection(e);
+ DateTime selectedDate = new DateTime(previousSelectedDate.Value.Year, previousSelectedDate.Value.Month, day);
+ if (!DatePickerHelper.IsSameDate(selectedDate, SelectedDate))
+ {
+ SelectedDate = selectedDate.Date;
+ }
+ }
+
+ break;
+ //// Need to handle the month selection changes.
+ case 1:
+ {
+ int month, day;
+ GetMonthFromCollection(e, dayFormat, monthFormat, maxDate, previousSelectedDate, out month, out day);
+ DateTime selectedDate = new DateTime(previousSelectedDate.Value.Year, month, day);
+ if (!DatePickerHelper.IsSameDate(selectedDate, SelectedDate))
+ {
+ SelectedDate = selectedDate.Date;
+ }
+ }
+
+ break;
+ //// Need to handle the year selection changes.
+ case 2:
+ {
+ int year, month, day;
+ GetYearFromCollection(e, dayFormat, monthFormat, maxDate, previousSelectedDate, out year, out month, out day);
+ DateTime selectedDate = new DateTime(year, month, day);
+ if (!DatePickerHelper.IsSameDate(selectedDate, SelectedDate))
+ {
+ SelectedDate = selectedDate.Date;
+ }
+ }
+
+ break;
+ }
+ }
+
+ ///
+ /// Method to get the year from the year collection.
+ ///
+ /// The event args.
+ /// The day format.
+ /// The month format.
+ /// The maximum date.
+ /// The previous selected date.
+ /// The selected year.
+ /// The selected month.
+ /// The selected day.
+ void GetYearFromCollection(PickerSelectionChangedEventArgs e, string dayFormat, string monthFormat, DateTime maxDate, DateTime? previousSelectedDate, out int year, out int month, out int day)
+ {
+ year = MinimumDate.Year;
+ month = MinimumDate.Month;
+ day = MinimumDate.Day;
+ if (previousSelectedDate == null)
+ {
+ return;
+ }
+
+ if (_yearColumn.ItemsSource != null && _yearColumn.ItemsSource is ObservableCollection yearCollection && yearCollection.Count > e.NewValue)
+ {
+ //// Get the year value based on the selected index changes value.
+ year = int.Parse(yearCollection[e.NewValue]);
+ }
+
+ ObservableCollection months = DatePickerHelper.GetMonths(monthFormat, year, MinimumDate, maxDate, MonthInterval);
+ ObservableCollection previousMonths = _monthColumn.ItemsSource is ObservableCollection previousMonthCollection ? previousMonthCollection : new ObservableCollection();
+ //// Check the year index changes needed to update the month collection.
+ if (!PickerHelper.IsCollectionEquals(months, previousMonths))
+ {
+ _monthColumn.ItemsSource = months;
+ }
+
+ //// Check the month collection have selected month value, if not then update the nearby value.
+ int monthIndex = DatePickerHelper.GetMonthIndex(monthFormat, months, previousSelectedDate.Value.Month);
+ month = 1;
+ if (monthFormat == "M" || monthFormat == "MM")
+ {
+ //// Get the month value based on the selected index changes value.
+ month = int.Parse(months[monthIndex]);
+ }
+ else if (monthFormat == "MMM")
+ {
+ List monthStrings = DateTimeFormatInfo.CurrentInfo.AbbreviatedMonthNames.ToList();
+ //// Get the month value based on the selected index changes value.
+ month = monthStrings.IndexOf(months[monthIndex]) + 1;
+ }
+
+ ObservableCollection days = DatePickerHelper.GetDays(dayFormat, month, year, MinimumDate, maxDate, DayInterval);
+ ObservableCollection previousDays = _dayColumn.ItemsSource is ObservableCollection previousDayCollection ? previousDayCollection : new ObservableCollection();
+ //// Check the year and month(if month items source updated) changes needed to change the day collection.
+ if (!PickerHelper.IsCollectionEquals(days, previousDays))
+ {
+ _dayColumn.ItemsSource = days;
+ }
+
+ //// Check the day collection have selected day value, if not then update the nearby value.
+ int index = DatePickerHelper.GetDayIndex(dayFormat, days, previousSelectedDate.Value.Day);
+ day = index == -1 ? 1 : int.Parse(days[index]);
+ }
+
+ ///
+ /// Method to get the month value from the month collection.
+ ///
+ /// The event args.
+ /// The day format.
+ /// The month format.
+ /// The maximum date.
+ /// The previous selected date.
+ /// The selected month.
+ /// The selected day.
+ void GetMonthFromCollection(PickerSelectionChangedEventArgs e, string dayFormat, string monthFormat, DateTime maxDate, DateTime? previousSelectedDate, out int month, out int day)
+ {
+ month = 1;
+ day = 1;
+ if (previousSelectedDate == null)
+ {
+ return;
+ }
+
+ if (_monthColumn.ItemsSource != null && _monthColumn.ItemsSource is ObservableCollection monthCollection && monthCollection.Count > e.NewValue)
+ {
+ if (monthFormat == "M" || monthFormat == "MM")
+ {
+ //// Get the month value based on the selected index changes value.
+ month = int.Parse(monthCollection[e.NewValue]);
+ }
+ else if (monthFormat == "MMM")
+ {
+ List months = DateTimeFormatInfo.CurrentInfo.AbbreviatedMonthNames.ToList();
+ //// Get the month value based on the selected index changes value.
+ month = months.IndexOf(monthCollection[e.NewValue]) + 1;
+ }
+ }
+
+ ObservableCollection days = DatePickerHelper.GetDays(dayFormat, month, previousSelectedDate.Value.Year, MinimumDate, maxDate, DayInterval);
+ ObservableCollection previousDays = _dayColumn.ItemsSource is ObservableCollection previousDayCollection ? previousDayCollection : new ObservableCollection();
+ //// Check the month selection changes needed to update the days collection.
+ if (!PickerHelper.IsCollectionEquals(days, previousDays))
+ {
+ _dayColumn.ItemsSource = days;
+ }
+
+ //// Check the new days collection have a selected day value, if not then update the nearby value.
+ int index = DatePickerHelper.GetDayIndex(dayFormat, days, previousSelectedDate.Value.Day);
+ day = index == -1 ? 1 : int.Parse(days[index]);
+ }
+
+ ///
+ /// Method to get the day value from day collection.
+ ///
+ /// The event args.
+ /// The day collection index value.
+ int GetDayFromCollection(PickerSelectionChangedEventArgs e)
+ {
+ int day = 1;
+ if (_dayColumn.ItemsSource != null && _dayColumn.ItemsSource is ObservableCollection dayCollection && dayCollection.Count > e.NewValue)
+ {
+ //// Get the day value based on the selected index changes value.
+ day = int.Parse(dayCollection[e.NewValue]);
+ }
+
+ return day;
+ }
+
+ ///
+ /// Method to update the minimum and maximum date value for all the picker column based on the date value.
+ ///
+ /// Minimum and Maximum oldvalue.
+ /// Minimum and Maximum newvalue.
+ void UpdateMinimumMaximumDate(object oldValue, object newValue)
+ {
+ string dayFormat;
+ string monthFormat;
+ List formatString = DatePickerHelper.GetFormatStringOrder(out dayFormat, out monthFormat, Format);
+ DateTime oldDate = (DateTime)oldValue;
+ DateTime newDate = (DateTime)newValue;
+ DateTime maxDate = DatePickerHelper.GetValidMaxDate(MinimumDate, MaximumDate);
+ DateTime? validSelectedDate = DatePickerHelper.GetValidDate(SelectedDate, MinimumDate, maxDate);
+ int yearIndex = formatString.IndexOf(2);
+ if (yearIndex != -1 && oldDate.Year != newDate.Year)
+ {
+ _yearColumn = GenerateYearColumn(validSelectedDate);
+ _columns[yearIndex] = _yearColumn;
+ }
+
+ if (validSelectedDate != null)
+ {
+ if (validSelectedDate.Value.Year == MinimumDate.Year || validSelectedDate.Value.Year == maxDate.Year)
+ {
+ ObservableCollection month = DatePickerHelper.GetMonths(monthFormat, validSelectedDate.Value.Year, MinimumDate, maxDate, MonthInterval);
+ ObservableCollection previousMonths = _monthColumn.ItemsSource is ObservableCollection previousMonthCollection ? previousMonthCollection : new ObservableCollection();
+ //// Check the year index changes needed to update the month collection.
+ if (!PickerHelper.IsCollectionEquals(month, previousMonths))
+ {
+ _monthColumn = new PickerColumn()
+ {
+ ItemsSource = month,
+ SelectedIndex = DatePickerHelper.GetMonthIndex(monthFormat, month, validSelectedDate.Value.Month),
+ HeaderText = SfPickerResources.GetLocalizedString(ColumnHeaderView.MonthHeaderText),
+ };
+ int monthIndex = formatString.IndexOf(1);
+ if (monthIndex != -1)
+ {
+ _columns[monthIndex] = _monthColumn;
+ }
+ }
+ }
+
+ if (!string.IsNullOrEmpty(dayFormat) && ((validSelectedDate.Value.Year == MinimumDate.Year && validSelectedDate.Value.Month == MinimumDate.Month) || (validSelectedDate.Value.Year == maxDate.Year && validSelectedDate.Value.Month == maxDate.Month)))
+ {
+ ObservableCollection days = DatePickerHelper.GetDays(dayFormat, validSelectedDate.Value.Month, validSelectedDate.Value.Year, MinimumDate, maxDate, DayInterval);
+ ObservableCollection previousDays = _dayColumn.ItemsSource is ObservableCollection previousDayCollection ? previousDayCollection : new ObservableCollection();
+ //// Check the year and month(if month items source updated) changes needed to change the day collection.
+ if (!PickerHelper.IsCollectionEquals(days, previousDays))
+ {
+ _dayColumn = new PickerColumn()
+ {
+ ItemsSource = days,
+ SelectedIndex = DatePickerHelper.GetDayIndex(dayFormat, days, validSelectedDate.Value.Day),
+ HeaderText = SfPickerResources.GetLocalizedString(ColumnHeaderView.DayHeaderText),
+ };
+ int dayIndex = formatString.IndexOf(0);
+ if (dayIndex != -1)
+ {
+ _columns[dayIndex] = _dayColumn;
+ }
+ }
+ }
+ }
+ }
+
+ ///
+ /// Method to update the selected index value for all the picker column based on the date value.
+ ///
+ /// The selected date value.
+ void UpdateSelectedIndex(DateTime? date)
+ {
+ if (date == null)
+ {
+ return;
+ }
+
+ string dayFormat;
+ string monthFormat;
+ DatePickerHelper.GetFormatStringOrder(out dayFormat, out monthFormat, Format);
+ if (_yearColumn.ItemsSource != null && _yearColumn.ItemsSource is ObservableCollection yearCollection && yearCollection.Count > 0)
+ {
+ int index = DatePickerHelper.GetYearIndex(yearCollection, date.Value.Year);
+ if (_yearColumn.SelectedIndex != index)
+ {
+ _yearColumn.SelectedIndex = index;
+ }
+ }
+
+ if (_monthColumn.ItemsSource != null && _monthColumn.ItemsSource is ObservableCollection monthCollection && !string.IsNullOrEmpty(monthFormat))
+ {
+ int index = DatePickerHelper.GetMonthIndex(monthFormat, monthCollection, date.Value.Month);
+ if (_monthColumn.SelectedIndex != index)
+ {
+ _monthColumn.SelectedIndex = index;
+ }
+ }
+
+ if (_dayColumn.ItemsSource != null && _dayColumn.ItemsSource is ObservableCollection dayCollection && !string.IsNullOrEmpty(dayFormat))
+ {
+ int index = DatePickerHelper.GetDayIndex(dayFormat, dayCollection, date.Value.Day);
+ if (_dayColumn.SelectedIndex != index)
+ {
+ _dayColumn.SelectedIndex = index;
+ }
+ }
+ }
+
+ ///
+ /// Method invokes on column header property changed.
+ ///
+ /// Column header view value.
+ /// Property changed arguments.
+ void OnColumnHeaderPropertyChanged(object? sender, PickerPropertyChangedEventArgs e)
+ {
+ if (e.PropertyName == nameof(DatePickerColumnHeaderView.Background))
+ {
+ BaseColumnHeaderView.Background = ColumnHeaderView.Background;
+ }
+ else if (e.PropertyName == nameof(DatePickerColumnHeaderView.Height))
+ {
+ BaseColumnHeaderView.Height = ColumnHeaderView.Height;
+ }
+ else if (e.PropertyName == nameof(DatePickerColumnHeaderView.DividerColor))
+ {
+ BaseColumnHeaderView.DividerColor = ColumnHeaderView.DividerColor;
+ }
+ else if (e.PropertyName == nameof(DatePickerColumnHeaderView.TextStyle))
+ {
+ SetInheritedBindingContext(ColumnHeaderView.TextStyle, BindingContext);
+ BaseColumnHeaderView.TextStyle = ColumnHeaderView.TextStyle;
+ }
+ else if (e.PropertyName == nameof(DatePickerColumnHeaderView.DayHeaderText))
+ {
+ _dayColumn.HeaderText = SfPickerResources.GetLocalizedString(ColumnHeaderView.DayHeaderText);
+ }
+ else if (e.PropertyName == nameof(DatePickerColumnHeaderView.MonthHeaderText))
+ {
+ _monthColumn.HeaderText = SfPickerResources.GetLocalizedString(ColumnHeaderView.MonthHeaderText);
+ }
+ else if (e.PropertyName == nameof(DatePickerColumnHeaderView.YearHeaderText))
+ {
+ _yearColumn.HeaderText = SfPickerResources.GetLocalizedString(ColumnHeaderView.YearHeaderText);
+ }
+ }
+
+ ///
+ /// Method to generate the day, month and year columns based on the selected date value.
+ ///
+ void GeneratePickerColumns()
+ {
+ string dayFormat;
+ string monthFormat;
+ List formatStringOrder = DatePickerHelper.GetFormatStringOrder(out dayFormat, out monthFormat, Format);
+ DateTime maxDate = DatePickerHelper.GetValidMaxDate(MinimumDate, MaximumDate);
+ DateTime? selectedDate = DatePickerHelper.GetValidDate(SelectedDate, MinimumDate, maxDate);
+ ObservableCollection pickerColumns = new ObservableCollection();
+ foreach (int index in formatStringOrder)
+ {
+ switch (index)
+ {
+ case 0:
+ _dayColumn = GenerateDayColumn(dayFormat, selectedDate);
+ _dayColumn.SelectedItem = SelectedDate != null ? PickerHelper.GetSelectedItemDefaultValue(_dayColumn) : null;
+ pickerColumns.Add(_dayColumn);
+ break;
+ case 1:
+ _monthColumn = GenerateMonthColumn(monthFormat, selectedDate);
+ _monthColumn.SelectedItem = SelectedDate != null ? PickerHelper.GetSelectedItemDefaultValue(_monthColumn) : null;
+ pickerColumns.Add(_monthColumn);
+ break;
+ case 2:
+ _yearColumn = GenerateYearColumn(selectedDate);
+ _yearColumn.SelectedItem = SelectedDate != null ? PickerHelper.GetSelectedItemDefaultValue(_yearColumn) : null;
+ pickerColumns.Add(_yearColumn);
+ break;
+ }
+ }
+
+ _columns = pickerColumns;
+ }
+
+ ///
+ /// Method to generate the day column with items source and selected index based on format.
+ ///
+ /// The day format.
+ /// The valid selected date value.
+ /// Returns day column details.
+ PickerColumn GenerateDayColumn(string format, DateTime? selectedDate)
+ {
+ DateTime maxDate = DatePickerHelper.GetValidMaxDate(MinimumDate, MaximumDate);
+
+ // Use the selectedDate if provided, otherwise use 'previous'
+ DateTime referenceDate = selectedDate ?? _previousSelectedDateTime;
+
+ ObservableCollection days = DatePickerHelper.GetDays(
+ format,
+ referenceDate.Month,
+ referenceDate.Year,
+ MinimumDate,
+ maxDate,
+ DayInterval);
+
+ int selectedIndex = DatePickerHelper.GetDayIndex(
+ format,
+ days,
+ referenceDate.Day);
+
+ return new PickerColumn
+ {
+ ItemsSource = days,
+ SelectedIndex = selectedIndex,
+ HeaderText = SfPickerResources.GetLocalizedString(ColumnHeaderView.DayHeaderText),
+ };
+ }
+
+ ///
+ /// Method to generate the month column with items source and selected index based on format.
+ ///
+ /// The month format value.
+ /// The valid selected date value.
+ /// Returns the month column instance.
+ PickerColumn GenerateMonthColumn(string format, DateTime? selectedDate)
+ {
+ DateTime maxDate = DatePickerHelper.GetValidMaxDate(MinimumDate, MaximumDate);
+
+ // Use the selectedDate if provided, otherwise use 'previous'
+ DateTime referenceDate = selectedDate ?? _previousSelectedDateTime;
+
+ ObservableCollection months = DatePickerHelper.GetMonths(
+ format,
+ referenceDate.Year,
+ MinimumDate,
+ maxDate,
+ MonthInterval);
+
+ int selectedIndex = DatePickerHelper.GetMonthIndex(
+ format,
+ months,
+ referenceDate.Month);
+
+ return new PickerColumn
+ {
+ ItemsSource = months,
+ SelectedIndex = selectedIndex,
+ HeaderText = SfPickerResources.GetLocalizedString(ColumnHeaderView.MonthHeaderText),
+ };
+ }
+
+ ///
+ /// Method to generate the year column with items source and selected index based on format.
+ ///
+ /// The valid selected date value.
+ /// Returns the year column instance.
+ PickerColumn GenerateYearColumn(DateTime? selectedDate)
+ {
+ DateTime maxDate = DatePickerHelper.GetValidMaxDate(MinimumDate, MaximumDate);
+
+ // Use 'selectedDate' if provided, otherwise fall back to 'previous'
+ int year = selectedDate?.Year ?? _previousSelectedDateTime.Year;
+
+ ObservableCollection years = DatePickerHelper.GetYears(
+ MinimumDate,
+ maxDate,
+ YearInterval);
+
+ int selectedIndex = DatePickerHelper.GetYearIndex(years, year);
+
+ return new PickerColumn
+ {
+ ItemsSource = years,
+ SelectedIndex = selectedIndex,
+ HeaderText = SfPickerResources.GetLocalizedString(ColumnHeaderView.YearHeaderText),
+ };
+ }
+
+ ///
+ /// Method trigged when the black out date collection gets changed.
+ ///
+ /// Date picker instance
+ /// Collection changed event arguments
+ void OnBlackoutDates_CollectionChanged(object? sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
+ {
+ if (SelectedDate != null)
+ {
+ DateTime currentDate = SelectedDate.Value;
+ while (BlackoutDates.Any(blackOutDate => DatePickerHelper.IsBlackoutDate(true, string.Empty, blackOutDate, currentDate)))
+ {
+ currentDate = currentDate.AddDays(1);
+ }
+
+ if (SelectedDate != currentDate)
+ {
+ SelectedDate = currentDate;
+ }
+ }
+ }
+
+ ///
+ /// Need to update the parent for the new value.
+ ///
+ /// The old value.
+ /// The new value.
+ void SetParent(Element? oldValue, Element? newValue)
+ {
+ if (oldValue != null)
+ {
+ oldValue.Parent = null;
+ }
+
+ if (newValue != null)
+ {
+ newValue.Parent = this;
+ }
+ }
+
+ ///
+ /// Method to initialize the theme and to set dynamic resources.
+ ///
+ void InitializeTheme()
+ {
+ ThemeElement.InitializeThemeResources(this, "SfDatePickerTheme");
+ SetDynamicResource(DatePickerBackgroundProperty, "SfDatePickerNormalBackground");
+
+ SetDynamicResource(HeaderBackgroundProperty, "SfDatePickerNormalHeaderBackground");
+ SetDynamicResource(HeaderDividerColorProperty, "SfDatePickerNormalHeaderDividerColor");
+ SetDynamicResource(HeaderTextColorProperty, "SfDatePickerNormalHeaderTextColor");
+ SetDynamicResource(HeaderFontSizeProperty, "SfDatePickerNormalHeaderFontSize");
+
+ SetDynamicResource(FooterBackgroundProperty, "SfDatePickerNormalFooterBackground");
+ SetDynamicResource(FooterDividerColorProperty, "SfDatePickerNormalFooterDividerColor");
+ SetDynamicResource(FooterTextColorProperty, "SfDatePickerNormalFooterTextColor");
+ SetDynamicResource(FooterFontSizeProperty, "SfDatePickerNormalFooterFontSize");
+
+ SetDynamicResource(SelectionBackgroundProperty, "SfDatePickerSelectionBackground");
+ SetDynamicResource(SelectionStrokeColorProperty, "SfDatePickerSelectionStroke");
+ SetDynamicResource(SelectionCornerRadiusProperty, "SfDatePickerSelectionCornerRadius");
+ SetDynamicResource(SelectedTextColorProperty, "SfDatePickerSelectedTextColor");
+ SetDynamicResource(SelectionTextColorProperty, "SfPickerSelectionTextColor");
+ SetDynamicResource(SelectedFontSizeProperty, "SfDatePickerSelectedFontSize");
+
+ SetDynamicResource(NormalTextColorProperty, "SfDatePickerNormalTextColor");
+ SetDynamicResource(NormalFontSizeProperty, "SfDatePickerNormalFontSize");
+
+ SetDynamicResource(DisabledTextColorProperty, "SfDatePickerDisabledTextColor");
+ }
+
+ #endregion
+
+ #region Override Methods
+
+ ///
+ /// Method to wire the events.
+ ///
+ protected override void Initialize()
+ {
+ base.Initialize();
+ BaseColumns = _columns;
+ BaseHeaderView = HeaderView;
+ if (ColumnHeaderView != null)
+ {
+ SetInheritedBindingContext(ColumnHeaderView, BindingContext);
+ ColumnHeaderView.PickerPropertyChanged += OnColumnHeaderPropertyChanged;
+ BaseColumnHeaderView = new PickerColumnHeaderView()
+ {
+ Background = ColumnHeaderView.Background,
+ DividerColor = ColumnHeaderView.DividerColor,
+ Height = ColumnHeaderView.Height,
+ TextStyle = ColumnHeaderView.TextStyle,
+ };
+ }
+ }
+
+ ///
+ /// Method triggers when the property binding context changed.
+ ///
+ protected override void OnBindingContextChanged()
+ {
+ base.OnBindingContextChanged();
+ if (HeaderView != null)
+ {
+ SetInheritedBindingContext(HeaderView, BindingContext);
+ if (HeaderView.TextStyle != null)
+ {
+ SetInheritedBindingContext(HeaderView.TextStyle, BindingContext);
+ }
+
+ if (HeaderView.SelectionTextStyle != null)
+ {
+ SetInheritedBindingContext(HeaderView.SelectionTextStyle, BindingContext);
+ }
+ }
+
+ if (ColumnHeaderView != null)
+ {
+ SetInheritedBindingContext(ColumnHeaderView, BindingContext);
+ if (ColumnHeaderView.TextStyle != null)
+ {
+ SetInheritedBindingContext(ColumnHeaderView.TextStyle, BindingContext);
+ }
+ }
+
+ if (FooterView != null)
+ {
+ SetInheritedBindingContext(FooterView, BindingContext);
+ if (FooterView.TextStyle != null)
+ {
+ SetInheritedBindingContext(FooterView.TextStyle, BindingContext);
+ }
+ }
+
+ if (SelectedTextStyle != null)
+ {
+ SetInheritedBindingContext(SelectedTextStyle, BindingContext);
+ }
+
+ if (TextStyle != null)
+ {
+ SetInheritedBindingContext(TextStyle, BindingContext);
+ }
+
+ if (SelectionView != null)
+ {
+ SetInheritedBindingContext(SelectionView, BindingContext);
+ }
+ }
+
+ ///
+ /// Method triggers when closed the date picker popup.
+ ///
+ /// The event arguments.
+ protected override void OnPopupClosed(EventArgs e)
+ {
+ InvokeClosedEvent(this, e);
+ }
+
+ ///
+ /// Method triggers when closing the date picker popup.
+ ///
+ /// The event arguments.
+ protected override void OnPopupClosing(CancelEventArgs e)
+ {
+ InvokeClosingEvent(this, e);
+ }
+
+ ///
+ /// Method triggers when opened the date picker popup.
+ ///
+ /// The event arguments.
+ protected override void OnPopupOpened(EventArgs e)
+ {
+ InvokeOpenedEvent(this, e);
+ }
+
+ ///
+ /// Method triggers when clicked the date picker ok button.
+ ///
+ /// The event arguments
+ protected override void OnOkButtonClicked(EventArgs e)
+ {
+ InvokeOkButtonClickedEvent(this, e);
+ if (AcceptCommand != null && AcceptCommand.CanExecute(e))
+ {
+ AcceptCommand.Execute(e);
+ }
+ }
+
+ ///
+ /// Method triggers when clicked the date picker cancel button.
+ ///
+ /// The event arguments.
+ protected override void OnCancelButtonClicked(EventArgs e)
+ {
+ InvokeCancelButtonClickedEvent(this, e);
+ if (DeclineCommand != null && DeclineCommand.CanExecute(e))
+ {
+ DeclineCommand.Execute(e);
+ }
+ }
+
+ #endregion
+
+ #region Property Changed Methods
+
+ ///
+ /// Method invokes on picker header view changed.
+ ///
+ /// The picker object.
+ /// Old value of the property.
+ /// New value of the property.
+ static void OnHeaderViewChanged(BindableObject bindable, object oldValue, object newValue)
+ {
+ SfDatePicker? picker = bindable as SfDatePicker;
+ if (picker == null)
+ {
+ return;
+ }
+
+ picker.HeaderView.Parent = picker;
+ picker.BaseHeaderView = picker.HeaderView;
+ if (bindable is SfDatePicker datePicker)
+ {
+ datePicker.SetParent(oldValue as Element, newValue as Element);
+ }
+ }
+
+ ///
+ /// Method invokes on picker column header view changed.
+ ///
+ /// The picker object.
+ /// Old value of the property.
+ /// New value of the property.
+ static void OnColumnHeaderViewChanged(BindableObject bindable, object oldValue, object newValue)
+ {
+ SfDatePicker? picker = bindable as SfDatePicker;
+ if (picker == null)
+ {
+ return;
+ }
+
+ if (oldValue is DatePickerColumnHeaderView oldStyle)
+ {
+ oldStyle.PickerPropertyChanged -= picker.OnColumnHeaderPropertyChanged;
+ oldStyle.BindingContext = null;
+ oldStyle.Parent = null;
+ }
+
+ if (newValue is DatePickerColumnHeaderView newStyle)
+ {
+ newStyle.Parent = picker;
+ SetInheritedBindingContext(newStyle, picker.BindingContext);
+ newStyle.PickerPropertyChanged += picker.OnColumnHeaderPropertyChanged;
+ picker.BaseColumnHeaderView = new PickerColumnHeaderView()
+ {
+ Background = newStyle.Background,
+ DividerColor = newStyle.DividerColor,
+ Height = newStyle.Height,
+ TextStyle = newStyle.TextStyle,
+ };
+
+ picker._dayColumn.HeaderText = SfPickerResources.GetLocalizedString(picker.ColumnHeaderView.DayHeaderText);
+ picker._monthColumn.HeaderText = SfPickerResources.GetLocalizedString(picker.ColumnHeaderView.MonthHeaderText);
+ picker._yearColumn.HeaderText = SfPickerResources.GetLocalizedString(picker.ColumnHeaderView.YearHeaderText);
+ }
+ }
+
+ ///
+ /// Method invokes on selection date property changed.
+ ///
+ /// The SfDatePicker object.
+ /// Property old value.
+ /// Property new value.
+ static void OnSelectedDatePropertyChanged(BindableObject bindable, object oldValue, object newValue)
+ {
+ SfDatePicker? datepicker = bindable as SfDatePicker;
+ if (datepicker == null)
+ {
+ return;
+ }
+
+ DateTime? previousSelectedDate = null;
+ DateTime? currentSelectedDate = null;
+ if (oldValue is DateTime oldSelectedDate)
+ {
+ previousSelectedDate = oldSelectedDate;
+ //// Prevents Selection changed event from triggering if old value is black out date.
+ if (datepicker.BlackoutDates.Any(blackOutDate => DatePickerHelper.IsBlackoutDate(true, string.Empty, blackOutDate, previousSelectedDate.Value)))
+ {
+ datepicker.UpdateSelectedIndex((DateTime)newValue);
+ //// Skip the update and event call by checking if the date is blackout value and within current month.
+ if (oldSelectedDate.Year == datepicker._previousSelectedDateTime.Year && oldSelectedDate.Month == datepicker._previousSelectedDateTime.Month)
+ {
+ return;
+ }
+
+ previousSelectedDate = datepicker._previousSelectedDateTime;
+ }
+
+ datepicker._previousSelectedDateTime = oldSelectedDate;
+ }
+
+ if (newValue is DateTime newSelectedDate)
+ {
+ //// Prevents Selection changed event from triggering if new value is black out date.
+ if (datepicker.BlackoutDates.Any(blackOutDate => DatePickerHelper.IsBlackoutDate(true, string.Empty, blackOutDate, newSelectedDate)))
+ {
+ return;
+ }
+
+ currentSelectedDate = newSelectedDate;
+ }
+
+ //// Skip the update and event call while the same date updated with different time value.
+ if (DatePickerHelper.IsSameDate(previousSelectedDate, currentSelectedDate))
+ {
+ return;
+ }
+
+ //// Update the column with valid date(date between min and max date).
+ if (newValue == null)
+ {
+ datepicker._dayColumn.SelectedItem = null;
+ datepicker._yearColumn.SelectedItem = null;
+ datepicker._monthColumn.SelectedItem = null;
+ datepicker.SelectedDate = null;
+ datepicker.SelectionChanged?.Invoke(datepicker, new DatePickerSelectionChangedEventArgs() { OldValue = previousSelectedDate, NewValue = currentSelectedDate });
+ return;
+ }
+ else
+ {
+ datepicker._dayColumn.SelectedItem = PickerHelper.GetSelectedItemDefaultValue(datepicker._dayColumn);
+ datepicker._yearColumn.SelectedItem = PickerHelper.GetSelectedItemDefaultValue(datepicker._yearColumn);
+ datepicker._monthColumn.SelectedItem = PickerHelper.GetSelectedItemDefaultValue(datepicker._monthColumn);
+ PickerContainer? pickerContainer = datepicker.GetPickerContainerValue();
+ pickerContainer?.UpdateScrollViewDraw();
+ pickerContainer?.InvalidateDrawable();
+ }
+
+ currentSelectedDate = DatePickerHelper.GetValidDate(currentSelectedDate, datepicker.MinimumDate, datepicker.MaximumDate);
+ var datePickerSelectionChangedEventArgs = new DatePickerSelectionChangedEventArgs() { OldValue = previousSelectedDate, NewValue = currentSelectedDate };
+ if (datepicker.SelectionChanged != null)
+ {
+ datepicker.SelectionChanged?.Invoke(datepicker, datePickerSelectionChangedEventArgs);
+ }
+
+ if (datepicker.SelectionChangedCommand != null && datepicker.SelectionChangedCommand.CanExecute(datePickerSelectionChangedEventArgs))
+ {
+ datepicker.SelectionChangedCommand.Execute(datePickerSelectionChangedEventArgs);
+ }
+
+ datepicker.UpdateSelectedIndex(currentSelectedDate);
+ }
+
+ ///
+ /// Method invokes on format property changed.
+ ///
+ /// The SfDatePicker object.
+ /// Property old value.
+ /// Property new value.
+ static void OnFormatPropertyChanged(BindableObject bindable, object oldValue, object newValue)
+ {
+ SfDatePicker? datepicker = bindable as SfDatePicker;
+ if (datepicker == null)
+ {
+ return;
+ }
+
+ datepicker.UpdateFormat();
+ }
+
+ ///
+ /// Method invokes on minimum date property changed.
+ ///
+ /// The SfDatePicker object.
+ /// Property old value.
+ /// Property new value.
+ static void OnMinimumDatePropertyChanged(BindableObject bindable, object oldValue, object newValue)
+ {
+ SfDatePicker? datepicker = bindable as SfDatePicker;
+ if (datepicker == null)
+ {
+ return;
+ }
+
+ datepicker.UpdateMinimumMaximumDate(oldValue, newValue);
+ DateTime? currentSelectedDate = DatePickerHelper.GetValidDate(datepicker.SelectedDate, datepicker.MinimumDate, datepicker.MaximumDate);
+ datepicker.UpdateSelectedIndex(currentSelectedDate);
+ }
+
+ ///
+ /// Method invokes on maximum date property changed.
+ ///
+ /// The SfDatePicker object.
+ /// Property old value.
+ /// Property new value.
+ static void OnMaximumDatePropertyChanged(BindableObject bindable, object oldValue, object newValue)
+ {
+ SfDatePicker? datepicker = bindable as SfDatePicker;
+ if (datepicker == null)
+ {
+ return;
+ }
+
+ DateTime newDate = DatePickerHelper.GetValidMaxDate(datepicker.MinimumDate, (DateTime)newValue);
+ datepicker.UpdateMinimumMaximumDate(oldValue, newDate);
+ DateTime? currentSelectedDate = DatePickerHelper.GetValidDate(datepicker.SelectedDate, datepicker.MinimumDate, newDate);
+ datepicker.UpdateSelectedIndex(currentSelectedDate);
+ }
+
+ ///
+ /// Method invokes on day interval property changed.
+ ///
+ /// The SfDatePicker object
+ /// Property old value.
+ /// Property new value.
+ static void OnDayIntervalPropertyChanged(BindableObject bindable, object oldValue, object newValue)
+ {
+ SfDatePicker? datepicker = bindable as SfDatePicker;
+ if (datepicker == null)
+ {
+ return;
+ }
+
+ string dayFormat;
+ //// Get the day format and format string order.
+ List formatStringOrder = DatePickerHelper.GetFormatStringOrder(out dayFormat, out _, datepicker.Format);
+ if (string.IsNullOrEmpty(dayFormat))
+ {
+ return;
+ }
+
+ DateTime maxDate = DatePickerHelper.GetValidMaxDate(datepicker.MinimumDate, datepicker.MaximumDate);
+ DateTime? currentSelectedDate = DatePickerHelper.GetValidDate(datepicker.SelectedDate, datepicker.MinimumDate, maxDate);
+ datepicker._dayColumn = datepicker.GenerateDayColumn(dayFormat, currentSelectedDate);
+ int dayIndex = formatStringOrder.IndexOf(0);
+ //// Replace the day column with updated day interval.
+ datepicker._columns[dayIndex] = datepicker._dayColumn;
+ }
+
+ ///
+ /// Method invokes on month interval property changed.
+ ///
+ /// The SfDatePicker object
+ /// Property old value.
+ /// Property new value.
+ static void OnMonthIntervalPropertyChanged(BindableObject bindable, object oldValue, object newValue)
+ {
+ SfDatePicker? datepicker = bindable as SfDatePicker;
+ if (datepicker == null)
+ {
+ return;
+ }
+
+ string monthFormat;
+ //// Get the day format and format string order.
+ List formatStringOrder = DatePickerHelper.GetFormatStringOrder(out _, out monthFormat, datepicker.Format);
+ if (string.IsNullOrEmpty(monthFormat))
+ {
+ return;
+ }
+
+ DateTime maxDate = DatePickerHelper.GetValidMaxDate(datepicker.MinimumDate, datepicker.MaximumDate);
+ DateTime? currentSelectedDate = DatePickerHelper.GetValidDate(datepicker.SelectedDate, datepicker.MinimumDate, maxDate);
+ datepicker._monthColumn = datepicker.GenerateMonthColumn(monthFormat, currentSelectedDate);
+ int monthIndex = formatStringOrder.IndexOf(1);
+ //// Replace the month column with updated month interval.
+ datepicker._columns[monthIndex] = datepicker._monthColumn;
+ }
+
+ ///
+ /// Method invokes on year interval property changed.
+ ///
+ /// The SfDatePicker object
+ /// Property old value.
+ /// Property new value.
+ static void OnYearIntervalPropertyChanged(BindableObject bindable, object oldValue, object newValue)
+ {
+ SfDatePicker? datepicker = bindable as SfDatePicker;
+ if (datepicker == null)
+ {
+ return;
+ }
+
+ //// Get the day format and format string order.
+ List formatStringOrder = DatePickerHelper.GetFormatStringOrder(out _, out _, datepicker.Format);
+ int yearIndex = formatStringOrder.IndexOf(2);
+ if (yearIndex == -1)
+ {
+ return;
+ }
+
+ DateTime maxDate = DatePickerHelper.GetValidMaxDate(datepicker.MinimumDate, datepicker.MaximumDate);
+ DateTime? currentSelectedDate = DatePickerHelper.GetValidDate(datepicker.SelectedDate, datepicker.MinimumDate, maxDate);
+ datepicker._yearColumn = datepicker.GenerateYearColumn(currentSelectedDate);
+ //// Replace the year column with updated year interval.
+ datepicker._columns[yearIndex] = datepicker._yearColumn;
+ }
+
+ ///
+ /// Method invokes on blackout dates property changed.
+ ///
+ /// The sfdatepicker object.
+ /// Property old value.
+ /// Property new value.
+ static void OnBlackOutDatesPropertyChanged(BindableObject bindable, object oldValue, object newValue)
+ {
+ SfDatePicker? datepicker = bindable as SfDatePicker;
+ if (datepicker == null)
+ {
+ return;
+ }
+
+ //// Unwires collection changed from old and wires on new collection.
+ ((ObservableCollection)oldValue).CollectionChanged -= datepicker.OnBlackoutDates_CollectionChanged;
+ ((ObservableCollection)newValue).CollectionChanged += datepicker.OnBlackoutDates_CollectionChanged;
+
+ if (datepicker.SelectedDate != null)
+ {
+ DateTime currentDate = datepicker.SelectedDate.Value;
+ while (datepicker.BlackoutDates.Any(blackOutDate => DatePickerHelper.IsBlackoutDate(true, string.Empty, blackOutDate, currentDate)))
+ {
+ currentDate = currentDate.AddDays(1);
+ }
+
+ if (datepicker.SelectedDate != currentDate)
+ {
+ datepicker.SelectedDate = currentDate;
+ }
+ }
+
+ //// Gets picker container value to update the view.
+ PickerContainer? pickerContainer = datepicker.GetPickerContainerValue();
+
+ pickerContainer?.UpdateScrollViewDraw();
+ pickerContainer?.UpdatePickerSelectionView();
+ }
+
+ #endregion
+
+ #region Internal Property Changed Methods
+
+ ///
+ /// called when property changed.
+ ///
+ /// The bindable object.
+ /// The old value.
+ /// The new value.
+ static void OnDatePickerBackgroundChanged(BindableObject bindable, object oldValue, object newValue)
+ {
+ SfDatePicker? picker = bindable as SfDatePicker;
+ if (picker == null)
+ {
+ return;
+ }
+
+ picker.BackgroundColor = picker.DatePickerBackground;
+ }
+
+ ///
+ /// Method invokes on the picker header background changed.
+ ///
+ /// The header settings object.
+ /// Property old value.
+ /// Property new value.
+ static void OnHeaderBackgroundChanged(BindableObject bindable, object oldValue, object newValue)
+ {
+ SfDatePicker? picker = bindable as SfDatePicker;
+ if (picker == null)
+ {
+ return;
+ }
+
+ picker.HeaderView.Background = picker.HeaderBackground;
+ }
+
+ ///
+ /// Method invokes on the picker footer background changed.
+ ///
+ /// The footer settings object.
+ /// Property old value.
+ /// Property new value.
+ static void OnFooterBackgroundChanged(BindableObject bindable, object oldValue, object newValue)
+ {
+ SfDatePicker? picker = bindable as SfDatePicker;
+ if (picker == null)
+ {
+ return;
+ }
+
+ picker.FooterView.Background = picker.FooterBackground;
+ }
+
+ ///
+ /// Method invokes on the picker selection background changed.
+ ///
+ /// The selection settings object.
+ /// Property old value.
+ /// Property new value.
+ static void OnSelectionBackgroundChanged(BindableObject bindable, object oldValue, object newValue)
+ {
+ SfDatePicker? picker = bindable as SfDatePicker;
+ if (picker == null)
+ {
+ return;
+ }
+
+ picker.SelectionView.Background = picker.SelectionBackground;
+ }
+
+ ///
+ /// Method invokes on the picker selection stroke color changed.
+ ///
+ /// The selection settings object.
+ /// Property old value.
+ /// Property new value.
+ static void OnSelectionStrokeColorChanged(BindableObject bindable, object oldValue, object newValue)
+ {
+ SfDatePicker? picker = bindable as SfDatePicker;
+ if (picker == null)
+ {
+ return;
+ }
+
+ picker.SelectionView.Stroke = picker.SelectionStrokeColor;
+ }
+
+ ///
+ /// Method invokes on the picker selection corner radius changed.
+ ///
+ /// The selection settings object.
+ /// Property old value.
+ /// Property new value.
+ static void OnSelectionCornerRadiusChanged(BindableObject bindable, object oldValue, object newValue)
+ {
+ SfDatePicker? picker = bindable as SfDatePicker;
+ if (picker == null)
+ {
+ return;
+ }
+
+ picker.SelectionView.CornerRadius = picker.SelectionCornerRadius;
+ }
+
+ ///
+ /// Method invokes on the picker header separator line background changed.
+ ///
+ /// The header settings object.
+ /// Property old value.
+ /// Property new value.
+ static void OnHeaderDividerColorChanged(BindableObject bindable, object oldValue, object newValue)
+ {
+ SfDatePicker? picker = bindable as SfDatePicker;
+ if (picker == null)
+ {
+ return;
+ }
+
+ picker.HeaderView.DividerColor = picker.HeaderDividerColor;
+ }
+
+ ///
+ /// Method invokes on the picker footer separator line background changed.
+ ///
+ /// The footer settings object.
+ /// Property old value.
+ /// Property new value.
+ static void OnFooterDividerColorChanged(BindableObject bindable, object oldValue, object newValue)
+ {
+ SfDatePicker? picker = bindable as SfDatePicker;
+ if (picker == null)
+ {
+ return;
+ }
+
+ picker.FooterView.DividerColor = picker.FooterDividerColor;
+ }
+
+ ///
+ /// Method invokes on the picker header text color changed.
+ ///
+ /// The header text style object.
+ /// Property old value.
+ /// Property new value.
+ static void OnHeaderTextColorChanged(BindableObject bindable, object oldValue, object newValue)
+ {
+ SfDatePicker? picker = bindable as SfDatePicker;
+ if (picker == null)
+ {
+ return;
+ }
+
+ picker.HeaderView.TextStyle.TextColor = picker.HeaderTextColor;
+ }
+
+ ///
+ /// Method invokes on the picker header font size changed.
+ ///
+ /// The header text style object.
+ /// Property old value.
+ /// Property new value.
+ static void OnHeaderFontSizeChanged(BindableObject bindable, object oldValue, object newValue)
+ {
+ SfDatePicker? picker = bindable as SfDatePicker;
+ if (picker == null)
+ {
+ return;
+ }
+
+ picker.HeaderView.TextStyle.FontSize = picker.HeaderFontSize;
+ }
+
+ ///
+ /// Method invokes on the picker footer text color changed.
+ ///
+ /// The footer text style object.
+ /// Property old value.
+ /// Property new value.
+ static void OnFooterTextColorChanged(BindableObject bindable, object oldValue, object newValue)
+ {
+ SfDatePicker? picker = bindable as SfDatePicker;
+ if (picker == null)
+ {
+ return;
+ }
+
+ picker.FooterView.TextStyle.TextColor = picker.FooterTextColor;
+ }
+
+ ///
+ /// Method invokes on the picker footer font size changed.
+ ///
+ /// The footer text style object.
+ /// Property old value.
+ /// Property new value.
+ static void OnFooterFontSizeChanged(BindableObject bindable, object oldValue, object newValue)
+ {
+ SfDatePicker? picker = bindable as SfDatePicker;
+ if (picker == null)
+ {
+ return;
+ }
+
+ picker.FooterView.TextStyle.FontSize = picker.FooterFontSize;
+ }
+
+ ///
+ /// Method invokes on the picker selection text color changed.
+ ///
+ /// The selection text style object.
+ /// Property old value.
+ /// Property new value.
+ static void OnSelectedTextColorChanged(BindableObject bindable, object oldValue, object newValue)
+ {
+ SfDatePicker? picker = bindable as SfDatePicker;
+ if (picker == null)
+ {
+ return;
+ }
+
+ picker.SelectedTextStyle.TextColor = picker.TextDisplayMode == PickerTextDisplayMode.Default ? picker.SelectedTextColor : picker.SelectionTextColor;
+ }
+
+ ///
+ /// Method invokes on the picker selection font size changed.
+ ///
+ /// The selection text style object.
+ /// Property old value.
+ /// Property new value.
+ static void OnSelectedFontSizeChanged(BindableObject bindable, object oldValue, object newValue)
+ {
+ SfDatePicker? picker = bindable as SfDatePicker;
+ if (picker == null)
+ {
+ return;
+ }
+
+ picker.SelectedTextStyle.FontSize = picker.SelectedFontSize;
+ }
+
+ ///
+ /// Method invokes on the picker normal text color changed.
+ ///
+ /// The text style object.
+ /// Property old value.
+ /// Property new value.
+ static void OnNormalTextColorChanged(BindableObject bindable, object oldValue, object newValue)
+ {
+ SfDatePicker? picker = bindable as SfDatePicker;
+ if (picker == null)
+ {
+ return;
+ }
+
+ picker.TextStyle.TextColor = picker.NormalTextColor;
+ }
+
+ ///
+ /// Method invokes on the picker normal font size changed.
+ ///
+ /// The text style object.
+ /// Property old value.
+ /// Property new value.
+ static void OnNormalFontSizeChanged(BindableObject bindable, object oldValue, object newValue)
+ {
+ SfDatePicker? picker = bindable as SfDatePicker;
+ if (picker == null)
+ {
+ return;
+ }
+
+ picker.TextStyle.FontSize = picker.NormalFontSize;
+ }
+
+ ///
+ /// Method invokes on the picker disabled text color changed.
+ ///
+ /// The text style object.
+ /// Property old value.
+ /// Property new value.
+ static void OnDisabledTextColorChanged(BindableObject bindable, object oldValue, object newValue)
+ {
+ SfDatePicker? picker = bindable as SfDatePicker;
+ if (picker == null)
+ {
+ return;
+ }
+
+ picker.DisabledTextStyle.TextColor = picker.DisabledTextColor;
+ }
+
+ #endregion
+
+ #region Interface Implementation
+
+ ///
+ /// This method is declared only in IParentThemeElement
+ /// and you need to implement this method only in main control.
+ ///
+ /// ResourceDictionary
+ ResourceDictionary IParentThemeElement.GetThemeDictionary()
+ {
+ return new SfDatePickerStyles();
+ }
+
+ ///
+ /// This method will be called when a theme dictionary
+ /// that contains the value for your control key is merged in application.
+ ///
+ /// The old value.
+ /// The new value.
+ void IThemeElement.OnCommonThemeChanged(string oldTheme, string newTheme)
+ {
+ }
+
+ ///
+ /// This method will be called when users merge a theme dictionary
+ /// that contains value for “SyncfusionTheme” dynamic resource key.
+ ///
+ /// Old theme.
+ /// New theme.
+ void IThemeElement.OnControlThemeChanged(string oldTheme, string newTheme)
+ {
+ }
+
+ #endregion
+
+ #region Events
+
+ ///
+ /// Occurs after the selected date is changed on SfDatePicker.
+ ///
+ public event EventHandler? SelectionChanged;
+
+ #endregion
+ }
+}
\ No newline at end of file
diff --git a/maui/src/Picker/SfDateTimePicker.cs b/maui/src/Picker/SfDateTimePicker.cs
new file mode 100644
index 00000000..d7fa9581
--- /dev/null
+++ b/maui/src/Picker/SfDateTimePicker.cs
@@ -0,0 +1,3135 @@
+using System.Collections.ObjectModel;
+using System.ComponentModel;
+using System.Globalization;
+using System.Windows.Input;
+using Syncfusion.Maui.Toolkit.Themes;
+
+namespace Syncfusion.Maui.Toolkit.Picker
+{
+ ///
+ /// Initializes a new instance of the class that represents a control, used to select the date with in specified date range.
+ ///
+ public class SfDateTimePicker : PickerBase, IParentThemeElement, IThemeElement
+ {
+ #region Fields
+
+ ///
+ /// Holds the day column information.
+ ///
+ PickerColumn _dayColumn;
+
+ ///
+ /// Holds the month column information.
+ ///
+ PickerColumn _monthColumn;
+
+ ///
+ /// Holds the year column information.
+ ///
+ PickerColumn _yearColumn;
+
+ ///
+ /// Holds the hour column information.
+ ///
+ PickerColumn _hourColumn;
+
+ ///
+ /// Holds the minute column information.
+ ///
+ PickerColumn _minuteColumn;
+
+ ///
+ /// Holds the second column information.
+ ///
+ PickerColumn _secondColumn;
+
+ ///
+ /// Holds the meridiem column information.
+ ///
+ PickerColumn _meridiemColumn;
+
+ ///
+ /// Holds the picker column collection.
+ ///
+ ObservableCollection _columns;
+
+ ///
+ /// Holds the value to identify the header selection(date or time).
+ ///
+ int _selectedIndex;
+
+ #endregion
+
+ #region Bindable Properties
+
+ ///
+ /// Identifies the dependency property.
+ ///
+ ///
+ /// The identifier for dependency property.
+ ///
+ public static readonly BindableProperty HeaderViewProperty =
+ BindableProperty.Create(
+ nameof(HeaderView),
+ typeof(DateTimePickerHeaderView),
+ typeof(SfDateTimePicker),
+ defaultValueCreator: bindable => new DateTimePickerHeaderView(),
+ propertyChanged: OnHeaderViewChanged);
+
+ ///
+ /// Identifies the dependency property.
+ ///
+ ///
+ /// The identifier for dependency property.
+ ///
+ public static readonly BindableProperty ColumnHeaderViewProperty =
+ BindableProperty.Create(
+ nameof(ColumnHeaderView),
+ typeof(DateTimePickerColumnHeaderView),
+ typeof(SfDateTimePicker),
+ defaultValueCreator: bindable => new DateTimePickerColumnHeaderView(),
+ propertyChanged: OnColumnHeaderViewChanged);
+
+ ///
+ /// Identifies the dependency property.
+ ///
+ ///
+ /// The identifier for dependency property.
+ ///
+ public static readonly BindableProperty SelectedDateProperty =
+ BindableProperty.Create(
+ nameof(SelectedDate),
+ typeof(DateTime?),
+ typeof(SfDateTimePicker),
+ defaultValueCreator: bindable => DateTime.Now,
+ propertyChanged: OnSelectedDatePropertyChanged);
+
+ ///
+ /// Identifies the dependency property.
+ ///
+ ///
+ /// The identifier for dependency property.
+ ///
+ public static readonly BindableProperty DayIntervalProperty =
+ BindableProperty.Create(
+ nameof(DayInterval),
+ typeof(int),
+ typeof(SfDateTimePicker),
+ 1,
+ propertyChanged: OnDayIntervalPropertyChanged);
+
+ ///
+ /// Identifies the dependency property.
+ ///
+ ///
+ /// The identifier for dependency property.
+ ///
+ public static readonly BindableProperty MonthIntervalProperty =
+ BindableProperty.Create(
+ nameof(MonthInterval),
+ typeof(int),
+ typeof(SfDateTimePicker),
+ 1,
+ propertyChanged: OnMonthIntervalPropertyChanged);
+
+ ///
+ /// Identifies the dependency property.
+ ///
+ ///
+ /// The identifier for dependency property.
+ ///
+ public static readonly BindableProperty YearIntervalProperty =
+ BindableProperty.Create(
+ nameof(YearInterval),
+ typeof(int),
+ typeof(SfDateTimePicker),
+ 1,
+ propertyChanged: OnYearIntervalPropertyChanged);
+
+ ///
+ /// Identifies the dependency property.
+ ///
+ ///
+ /// The identifier for dependency property.
+ ///
+ public static readonly BindableProperty HourIntervalProperty =
+ BindableProperty.Create(
+ nameof(HourInterval),
+ typeof(int),
+ typeof(SfDateTimePicker),
+ 1,
+ propertyChanged: OnHourIntervalPropertyChanged);
+
+ ///
+ /// Identifies the dependency property.
+ ///
+ ///
+ /// The identifier for dependency property.
+ ///
+ public static readonly BindableProperty MinuteIntervalProperty =
+ BindableProperty.Create(
+ nameof(MinuteInterval),
+ typeof(int),
+ typeof(SfDateTimePicker),
+ 1,
+ propertyChanged: OnMinuteIntervalPropertyChanged);
+
+ ///
+ /// Identifies the dependency property.
+ ///
+ ///
+ /// The identifier for dependency property.
+ ///
+ public static readonly BindableProperty SecondIntervalProperty =
+ BindableProperty.Create(
+ nameof(SecondInterval),
+ typeof(int),
+ typeof(SfDateTimePicker),
+ 1,
+ propertyChanged: OnSecondIntervalPropertyChanged);
+
+ ///
+ /// Identifies the dependency property.
+ ///
+ ///
+ /// The identifier for dependency property.
+ ///
+ public static readonly BindableProperty DateFormatProperty =
+ BindableProperty.Create(
+ nameof(DateFormat),
+ typeof(PickerDateFormat),
+ typeof(SfDateTimePicker),
+ PickerDateFormat.yyyy_MM_dd,
+ propertyChanged: OnDateFormatPropertyChanged);
+
+ ///
+ /// Identifies the dependency property.
+ ///
+ ///
+ /// The identifier for dependency property.
+ ///
+ public static readonly BindableProperty TimeFormatProperty =
+ BindableProperty.Create(
+ nameof(TimeFormat),
+ typeof(PickerTimeFormat),
+ typeof(SfDateTimePicker),
+ PickerTimeFormat.HH_mm_ss,
+ propertyChanged: OnTimeFormatPropertyChanged);
+
+ ///
+ /// Identifies the dependency property.
+ ///
+ ///
+ /// The identifier for dependency property.
+ ///
+ public static readonly BindableProperty MinimumDateProperty =
+ BindableProperty.Create(
+ nameof(MinimumDate),
+ typeof(DateTime),
+ typeof(SfDateTimePicker),
+ defaultValueCreator: bindable => new DateTime(1900, 01, 01),
+ propertyChanged: OnMinimumDatePropertyChanged);
+
+ ///
+ /// Identifies the dependency property.
+ ///
+ ///
+ /// The identifier for dependency property.
+ ///
+ public static readonly BindableProperty MaximumDateProperty =
+ BindableProperty.Create(
+ nameof(MaximumDate),
+ typeof(DateTime),
+ typeof(SfDateTimePicker),
+ defaultValueCreator: bindable => new DateTime(2100, 12, 31, 23, 59, 59),
+ propertyChanged: OnMaximumDatePropertyChanged);
+
+ ///
+ /// Identifies the dependency property.
+ ///
+ ///
+ /// The identifier for dependency property.
+ ///
+ public static readonly BindableProperty SelectionChangedCommandProperty =
+ BindableProperty.Create(
+ nameof(SelectionChangedCommand),
+ typeof(ICommand),
+ typeof(SfDateTimePicker),
+ null);
+
+ ///
+ /// Identifies the dependency property.
+ ///
+ ///
+ /// The identifier for dependency property.
+ ///
+ public static readonly BindableProperty BlackoutDateTimesProperty =
+ BindableProperty.Create(
+ nameof(BlackoutDateTimes),
+ typeof(ObservableCollection),
+ typeof(SfDateTimePicker),
+ defaultValueCreator: bindable => new ObservableCollection(),
+ propertyChanged: OnBlackOutDateTimesPropertyChanged);
+
+ #endregion
+
+ #region Internal Bindable Properties
+
+ ///
+ /// Identifies the bindable property.
+ ///
+ ///
+ /// The identifier for bindable property.
+ ///
+ internal static readonly BindableProperty DateTimePickerBackgroundProperty =
+ BindableProperty.Create(
+ nameof(DateTimePickerBackground),
+ typeof(Color),
+ typeof(SfDateTimePicker),
+ defaultValueCreator: bindable => Color.FromArgb("#EEE8F4"),
+ propertyChanged: OnDateTimePickerBackgroundChanged);
+
+ ///
+ /// Identifies the dependency property.
+ ///
+ ///
+ /// Identifies the bindable property.
+ ///
+ internal static readonly BindableProperty FooterBackgroundProperty =
+ BindableProperty.Create(
+ nameof(FooterBackground),
+ typeof(Brush),
+ typeof(SfDateTimePicker),
+ defaultValueCreator: bindable => Brush.Transparent,
+ propertyChanged: OnFooterBackgroundChanged);
+
+ ///
+ /// Identifies the dependency property.
+ ///
+ ///
+ /// Identifies the bindable property.
+ ///
+ internal static readonly BindableProperty SelectionBackgroundProperty =
+ BindableProperty.Create(
+ nameof(SelectionBackground),
+ typeof(Brush),
+ typeof(SfDateTimePicker),
+ defaultValueCreator: bindable => new SolidColorBrush(Color.FromArgb("#6750A4")),
+ propertyChanged: OnSelectionBackgroundChanged);
+
+ ///
+ /// Identifies the dependency property.
+ ///
+ ///
+ /// Identifies the bindable property.
+ ///
+ internal static readonly BindableProperty SelectionStrokeColorProperty =
+ BindableProperty.Create(
+ nameof(SelectionStrokeColor),
+ typeof(Color),
+ typeof(SfDateTimePicker),
+ defaultValueCreator: bindable => Colors.Transparent,
+ propertyChanged: OnSelectionStrokeColorChanged);
+
+ ///
+ /// Identifies the dependency property.
+ ///
+ ///
+ /// The identifier for dependency property.
+ ///
+ internal static readonly BindableProperty SelectionCornerRadiusProperty =
+ BindableProperty.Create(
+ nameof(SelectionCornerRadius),
+ typeof(CornerRadius),
+ typeof(SfDateTimePicker),
+ defaultValueCreator: bindable => new CornerRadius(20),
+ propertyChanged: OnSelectionCornerRadiusChanged);
+
+ ///
+ /// Identifies the dependency property.
+ ///
+ ///
+ /// Identifies the bindable property.
+ ///
+ internal static readonly BindableProperty FooterDividerColorProperty =
+ BindableProperty.Create(
+ nameof(FooterDividerColor),
+ typeof(Color),
+ typeof(SfDateTimePicker),
+ defaultValueCreator: bindable => Color.FromArgb("#CAC4D0"),
+ propertyChanged: OnFooterDividerColorChanged);
+
+ ///
+ /// Identifies the dependency property.
+ ///
+ ///
+ /// The identifier for dependency property.
+ ///
+ internal static readonly BindableProperty FooterTextColorProperty =
+ BindableProperty.Create(
+ nameof(FooterTextColor),
+ typeof(Color),
+ typeof(SfDateTimePicker),
+ defaultValueCreator: bindable => Color.FromArgb("#6750A4"),
+ propertyChanged: OnFooterTextColorChanged);
+
+ ///
+ /// Identifies the dependency property.
+ ///
+ ///
+ /// The identifier for dependency property.
+ ///
+ internal static readonly BindableProperty FooterFontSizeProperty =
+ BindableProperty.Create(
+ nameof(FooterFontSize),
+ typeof(double),
+ typeof(SfDateTimePicker),
+ defaultValueCreator: bindable => 14d,
+ propertyChanged: OnFooterFontSizeChanged);
+
+ ///
+ /// Identifies the dependency property.
+ ///
+ ///
+ /// The identifier for dependency property.
+ ///
+ internal static readonly BindableProperty SelectedTextColorProperty =
+ BindableProperty.Create(
+ nameof(SelectedTextColor),
+ typeof(Color),
+ typeof(SfDateTimePicker),
+ defaultValueCreator: bindable => Colors.White,
+ propertyChanged: OnSelectedTextColorChanged);
+
+ ///
+ /// Identifies the dependency property.
+ ///
+ ///
+ /// The identifier for dependency property.
+ ///
+ internal static readonly BindableProperty SelectionTextColorProperty =
+ BindableProperty.Create(
+ nameof(SelectionTextColor),
+ typeof(Color),
+ typeof(SfPicker),
+ defaultValueCreator: bindable => Color.FromArgb("#6750A4"),
+ propertyChanged: OnSelectedTextColorChanged);
+
+ ///
+ /// Identifies the dependency property.
+ ///
+ ///
+ /// The identifier for dependency property.
+ ///
+ internal static readonly BindableProperty SelectedFontSizeProperty =
+ BindableProperty.Create(
+ nameof(SelectedFontSize),
+ typeof(double),
+ typeof(SfDateTimePicker),
+ defaultValueCreator: bindable => 16d,
+ propertyChanged: OnSelectedFontSizeChanged);
+
+ ///
+ /// Identifies the dependency property.
+ ///
+ ///
+ /// The identifier for dependency property.
+ ///
+ internal static readonly BindableProperty NormalTextColorProperty =
+ BindableProperty.Create(
+ nameof(NormalTextColor),
+ typeof(Color),
+ typeof(SfDateTimePicker),
+ defaultValueCreator: bindable => Color.FromArgb("#1C1B1F"),
+ propertyChanged: OnNormalTextColorChanged);
+
+ ///
+ /// Identifies the dependency property.
+ ///
+ ///
+ /// The identifier for dependency property.
+ ///
+ internal static readonly BindableProperty NormalFontSizeProperty =
+ BindableProperty.Create(
+ nameof(NormalFontSize),
+ typeof(double),
+ typeof(SfDateTimePicker),
+ defaultValueCreator: bindable => 16d,
+ propertyChanged: OnNormalFontSizeChanged);
+
+ ///
+ /// Identifies the dependency property.
+ ///
+ ///
+ /// The identifier for dependency property.
+ ///
+ internal static readonly BindableProperty DisabledTextColorProperty =
+ BindableProperty.Create(
+ nameof(DisabledTextColor),
+ typeof(Color),
+ typeof(SfDateTimePicker),
+ defaultValueCreator: bindable => Color.FromArgb("#611C1B1F"),
+ propertyChanged: OnDisabledTextColorChanged);
+
+ #endregion
+
+ #region Constructor
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ public SfDateTimePicker()
+ {
+ _dayColumn = new PickerColumn();
+ _monthColumn = new PickerColumn();
+ _yearColumn = new PickerColumn();
+ _hourColumn = new PickerColumn();
+ _minuteColumn = new PickerColumn();
+ _secondColumn = new PickerColumn();
+ _meridiemColumn = new PickerColumn();
+ _columns = new ObservableCollection();
+ _selectedIndex = 0;
+ Initialize();
+ GeneratePickerColumns();
+ BaseColumns = _columns;
+ SelectionIndexChanged += OnPickerSelectionIndexChanged;
+ BlackoutDateTimes.CollectionChanged += OnBlackoutDateTimes_CollectionChanged;
+ BackgroundColor = DateTimePickerBackground;
+ Dispatcher.Dispatch(() =>
+ {
+ InitializeTheme();
+ });
+ HeaderView.Parent = this;
+ ColumnHeaderView.Parent = this;
+ }
+
+ #endregion
+
+ #region Properties
+
+ ///
+ /// Gets or sets the value of header view. This property can be used to customize the header in SfDateTimePicker.
+ ///
+ ///
+ /// The following example demonstrates how to customize the header view of SfDateTimePicker.
+ ///
+ ///
+ ///
+ ///
+ public DateTimePickerHeaderView HeaderView
+ {
+ get { return (DateTimePickerHeaderView)GetValue(HeaderViewProperty); }
+ set { SetValue(HeaderViewProperty, value); }
+ }
+
+ ///
+ /// Gets or sets the value of column header view. This property can be used to customize the header column in SfDateTimePicker.
+ ///
+ ///
+ /// The following example demonstrates how to customize the column header view of SfDateTimePicker.
+ ///
+ ///
+ ///
+ ///
+ public DateTimePickerColumnHeaderView ColumnHeaderView
+ {
+ get { return (DateTimePickerColumnHeaderView)GetValue(ColumnHeaderViewProperty); }
+ set { SetValue(ColumnHeaderViewProperty, value); }
+ }
+
+ ///
+ /// Gets or sets the date picker selected date in SfDateTimePicker.
+ ///
+ /// The default value of is .
+ ///
+ /// The following examples demonstrate how to set the selected date in SfDateTimePicker.
+ /// # [XAML](#tab/tabid-1)
+ ///
+ ///
+ /// ]]>
+ ///
+ /// # [C#](#tab/tabid-2)
+ ///
+ /// SfDateTimePicker dateTimePicker = new SfDateTimePicker();
+ /// dateTimePicker.SelectedDate = new DateTime(2023, 6, 15, 14, 30, 0);
+ ///
+ ///
+ public DateTime? SelectedDate
+ {
+ get { return (DateTime?)GetValue(SelectedDateProperty); }
+ set { SetValue(SelectedDateProperty, value); }
+ }
+
+ ///
+ /// Gets or sets the day interval in SfDateTimePicker.
+ ///
+ /// The default value of is 1.
+ ///
+ /// The following examples demonstrate how to set the day interval in SfDateTimePicker.
+ /// # [XAML](#tab/tabid-3)
+ ///
+ ///
+ /// ]]>
+ ///
+ /// # [C#](#tab/tabid-4)
+ ///
+ /// SfDateTimePicker dateTimePicker = new SfDateTimePicker();
+ /// dateTimePicker.DayInterval = 2;
+ ///
+ ///
+ public int DayInterval
+ {
+ get { return (int)GetValue(DayIntervalProperty); }
+ set { SetValue(DayIntervalProperty, value); }
+ }
+
+ ///
+ /// Gets or sets the month interval in SfDateTimePicker.
+ ///
+ /// The default value of is 1.
+ ///
+ /// The following examples demonstrate how to set the month interval in SfDateTimePicker.
+ /// # [XAML](#tab/tabid-5)
+ ///
+ ///
+ /// ]]>
+ ///
+ /// # [C#](#tab/tabid-6)
+ ///
+ /// SfDateTimePicker dateTimePicker = new SfDateTimePicker();
+ /// dateTimePicker.MonthInterval = 2;
+ ///
+ ///
+ public int MonthInterval
+ {
+ get { return (int)GetValue(MonthIntervalProperty); }
+ set { SetValue(MonthIntervalProperty, value); }
+ }
+
+ ///
+ /// Gets or sets the year interval in SfDateTimePicker.
+ ///
+ /// The default value of is 1.
+ ///
+ /// The following examples demonstrate how to set the year interval in SfDateTimePicker.
+ /// # [XAML](#tab/tabid-7)
+ ///
+ ///
+ /// ]]>
+ ///
+ /// # [C#](#tab/tabid-8)
+ ///
+ /// SfDateTimePicker dateTimePicker = new SfDateTimePicker();
+ /// dateTimePicker.YearInterval = 2;
+ ///
+ ///
+ public int YearInterval
+ {
+ get { return (int)GetValue(YearIntervalProperty); }
+ set { SetValue(YearIntervalProperty, value); }
+ }
+
+ ///
+ /// Gets or sets the hour interval in SfDateTimePicker.
+ ///
+ /// The default value of is 1.
+ ///
+ /// The following examples demonstrate how to set the hour interval in SfDateTimePicker.
+ /// # [XAML](#tab/tabid-9)
+ ///
+ ///
+ /// ]]>
+ ///
+ /// # [C#](#tab/tabid-10)
+ ///
+ /// SfDateTimePicker dateTimePicker = new SfDateTimePicker();
+ /// dateTimePicker.HourInterval = 2;
+ ///
+ ///
+ public int HourInterval
+ {
+ get { return (int)GetValue(HourIntervalProperty); }
+ set { SetValue(HourIntervalProperty, value); }
+ }
+
+ ///
+ /// Gets or sets the minute interval in SfDateTimePicker.
+ ///
+ /// The default value of is 1.
+ ///
+ /// The following examples demonstrate how to set the minute interval in SfDateTimePicker.
+ /// # [XAML](#tab/tabid-11)
+ ///
+ ///
+ /// ]]>
+ ///
+ /// # [C#](#tab/tabid-12)
+ ///
+ /// SfDateTimePicker dateTimePicker = new SfDateTimePicker();
+ /// dateTimePicker.MinuteInterval = 2;
+ ///
+ ///
+ public int MinuteInterval
+ {
+ get { return (int)GetValue(MinuteIntervalProperty); }
+ set { SetValue(MinuteIntervalProperty, value); }
+ }
+
+ ///
+ /// Gets or sets the second interval in SfDateTimePicker.
+ ///
+ /// The default value of is 1.
+ /// ///
+ /// The following examples demonstrate how to set the second interval in SfDateTimePicker.
+ /// # [XAML](#tab/tabid-13)
+ ///
+ ///
+ /// ]]>
+ ///
+ /// # [C#](#tab/tabid-14)
+ ///
+ /// SfDateTimePicker dateTimePicker = new SfDateTimePicker();
+ /// dateTimePicker.SecondInterval = 2;
+ ///
+ ///
+ public int SecondInterval
+ {
+ get { return (int)GetValue(SecondIntervalProperty); }
+ set { SetValue(SecondIntervalProperty, value); }
+ }
+
+ ///
+ /// Gets or sets the picker date format in SfDateTimePicker.
+ ///
+ /// The default value of is .
+ ///
+ /// The following examples demonstrate how to set the date format in SfDateTimePicker.
+ /// # [XAML](#tab/tabid-15)
+ ///
+ ///
+ /// ]]>
+ ///
+ /// # [C#](#tab/tabid-16)
+ ///
+ /// SfDateTimePicker dateTimePicker = new SfDateTimePicker();
+ /// dateTimePicker.DateFormat = PickerDateFormat.dd_MM_yyyy;
+ ///
+ ///
+ public PickerDateFormat DateFormat
+ {
+ get { return (PickerDateFormat)GetValue(DateFormatProperty); }
+ set { SetValue(DateFormatProperty, value); }
+ }
+
+ ///
+ /// Gets or sets the picker time format in SfDateTimePicker.
+ ///
+ /// The default value of is .
+ ///
+ /// The following examples demonstrate how to set the time format in SfDateTimePicker.
+ /// # [XAML](#tab/tabid-17)
+ ///
+ ///
+ /// ]]>
+ ///
+ /// # [C#](#tab/tabid-18)
+ ///
+ /// SfDateTimePicker dateTimePicker = new SfDateTimePicker();
+ /// dateTimePicker.TimeFormat = PickerTimeFormat.hh_mm_tt;
+ ///
+ ///
+ public PickerTimeFormat TimeFormat
+ {
+ get { return (PickerTimeFormat)GetValue(TimeFormatProperty); }
+ set { SetValue(TimeFormatProperty, value); }
+ }
+
+ ///
+ /// Gets or sets the minimum date in SfDateTimePicker.
+ ///
+ /// The default value of is "DateTime(1900, 01, 01)".
+ ///
+ /// The following examples demonstrate how to set the minimum date in SfDateTimePicker.
+ /// # [XAML](#tab/tabid-19)
+ ///
+ ///
+ /// ]]>
+ ///
+ /// # [C#](#tab/tabid-20)
+ ///
+ /// SfDateTimePicker dateTimePicker = new SfDateTimePicker();
+ /// dateTimePicker.MinimumDate = new DateTime(2023, 1, 1);
+ ///
+ ///
+ public DateTime MinimumDate
+ {
+ get { return (DateTime)GetValue(MinimumDateProperty); }
+ set { SetValue(MinimumDateProperty, value); }
+ }
+
+ ///
+ /// Gets or sets the maximum date in SfDateTimePicker.
+ ///
+ /// The default value of is "DateTime(2100, 12, 31, 23, 59, 59)".
+ ///
+ /// The following examples demonstrate how to set the maximum date in SfDateTimePicker.
+ /// # [XAML](#tab/tabid-21)
+ ///
+ ///
+ /// ]]>
+ ///
+ /// # [C#](#tab/tabid-22)
+ ///
+ /// SfDateTimePicker dateTimePicker = new SfDateTimePicker();
+ /// dateTimePicker.MaximumDate = new DateTime(2023, 12, 31, 23, 59, 59);
+ ///
+ ///
+ public DateTime MaximumDate
+ {
+ get { return (DateTime)GetValue(MaximumDateProperty); }
+ set { SetValue(MaximumDateProperty, value); }
+ }
+
+ ///
+ /// Gets or sets the selection changed command in SfDateTimePicker.
+ ///
+ /// The default value of is null.
+ ///
+ /// The following example demonstrates how to set the selection changed command in SfDateTimePicker.
+ /// # [XAML](#tab/tabid-23)
+ ///
+ ///
+ ///
+ ///
+ ///
+ /// ]]>
+ /// # [C#](#tab/tabid-24)
+ ///
+ ///
+ public ICommand SelectionChangedCommand
+ {
+ get { return (ICommand)GetValue(SelectionChangedCommandProperty); }
+ set { SetValue(SelectionChangedCommandProperty, value); }
+ }
+
+ ///
+ /// Gets or sets the BlackoutDateTimes in SfDateTimePicker.
+ ///
+ /// The selection view will not be applicable when setting blackout datetimes.
+ ///
+ /// The following examples demonstrate how to set the blackout date times in SfDateTimePicker.
+ /// # [XAML](#tab/tabid-25)
+ ///
+ ///
+ ///
+ /// 2001-08-10
+ /// 2001-08-12
+ /// 2001-08-14
+ /// 2001-08-15 12:11:00
+ /// 2001-08-15 12:12:00
+ /// 2001-08-15 12:08:00
+ /// 2001-08-15 12:06:00
+ ///
+ ///
+ /// ]]>
+ ///
+ /// # [C#](#tab/tabid-26)
+ ///
+ /// SfDatePicker picker = new SfDatePicker();
+ /// picker.BlackoutDateTimes.Add(new DateTime(2001, 8, 10));
+ /// picker.BlackoutDateTimes.Add(new DateTime(2001, 8, 12));
+ /// picker.BlackoutDateTimes.Add(new DateTime(2001, 8, 14));
+ /// picker.BlackoutDateTimes.Add(new DateTime(2001, 8, 17));
+ /// picker.BlackoutDateTimes.Add(new DateTime(2001, 8, 15, 12, 11, 0));
+ /// picker.BlackoutDateTimes.Add(new DateTime(2001, 8, 15, 12, 12, 0));
+ /// picker.BlackoutDateTimes.Add(new DateTime(2001, 8, 15, 12, 8, 0));
+ ///
+ ///
+ public ObservableCollection BlackoutDateTimes
+ {
+ get { return (ObservableCollection)GetValue(BlackoutDateTimesProperty); }
+ set { SetValue(BlackoutDateTimesProperty, value); }
+ }
+
+ #endregion
+
+ #region Internal Properties
+
+ ///
+ /// Gets or sets the background color of the picker.
+ ///
+ internal Color DateTimePickerBackground
+ {
+ get { return (Color)GetValue(DateTimePickerBackgroundProperty); }
+ set { SetValue(DateTimePickerBackgroundProperty, value); }
+ }
+
+ ///
+ /// Gets or sets the background of the footer view in SfDateTimePicker.
+ ///
+ internal Brush FooterBackground
+ {
+ get { return (Brush)GetValue(FooterBackgroundProperty); }
+ set { SetValue(FooterBackgroundProperty, value); }
+ }
+
+ ///
+ /// Gets or sets the background of the selection view in SfDateTimePicker.
+ ///
+ internal Brush SelectionBackground
+ {
+ get { return (Brush)GetValue(SelectionBackgroundProperty); }
+ set { SetValue(SelectionBackgroundProperty, value); }
+ }
+
+ ///
+ /// Gets or sets the stroke color of the selection view in SfPicker.
+ ///
+ internal Color SelectionStrokeColor
+ {
+ get { return (Color)GetValue(SelectionStrokeColorProperty); }
+ set { SetValue(SelectionStrokeColorProperty, value); }
+ }
+
+ ///
+ /// Gets or sets the corner radius of the selection view in SfPicker.
+ ///
+ internal CornerRadius SelectionCornerRadius
+ {
+ get { return (CornerRadius)GetValue(SelectionCornerRadiusProperty); }
+ set { SetValue(SelectionCornerRadiusProperty, value); }
+ }
+
+ ///
+ /// Gets or sets the background of the footer separator line background in SfPicker.
+ ///
+ internal Color FooterDividerColor
+ {
+ get { return (Color)GetValue(FooterDividerColorProperty); }
+ set { SetValue(FooterDividerColorProperty, value); }
+ }
+
+ ///
+ /// Gets or sets the footer text color of the text style.
+ ///
+ internal Color FooterTextColor
+ {
+ get { return (Color)GetValue(FooterTextColorProperty); }
+ set { SetValue(FooterTextColorProperty, value); }
+ }
+
+ ///
+ /// Gets or sets the footer font size of the text style.
+ ///
+ internal double FooterFontSize
+ {
+ get { return (double)GetValue(FooterFontSizeProperty); }
+ set { SetValue(FooterFontSizeProperty, value); }
+ }
+
+ ///
+ /// Gets or sets the selection text color of the text style.
+ ///
+ ///
+ /// This color applicable for default text display mode.
+ ///
+ internal Color SelectedTextColor
+ {
+ get { return (Color)GetValue(SelectedTextColorProperty); }
+ set { SetValue(SelectedTextColorProperty, value); }
+ }
+
+ ///
+ /// Gets or sets the selection text color of the text style.
+ ///
+ ///
+ /// This color is used for Fade, Shrink and FadeAndShrink mode.
+ ///
+ internal Color SelectionTextColor
+ {
+ get { return (Color)GetValue(SelectionTextColorProperty); }
+ set { SetValue(SelectionTextColorProperty, value); }
+ }
+
+ ///
+ /// Gets or sets the selection font size of the text style.
+ ///
+ internal double SelectedFontSize
+ {
+ get { return (double)GetValue(SelectedFontSizeProperty); }
+ set { SetValue(SelectedFontSizeProperty, value); }
+ }
+
+ ///
+ /// Gets or sets the normal text color of the text style.
+ ///
+ internal Color NormalTextColor
+ {
+ get { return (Color)GetValue(NormalTextColorProperty); }
+ set { SetValue(NormalTextColorProperty, value); }
+ }
+
+ ///
+ /// Gets or sets the normal font size of the text style.
+ ///
+ internal double NormalFontSize
+ {
+ get { return (double)GetValue(NormalFontSizeProperty); }
+ set { SetValue(NormalFontSizeProperty, value); }
+ }
+
+ ///
+ /// Gets or sets the disabled text color of the text style.
+ ///
+ internal Color DisabledTextColor
+ {
+ get { return (Color)GetValue(DisabledTextColorProperty); }
+ set { SetValue(DisabledTextColorProperty, value); }
+ }
+
+ #endregion
+
+ #region Internal Methods
+
+ ///
+ /// Method to reset the picker date columns.
+ ///
+ internal void ResetDateColumns()
+ {
+ _dayColumn = new PickerColumn();
+ _monthColumn = new PickerColumn();
+ _yearColumn = new PickerColumn();
+ GeneratePickerColumns();
+ BaseColumns.Clear();
+ BaseColumns = _columns;
+ }
+
+ ///
+ /// Method to get the date header text based on format.
+ ///
+ /// Returns the date header text.
+ internal string GetDateHeaderText()
+ {
+ DateTime maxDate = DatePickerHelper.GetValidMaxDate(MinimumDate, MaximumDate);
+ DateTime? selectedDateNullable = SelectedDate;
+
+ // Check if selectedDateNullable is null, and return an empty string if it is
+ if (!selectedDateNullable.HasValue)
+ {
+ return SfPickerResources.GetLocalizedString("Date");
+ }
+
+ DateTime selectedDate = DatePickerHelper.GetValidDateTime(selectedDateNullable.Value, MinimumDate, maxDate);
+ string value = selectedDate.ToString(HeaderView.DateFormat, CultureInfo.CurrentUICulture);
+ value = DatePickerHelper.ReplaceCultureMonthString(value, HeaderView.DateFormat, selectedDate);
+ value = DatePickerHelper.ReplaceCultureMeridiemString(value, HeaderView.DateFormat);
+ return value;
+ }
+
+ ///
+ /// Method to get the time header text based on format.
+ ///
+ /// Returns the time header text.
+ internal string GetTimeHeaderText()
+ {
+ DateTime maxDate = DatePickerHelper.GetValidMaxDate(MinimumDate, MaximumDate);
+ DateTime? selectedTimeNullable = SelectedDate;
+ if (!selectedTimeNullable.HasValue)
+ {
+ return SfPickerResources.GetLocalizedString("Time");
+ }
+
+ DateTime selectedDate = DatePickerHelper.GetValidDateTime(SelectedDate, MinimumDate, maxDate);
+ string value = selectedDate.ToString(HeaderView.TimeFormat, CultureInfo.InvariantCulture);
+ value = DatePickerHelper.ReplaceCultureMonthString(value, HeaderView.TimeFormat, selectedDate);
+ value = DatePickerHelper.ReplaceCultureMeridiemString(value, HeaderView.TimeFormat);
+ return value;
+ }
+
+ #endregion
+
+ #region Private Methods
+
+ ///
+ /// Method trigged whenever the base panel selection is changed.
+ ///
+ /// Base picker instance value.
+ /// Selection changed event arguments.
+ void OnPickerSelectionIndexChanged(object? sender, PickerSelectionChangedEventArgs e)
+ {
+ if (_selectedIndex == 0)
+ {
+ OnDatePickerSelectionIndexChanged(e);
+ }
+ else
+ {
+ OnTimePickerSelectionIndexChanged(e);
+ }
+ }
+
+ ///
+ /// Method trigged whenever the base panel date selection is changed.
+ ///
+ /// Selection changed event arguments.
+ void OnDatePickerSelectionIndexChanged(PickerSelectionChangedEventArgs e)
+ {
+ string dayFormat;
+ string monthFormat;
+ List formatStringOrder = DatePickerHelper.GetFormatStringOrder(out dayFormat, out monthFormat, DateFormat);
+ int changedColumnValue = formatStringOrder[e.ColumnIndex];
+ DateTime maxDate = DatePickerHelper.GetValidMaxDate(MinimumDate, MaximumDate);
+ DateTime date = SelectedDate ?? _previousSelectedDateTime;
+ DateTime previousSelectedDate = DatePickerHelper.GetValidDateTime(date, MinimumDate, maxDate);
+ switch (changedColumnValue)
+ {
+ //// Need to handle the day selection changes.
+ case 0:
+ {
+ int day = 1;
+ if (_dayColumn.ItemsSource != null && _dayColumn.ItemsSource is ObservableCollection dayCollection && dayCollection.Count > e.NewValue)
+ {
+ //// Get the day value based on the selected index changes value.
+ day = int.Parse(dayCollection[e.NewValue]);
+ }
+
+ DateTime selectedDate = new DateTime(previousSelectedDate.Year, previousSelectedDate.Month, day, previousSelectedDate.Hour, previousSelectedDate.Minute, previousSelectedDate.Second);
+ if (!DatePickerHelper.IsSameDateTime(selectedDate, SelectedDate))
+ {
+ SelectedDate = selectedDate;
+ }
+ }
+
+ break;
+ //// Need to handle the month selection changes.
+ case 1:
+ {
+ int month = 1;
+ if (_monthColumn.ItemsSource != null && _monthColumn.ItemsSource is ObservableCollection monthCollection && monthCollection.Count > e.NewValue)
+ {
+ if (monthFormat == "M" || monthFormat == "MM")
+ {
+ //// Get the month value based on the selected index changes value.
+ month = int.Parse(monthCollection[e.NewValue]);
+ }
+ else if (monthFormat == "MMM")
+ {
+ List months = DateTimeFormatInfo.CurrentInfo.AbbreviatedMonthNames.ToList();
+ //// Get the month value based on the selected index changes value.
+ month = months.IndexOf(monthCollection[e.NewValue]) + 1;
+ }
+ }
+
+ ObservableCollection days = DatePickerHelper.GetDays(dayFormat, month, previousSelectedDate.Year, MinimumDate, maxDate, DayInterval);
+ ObservableCollection previousDays = _dayColumn.ItemsSource is ObservableCollection previousDayCollection ? previousDayCollection : new ObservableCollection();
+ //// Check the month selection changes needed to update the days collection.
+ if (!PickerHelper.IsCollectionEquals(days, previousDays))
+ {
+ _dayColumn.ItemsSource = days;
+ }
+
+ //// Check the new days collection have a selected day value, if not then update the nearby value.
+ int index = DatePickerHelper.GetDayIndex(dayFormat, days, previousSelectedDate.Day);
+ int day = index == -1 ? 1 : int.Parse(days[index]);
+
+ DateTime selectedDate = new DateTime(previousSelectedDate.Year, month, day, previousSelectedDate.Hour, previousSelectedDate.Minute, previousSelectedDate.Second);
+ if (!DatePickerHelper.IsSameDateTime(selectedDate, SelectedDate))
+ {
+ SelectedDate = selectedDate;
+ }
+ }
+
+ break;
+ //// Need to handle the year selection changes.
+ case 2:
+ {
+ int year = MinimumDate.Year;
+ if (_yearColumn.ItemsSource != null && _yearColumn.ItemsSource is ObservableCollection yearCollection && yearCollection.Count > e.NewValue)
+ {
+ //// Get the year value based on the selected index changes value.
+ year = int.Parse(yearCollection[e.NewValue]);
+ }
+
+ ObservableCollection months = DatePickerHelper.GetMonths(monthFormat, year, MinimumDate, maxDate, MonthInterval);
+ ObservableCollection previousMonths = _monthColumn.ItemsSource is ObservableCollection previousMonthCollection ? previousMonthCollection : new ObservableCollection();
+ //// Check the year index changes needed to update the month collection.
+ if (!PickerHelper.IsCollectionEquals(months, previousMonths))
+ {
+ _monthColumn.ItemsSource = months;
+ }
+
+ //// Check the month collection have selected month value, if not then update the nearby value.
+ int monthIndex = DatePickerHelper.GetMonthIndex(monthFormat, months, previousSelectedDate.Month);
+ int month = 1;
+ if (monthFormat == "M" || monthFormat == "MM")
+ {
+ //// Get the month value based on the selected index changes value.
+ month = int.Parse(months[monthIndex]);
+ }
+ else if (monthFormat == "MMM")
+ {
+ List monthStrings = DateTimeFormatInfo.CurrentInfo.AbbreviatedMonthNames.ToList();
+ //// Get the month value based on the selected index changes value.
+ month = monthStrings.IndexOf(months[monthIndex]) + 1;
+ }
+
+ ObservableCollection days = DatePickerHelper.GetDays(dayFormat, month, year, MinimumDate, maxDate, DayInterval);
+ ObservableCollection previousDays = _dayColumn.ItemsSource is ObservableCollection previousDayCollection ? previousDayCollection : new ObservableCollection();
+ //// Check the year and month(if month items source updated) changes needed to change the day collection.
+ if (!PickerHelper.IsCollectionEquals(days, previousDays))
+ {
+ _dayColumn.ItemsSource = days;
+ }
+
+ //// Check the day collection have selected day value, if not then update the nearby value.
+ int index = DatePickerHelper.GetDayIndex(dayFormat, days, previousSelectedDate.Day);
+ int day = index == -1 ? 1 : int.Parse(days[index]);
+
+ DateTime selectedDate = new DateTime(year, month, day, previousSelectedDate.Hour, previousSelectedDate.Minute, previousSelectedDate.Second);
+ if (!DatePickerHelper.IsSameDateTime(selectedDate, SelectedDate))
+ {
+ SelectedDate = selectedDate;
+ }
+ }
+
+ break;
+ }
+ }
+
+ ///
+ /// Method trigged whenever the base panel time picker selection is changed.
+ ///
+ /// Selection changed event arguments.
+ void OnTimePickerSelectionIndexChanged(PickerSelectionChangedEventArgs e)
+ {
+ string hourFormat;
+ List formatStringOrder = TimePickerHelper.GetFormatStringOrder(out hourFormat, TimeFormat);
+ int changedColumnValue = formatStringOrder[e.ColumnIndex];
+ DateTime maxDate = DatePickerHelper.GetValidMaxDate(MinimumDate, MaximumDate);
+ DateTime date = SelectedDate ?? _previousSelectedDateTime;
+ DateTime previousSelectedDate = DatePickerHelper.GetValidDateTime(date, MinimumDate, maxDate);
+ bool isMinDate = previousSelectedDate.Date == MinimumDate.Date;
+ bool isMaxDate = previousSelectedDate.Date == maxDate.Date;
+ switch (changedColumnValue)
+ {
+ case 0:
+ {
+ int hour = 0;
+ if (_hourColumn.ItemsSource != null && _hourColumn.ItemsSource is ObservableCollection hourCollection && hourCollection.Count > e.NewValue)
+ {
+ //// Get the hour value based on the selected index changes value.
+ hour = int.Parse(hourCollection[e.NewValue]);
+ }
+
+ if (hourFormat == "h" || hourFormat == "hh")
+ {
+ hour = hour == 12 ? 0 : hour;
+ if (previousSelectedDate.Hour >= 12)
+ {
+ hour += 12;
+ }
+ }
+
+ ObservableCollection minutes = TimePickerHelper.GetMinutes(MinuteInterval, hour, previousSelectedDate, isMinDate ? MinimumDate : null, isMaxDate ? maxDate : null);
+ ObservableCollection previousMinutes = _minuteColumn.ItemsSource is ObservableCollection previousMinuteCollection ? previousMinuteCollection : new ObservableCollection();
+ if (!PickerHelper.IsCollectionEquals(minutes, previousMinutes))
+ {
+ _minuteColumn.ItemsSource = minutes;
+ }
+
+ int minuteIndex = TimePickerHelper.GetMinuteOrSecondIndex(minutes, previousSelectedDate.Minute);
+ //// Get the minute value based on the selected index changes value.
+ int minute = int.Parse(minutes[minuteIndex]);
+
+ ObservableCollection seconds = TimePickerHelper.GetSeconds(SecondInterval, hour, minute, previousSelectedDate, isMinDate ? MinimumDate : null, isMaxDate ? maxDate : null);
+ ObservableCollection previousSeconds = _secondColumn.ItemsSource is ObservableCollection previousSecondCollection ? previousSecondCollection : new ObservableCollection();
+ if (!PickerHelper.IsCollectionEquals(seconds, previousSeconds))
+ {
+ _secondColumn.ItemsSource = seconds;
+ }
+
+ int secondIndex = TimePickerHelper.GetMinuteOrSecondIndex(seconds, previousSelectedDate.Second);
+ //// Get the second value based on the selected index changes value.
+ int second = int.Parse(seconds[secondIndex]);
+
+ DateTime selectedDate = new DateTime(previousSelectedDate.Year, previousSelectedDate.Month, previousSelectedDate.Day, hour, minute, second);
+ if (!DatePickerHelper.IsSameDateTime(selectedDate, SelectedDate))
+ {
+ SelectedDate = selectedDate;
+ }
+ }
+
+ break;
+ case 1:
+ {
+ int minutes = 0;
+ if (_minuteColumn.ItemsSource != null && _minuteColumn.ItemsSource is ObservableCollection minuteCollection && minuteCollection.Count > e.NewValue)
+ {
+ //// Get the minute value based on the selected index changes value.
+ minutes = int.Parse(minuteCollection[e.NewValue]);
+ }
+
+ ObservableCollection seconds = TimePickerHelper.GetSeconds(SecondInterval, previousSelectedDate.Hour, minutes, previousSelectedDate, isMinDate ? MinimumDate : null, isMaxDate ? maxDate : null);
+ ObservableCollection previousSeconds = _secondColumn.ItemsSource is ObservableCollection previousSecondCollection ? previousSecondCollection : new ObservableCollection();
+ if (!PickerHelper.IsCollectionEquals(seconds, previousSeconds))
+ {
+ _secondColumn.ItemsSource = seconds;
+ }
+
+ int secondIndex = TimePickerHelper.GetMinuteOrSecondIndex(seconds, previousSelectedDate.Second);
+ //// Get the second value based on the selected index changes value.
+ int second = int.Parse(seconds[secondIndex]);
+
+ DateTime selectedDate = new DateTime(previousSelectedDate.Year, previousSelectedDate.Month, previousSelectedDate.Day, previousSelectedDate.Hour, minutes, second);
+ if (!DatePickerHelper.IsSameDateTime(selectedDate, previousSelectedDate))
+ {
+ SelectedDate = selectedDate;
+ }
+ }
+
+ break;
+ case 2:
+ {
+ int seconds = 0;
+ if (_secondColumn.ItemsSource != null && _secondColumn.ItemsSource is ObservableCollection secondCollection && secondCollection.Count > e.NewValue)
+ {
+ //// Get the seconds value based on the selected index changes value.
+ seconds = int.Parse(secondCollection[e.NewValue]);
+ }
+
+ DateTime selectedDate = new DateTime(previousSelectedDate.Year, previousSelectedDate.Month, previousSelectedDate.Day, previousSelectedDate.Hour, previousSelectedDate.Minute, seconds);
+ if (!DatePickerHelper.IsSameDateTime(selectedDate, SelectedDate))
+ {
+ SelectedDate = selectedDate;
+ }
+ }
+
+ break;
+ case 3:
+ {
+ ObservableCollection meridiemCollection = new ObservableCollection();
+ if (_meridiemColumn.ItemsSource != null && _meridiemColumn.ItemsSource is ObservableCollection meridiems)
+ {
+ meridiemCollection = meridiems;
+ }
+
+ if (meridiemCollection.Count <= e.NewValue)
+ {
+ return;
+ }
+
+ bool isAMSelected = TimePickerHelper.IsAMText(meridiemCollection, e.NewValue);
+ int neededHour = isAMSelected ? 0 : 12;
+
+ DateTime selectedDate = new DateTime(previousSelectedDate.Year, previousSelectedDate.Month, previousSelectedDate.Day, (previousSelectedDate.Hour % 12) + neededHour, previousSelectedDate.Minute, previousSelectedDate.Second);
+
+ ObservableCollection hours = TimePickerHelper.GetHours(hourFormat, HourInterval, selectedDate, MinimumDate, maxDate);
+ ObservableCollection previousHour = _hourColumn.ItemsSource is ObservableCollection previousHourCollection ? previousHourCollection : new ObservableCollection();
+ if (!PickerHelper.IsCollectionEquals(hours, previousHour))
+ {
+ _hourColumn.ItemsSource = hours;
+ }
+
+ int? hourIndex = TimePickerHelper.GetHourIndex(hourFormat, hours, previousSelectedDate.Hour);
+ if (hourIndex.HasValue)
+ {
+ int hour = int.Parse(hours[hourIndex.Value]);
+ hour = (hour % 12) + neededHour;
+
+ ObservableCollection minutes = TimePickerHelper.GetMinutes(MinuteInterval, hour, previousSelectedDate, isMinDate ? MinimumDate : null, isMaxDate ? maxDate : null);
+ ObservableCollection previousMinutes = _minuteColumn.ItemsSource is ObservableCollection previousMinuteCollection ? previousMinuteCollection : new ObservableCollection();
+ if (!PickerHelper.IsCollectionEquals(minutes, previousMinutes))
+ {
+ _minuteColumn.ItemsSource = minutes;
+ }
+
+ int minuteIndex = TimePickerHelper.GetMinuteOrSecondIndex(minutes, previousSelectedDate.Minute);
+ //// Get the minute value based on the selected index changes value.
+ int minute = int.Parse(minutes[minuteIndex]);
+
+ ObservableCollection seconds = TimePickerHelper.GetSeconds(SecondInterval, hour, minute, previousSelectedDate, isMinDate ? MinimumDate : null, isMaxDate ? maxDate : null);
+ ObservableCollection previousSeconds = _secondColumn.ItemsSource is ObservableCollection previousSecondCollection ? previousSecondCollection : new ObservableCollection();
+ if (!PickerHelper.IsCollectionEquals(seconds, previousSeconds))
+ {
+ _secondColumn.ItemsSource = seconds;
+ }
+
+ int secondIndex = TimePickerHelper.GetMinuteOrSecondIndex(seconds, previousSelectedDate.Second);
+ //// Get the second value based on the selected index changes value.
+ int second = int.Parse(seconds[secondIndex]);
+
+ selectedDate = new DateTime(previousSelectedDate.Year, previousSelectedDate.Month, previousSelectedDate.Day, hour, minute, second);
+ if (!DatePickerHelper.IsSameDateTime(selectedDate, SelectedDate))
+ {
+ SelectedDate = selectedDate;
+ }
+ }
+ }
+
+ break;
+ }
+ }
+
+ ///
+ /// Method to update the selected index value for all the picker column based on the date value.
+ ///
+ /// The selected date value.
+ void UpdateSelectedIndex(DateTime? date)
+ {
+ if (_selectedIndex == 0)
+ {
+ UpdateSelectedDateIndex(date);
+ }
+ else
+ {
+ UpdateSelectedTimeIndex(date);
+ }
+
+ BaseHeaderView.DateText = GetDateHeaderText();
+ BaseHeaderView.TimeText = GetTimeHeaderText();
+ }
+
+ ///
+ /// Method to update the minimum and maximum date value for all the picker column based on the date value.
+ ///
+ /// Minimum and Maximum oldvalue.
+ /// Minimum and Maximum newvalue.
+ void UpdateMinimumMaximumDate(object oldValue, object newValue)
+ {
+ DateTime oldDate = (DateTime)oldValue;
+ DateTime newDate = (DateTime)newValue;
+ DateTime maxDate = DatePickerHelper.GetValidMaxDate(MinimumDate, MaximumDate);
+ DateTime validSelectedDate = DatePickerHelper.GetValidDateTime(SelectedDate, MinimumDate, maxDate);
+ if (_selectedIndex == 0)
+ {
+ UpdateMinimumMaximumDateColumns(oldDate, newDate, validSelectedDate, maxDate);
+ }
+ else
+ {
+ UpdateMinimumMaximumTimeColumns(oldDate, newDate, validSelectedDate, maxDate);
+ }
+ }
+
+ ///
+ /// Method to update the minimum and maximum date column based on the date value.
+ ///
+ /// Minimum and Maximum old value.
+ /// Minimum and Maximum new value.
+ /// Valid Selected Date.
+ /// Maximum Date.
+ void UpdateMinimumMaximumDateColumns(DateTime oldDate, DateTime newDate, DateTime validSelectedDate, DateTime maxDate)
+ {
+ string dayFormat;
+ string monthFormat;
+ List formatString = DatePickerHelper.GetFormatStringOrder(out dayFormat, out monthFormat, DateFormat);
+ int yearIndex = formatString.IndexOf(2);
+ if (yearIndex != -1 && oldDate.Year != newDate.Year)
+ {
+ _yearColumn = GenerateYearColumn(validSelectedDate);
+ _columns[yearIndex] = _yearColumn;
+ }
+
+ if (validSelectedDate.Year == MinimumDate.Year || validSelectedDate.Year == maxDate.Year)
+ {
+ ObservableCollection month = DatePickerHelper.GetMonths(monthFormat, validSelectedDate.Year, MinimumDate, maxDate, MonthInterval);
+ ObservableCollection previousMonths = _monthColumn.ItemsSource is ObservableCollection previousMonthCollection ? previousMonthCollection : new ObservableCollection();
+ //// Check the year index changes needed to update the month collection.
+ if (!PickerHelper.IsCollectionEquals(month, previousMonths))
+ {
+ _monthColumn = new PickerColumn()
+ {
+ ItemsSource = month,
+ SelectedIndex = DatePickerHelper.GetMonthIndex(monthFormat, month, validSelectedDate.Month),
+ HeaderText = SfPickerResources.GetLocalizedString(ColumnHeaderView.MonthHeaderText),
+ };
+ int monthIndex = formatString.IndexOf(1);
+ if (monthIndex != -1)
+ {
+ _columns[monthIndex] = _monthColumn;
+ }
+ }
+ }
+
+ if (!string.IsNullOrEmpty(dayFormat) && ((validSelectedDate.Year == MinimumDate.Year && validSelectedDate.Month == MinimumDate.Month) || (validSelectedDate.Year == maxDate.Year && validSelectedDate.Month == maxDate.Month)))
+ {
+ ObservableCollection days = DatePickerHelper.GetDays(dayFormat, validSelectedDate.Month, validSelectedDate.Year, MinimumDate, maxDate, DayInterval);
+ ObservableCollection previousDays = _dayColumn.ItemsSource is ObservableCollection previousDayCollection ? previousDayCollection : new ObservableCollection();
+ //// Check the year and month(if month items source updated) changes needed to change the day collection.
+ if (!PickerHelper.IsCollectionEquals(days, previousDays))
+ {
+ _dayColumn = new PickerColumn()
+ {
+ ItemsSource = days,
+ SelectedIndex = DatePickerHelper.GetDayIndex(dayFormat, days, validSelectedDate.Day),
+ HeaderText = SfPickerResources.GetLocalizedString(ColumnHeaderView.DayHeaderText),
+ };
+ int dayIndex = formatString.IndexOf(0);
+ if (dayIndex != -1)
+ {
+ _columns[dayIndex] = _dayColumn;
+ }
+ }
+ }
+ }
+
+ ///
+ /// Method to update the minimum and maximum time column based on the date value.
+ ///
+ /// Minimum and Maximum old value.
+ /// Minimum and Maximum new value.
+ /// Valid Selected Date.
+ /// Maximum Date.
+ void UpdateMinimumMaximumTimeColumns(DateTime oldDate, DateTime newDate, DateTime validSelectedDate, DateTime maxDate)
+ {
+ bool isSelectedDate = validSelectedDate.Date == oldDate.Date || validSelectedDate.Date == newDate.Date;
+ if (!isSelectedDate)
+ {
+ return;
+ }
+
+ string hourFormat;
+ List formatStringOrder = TimePickerHelper.GetFormatStringOrder(out hourFormat, TimeFormat);
+ int index = formatStringOrder.IndexOf(0);
+ if (oldDate.Hour != newDate.Hour && index != -1)
+ {
+ TimeSpan selectedTime = new TimeSpan(validSelectedDate.Hour, validSelectedDate.Minute, validSelectedDate.Second);
+ _hourColumn = GenerateHourColumn(hourFormat, selectedTime, validSelectedDate);
+ int hourIndex = index;
+ _columns[hourIndex] = _hourColumn;
+ }
+
+ index = formatStringOrder.IndexOf(1);
+ if (index != -1 && (validSelectedDate.Hour == oldDate.Hour || validSelectedDate.Hour == newDate.Hour))
+ {
+ ObservableCollection minutes = TimePickerHelper.GetMinutes(MinuteInterval, validSelectedDate.Hour, validSelectedDate, MinimumDate, maxDate);
+ ObservableCollection previousMinutes = _minuteColumn.ItemsSource is ObservableCollection previousMinuteCollection ? previousMinuteCollection : new ObservableCollection();
+ if (!PickerHelper.IsCollectionEquals(minutes, previousMinutes))
+ {
+ _minuteColumn = new PickerColumn()
+ {
+ ItemsSource = minutes,
+ SelectedIndex = TimePickerHelper.GetMinuteOrSecondIndex(minutes, validSelectedDate.Minute),
+ HeaderText = SfPickerResources.GetLocalizedString(ColumnHeaderView.MinuteHeaderText),
+ };
+ _columns[index] = _minuteColumn;
+ }
+ }
+
+ index = formatStringOrder.IndexOf(2);
+ if (index != -1 && ((validSelectedDate.Hour == oldDate.Hour && validSelectedDate.Minute == oldDate.Minute) || (validSelectedDate.Hour == newDate.Hour && validSelectedDate.Minute == newDate.Minute)))
+ {
+ ObservableCollection seconds = TimePickerHelper.GetSeconds(SecondInterval, validSelectedDate.Hour, validSelectedDate.Minute, validSelectedDate, MinimumDate, maxDate);
+ ObservableCollection previousSeconds = _secondColumn.ItemsSource is ObservableCollection previousSecondCollection ? previousSecondCollection : new ObservableCollection();
+ if (!PickerHelper.IsCollectionEquals(seconds, previousSeconds))
+ {
+ _secondColumn = new PickerColumn()
+ {
+ ItemsSource = seconds,
+ SelectedIndex = TimePickerHelper.GetMinuteOrSecondIndex(seconds, validSelectedDate.Second),
+ HeaderText = SfPickerResources.GetLocalizedString(ColumnHeaderView.SecondHeaderText),
+ };
+ _columns[index] = _secondColumn;
+ }
+ }
+
+ index = formatStringOrder.IndexOf(3);
+ if (index != -1 && ((int)(validSelectedDate.Hour / 12) == (int)(oldDate.Hour / 12) || (int)(validSelectedDate.Hour / 12) == (int)(newDate.Hour / 12)))
+ {
+ ObservableCollection meridiems = TimePickerHelper.GetMeridiem(MinimumDate, maxDate, validSelectedDate);
+ ObservableCollection previousCollection = _meridiemColumn.ItemsSource is ObservableCollection previousMeridiemCollection ? previousMeridiemCollection : new ObservableCollection();
+ if (!PickerHelper.IsCollectionEquals(meridiems, previousCollection))
+ {
+ _meridiemColumn = new PickerColumn()
+ {
+ ItemsSource = meridiems,
+ SelectedIndex = validSelectedDate.Hour >= 12 ? meridiems.Count > 1 ? 1 : 0 : 0,
+ HeaderText = SfPickerResources.GetLocalizedString(ColumnHeaderView.MeridiemHeaderText),
+ };
+ _columns[index] = _meridiemColumn;
+ }
+ }
+ }
+
+ ///
+ /// Method to update the selected index value for all the picker column based on the date value.
+ ///
+ /// The selected date value.
+ void UpdateSelectedDateIndex(DateTime? date)
+ {
+ if (date == null)
+ {
+ return;
+ }
+
+ string dayFormat;
+ string monthFormat;
+ DatePickerHelper.GetFormatStringOrder(out dayFormat, out monthFormat, DateFormat);
+ if (_yearColumn.ItemsSource != null && _yearColumn.ItemsSource is ObservableCollection yearCollection && yearCollection.Count > 0)
+ {
+ int index = DatePickerHelper.GetYearIndex(yearCollection, date.Value.Year);
+ if (_yearColumn.SelectedIndex != index)
+ {
+ _yearColumn.SelectedIndex = index;
+ }
+ }
+
+ if (_monthColumn.ItemsSource != null && _monthColumn.ItemsSource is ObservableCollection monthCollection && !string.IsNullOrEmpty(monthFormat))
+ {
+ int index = DatePickerHelper.GetMonthIndex(monthFormat, monthCollection, date.Value.Month);
+ if (_monthColumn.SelectedIndex != index)
+ {
+ _monthColumn.SelectedIndex = index;
+ }
+ }
+
+ if (_dayColumn.ItemsSource != null && _dayColumn.ItemsSource is ObservableCollection dayCollection && !string.IsNullOrEmpty(dayFormat))
+ {
+ int index = DatePickerHelper.GetDayIndex(dayFormat, dayCollection, date.Value.Day);
+ if (_dayColumn.SelectedIndex != index)
+ {
+ _dayColumn.SelectedIndex = index;
+ }
+ }
+ }
+
+ ///
+ /// Method to update the selected index value for all the picker column based on the selected time value.
+ ///
+ /// The selected date value.
+ void UpdateSelectedTimeIndex(DateTime? date)
+ {
+ if (date == null)
+ {
+ return;
+ }
+
+ string hourFormat;
+ TimePickerHelper.GetFormatStringOrder(out hourFormat, TimeFormat);
+ if (_hourColumn.ItemsSource != null && _hourColumn.ItemsSource is ObservableCollection hourCollection && hourCollection.Count > 0)
+ {
+ int? index = TimePickerHelper.GetHourIndex(hourFormat, hourCollection, date.Value.Hour);
+ if (index.HasValue && _hourColumn.SelectedIndex != index)
+ {
+ _hourColumn.SelectedIndex = index.Value;
+ }
+ }
+
+ if (_minuteColumn.ItemsSource != null && _minuteColumn.ItemsSource is ObservableCollection minuteCollection && minuteCollection.Count > 0)
+ {
+ int index = TimePickerHelper.GetMinuteOrSecondIndex(minuteCollection, date.Value.Minute);
+ if (_minuteColumn.SelectedIndex != index)
+ {
+ _minuteColumn.SelectedIndex = index;
+ }
+ }
+
+ if (_secondColumn.ItemsSource != null && _secondColumn.ItemsSource is ObservableCollection secondCollection && secondCollection.Count > 0)
+ {
+ int index = TimePickerHelper.GetMinuteOrSecondIndex(secondCollection, date.Value.Second);
+ if (_secondColumn.SelectedIndex != index)
+ {
+ _secondColumn.SelectedIndex = index;
+ }
+ }
+
+ if (_meridiemColumn.ItemsSource != null && _meridiemColumn.ItemsSource is ObservableCollection meridiemCollection && meridiemCollection.Count > 0)
+ {
+ int index = date.Value.Hour >= 12 ? 1 : 0;
+ if (_meridiemColumn.SelectedIndex != index)
+ {
+ _meridiemColumn.SelectedIndex = index;
+ }
+ }
+ }
+
+ ///
+ /// Method invokes on column header property changed.
+ ///
+ /// Column header view value.
+ /// Property changed arguments.
+ void OnColumnHeaderPropertyChanged(object? sender, PickerPropertyChangedEventArgs e)
+ {
+ if (e.PropertyName == nameof(DateTimePickerColumnHeaderView.Background))
+ {
+ BaseColumnHeaderView.Background = ColumnHeaderView.Background;
+ }
+ else if (e.PropertyName == nameof(DateTimePickerColumnHeaderView.Height))
+ {
+ BaseColumnHeaderView.Height = ColumnHeaderView.Height;
+ }
+ else if (e.PropertyName == nameof(DateTimePickerColumnHeaderView.DividerColor))
+ {
+ BaseColumnHeaderView.DividerColor = ColumnHeaderView.DividerColor;
+ }
+ else if (e.PropertyName == nameof(DateTimePickerColumnHeaderView.TextStyle))
+ {
+ SetInheritedBindingContext(ColumnHeaderView.TextStyle, BindingContext);
+ BaseColumnHeaderView.TextStyle = ColumnHeaderView.TextStyle;
+ }
+ else if (e.PropertyName == nameof(DateTimePickerColumnHeaderView.DayHeaderText))
+ {
+ _dayColumn.HeaderText = SfPickerResources.GetLocalizedString(ColumnHeaderView.DayHeaderText);
+ }
+ else if (e.PropertyName == nameof(DateTimePickerColumnHeaderView.MonthHeaderText))
+ {
+ _monthColumn.HeaderText = SfPickerResources.GetLocalizedString(ColumnHeaderView.MonthHeaderText);
+ }
+ else if (e.PropertyName == nameof(DateTimePickerColumnHeaderView.YearHeaderText))
+ {
+ _yearColumn.HeaderText = SfPickerResources.GetLocalizedString(ColumnHeaderView.YearHeaderText);
+ }
+ else if (e.PropertyName == nameof(DateTimePickerColumnHeaderView.HourHeaderText))
+ {
+ _hourColumn.HeaderText = SfPickerResources.GetLocalizedString(ColumnHeaderView.HourHeaderText);
+ }
+ else if (e.PropertyName == nameof(DateTimePickerColumnHeaderView.MinuteHeaderText))
+ {
+ _minuteColumn.HeaderText = SfPickerResources.GetLocalizedString(ColumnHeaderView.MinuteHeaderText);
+ }
+ else if (e.PropertyName == nameof(DateTimePickerColumnHeaderView.SecondHeaderText))
+ {
+ _secondColumn.HeaderText = SfPickerResources.GetLocalizedString(ColumnHeaderView.SecondHeaderText);
+ }
+ else if (e.PropertyName == nameof(DateTimePickerColumnHeaderView.MeridiemHeaderText))
+ {
+ _meridiemColumn.HeaderText = SfPickerResources.GetLocalizedString(ColumnHeaderView.MeridiemHeaderText);
+ }
+ }
+
+ ///
+ /// Method invokes on header property changed.
+ ///
+ /// Header view value.
+ /// Property changed arguments.
+ void OnHeaderPropertyChanged(object? sender, PickerPropertyChangedEventArgs e)
+ {
+ if (e.PropertyName == nameof(DateTimePickerHeaderView.Background))
+ {
+ BaseHeaderView.Background = HeaderView.Background;
+ }
+ else if (e.PropertyName == nameof(DateTimePickerHeaderView.Height))
+ {
+ BaseHeaderView.Height = HeaderView.Height;
+ }
+ else if (e.PropertyName == nameof(DateTimePickerHeaderView.DividerColor))
+ {
+ BaseHeaderView.DividerColor = HeaderView.DividerColor;
+ }
+ else if (e.PropertyName == nameof(DateTimePickerHeaderView.TextStyle))
+ {
+ SetInheritedBindingContext(HeaderView.TextStyle, BindingContext);
+ BaseHeaderView.TextStyle = HeaderView.TextStyle;
+ }
+ else if (e.PropertyName == nameof(DateTimePickerHeaderView.SelectionTextStyle))
+ {
+ SetInheritedBindingContext(HeaderView.SelectionTextStyle, BindingContext);
+ BaseHeaderView.SelectionTextStyle = HeaderView.SelectionTextStyle;
+ }
+ else if (e.PropertyName == nameof(DateTimePickerHeaderView.DateFormat))
+ {
+ BaseHeaderView.DateText = GetDateHeaderText();
+ }
+ else if (e.PropertyName == nameof(DateTimePickerHeaderView.TimeFormat))
+ {
+ BaseHeaderView.TimeText = GetTimeHeaderText();
+ }
+ }
+
+ ///
+ /// Method to generate the day and time columns based on the selected date time value.
+ ///
+ void GeneratePickerColumns()
+ {
+ if (_selectedIndex == 0)
+ {
+ GenerateDatePickerColumns();
+ }
+ else
+ {
+ GenerateTimePickerColumns();
+ }
+ }
+
+ ///
+ /// Method to generate the day, month, year columns based on the selected date value.
+ ///
+ void GenerateDatePickerColumns()
+ {
+ string dayFormat;
+ string monthFormat;
+ List formatStringOrder = DatePickerHelper.GetFormatStringOrder(out dayFormat, out monthFormat, DateFormat);
+ DateTime maxDate = DatePickerHelper.GetValidMaxDate(MinimumDate, MaximumDate);
+ DateTime date = SelectedDate ?? _previousSelectedDateTime;
+ DateTime selectedDate = DatePickerHelper.GetValidDateTime(date, MinimumDate, maxDate);
+ ObservableCollection pickerColumns = new ObservableCollection();
+ foreach (int index in formatStringOrder)
+ {
+ switch (index)
+ {
+ case 0:
+ _dayColumn = GenerateDayColumn(dayFormat, selectedDate);
+ _dayColumn.SelectedItem = SelectedDate != null ? PickerHelper.GetSelectedItemDefaultValue(_dayColumn) : null;
+ pickerColumns.Add(_dayColumn);
+ break;
+ case 1:
+ _monthColumn = GenerateMonthColumn(monthFormat, selectedDate);
+ _monthColumn.SelectedItem = SelectedDate != null ? PickerHelper.GetSelectedItemDefaultValue(_monthColumn) : null;
+ pickerColumns.Add(_monthColumn);
+ break;
+ case 2:
+ _yearColumn = GenerateYearColumn(selectedDate);
+ _yearColumn.SelectedItem = SelectedDate != null ? PickerHelper.GetSelectedItemDefaultValue(_yearColumn) : null;
+ pickerColumns.Add(_yearColumn);
+ break;
+ }
+ }
+
+ _columns = pickerColumns;
+ }
+
+ ///
+ /// Method to reset the picker time columns.
+ ///
+ void ResetTimeColumns()
+ {
+ _hourColumn = new PickerColumn();
+ _minuteColumn = new PickerColumn();
+ _secondColumn = new PickerColumn();
+ _meridiemColumn = new PickerColumn();
+ _columns = new ObservableCollection();
+ GeneratePickerColumns();
+ BaseColumns.Clear();
+ BaseColumns = _columns;
+ }
+
+ ///
+ /// Method to generate the day column with items source and selected index based on format.
+ ///
+ /// The day format.
+ /// The valid selected date value.
+ /// Returns day column details.
+ PickerColumn GenerateDayColumn(string format, DateTime? selectedDate)
+ {
+ DateTime maxDate = DatePickerHelper.GetValidMaxDate(MinimumDate, MaximumDate);
+ ObservableCollection days = DatePickerHelper.GetDays(format, DateTime.Now.Month, DateTime.Now.Year, MinimumDate, maxDate, DayInterval);
+
+ return new PickerColumn()
+ {
+ ItemsSource = days,
+ SelectedIndex = selectedDate != null ? DatePickerHelper.GetDayIndex(format, days, selectedDate.Value.Day) : _previousSelectedDateTime.Day - 1,
+ HeaderText = SfPickerResources.GetLocalizedString(ColumnHeaderView.DayHeaderText),
+ };
+ }
+
+ ///
+ /// Method to generate the month column with items source and selected index based on format.
+ ///
+ /// The month format value.
+ /// The valid selected date value.
+ /// Returns the month column instance.
+ PickerColumn GenerateMonthColumn(string format, DateTime? selectedDate)
+ {
+ DateTime maxDate = DatePickerHelper.GetValidMaxDate(MinimumDate, MaximumDate);
+ ObservableCollection months = DatePickerHelper.GetMonths(format, DateTime.Now.Year, MinimumDate, maxDate, MonthInterval);
+
+ return new PickerColumn()
+ {
+ ItemsSource = months,
+ SelectedIndex = selectedDate == null ? _previousSelectedDateTime.Month - 1 : DatePickerHelper.GetMonthIndex(format, months, selectedDate.Value.Month),
+ HeaderText = SfPickerResources.GetLocalizedString(ColumnHeaderView.MonthHeaderText),
+ };
+ }
+
+ ///
+ /// Method to generate the year column with items source and selected index based on format.
+ ///
+ /// The valid selected date value.
+ /// Returns the year column instance.
+ PickerColumn GenerateYearColumn(DateTime? selectedDate)
+ {
+ DateTime maxDate = DatePickerHelper.GetValidMaxDate(MinimumDate, MaximumDate);
+ ObservableCollection years = DatePickerHelper.GetYears(MinimumDate, maxDate, YearInterval);
+ return new PickerColumn()
+ {
+ ItemsSource = years,
+ SelectedIndex = selectedDate != null ? DatePickerHelper.GetYearIndex(years, selectedDate.Value.Year) : years.IndexOf(_previousSelectedDateTime.Year.ToString()),
+ HeaderText = SfPickerResources.GetLocalizedString(ColumnHeaderView.YearHeaderText),
+ };
+ }
+
+ ///
+ /// Method to generate the hour, minute, second and meridiem columns based on the selected time value.
+ ///
+ void GenerateTimePickerColumns()
+ {
+ string hourFormat;
+ List formatStringOrder = TimePickerHelper.GetFormatStringOrder(out hourFormat, TimeFormat);
+ DateTime maxDate = DatePickerHelper.GetValidMaxDate(MinimumDate, MaximumDate);
+ DateTime date = SelectedDate ?? _previousSelectedDateTime;
+ DateTime selectedDate = DatePickerHelper.GetValidDateTime(date, MinimumDate, maxDate);
+ TimeSpan selectedTime = new TimeSpan(selectedDate.Hour, selectedDate.Minute, selectedDate.Second);
+ ObservableCollection pickerColumns = new ObservableCollection();
+ foreach (int index in formatStringOrder)
+ {
+ switch (index)
+ {
+ case 0:
+ _hourColumn = GenerateHourColumn(hourFormat, selectedTime, selectedDate);
+ _hourColumn.SelectedItem = SelectedDate != null ? PickerHelper.GetSelectedItemDefaultValue(_hourColumn) : null;
+ pickerColumns.Add(_hourColumn);
+ break;
+ case 1:
+ _minuteColumn = GenerateMinuteColumn(selectedTime, selectedDate);
+ _minuteColumn.SelectedItem = SelectedDate != null ? PickerHelper.GetSelectedItemDefaultValue(_minuteColumn) : null;
+ pickerColumns.Add(_minuteColumn);
+ break;
+ case 2:
+ _secondColumn = GenerateSecondColumn(selectedTime, selectedDate);
+ _secondColumn.SelectedItem = SelectedDate != null ? PickerHelper.GetSelectedItemDefaultValue(_secondColumn) : null;
+ pickerColumns.Add(_secondColumn);
+ break;
+ case 3:
+ _meridiemColumn = GenerateMeridiemColumn(selectedTime, selectedDate);
+ _meridiemColumn.SelectedItem = SelectedDate != null ? PickerHelper.GetSelectedItemDefaultValue(_meridiemColumn) : null;
+ pickerColumns.Add(_meridiemColumn);
+ break;
+ }
+ }
+
+ _columns = pickerColumns;
+ }
+
+ ///
+ /// Method to generate the hour column with items source and selected index based on format.
+ ///
+ /// The hour format.
+ /// The selected time value.
+ /// The valid selected date value.
+ /// Returns hour column details.
+ PickerColumn GenerateHourColumn(string format, TimeSpan? selectedTime, DateTime? selectedDate)
+ {
+ DateTime? minimumDate = null;
+ DateTime? maximumDate = null;
+ DateTime maxDate = DatePickerHelper.GetValidMaxDate(MinimumDate, MaximumDate);
+ if (selectedDate != null && selectedDate.Value.Date <= MinimumDate.Date)
+ {
+ minimumDate = MinimumDate;
+ }
+
+ if (selectedDate != null && selectedDate.Value.Date >= maxDate.Date)
+ {
+ maximumDate = maxDate;
+ }
+
+ ObservableCollection hours = TimePickerHelper.GetHours(format, HourInterval, selectedDate, minimumDate, maximumDate);
+
+ int? hourIndex = selectedTime != null ? TimePickerHelper.GetHourIndex(format, hours, selectedTime.Value.Hours) : _previousSelectedDateTime.Hour;
+
+ return new PickerColumn()
+ {
+ ItemsSource = hours,
+ SelectedIndex = hourIndex != null ? (int)hourIndex : _previousSelectedDateTime.Hour,
+ HeaderText = SfPickerResources.GetLocalizedString(ColumnHeaderView.HourHeaderText),
+ };
+ }
+
+ ///
+ /// Method to generate the minute column with items source and selected index based on format.
+ ///
+ /// The selected time value.
+ /// The valid selected date value.
+ /// Returns minute column details.
+ PickerColumn GenerateMinuteColumn(TimeSpan? selectedTime, DateTime? selectedDate)
+ {
+ DateTime? minimumDate = null;
+ DateTime? maximumDate = null;
+ DateTime maxDate = DatePickerHelper.GetValidMaxDate(MinimumDate, MaximumDate);
+ DateTime? date = SelectedDate ?? _previousSelectedDateTime;
+ if (date.Value.Date <= MinimumDate.Date)
+ {
+ minimumDate = MinimumDate;
+ }
+
+ if (date.Value.Date >= maxDate.Date)
+ {
+ maximumDate = maxDate;
+ }
+
+ ObservableCollection minutes = TimePickerHelper.GetMinutes(MinuteInterval, date.Value.Hour, selectedDate, minimumDate, maximumDate);
+ return new PickerColumn()
+ {
+ ItemsSource = minutes,
+ SelectedIndex = selectedTime != null ? TimePickerHelper.GetMinuteOrSecondIndex(minutes, date.Value.Minute) : _previousSelectedDateTime.Minute,
+ HeaderText = SfPickerResources.GetLocalizedString(ColumnHeaderView.MinuteHeaderText),
+ };
+ }
+
+ ///
+ /// Method to generate the second column with items source and selected index based on format.
+ ///
+ /// The selected time value.
+ /// The valid selected date value.
+ /// Returns second column details.
+ PickerColumn GenerateSecondColumn(TimeSpan? selectedTime, DateTime? selectedDate)
+ {
+ DateTime? minimumDate = null;
+ DateTime? maximumDate = null;
+ DateTime maxDate = DatePickerHelper.GetValidMaxDate(MinimumDate, MaximumDate);
+ DateTime date = SelectedDate ?? _previousSelectedDateTime;
+ if (date.Date <= MinimumDate.Date)
+ {
+ minimumDate = MinimumDate;
+ }
+
+ if (date.Date >= maxDate.Date)
+ {
+ maximumDate = maxDate;
+ }
+
+ ObservableCollection seconds = TimePickerHelper.GetSeconds(SecondInterval, date.Hour, date.Minute, selectedDate, minimumDate, maximumDate);
+ return new PickerColumn()
+ {
+ ItemsSource = seconds,
+ SelectedIndex = selectedTime != null ? TimePickerHelper.GetMinuteOrSecondIndex(seconds, selectedTime.Value.Seconds) : _previousSelectedDateTime.Second,
+ HeaderText = SfPickerResources.GetLocalizedString(ColumnHeaderView.SecondHeaderText),
+ };
+ }
+
+ ///
+ /// Method to generate the meridiem column with items source and selected index based on format.
+ ///
+ /// The selected time value.
+ /// The valid selected date value.
+ /// Returns meridiem column details.
+ PickerColumn GenerateMeridiemColumn(TimeSpan? selectedTime, DateTime? selectedDate)
+ {
+ DateTime? minimumDate = null;
+ DateTime? maximumDate = null;
+ DateTime maxDate = DatePickerHelper.GetValidMaxDate(MinimumDate, MaximumDate);
+ if (selectedDate != null && selectedDate.Value.Date <= MinimumDate.Date)
+ {
+ minimumDate = MinimumDate;
+ }
+
+ if (selectedDate != null && selectedDate.Value.Date >= maxDate.Date)
+ {
+ maximumDate = maxDate;
+ }
+
+ ObservableCollection meridiems = TimePickerHelper.GetMeridiem(minimumDate, maximumDate, selectedDate);
+ return new PickerColumn()
+ {
+ ItemsSource = meridiems,
+ SelectedIndex = selectedTime != null && selectedTime.Value.Hours >= 12 ? meridiems.Count > 1 ? 1 : 0 : 0,
+ HeaderText = SfPickerResources.GetLocalizedString(ColumnHeaderView.MeridiemHeaderText),
+ };
+ }
+
+ ///
+ /// Method trigged when the black out datetime collection gets changed.
+ ///
+ /// Datetime picker instance.
+ /// Collection changed event arguments.
+ void OnBlackoutDateTimes_CollectionChanged(object? sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
+ {
+ if (SelectedDate != null)
+ {
+ DateTime currentDate = SelectedDate.Value;
+ bool isTimeSpanAtZero = false;
+ while (BlackoutDateTimes.Any(blackOutDate => DatePickerHelper.IsBlackoutDateTime(blackOutDate, currentDate, out isTimeSpanAtZero)))
+ {
+ currentDate = isTimeSpanAtZero ? currentDate.AddDays(1) : currentDate.AddMinutes(1);
+ }
+
+ if (SelectedDate != currentDate)
+ {
+ SelectedDate = currentDate;
+ }
+ }
+ }
+
+ ///
+ /// Method to initialize the theme and to set dynamic resources.
+ ///
+ void InitializeTheme()
+ {
+ ThemeElement.InitializeThemeResources(this, "SfDateTimePickerTheme");
+ SetDynamicResource(DateTimePickerBackgroundProperty, "SfDateTimePickerNormalBackground");
+ SetDynamicResource(FooterBackgroundProperty, "SfDateTimePickerNormalFooterBackground");
+ SetDynamicResource(FooterDividerColorProperty, "SfDateTimePickerNormalFooterDividerColor");
+ SetDynamicResource(FooterTextColorProperty, "SfDateTimePickerNormalFooterTextColor");
+ SetDynamicResource(FooterFontSizeProperty, "SfDateTimePickerNormalFooterFontSize");
+
+ SetDynamicResource(SelectionBackgroundProperty, "SfDateTimePickerSelectionBackground");
+ SetDynamicResource(SelectionStrokeColorProperty, "SfDateTimePickerSelectionStroke");
+ SetDynamicResource(SelectionCornerRadiusProperty, "SfDateTimePickerSelectionCornerRadius");
+ SetDynamicResource(SelectedTextColorProperty, "SfDateTimePickerSelectedTextColor");
+ SetDynamicResource(SelectionTextColorProperty, "SfPickerSelectionTextColor");
+ SetDynamicResource(SelectedFontSizeProperty, "SfDateTimePickerSelectedFontSize");
+
+ SetDynamicResource(NormalTextColorProperty, "SfDateTimePickerNormalTextColor");
+ SetDynamicResource(NormalFontSizeProperty, "SfDateTimePickerNormalFontSize");
+
+ SetDynamicResource(DisabledTextColorProperty, "SfDateTimePickerDisabledTextColor");
+ }
+
+ #endregion
+
+ #region Override Methods
+
+ ///
+ /// Method to wire the events.
+ ///
+ protected override void Initialize()
+ {
+ base.Initialize();
+ BaseColumns = _columns;
+ if (HeaderView != null)
+ {
+ SetInheritedBindingContext(HeaderView, BindingContext);
+ HeaderView.PickerPropertyChanged += OnHeaderPropertyChanged;
+ BaseHeaderView = new PickerHeaderView()
+ {
+ Background = HeaderView.Background,
+ DividerColor = HeaderView.DividerColor,
+ Height = HeaderView.Height,
+ TextStyle = HeaderView.TextStyle,
+ SelectionTextStyle = HeaderView.SelectionTextStyle,
+ TimeText = GetTimeHeaderText(),
+ DateText = GetDateHeaderText(),
+ };
+ }
+
+ if (ColumnHeaderView != null)
+ {
+ SetInheritedBindingContext(ColumnHeaderView, BindingContext);
+ ColumnHeaderView.PickerPropertyChanged += OnColumnHeaderPropertyChanged;
+ BaseColumnHeaderView = new PickerColumnHeaderView()
+ {
+ Background = ColumnHeaderView.Background,
+ DividerColor = ColumnHeaderView.DividerColor,
+ Height = ColumnHeaderView.Height,
+ TextStyle = ColumnHeaderView.TextStyle,
+ };
+ }
+ }
+
+ ///
+ /// Method triggers when the property binding context changed.
+ ///
+ protected override void OnBindingContextChanged()
+ {
+ base.OnBindingContextChanged();
+ if (HeaderView != null)
+ {
+ SetInheritedBindingContext(HeaderView, BindingContext);
+ if (HeaderView.TextStyle != null)
+ {
+ SetInheritedBindingContext(HeaderView.TextStyle, BindingContext);
+ }
+
+ if (HeaderView.SelectionTextStyle != null)
+ {
+ SetInheritedBindingContext(HeaderView.SelectionTextStyle, BindingContext);
+ }
+ }
+
+ if (ColumnHeaderView != null)
+ {
+ SetInheritedBindingContext(ColumnHeaderView, BindingContext);
+ if (ColumnHeaderView.TextStyle != null)
+ {
+ SetInheritedBindingContext(ColumnHeaderView.TextStyle, BindingContext);
+ }
+ }
+
+ if (FooterView != null)
+ {
+ SetInheritedBindingContext(FooterView, BindingContext);
+ if (FooterView.TextStyle != null)
+ {
+ SetInheritedBindingContext(FooterView.TextStyle, BindingContext);
+ }
+ }
+
+ if (SelectedTextStyle != null)
+ {
+ SetInheritedBindingContext(SelectedTextStyle, BindingContext);
+ }
+
+ if (TextStyle != null)
+ {
+ SetInheritedBindingContext(TextStyle, BindingContext);
+ }
+
+ if (SelectionView != null)
+ {
+ SetInheritedBindingContext(SelectionView, BindingContext);
+ }
+ }
+
+ ///
+ /// Method triggers while the header button clicked.
+ ///
+ /// Index of the header button.
+ protected override void OnHeaderButtonClicked(int index)
+ {
+ if (_selectedIndex == index)
+ {
+ return;
+ }
+
+ _selectedIndex = index;
+ if (_selectedIndex == 0)
+ {
+ ResetDateColumns();
+ }
+ else
+ {
+ ResetTimeColumns();
+ }
+ }
+
+ ///
+ /// Method triggers while the popup opening or switched from popup to default.
+ ///
+ protected override void OnPickerLoading()
+ {
+ if (_selectedIndex == 0)
+ {
+ string dayFormat;
+ string monthFormat;
+ List formatStringOrder = DatePickerHelper.GetFormatStringOrder(out dayFormat, out monthFormat, DateFormat);
+ DateTime selectedDate = DatePickerHelper.GetValidDateTime(SelectedDate, MinimumDate, MaximumDate);
+ foreach (int index in formatStringOrder)
+ {
+ switch (index)
+ {
+ case 0:
+ int dayIndex = DatePickerHelper.GetDayIndex(dayFormat, (ObservableCollection)_dayColumn.ItemsSource, selectedDate.Day);
+ if (_dayColumn.SelectedIndex != dayIndex)
+ {
+ _dayColumn.SelectedIndex = dayIndex;
+ }
+
+ break;
+ case 1:
+ int monthIndex = DatePickerHelper.GetMonthIndex(monthFormat, (ObservableCollection)_monthColumn.ItemsSource, selectedDate.Month);
+ if (_monthColumn.SelectedIndex != monthIndex)
+ {
+ _monthColumn.SelectedIndex = monthIndex;
+ }
+
+ break;
+ case 2:
+ int yearIndex = DatePickerHelper.GetYearIndex((ObservableCollection)_yearColumn.ItemsSource, selectedDate.Year);
+ if (_yearColumn.SelectedIndex != yearIndex)
+ {
+ _yearColumn.SelectedIndex = yearIndex;
+ }
+
+ break;
+ }
+ }
+ }
+ else
+ {
+ _selectedIndex = 0;
+ ResetHeaderHighlight();
+ ResetDateColumns();
+ }
+ }
+
+ ///
+ /// Method triggers when the date time picker popup closed.
+ ///
+ /// The event arguments.
+ protected override void OnPopupClosed(EventArgs e)
+ {
+ InvokeClosedEvent(this, e);
+ }
+
+ ///
+ /// Method triggers when the date time picker popup closing.
+ ///
+ /// The event arguments.
+ protected override void OnPopupClosing(CancelEventArgs e)
+ {
+ InvokeClosingEvent(this, e);
+ }
+
+ ///
+ /// Method triggers when the date time picker popup opened.
+ ///
+ /// The event arguments.
+ protected override void OnPopupOpened(EventArgs e)
+ {
+ InvokeOpenedEvent(this, e);
+ }
+
+ ///
+ /// Method triggers when the date time picker ok button clicked.
+ ///
+ /// The event arguments.
+ protected override void OnOkButtonClicked(EventArgs e)
+ {
+ InvokeOkButtonClickedEvent(this, e);
+ if (AcceptCommand != null && AcceptCommand.CanExecute(e))
+ {
+ AcceptCommand.Execute(e);
+ }
+ }
+
+ ///
+ /// Method triggers when the date time picker cancel button clicked.
+ ///
+ /// The event arguments.
+ protected override void OnCancelButtonClicked(EventArgs e)
+ {
+ InvokeCancelButtonClickedEvent(this, e);
+ if (DeclineCommand != null && DeclineCommand.CanExecute(e))
+ {
+ DeclineCommand.Execute(e);
+ }
+ }
+
+ #endregion
+
+ #region Property Changed Methods
+
+ ///
+ /// Method invokes on picker header view changed.
+ ///
+ /// The picker object.
+ /// Old value of the property.
+ /// New value of the property.
+ static void OnHeaderViewChanged(BindableObject bindable, object oldValue, object newValue)
+ {
+ SfDateTimePicker? picker = bindable as SfDateTimePicker;
+ if (picker == null)
+ {
+ return;
+ }
+
+ if (oldValue is DateTimePickerHeaderView oldStyle)
+ {
+ picker.HeaderView.PickerPropertyChanged -= picker.OnHeaderPropertyChanged;
+ picker.HeaderView.BindingContext = null;
+ oldStyle.Parent = null;
+ }
+
+ if (newValue is DateTimePickerHeaderView newStyle)
+ {
+ newStyle.Parent = picker;
+ SetInheritedBindingContext(picker.HeaderView, picker.BindingContext);
+ picker.HeaderView.PickerPropertyChanged += picker.OnHeaderPropertyChanged;
+ picker.BaseHeaderView = new PickerHeaderView()
+ {
+ Background = picker.HeaderView.Background,
+ DividerColor = picker.HeaderView.DividerColor,
+ Height = picker.HeaderView.Height,
+ TextStyle = picker.HeaderView.TextStyle,
+ SelectionTextStyle = picker.HeaderView.SelectionTextStyle,
+ TimeText = picker.GetTimeHeaderText(),
+ DateText = picker.GetDateHeaderText(),
+ };
+ }
+ }
+
+ ///
+ /// Method invokes on picker column header view changed.
+ ///
+ /// The picker object.
+ /// Old value of the property.
+ /// New value of the property.
+ static void OnColumnHeaderViewChanged(BindableObject bindable, object oldValue, object newValue)
+ {
+ SfDateTimePicker? picker = bindable as SfDateTimePicker;
+ if (picker == null)
+ {
+ return;
+ }
+
+ if (oldValue is DateTimePickerColumnHeaderView oldStyle)
+ {
+ oldStyle.PickerPropertyChanged -= picker.OnColumnHeaderPropertyChanged;
+ oldStyle.BindingContext = null;
+ oldStyle.Parent = null;
+ }
+
+ if (newValue is DateTimePickerColumnHeaderView newStyle)
+ {
+ newStyle.Parent = picker;
+ SetInheritedBindingContext(newStyle, picker.BindingContext);
+ newStyle.PickerPropertyChanged += picker.OnColumnHeaderPropertyChanged;
+ picker.BaseColumnHeaderView = new PickerColumnHeaderView()
+ {
+ Background = newStyle.Background,
+ DividerColor = newStyle.DividerColor,
+ Height = newStyle.Height,
+ TextStyle = newStyle.TextStyle,
+ };
+
+ picker._dayColumn.HeaderText = SfPickerResources.GetLocalizedString(picker.ColumnHeaderView.DayHeaderText);
+ picker._monthColumn.HeaderText = SfPickerResources.GetLocalizedString(picker.ColumnHeaderView.MonthHeaderText);
+ picker._yearColumn.HeaderText = SfPickerResources.GetLocalizedString(picker.ColumnHeaderView.YearHeaderText);
+ picker._hourColumn.HeaderText = SfPickerResources.GetLocalizedString(picker.ColumnHeaderView.HourHeaderText);
+ picker._minuteColumn.HeaderText = SfPickerResources.GetLocalizedString(picker.ColumnHeaderView.MinuteHeaderText);
+ picker._secondColumn.HeaderText = SfPickerResources.GetLocalizedString(picker.ColumnHeaderView.SecondHeaderText);
+ picker._meridiemColumn.HeaderText = SfPickerResources.GetLocalizedString(picker.ColumnHeaderView.MeridiemHeaderText);
+ }
+ }
+
+ ///
+ /// Method invokes on selected date property changed.
+ ///
+ /// The picker object.
+ /// Property old value.
+ /// Property new value.
+ static void OnSelectedDatePropertyChanged(BindableObject bindable, object oldValue, object newValue)
+ {
+ SfDateTimePicker? picker = bindable as SfDateTimePicker;
+ if (picker == null)
+ {
+ return;
+ }
+
+ DateTime? previousSelectedDate = null;
+ DateTime? currentSelectedDate = null;
+ if (oldValue is DateTime oldSelectedDate)
+ {
+ previousSelectedDate = oldSelectedDate;
+ bool isTimeSpanAtZero = false;
+ //// Prevents Selection changed event from triggering if old value is black out date time.
+ if (picker.BlackoutDateTimes.Any(blackOutDateTime => DatePickerHelper.IsBlackoutDateTime(blackOutDateTime, previousSelectedDate, out isTimeSpanAtZero)))
+ {
+ picker.UpdateSelectedIndex((DateTime)newValue);
+
+ //// Skip the update and event call by checking if the whole date is blackout value or particular time.
+ if (isTimeSpanAtZero)
+ {
+ if (oldSelectedDate.Year == picker._previousSelectedDateTime.Year && oldSelectedDate.Month == picker._previousSelectedDateTime.Month)
+ {
+ return;
+ }
+ }
+ else
+ {
+ if (oldSelectedDate.Hour == picker._previousSelectedDateTime.Hour)
+ {
+ return;
+ }
+ }
+
+ previousSelectedDate = picker._previousSelectedDateTime;
+ }
+
+ picker._previousSelectedDateTime = oldSelectedDate;
+ }
+
+ if (newValue is DateTime newSelectedDate)
+ {
+ //// Prevents Selection changed event from triggering if new value is black out date time.
+ if (picker.BlackoutDateTimes.Any(blackOutDateTime => DatePickerHelper.IsBlackoutDateTime(blackOutDateTime, newSelectedDate, out bool isTimeSpanAtZero)))
+ {
+ return;
+ }
+
+ currentSelectedDate = newSelectedDate;
+ }
+
+ //// Skip the update and event call while the same date updated with different time value.
+ if (DatePickerHelper.IsSameDateTime(previousSelectedDate, currentSelectedDate))
+ {
+ return;
+ }
+
+ if (newValue == null)
+ {
+ picker._yearColumn.SelectedItem = null;
+ picker._monthColumn.SelectedItem = null;
+ picker._dayColumn.SelectedItem = null;
+ picker._hourColumn.SelectedItem = null;
+ picker._minuteColumn.SelectedItem = null;
+ picker._secondColumn.SelectedItem = null;
+ picker._meridiemColumn.SelectedItem = null;
+ picker.UpdateSelectedDateIndex(previousSelectedDate);
+ picker.UpdateSelectedTimeIndex(previousSelectedDate);
+ picker.SelectionChanged?.Invoke(picker, new DateTimePickerSelectionChangedEventArgs() { OldValue = previousSelectedDate, NewValue = currentSelectedDate });
+ picker.BaseHeaderView.DateText = picker.GetDateHeaderText();
+ picker.BaseHeaderView.TimeText = picker.GetTimeHeaderText();
+ return;
+ }
+ else
+ {
+ picker._yearColumn.SelectedItem = PickerHelper.GetSelectedItemDefaultValue(picker._yearColumn);
+ picker._monthColumn.SelectedItem = PickerHelper.GetSelectedItemDefaultValue(picker._monthColumn);
+ picker._dayColumn.SelectedItem = PickerHelper.GetSelectedItemDefaultValue(picker._dayColumn);
+ picker._hourColumn.SelectedItem = PickerHelper.GetSelectedItemDefaultValue(picker._hourColumn);
+ picker._minuteColumn.SelectedItem = PickerHelper.GetSelectedItemDefaultValue(picker._minuteColumn);
+ picker._secondColumn.SelectedItem = PickerHelper.GetSelectedItemDefaultValue(picker._secondColumn);
+ picker._meridiemColumn.SelectedItem = PickerHelper.GetSelectedItemDefaultValue(picker._meridiemColumn);
+ PickerContainer? pickerContainer = picker.GetPickerContainerValue();
+ pickerContainer?.UpdateScrollViewDraw();
+ pickerContainer?.InvalidateDrawable();
+ }
+
+ //// Update the column with valid date(date between min and max date).
+ currentSelectedDate = DatePickerHelper.GetValidDateTime(currentSelectedDate, picker.MinimumDate, picker.MaximumDate);
+ var dateTimePickerSelectionChangedEventArgs = new DateTimePickerSelectionChangedEventArgs() { OldValue = previousSelectedDate, NewValue = currentSelectedDate };
+ if (picker.SelectionChanged != null)
+ {
+ picker.SelectionChanged?.Invoke(picker, dateTimePickerSelectionChangedEventArgs);
+ }
+
+ if (picker.SelectionChangedCommand != null && picker.SelectionChangedCommand.CanExecute(dateTimePickerSelectionChangedEventArgs))
+ {
+ picker.SelectionChangedCommand.Execute(dateTimePickerSelectionChangedEventArgs);
+ }
+
+ picker.UpdateSelectedIndex(currentSelectedDate);
+ }
+
+ ///
+ /// Method invokes on date format property changed.
+ ///
+ /// The picker object.
+ /// Property old value.
+ /// Property new value.
+ static void OnDateFormatPropertyChanged(BindableObject bindable, object oldValue, object newValue)
+ {
+ SfDateTimePicker? picker = bindable as SfDateTimePicker;
+ if (picker == null)
+ {
+ return;
+ }
+
+ picker.ResetDateColumns();
+ }
+
+ ///
+ /// Method invokes on time format property changed.
+ ///
+ /// The picker object.
+ /// Property old value.
+ /// Property new value.
+ static void OnTimeFormatPropertyChanged(BindableObject bindable, object oldValue, object newValue)
+ {
+ SfDateTimePicker? picker = bindable as SfDateTimePicker;
+ if (picker == null)
+ {
+ return;
+ }
+
+ picker.ResetTimeColumns();
+ }
+
+ ///
+ /// Method invokes on minimum date property changed.
+ ///
+ /// The picker object.
+ /// Property old value.
+ /// Property new value.
+ static void OnMinimumDatePropertyChanged(BindableObject bindable, object oldValue, object newValue)
+ {
+ SfDateTimePicker? picker = bindable as SfDateTimePicker;
+ if (picker == null)
+ {
+ return;
+ }
+
+ picker.UpdateMinimumMaximumDate(oldValue, newValue);
+ DateTime currentSelectedDate = DatePickerHelper.GetValidDateTime(picker.SelectedDate, picker.MinimumDate, picker.MaximumDate);
+ picker.UpdateSelectedIndex(currentSelectedDate);
+ }
+
+ ///
+ /// Method invokes on maximum date property changed.
+ ///
+ /// The picker object.
+ /// Property old value.
+ /// Property new value.
+ static void OnMaximumDatePropertyChanged(BindableObject bindable, object oldValue, object newValue)
+ {
+ SfDateTimePicker? picker = bindable as SfDateTimePicker;
+ if (picker == null)
+ {
+ return;
+ }
+
+ DateTime newDate = DatePickerHelper.GetValidMaxDate(picker.MinimumDate, (DateTime)newValue);
+ picker.UpdateMinimumMaximumDate(oldValue, newDate);
+ DateTime currentSelectedDate = DatePickerHelper.GetValidDateTime(picker.SelectedDate, picker.MinimumDate, newDate);
+ picker.UpdateSelectedIndex(currentSelectedDate);
+ }
+
+ ///
+ /// Method invokes on day interval property changed.
+ ///
+ /// The picker object.
+ /// Property old value.
+ /// Property new value.
+ static void OnDayIntervalPropertyChanged(BindableObject bindable, object oldValue, object newValue)
+ {
+ SfDateTimePicker? picker = bindable as SfDateTimePicker;
+ if (picker == null)
+ {
+ return;
+ }
+
+ string dayFormat;
+ //// Get the day format and format string order and check the index.
+ List formatStringOrder = DatePickerHelper.GetFormatStringOrder(out dayFormat, out _, picker.DateFormat);
+ if (string.IsNullOrEmpty(dayFormat) || picker._selectedIndex != 0)
+ {
+ return;
+ }
+
+ DateTime maxDate = DatePickerHelper.GetValidMaxDate(picker.MinimumDate, picker.MaximumDate);
+ DateTime currentSelectedDate = DatePickerHelper.GetValidDateTime(picker.SelectedDate, picker.MinimumDate, maxDate);
+ picker._dayColumn = picker.GenerateDayColumn(dayFormat, currentSelectedDate);
+ int dayIndex = formatStringOrder.IndexOf(0);
+ //// Replace the day column with day interval.
+ picker._columns[dayIndex] = picker._dayColumn;
+ }
+
+ ///
+ /// Method invokes on month interval property changed.
+ ///
+ /// The picker object.
+ /// Property old value.
+ /// Property new value.
+ static void OnMonthIntervalPropertyChanged(BindableObject bindable, object oldValue, object newValue)
+ {
+ SfDateTimePicker? picker = bindable as SfDateTimePicker;
+ if (picker == null)
+ {
+ return;
+ }
+
+ string monthFormat;
+ //// Get the month format and format string order and check the index.
+ List formatStringOrder = DatePickerHelper.GetFormatStringOrder(out _, out monthFormat, picker.DateFormat);
+ if (string.IsNullOrEmpty(monthFormat) || picker._selectedIndex != 0)
+ {
+ return;
+ }
+
+ DateTime maxDate = DatePickerHelper.GetValidMaxDate(picker.MinimumDate, picker.MaximumDate);
+ DateTime currentSelectedDate = DatePickerHelper.GetValidDateTime(picker.SelectedDate, picker.MinimumDate, maxDate);
+ picker._monthColumn = picker.GenerateMonthColumn(monthFormat, currentSelectedDate);
+ int monthIndex = formatStringOrder.IndexOf(1);
+ //// Replace the month column with month interval.
+ picker._columns[monthIndex] = picker._monthColumn;
+ }
+
+ ///
+ /// Method invokes on year interval property changed.
+ ///
+ /// The picker object.
+ /// Property old value.
+ /// Property new value.
+ static void OnYearIntervalPropertyChanged(BindableObject bindable, object oldValue, object newValue)
+ {
+ SfDateTimePicker? picker = bindable as SfDateTimePicker;
+ if (picker == null)
+ {
+ return;
+ }
+
+ //// Get the format string order and check the index.
+ List formatStringOrder = DatePickerHelper.GetFormatStringOrder(out _, out _, picker.DateFormat);
+ int yearIndex = formatStringOrder.IndexOf(2);
+ if (yearIndex == -1 || picker._selectedIndex != 0)
+ {
+ return;
+ }
+
+ DateTime maxDate = DatePickerHelper.GetValidMaxDate(picker.MinimumDate, picker.MaximumDate);
+ DateTime currentSelectedDate = DatePickerHelper.GetValidDateTime(picker.SelectedDate, picker.MinimumDate, maxDate);
+ picker._yearColumn = picker.GenerateYearColumn(currentSelectedDate);
+ //// Replace the year column with year interval.
+ picker._columns[yearIndex] = picker._yearColumn;
+ }
+
+ ///
+ /// Method invokes on hour interval property changed.
+ ///
+ /// The picker object.
+ /// Property old value.
+ /// Property new value.
+ static void OnHourIntervalPropertyChanged(BindableObject bindable, object oldValue, object newValue)
+ {
+ SfDateTimePicker? picker = bindable as SfDateTimePicker;
+ if (picker == null)
+ {
+ return;
+ }
+
+ string hourFormat;
+ //// Get the format string order and check the index.
+ List formatStringOrder = TimePickerHelper.GetFormatStringOrder(out hourFormat, picker.TimeFormat);
+ DateTime maxDate = DatePickerHelper.GetValidMaxDate(picker.MinimumDate, picker.MaximumDate);
+ DateTime selectedDate = DatePickerHelper.GetValidDateTime(picker.SelectedDate, picker.MinimumDate, maxDate);
+ TimeSpan selectedTime = new TimeSpan(selectedDate.Hour, selectedDate.Minute, selectedDate.Second);
+ picker._hourColumn = picker.GenerateHourColumn(hourFormat, selectedTime, selectedDate);
+ if (picker._selectedIndex == 1)
+ {
+ int hourIndex = formatStringOrder.IndexOf(0);
+ //// Replace the hour column with hour interval.
+ picker._columns[hourIndex] = picker._hourColumn;
+ }
+ }
+
+ ///
+ /// Method invokes on minute interval property changed.
+ ///
+ /// The picker object.
+ /// Property old value.
+ /// Property new value.
+ static void OnMinuteIntervalPropertyChanged(BindableObject bindable, object oldValue, object newValue)
+ {
+ SfDateTimePicker? picker = bindable as SfDateTimePicker;
+ if (picker == null)
+ {
+ return;
+ }
+
+ //// Get the format string order and check the index.
+ List formatStringOrder = TimePickerHelper.GetFormatStringOrder(out _, picker.TimeFormat);
+ int minuteIndex = formatStringOrder.IndexOf(1);
+ if (minuteIndex == -1)
+ {
+ return;
+ }
+
+ DateTime maxDate = DatePickerHelper.GetValidMaxDate(picker.MinimumDate, picker.MaximumDate);
+ DateTime selectedDate = DatePickerHelper.GetValidDateTime(picker.SelectedDate, picker.MinimumDate, maxDate);
+ TimeSpan selectedTime = new TimeSpan(selectedDate.Hour, selectedDate.Minute, selectedDate.Second);
+ if (picker._selectedIndex == 1)
+ {
+ picker._minuteColumn = picker.GenerateMinuteColumn(selectedTime, selectedDate);
+ //// Replace the minute column with minute interval.
+ picker._columns[minuteIndex] = picker._minuteColumn;
+ }
+ }
+
+ ///
+ /// Method invokes on second interval property changed.
+ ///
+ /// The picker object.
+ /// Property old value.
+ /// Property new value.
+ static void OnSecondIntervalPropertyChanged(BindableObject bindable, object oldValue, object newValue)
+ {
+ SfDateTimePicker? picker = bindable as SfDateTimePicker;
+ if (picker == null)
+ {
+ return;
+ }
+
+ //// Get the format string order and check the index.
+ List formatStringOrder = TimePickerHelper.GetFormatStringOrder(out _, picker.TimeFormat);
+ int secondIndex = formatStringOrder.IndexOf(2);
+ if (secondIndex == -1)
+ {
+ return;
+ }
+
+ DateTime maxDate = DatePickerHelper.GetValidMaxDate(picker.MinimumDate, picker.MaximumDate);
+ DateTime selectedDate = DatePickerHelper.GetValidDateTime(picker.SelectedDate, picker.MinimumDate, maxDate);
+ TimeSpan selectedTime = new TimeSpan(selectedDate.Hour, selectedDate.Minute, selectedDate.Second);
+ if (picker._selectedIndex == 1)
+ {
+ picker._secondColumn = picker.GenerateSecondColumn(selectedTime, selectedDate);
+ //// Replace the second column with second interval.
+ picker._columns[secondIndex] = picker._secondColumn;
+ }
+ }
+
+ ///
+ /// Method invokes on blackout datetimes property changed.
+ ///
+ /// The sfdatetimepicker object.
+ /// Property old value.
+ /// Property new value.
+ static void OnBlackOutDateTimesPropertyChanged(BindableObject bindable, object oldValue, object newValue)
+ {
+ SfDateTimePicker? datetimepicker = bindable as SfDateTimePicker;
+ if (datetimepicker == null)
+ {
+ return;
+ }
+
+ //// Unwires collection changed from old and wires on new collection.
+ ((ObservableCollection)oldValue).CollectionChanged -= datetimepicker.OnBlackoutDateTimes_CollectionChanged;
+ ((ObservableCollection)newValue).CollectionChanged += datetimepicker.OnBlackoutDateTimes_CollectionChanged;
+
+ if (datetimepicker.SelectedDate != null)
+ {
+ DateTime currentDate = datetimepicker.SelectedDate.Value;
+ bool isTimeSpanAtZero = false;
+ while (datetimepicker.BlackoutDateTimes.Any(blackOutDate => DatePickerHelper.IsBlackoutDateTime(blackOutDate, currentDate, out isTimeSpanAtZero)))
+ {
+ currentDate = isTimeSpanAtZero ? currentDate.AddDays(1) : currentDate.AddMinutes(1);
+ }
+
+ if (datetimepicker.SelectedDate != currentDate)
+ {
+ datetimepicker.SelectedDate = currentDate;
+ }
+ }
+
+ //// Gets picker container value to update the view.
+ PickerContainer? pickerContainer = datetimepicker.GetPickerContainerValue();
+
+ pickerContainer?.UpdateScrollViewDraw();
+ pickerContainer?.UpdatePickerSelectionView();
+ }
+
+ #endregion
+
+ #region Internal Property Changed Methods
+
+ ///
+ /// called when property changed.
+ ///
+ /// The bindable object.
+ /// The old value.
+ /// The new value.
+ static void OnDateTimePickerBackgroundChanged(BindableObject bindable, object oldValue, object newValue)
+ {
+ SfDateTimePicker? picker = bindable as SfDateTimePicker;
+ if (picker == null)
+ {
+ return;
+ }
+
+ picker.BackgroundColor = picker.DateTimePickerBackground;
+ }
+
+ ///
+ /// Method invokes on the picker footer background changed.
+ ///
+ /// The footer settings object.
+ /// Property old value.
+ /// Property new value.
+ static void OnFooterBackgroundChanged(BindableObject bindable, object oldValue, object newValue)
+ {
+ SfDateTimePicker? picker = bindable as SfDateTimePicker;
+ if (picker == null)
+ {
+ return;
+ }
+
+ picker.FooterView.Background = picker.FooterBackground;
+ }
+
+ ///
+ /// Method invokes on the picker selection background changed.
+ ///
+ /// The selection settings object.
+ /// Property old value.
+ /// Property new value.
+ static void OnSelectionBackgroundChanged(BindableObject bindable, object oldValue, object newValue)
+ {
+ SfDateTimePicker? picker = bindable as SfDateTimePicker;
+ if (picker == null)
+ {
+ return;
+ }
+
+ picker.SelectionView.Background = picker.SelectionBackground;
+ }
+
+ ///
+ /// Method invokes on the picker selection stroke color changed.
+ ///
+ /// The selection settings object.
+ /// Property old value.
+ /// Property new value.
+ static void OnSelectionStrokeColorChanged(BindableObject bindable, object oldValue, object newValue)
+ {
+ SfDateTimePicker? picker = bindable as SfDateTimePicker;
+ if (picker == null)
+ {
+ return;
+ }
+
+ picker.SelectionView.Stroke = picker.SelectionStrokeColor;
+ }
+
+ ///
+ /// Method invokes on the picker selection corner radius changed.
+ ///
+ /// The selection settings object.
+ /// Property old value.
+ /// Property new value.
+ static void OnSelectionCornerRadiusChanged(BindableObject bindable, object oldValue, object newValue)
+ {
+ SfDateTimePicker? picker = bindable as SfDateTimePicker;
+ if (picker == null)
+ {
+ return;
+ }
+
+ picker.SelectionView.CornerRadius = picker.SelectionCornerRadius;
+ }
+
+ ///
+ /// Method invokes on the picker footer separator line background changed.
+ ///
+ /// The footer settings object.
+ /// Property old value.
+ /// Property new value.
+ static void OnFooterDividerColorChanged(BindableObject bindable, object oldValue, object newValue)
+ {
+ SfDateTimePicker? picker = bindable as SfDateTimePicker;
+ if (picker == null)
+ {
+ return;
+ }
+
+ picker.FooterView.DividerColor = picker.FooterDividerColor;
+ }
+
+ ///
+ /// Method invokes on the picker footer text color changed.
+ ///
+ /// The footer text style object.
+ /// Property old value.
+ /// Property new value.
+ static void OnFooterTextColorChanged(BindableObject bindable, object oldValue, object newValue)
+ {
+ SfDateTimePicker? picker = bindable as SfDateTimePicker;
+ if (picker == null)
+ {
+ return;
+ }
+
+ picker.FooterView.TextStyle.TextColor = picker.FooterTextColor;
+ }
+
+ ///
+ /// Method invokes on the picker footer font size changed.
+ ///
+ /// The footer text style object.
+ /// Property old value.
+ /// Property new value.
+ static void OnFooterFontSizeChanged(BindableObject bindable, object oldValue, object newValue)
+ {
+ SfDateTimePicker? picker = bindable as SfDateTimePicker;
+ if (picker == null)
+ {
+ return;
+ }
+
+ picker.FooterView.TextStyle.FontSize = picker.FooterFontSize;
+ }
+
+ ///
+ /// Method invokes on the picker selection text color changed.
+ ///
+ /// The selection text style object.
+ /// Property old value.
+ /// Property new value.
+ static void OnSelectedTextColorChanged(BindableObject bindable, object oldValue, object newValue)
+ {
+ SfDateTimePicker? picker = bindable as SfDateTimePicker;
+ if (picker == null)
+ {
+ return;
+ }
+
+ picker.SelectedTextStyle.TextColor = picker.TextDisplayMode == PickerTextDisplayMode.Default ? picker.SelectedTextColor : picker.SelectionTextColor;
+ }
+
+ ///
+ /// Method invokes on the picker selection font size changed.
+ ///
+ /// The selection text style object.
+ /// Property old value.
+ /// Property new value.
+ static void OnSelectedFontSizeChanged(BindableObject bindable, object oldValue, object newValue)
+ {
+ SfDateTimePicker? picker = bindable as SfDateTimePicker;
+ if (picker == null)
+ {
+ return;
+ }
+
+ picker.SelectedTextStyle.FontSize = picker.SelectedFontSize;
+ }
+
+ ///
+ /// Method invokes on the picker normal text color changed.
+ ///
+ /// The text style object.
+ /// Property old value.
+ /// Property new value.
+ static void OnNormalTextColorChanged(BindableObject bindable, object oldValue, object newValue)
+ {
+ SfDateTimePicker? picker = bindable as SfDateTimePicker;
+ if (picker == null)
+ {
+ return;
+ }
+
+ picker.TextStyle.TextColor = picker.NormalTextColor;
+ }
+
+ ///
+ /// Method invokes on the picker normal font size changed.
+ ///
+ /// The text style object.
+ /// Property old value.
+ /// Property new value.
+ static void OnNormalFontSizeChanged(BindableObject bindable, object oldValue, object newValue)
+ {
+ SfDateTimePicker? picker = bindable as SfDateTimePicker;
+ if (picker == null)
+ {
+ return;
+ }
+
+ picker.TextStyle.FontSize = picker.NormalFontSize;
+ }
+
+ ///
+ /// Method invokes on the picker disabled text color changed.
+ ///
+ /// The text style object.
+ /// Property old value.
+ /// Property new value.
+ static void OnDisabledTextColorChanged(BindableObject bindable, object oldValue, object newValue)
+ {
+ SfDateTimePicker? picker = bindable as SfDateTimePicker;
+ if (picker == null)
+ {
+ return;
+ }
+
+ picker.DisabledTextStyle.TextColor = picker.DisabledTextColor;
+ }
+
+ #endregion
+
+ #region Interface Implementation
+
+ ///
+ /// This method is declared only in IParentThemeElement
+ /// and you need to implement this method only in main control.
+ ///
+ /// ResourceDictionary
+ ResourceDictionary IParentThemeElement.GetThemeDictionary()
+ {
+ return new SfDateTimePickerStyles();
+ }
+
+ ///
+ /// This method will be called when a theme dictionary
+ /// that contains the value for your control key is merged in application.
+ ///
+ /// The old value.
+ /// The new value.
+ void IThemeElement.OnCommonThemeChanged(string oldTheme, string newTheme)
+ {
+ }
+
+ ///
+ /// This method will be called when users merge a theme dictionary
+ /// that contains value for “SyncfusionTheme” dynamic resource key.
+ ///
+ /// Old theme.
+ /// New theme.
+ void IThemeElement.OnControlThemeChanged(string oldTheme, string newTheme)
+ {
+ }
+
+ #endregion
+
+ #region Events
+
+ ///
+ /// Occurs after the selected date is changed on SfDateTimePicker.
+ ///
+ public event EventHandler? SelectionChanged;
+
+ #endregion
+ }
+}
\ No newline at end of file
diff --git a/maui/src/Picker/SfPicker.cs b/maui/src/Picker/SfPicker.cs
new file mode 100644
index 00000000..6d44a346
--- /dev/null
+++ b/maui/src/Picker/SfPicker.cs
@@ -0,0 +1,1301 @@
+using System.Collections.ObjectModel;
+using System.ComponentModel;
+using System.Windows.Input;
+using Syncfusion.Maui.Toolkit.Themes;
+
+namespace Syncfusion.Maui.Toolkit.Picker
+{
+ ///
+ /// Initializes a new instance of the class that represents a control, that allows you pick an item among a list of items.
+ ///
+ public class SfPicker : PickerBase, IParentThemeElement
+ {
+ #region Bindable Properties
+
+ ///
+ /// Identifies the dependency property.
+ ///
+ ///
+ /// The identifier for dependency property.
+ ///
+ public static readonly BindableProperty HeaderViewProperty =
+ BindableProperty.Create(
+ nameof(HeaderView),
+ typeof(PickerHeaderView),
+ typeof(SfPicker),
+ defaultValueCreator: bindable => new PickerHeaderView(),
+ propertyChanged: OnHeaderViewChanged);
+
+ ///
+ /// Identifies the dependency property.
+ ///
+ ///
+ /// The identifier for dependency property.
+ ///
+ public static readonly BindableProperty ColumnHeaderViewProperty =
+ BindableProperty.Create(
+ nameof(ColumnHeaderView),
+ typeof(PickerColumnHeaderView),
+ typeof(SfPicker),
+ defaultValueCreator: bindable => new PickerColumnHeaderView(),
+ propertyChanged: OnColumnHeaderViewChanged);
+
+ ///
+ /// Identifies the dependency property.
+ ///
+ ///
+ /// The identifier for dependency property.
+ ///
+ public static readonly BindableProperty ColumnsProperty =
+ BindableProperty.Create(
+ nameof(Columns),
+ typeof(ObservableCollection),
+ typeof(SfPicker),
+ defaultValueCreator: bindable => new ObservableCollection(),
+ propertyChanged: OnColumnsChanged);
+
+ ///
+ /// Identifies the dependency property.
+ ///
+ ///
+ /// The identifier for dependency property.
+ ///
+ public static readonly BindableProperty ItemTemplateProperty =
+ BindableProperty.Create(
+ nameof(ItemTemplate),
+ typeof(DataTemplate),
+ typeof(SfPicker),
+ null,
+ propertyChanged: OnItemTemplateChanged);
+
+ ///
+ /// Identifies the dependency property.
+ ///
+ ///
+ /// The identifier for dependency property.
+ ///
+ public static readonly BindableProperty SelectionChangedCommandProperty =
+ BindableProperty.Create(
+ nameof(SelectionChangedCommand),
+ typeof(ICommand),
+ typeof(SfPicker),
+ null);
+
+ #endregion
+
+ #region Internal Bindable Properties
+
+ ///
+ /// Identifies the bindable property.
+ ///
+ ///
+ /// The identifier for bindable property.
+ ///
+ internal static readonly BindableProperty PickerBackgroundProperty =
+ BindableProperty.Create(
+ nameof(PickerBackground),
+ typeof(Color),
+ typeof(SfPicker),
+ defaultValueCreator: bindable => Color.FromArgb("#EEE8F4"),
+ propertyChanged: OnPickerBackgroundChanged);
+
+ ///
+ /// Identifies the dependency property.
+ ///
+ ///
+ /// Identifies the bindable property.
+ ///
+ internal static readonly BindableProperty HeaderBackgroundProperty =
+ BindableProperty.Create(
+ nameof(HeaderBackground),
+ typeof(Brush),
+ typeof(SfPicker),
+ defaultValueCreator: bindable => new SolidColorBrush(Color.FromArgb("#F7F2FB")),
+ propertyChanged: OnHeaderBackgroundChanged);
+
+ ///
+ /// Identifies the dependency property.
+ ///
+ ///
+ /// Identifies the bindable property.
+ ///
+ internal static readonly BindableProperty FooterBackgroundProperty =
+ BindableProperty.Create(
+ nameof(FooterBackground),
+ typeof(Brush),
+ typeof(SfPicker),
+ defaultValueCreator: bindable => Brush.Transparent,
+ propertyChanged: OnFooterBackgroundChanged);
+
+ ///
+ /// Identifies the dependency property.
+ ///
+ ///
+ /// Identifies the bindable property.
+ ///
+ internal static readonly BindableProperty SelectionBackgroundProperty =
+ BindableProperty.Create(
+ nameof(SelectionBackground),
+ typeof(Brush),
+ typeof(SfPicker),
+ defaultValueCreator: bindable => new SolidColorBrush(Color.FromArgb("#6750A4")),
+ propertyChanged: OnSelectionBackgroundChanged);
+
+ ///
+ /// Identifies the dependency property.
+ ///
+ ///
+ /// Identifies the bindable property.
+ ///
+ internal static readonly BindableProperty SelectionStrokeColorProperty =
+ BindableProperty.Create(
+ nameof(SelectionStrokeColor),
+ typeof(Color),
+ typeof(SfPicker),
+ defaultValueCreator: bindable => Colors.Transparent,
+ propertyChanged: OnSelectionStrokeColorChanged);
+
+ ///
+ /// Identifies the dependency property.
+ ///
+ ///
+ /// The identifier for dependency property.
+ ///
+ internal static readonly BindableProperty SelectionCornerRadiusProperty =
+ BindableProperty.Create(
+ nameof(SelectionCornerRadius),
+ typeof(CornerRadius),
+ typeof(SfPicker),
+ defaultValueCreator: bindable => new CornerRadius(20),
+ propertyChanged: OnSelectionCornerRadiusChanged);
+
+ ///
+ /// Identifies the dependency property.
+ ///
+ ///
+ /// Identifies the bindable property.
+ ///
+ internal static readonly BindableProperty HeaderDividerColorProperty =
+ BindableProperty.Create(
+ nameof(HeaderDividerColor),
+ typeof(Color),
+ typeof(SfPicker),
+ defaultValueCreator: bindable => Color.FromArgb("#CAC4D0"),
+ propertyChanged: OnHeaderDividerColorChanged);
+
+ ///
+ /// Identifies the dependency property.
+ ///
+ ///
+ /// Identifies the bindable property.
+ ///
+ internal static readonly BindableProperty FooterDividerColorProperty =
+ BindableProperty.Create(
+ nameof(FooterDividerColor),
+ typeof(Color),
+ typeof(SfPicker),
+ defaultValueCreator: bindable => Color.FromArgb("#CAC4D0"),
+ propertyChanged: OnFooterDividerColorChanged);
+
+ ///
+ /// Identifies the dependency property.
+ ///
+ ///
+ /// The identifier for dependency property.
+ ///
+ internal static readonly BindableProperty HeaderTextColorProperty =
+ BindableProperty.Create(
+ nameof(HeaderTextColor),
+ typeof(Color),
+ typeof(SfPicker),
+ defaultValueCreator: bindable => Color.FromArgb("#49454F"),
+ propertyChanged: OnHeaderTextColorChanged);
+
+ ///
+ /// Identifies the dependency property.
+ ///
+ ///
+ /// The identifier for dependency property.
+ ///
+ internal static readonly BindableProperty HeaderFontSizeProperty =
+ BindableProperty.Create(
+ nameof(HeaderFontSize),
+ typeof(double),
+ typeof(SfPicker),
+ defaultValueCreator: bindable => 16d,
+ propertyChanged: OnHeaderFontSizeChanged);
+
+ ///
+ /// Identifies the dependency property.
+ ///
+ ///
+ /// The identifier for dependency property.
+ ///
+ internal static readonly BindableProperty FooterTextColorProperty =
+ BindableProperty.Create(
+ nameof(FooterTextColor),
+ typeof(Color),
+ typeof(SfPicker),
+ defaultValueCreator: bindable => Color.FromArgb("#6750A4"),
+ propertyChanged: OnFooterTextColorChanged);
+
+ ///
+ /// Identifies the dependency property.
+ ///
+ ///
+ /// The identifier for dependency property.
+ ///
+ internal static readonly BindableProperty FooterFontSizeProperty =
+ BindableProperty.Create(
+ nameof(FooterFontSize),
+ typeof(double),
+ typeof(SfPicker),
+ defaultValueCreator: bindable => 14d,
+ propertyChanged: OnFooterFontSizeChanged);
+
+ ///
+ /// Identifies the dependency property.
+ ///
+ ///
+ /// The identifier for dependency property.
+ ///
+ internal static readonly BindableProperty SelectedTextColorProperty =
+ BindableProperty.Create(
+ nameof(SelectedTextColor),
+ typeof(Color),
+ typeof(SfPicker),
+ defaultValueCreator: bindable => Colors.White,
+ propertyChanged: OnSelectedTextColorChanged);
+
+ ///
+ /// Identifies the dependency property.
+ ///
+ ///
+ /// The identifier for dependency property.
+ ///
+ internal static readonly BindableProperty SelectionTextColorProperty =
+ BindableProperty.Create(
+ nameof(SelectionTextColor),
+ typeof(Color),
+ typeof(SfPicker),
+ defaultValueCreator: bindable => Color.FromArgb("#6750A4"),
+ propertyChanged: OnSelectedTextColorChanged);
+
+ ///
+ /// Identifies the dependency property.
+ ///
+ ///
+ /// The identifier for dependency property.
+ ///
+ internal static readonly BindableProperty SelectedFontSizeProperty =
+ BindableProperty.Create(
+ nameof(SelectedFontSize),
+ typeof(double),
+ typeof(SfPicker),
+ defaultValueCreator: bindable => 16d,
+ propertyChanged: OnSelectedFontSizeChanged);
+
+ ///
+ /// Identifies the dependency property.
+ ///
+ ///
+ /// The identifier for dependency property.
+ ///
+ internal static readonly BindableProperty NormalTextColorProperty =
+ BindableProperty.Create(
+ nameof(NormalTextColor),
+ typeof(Color),
+ typeof(SfPicker),
+ defaultValueCreator: bindable => Color.FromArgb("#1C1B1F"),
+ propertyChanged: OnNormalTextColorChanged);
+
+ ///
+ /// Identifies the dependency property.
+ ///
+ ///
+ /// The identifier for dependency property.
+ ///
+ internal static readonly BindableProperty NormalFontSizeProperty =
+ BindableProperty.Create(
+ nameof(NormalFontSize),
+ typeof(double),
+ typeof(SfPicker),
+ defaultValueCreator: bindable => 16d,
+ propertyChanged: OnNormalFontSizeChanged);
+
+ #endregion
+
+ #region Constructor
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ public SfPicker()
+ {
+ Initialize();
+ Focus();
+ ColumnHeaderView.Parent = this;
+ BackgroundColor = PickerBackground;
+ Dispatcher.Dispatch(() =>
+ {
+ InitializeTheme();
+ });
+ }
+
+ #endregion
+
+ #region Properties
+
+ ///
+ /// Gets or sets the value of header view. This property can be used to customize the header in SfPicker.
+ ///
+ ///
+ /// The following example demonstrates how to customize the header view of SfPicker.
+ ///
+ ///
+ ///
+ ///
+ public PickerHeaderView HeaderView
+ {
+ get { return (PickerHeaderView)GetValue(HeaderViewProperty); }
+ set { SetValue(HeaderViewProperty, value); }
+ }
+
+ ///
+ /// Gets or sets the value of column header view. This property can be used to customize the column header in SfPicker.
+ ///
+ ///
+ /// The following example demonstrates how to customize the column header view of SfPicker.
+ ///
+ ///
+ ///
+ ///
+ public PickerColumnHeaderView ColumnHeaderView
+ {
+ get { return (PickerColumnHeaderView)GetValue(ColumnHeaderViewProperty); }
+ set { SetValue(ColumnHeaderViewProperty, value); }
+ }
+
+ ///
+ /// Gets or sets the value of picker columns. This property can be used to customize the column in SfPicker.
+ ///
+ ///
+ /// The following examples demonstrate how to set the columns in SfPicker.
+ /// # [XAML](#tab/tabid-1)
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ /// ]]>
+ ///
+ /// # [C#](#tab/tabid-2)
+ ///
+ ///
+ /// {
+ /// new PickerColumn { ItemsSource = new List { "Red", "Green", "Blue" } },
+ /// new PickerColumn { ItemsSource = new List { 1, 2, 3 } }
+ /// };
+ /// ]]>
+ ///
+ ///
+ public ObservableCollection Columns
+ {
+ get { return (ObservableCollection)GetValue(ColumnsProperty); }
+ set { SetValue(ColumnsProperty, value); }
+ }
+
+ ///
+ /// Gets or sets the picker item template in SfPicker.
+ ///
+ ///
+ /// The following examples demonstrate how to set the item template in SfPicker.
+ /// # [XAML](#tab/tabid-1)
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ /// ]]>
+ ///
+ /// # [C#](#tab/tabid-2)
+ ///
+ /// SfPicker picker = new SfPicker();
+ /// DataTemplate customView = new DataTemplate(() =>
+ /// {
+ /// Grid grid = new Grid
+ /// {
+ /// Padding = new Thickness(0, 1, 0, 1),
+ /// };
+ /// Label label = new Label
+ /// {
+ /// HorizontalOptions = LayoutOptions.Center,
+ /// VerticalOptions = LayoutOptions.Center,
+ /// HorizontalTextAlignment = TextAlignment.Center,
+ /// VerticalTextAlignment = TextAlignment.Center,
+ /// TextColor = Colors.Black,
+ /// };
+ /// label.SetBinding(Label.TextProperty, new Binding("Data"));
+ /// grid.Children.Add(label);
+ /// return grid;
+ /// });
+ /// picker.ItemTemplate = customView;
+ ///
+ ///
+ public DataTemplate ItemTemplate
+ {
+ get { return (DataTemplate)GetValue(ItemTemplateProperty); }
+ set { SetValue(ItemTemplateProperty, value); }
+ }
+
+ ///
+ /// Gets or sets the picker item selection changed command.
+ ///
+ /// The default value of is null.
+ ///
+ /// The following example demonstrates how to set the selection changed command in SfPicker.
+ /// # [XAML](#tab/tabid-49)
+ ///
+ ///
+ ///
+ ///
+ ///
+ /// ]]>
+ /// # [C#](#tab/tabid-50)
+ ///
+ ///
+ public ICommand SelectionChangedCommand
+ {
+ get { return (ICommand)GetValue(SelectionChangedCommandProperty); }
+ set { SetValue(SelectionChangedCommandProperty, value); }
+ }
+
+ #endregion
+
+ #region Internal Properties
+
+ ///
+ /// Gets or sets the background color of the picker.
+ ///
+ internal Color PickerBackground
+ {
+ get { return (Color)GetValue(PickerBackgroundProperty); }
+ set { SetValue(PickerBackgroundProperty, value); }
+ }
+
+ ///
+ /// Gets or sets the background of the header view in picker.
+ ///
+ internal Brush HeaderBackground
+ {
+ get { return (Brush)GetValue(HeaderBackgroundProperty); }
+ set { SetValue(HeaderBackgroundProperty, value); }
+ }
+
+ ///
+ /// Gets or sets the background of the footer view in SfPicker.
+ ///
+ internal Brush FooterBackground
+ {
+ get { return (Brush)GetValue(FooterBackgroundProperty); }
+ set { SetValue(FooterBackgroundProperty, value); }
+ }
+
+ ///
+ /// Gets or sets the background of the selection view in SfPicker.
+ ///
+ internal Brush SelectionBackground
+ {
+ get { return (Brush)GetValue(SelectionBackgroundProperty); }
+ set { SetValue(SelectionBackgroundProperty, value); }
+ }
+
+ ///
+ /// Gets or sets the stroke color of the selection view in SfPicker.
+ ///
+ internal Color SelectionStrokeColor
+ {
+ get { return (Color)GetValue(SelectionStrokeColorProperty); }
+ set { SetValue(SelectionStrokeColorProperty, value); }
+ }
+
+ ///
+ /// Gets or sets the corner radius of the selection view in SfPicker.
+ ///
+ internal CornerRadius SelectionCornerRadius
+ {
+ get { return (CornerRadius)GetValue(SelectionCornerRadiusProperty); }
+ set { SetValue(SelectionCornerRadiusProperty, value); }
+ }
+
+ ///
+ /// Gets or sets the color of the header separator line in picker.
+ ///
+ internal Color HeaderDividerColor
+ {
+ get { return (Color)GetValue(HeaderDividerColorProperty); }
+ set { SetValue(HeaderDividerColorProperty, value); }
+ }
+
+ ///
+ /// Gets or sets the background of the footer separator line background in SfPicker.
+ ///
+ internal Color FooterDividerColor
+ {
+ get { return (Color)GetValue(FooterDividerColorProperty); }
+ set { SetValue(FooterDividerColorProperty, value); }
+ }
+
+ ///
+ /// Gets or sets the header text color of the text style.
+ ///
+ internal Color HeaderTextColor
+ {
+ get { return (Color)GetValue(HeaderTextColorProperty); }
+ set { SetValue(HeaderTextColorProperty, value); }
+ }
+
+ ///
+ /// Gets or sets the header font size of the text style.
+ ///
+ internal double HeaderFontSize
+ {
+ get { return (double)GetValue(HeaderFontSizeProperty); }
+ set { SetValue(HeaderFontSizeProperty, value); }
+ }
+
+ ///
+ /// Gets or sets the footer text color of the text style.
+ ///
+ internal Color FooterTextColor
+ {
+ get { return (Color)GetValue(FooterTextColorProperty); }
+ set { SetValue(FooterTextColorProperty, value); }
+ }
+
+ ///
+ /// Gets or sets the footer font size of the text style.
+ ///
+ internal double FooterFontSize
+ {
+ get { return (double)GetValue(FooterFontSizeProperty); }
+ set { SetValue(FooterFontSizeProperty, value); }
+ }
+
+ ///
+ /// Gets or sets the selection text color of the text style.
+ ///
+ ///
+ /// This color applicable for default text display mode.
+ ///
+ internal Color SelectedTextColor
+ {
+ get { return (Color)GetValue(SelectedTextColorProperty); }
+ set { SetValue(SelectedTextColorProperty, value); }
+ }
+
+ ///
+ /// Gets or sets the selection text color of the text style/
+ ///
+ ///
+ /// This color is used for Fade, Shrink and FadeAndShrink mode.
+ ///
+ internal Color SelectionTextColor
+ {
+ get { return (Color)GetValue(SelectionTextColorProperty); }
+ set { SetValue(SelectionTextColorProperty, value); }
+ }
+
+ ///
+ /// Gets or sets the selection font size of the text style.
+ ///
+ internal double SelectedFontSize
+ {
+ get { return (double)GetValue(SelectedFontSizeProperty); }
+ set { SetValue(SelectedFontSizeProperty, value); }
+ }
+
+ ///
+ /// Gets or sets the normal text color of the text style.
+ ///
+ internal Color NormalTextColor
+ {
+ get { return (Color)GetValue(NormalTextColorProperty); }
+ set { SetValue(NormalTextColorProperty, value); }
+ }
+
+ ///
+ /// Gets or sets the normal font size of the text style.
+ ///
+ internal double NormalFontSize
+ {
+ get { return (double)GetValue(NormalFontSizeProperty); }
+ set { SetValue(NormalFontSizeProperty, value); }
+ }
+
+ #endregion
+
+ #region Private Methods
+
+ ///
+ /// Method trigged whenever the base panel selection is changed.
+ ///
+ /// Base picker instance value.
+ /// Selection changed event arguments.
+ void OnPickerSelectionIndexChanged(object? sender, PickerSelectionChangedEventArgs e)
+ {
+ SelectionChanged?.Invoke(this, e);
+ if (SelectionChangedCommand != null && SelectionChangedCommand.CanExecute(e))
+ {
+ SelectionChangedCommand.Execute(e);
+ }
+ }
+
+ ///
+ /// Need to update the parent for the new value.
+ ///
+ /// The old value.
+ /// The new value.
+ void SetParent(Element? oldValue, Element? newValue)
+ {
+ if (oldValue != null)
+ {
+ oldValue.Parent = null;
+ }
+
+ if (newValue != null)
+ {
+ newValue.Parent = this;
+ }
+ }
+
+ ///
+ /// Method to initialize the theme and to set dynamic resources.
+ ///
+ void InitializeTheme()
+ {
+ ThemeElement.InitializeThemeResources(this, "SfPickerTheme");
+ SetDynamicResource(PickerBackgroundProperty, "SfPickerNormalBackground");
+
+ SetDynamicResource(HeaderBackgroundProperty, "SfPickerNormalHeaderBackground");
+ SetDynamicResource(HeaderDividerColorProperty, "SfPickerNormalHeaderDividerColor");
+ SetDynamicResource(HeaderTextColorProperty, "SfPickerNormalHeaderTextColor");
+ SetDynamicResource(HeaderFontSizeProperty, "SfPickerNormalHeaderFontSize");
+
+ SetDynamicResource(FooterBackgroundProperty, "SfPickerNormalFooterBackground");
+ SetDynamicResource(FooterDividerColorProperty, "SfPickerNormalFooterDividerColor");
+ SetDynamicResource(FooterTextColorProperty, "SfPickerNormalFooterTextColor");
+ SetDynamicResource(FooterFontSizeProperty, "SfPickerNormalFooterFontSize");
+
+ SetDynamicResource(SelectionBackgroundProperty, "SfPickerSelectionBackground");
+ SetDynamicResource(SelectionStrokeColorProperty, "SfPickerSelectionStroke");
+ SetDynamicResource(SelectionCornerRadiusProperty, "SfPickerSelectionCornerRadius");
+ SetDynamicResource(SelectedTextColorProperty, "SfPickerSelectedTextColor");
+ SetDynamicResource(SelectedFontSizeProperty, "SfPickerSelectedFontSize");
+ SetDynamicResource(SelectionTextColorProperty, "SfPickerSelectionTextColor");
+
+ SetDynamicResource(NormalTextColorProperty, "SfPickerNormalTextColor");
+ SetDynamicResource(NormalFontSizeProperty, "SfPickerNormalFontSize");
+ }
+
+ #endregion
+
+ #region Override Methods
+
+ ///
+ /// Method to wire the events.
+ ///
+ protected override void Initialize()
+ {
+ base.Initialize();
+ BaseColumnHeaderView = ColumnHeaderView;
+ BaseColumns = Columns;
+ BaseHeaderView = HeaderView;
+ BaseItemTemplate = ItemTemplate;
+ SelectionIndexChanged += OnPickerSelectionIndexChanged;
+ }
+
+ ///
+ /// Method triggers when property binding context changed.
+ ///
+ protected override void OnBindingContextChanged()
+ {
+ base.OnBindingContextChanged();
+ if (HeaderView != null)
+ {
+ SetInheritedBindingContext(HeaderView, BindingContext);
+ if (HeaderView.TextStyle != null)
+ {
+ SetInheritedBindingContext(HeaderView.TextStyle, BindingContext);
+ }
+
+ if (HeaderView.SelectionTextStyle != null)
+ {
+ SetInheritedBindingContext(HeaderView.SelectionTextStyle, BindingContext);
+ }
+ }
+
+ if (ColumnHeaderView != null)
+ {
+ SetInheritedBindingContext(ColumnHeaderView, BindingContext);
+ if (ColumnHeaderView.TextStyle != null)
+ {
+ SetInheritedBindingContext(ColumnHeaderView.TextStyle, BindingContext);
+ }
+ }
+
+ if (FooterView != null)
+ {
+ SetInheritedBindingContext(FooterView, BindingContext);
+ if (FooterView.TextStyle != null)
+ {
+ SetInheritedBindingContext(FooterView.TextStyle, BindingContext);
+ }
+ }
+
+ if (Columns != null)
+ {
+ for (int index = 0; index < Columns.Count; index++)
+ {
+ PickerColumn column = Columns[index];
+ column._columnIndex = index;
+ SetInheritedBindingContext(column, BindingContext);
+ }
+ }
+
+ if (SelectedTextStyle != null)
+ {
+ SetInheritedBindingContext(SelectedTextStyle, BindingContext);
+ }
+
+ if (TextStyle != null)
+ {
+ SetInheritedBindingContext(TextStyle, BindingContext);
+ }
+
+ if (SelectionView != null)
+ {
+ SetInheritedBindingContext(SelectionView, BindingContext);
+ }
+ }
+
+ ///
+ /// Method triggers when the picker popup closed.
+ ///
+ /// The event arguments.
+ protected override void OnPopupClosed(EventArgs e)
+ {
+ InvokeClosedEvent(this, e);
+ }
+
+ ///
+ /// Method triggers when the picker popup closing.
+ ///
+ /// The event arguments.
+ protected override void OnPopupClosing(CancelEventArgs e)
+ {
+ InvokeClosingEvent(this, e);
+ }
+
+ ///
+ /// Method triggers when the picker popup opened.
+ ///
+ /// The event arguments.
+ protected override void OnPopupOpened(EventArgs e)
+ {
+ InvokeOpenedEvent(this, e);
+ }
+
+ ///
+ /// Method triggers when the picker ok button clicked.
+ ///
+ /// The event arguments.
+ protected override void OnOkButtonClicked(EventArgs e)
+ {
+ InvokeOkButtonClickedEvent(this, e);
+ if (AcceptCommand != null && AcceptCommand.CanExecute(e))
+ {
+ AcceptCommand.Execute(e);
+ }
+ }
+
+ ///
+ /// Method triggers when the picker cancel button clicked.
+ ///
+ /// The event arguments.
+ protected override void OnCancelButtonClicked(EventArgs e)
+ {
+ InvokeCancelButtonClickedEvent(this, e);
+ if (DeclineCommand != null && DeclineCommand.CanExecute(e))
+ {
+ DeclineCommand.Execute(e);
+ }
+ }
+
+ #endregion
+
+ #region Property Changed Methods
+
+ ///
+ /// Method invokes on picker header view changed.
+ ///
+ /// The picker object.
+ /// Old value of the property.
+ /// New value of the property.
+ static void OnHeaderViewChanged(BindableObject bindable, object oldValue, object newValue)
+ {
+ SfPicker? picker = bindable as SfPicker;
+ if (picker == null)
+ {
+ return;
+ }
+
+ picker.HeaderView.Parent = picker;
+ picker.BaseHeaderView = picker.HeaderView;
+ if (bindable is SfPicker pickerView)
+ {
+ pickerView.SetParent(oldValue as Element, newValue as Element);
+ }
+ }
+
+ ///
+ /// Method invokes on picker column header view changed.
+ ///
+ /// The picker object.
+ /// Old value of the property.
+ /// New value of the property.
+ static void OnColumnHeaderViewChanged(BindableObject bindable, object oldValue, object newValue)
+ {
+ SfPicker? picker = bindable as SfPicker;
+ if (picker == null)
+ {
+ return;
+ }
+
+ picker.BaseColumnHeaderView = picker.ColumnHeaderView;
+ }
+
+ ///
+ /// Method invokes on picker columns changed.
+ ///
+ /// The picker object.
+ /// Old value of the property.
+ /// New value of the property.
+ static void OnColumnsChanged(BindableObject bindable, object oldValue, object newValue)
+ {
+ SfPicker? picker = bindable as SfPicker;
+ if (picker == null)
+ {
+ return;
+ }
+
+ picker.BaseColumns = picker.Columns;
+ }
+
+ ///
+ /// Method invokes on the picker item template changed.
+ ///
+ /// The picker settings object.
+ /// Property old value.
+ /// Property new value.
+ static void OnItemTemplateChanged(BindableObject bindable, object oldValue, object newValue)
+ {
+ SfPicker? picker = bindable as SfPicker;
+ if (picker == null)
+ {
+ return;
+ }
+
+ picker.BaseItemTemplate = picker.ItemTemplate;
+ }
+
+ #endregion
+
+ #region Internal Property Changed Methods
+
+ ///
+ /// called when property changed.
+ ///
+ /// The bindable object.
+ /// The old value.
+ /// The new value.
+ static void OnPickerBackgroundChanged(BindableObject bindable, object oldValue, object newValue)
+ {
+ SfPicker? picker = bindable as SfPicker;
+ if (picker == null)
+ {
+ return;
+ }
+
+ picker.BackgroundColor = picker.PickerBackground;
+ }
+
+ ///
+ /// Method invokes on the picker header background changed.
+ ///
+ /// The header settings object.
+ /// Property old value.
+ /// Property new value.
+ static void OnHeaderBackgroundChanged(BindableObject bindable, object oldValue, object newValue)
+ {
+ SfPicker? picker = bindable as SfPicker;
+ if (picker == null)
+ {
+ return;
+ }
+
+ picker.HeaderView.Background = picker.HeaderBackground;
+ }
+
+ ///
+ /// Method invokes on the picker footer background changed.
+ ///
+ /// The footer settings object.
+ /// Property old value.
+ /// Property new value.
+ static void OnFooterBackgroundChanged(BindableObject bindable, object oldValue, object newValue)
+ {
+ SfPicker? picker = bindable as SfPicker;
+ if (picker == null)
+ {
+ return;
+ }
+
+ picker.FooterView.Background = picker.FooterBackground;
+ }
+
+ ///
+ /// Method invokes on the picker selection background changed.
+ ///
+ /// The selection settings object.
+ /// Property old value.
+ /// Property new value.
+ static void OnSelectionBackgroundChanged(BindableObject bindable, object oldValue, object newValue)
+ {
+ SfPicker? picker = bindable as SfPicker;
+ if (picker == null)
+ {
+ return;
+ }
+
+ picker.SelectionView.Background = picker.SelectionBackground;
+ }
+
+ ///
+ /// Method invokes on the picker selection stroke color changed.
+ ///
+ /// The selection settings object.
+ /// Property old value.
+ /// Property new value.
+ static void OnSelectionStrokeColorChanged(BindableObject bindable, object oldValue, object newValue)
+ {
+ SfPicker? picker = bindable as SfPicker;
+ if (picker == null)
+ {
+ return;
+ }
+
+ picker.SelectionView.Stroke = picker.SelectionStrokeColor;
+ }
+
+ ///
+ /// Method invokes on the picker selection corner radius changed.
+ ///
+ /// The selection settings object.
+ /// Property old value.
+ /// Property new value.
+ static void OnSelectionCornerRadiusChanged(BindableObject bindable, object oldValue, object newValue)
+ {
+ SfPicker? picker = bindable as SfPicker;
+ if (picker == null)
+ {
+ return;
+ }
+
+ picker.SelectionView.CornerRadius = picker.SelectionCornerRadius;
+ }
+
+ ///
+ /// Method invokes on the picker header separator line background changed.
+ ///
+ /// The header settings object.
+ /// Property old value.
+ /// Property new value.
+ static void OnHeaderDividerColorChanged(BindableObject bindable, object oldValue, object newValue)
+ {
+ SfPicker? picker = bindable as SfPicker;
+ if (picker == null)
+ {
+ return;
+ }
+
+ picker.HeaderView.DividerColor = picker.HeaderDividerColor;
+ }
+
+ ///
+ /// Method invokes on the picker footer separator line background changed.
+ ///
+ /// The footer settings object.
+ /// Property old value.
+ /// Property new value.
+ static void OnFooterDividerColorChanged(BindableObject bindable, object oldValue, object newValue)
+ {
+ SfPicker? picker = bindable as SfPicker;
+ if (picker == null)
+ {
+ return;
+ }
+
+ picker.FooterView.DividerColor = picker.FooterDividerColor;
+ }
+
+ ///
+ /// Method invokes on the picker header text color changed.
+ ///
+ /// The header text style object.
+ /// Property old value.
+ /// Property new value.
+ static void OnHeaderTextColorChanged(BindableObject bindable, object oldValue, object newValue)
+ {
+ SfPicker? picker = bindable as SfPicker;
+ if (picker == null)
+ {
+ return;
+ }
+
+ picker.HeaderView.TextStyle.TextColor = picker.HeaderTextColor;
+ }
+
+ ///
+ /// Method invokes on the picker header font size changed.
+ ///
+ /// The header text style object.
+ /// Property old value.
+ /// Property new value.
+ static void OnHeaderFontSizeChanged(BindableObject bindable, object oldValue, object newValue)
+ {
+ SfPicker? picker = bindable as SfPicker;
+ if (picker == null)
+ {
+ return;
+ }
+
+ picker.HeaderView.TextStyle.FontSize = picker.HeaderFontSize;
+ }
+
+ ///
+ /// Method invokes on the picker footer text color changed.
+ ///
+ /// The footer text style object.
+ /// Property old value.
+ /// Property new value.
+ static void OnFooterTextColorChanged(BindableObject bindable, object oldValue, object newValue)
+ {
+ SfPicker? picker = bindable as SfPicker;
+ if (picker == null)
+ {
+ return;
+ }
+
+ picker.FooterView.TextStyle.TextColor = picker.FooterTextColor;
+ }
+
+ ///
+ /// Method invokes on the picker footer font size changed.
+ ///
+ /// The footer text style object.
+ /// Property old value.
+ /// Property new value.
+ static void OnFooterFontSizeChanged(BindableObject bindable, object oldValue, object newValue)
+ {
+ SfPicker? picker = bindable as SfPicker;
+ if (picker == null)
+ {
+ return;
+ }
+
+ picker.FooterView.TextStyle.FontSize = picker.FooterFontSize;
+ }
+
+ ///
+ /// Method invokes on the picker selection text color changed.
+ ///
+ /// The selection text style object.
+ /// Property old value.
+ /// Property new value.
+ static void OnSelectedTextColorChanged(BindableObject bindable, object oldValue, object newValue)
+ {
+ SfPicker? picker = bindable as SfPicker;
+ if (picker == null)
+ {
+ return;
+ }
+
+ picker.SelectedTextStyle.TextColor = picker.TextDisplayMode == PickerTextDisplayMode.Default ? picker.SelectedTextColor : picker.SelectionTextColor;
+ }
+
+ ///
+ /// Method invokes on the picker selection font size changed.
+ ///
+ /// The selection text style object.
+ /// Property old value.
+ /// Property new value.
+ static void OnSelectedFontSizeChanged(BindableObject bindable, object oldValue, object newValue)
+ {
+ SfPicker? picker = bindable as SfPicker;
+ if (picker == null)
+ {
+ return;
+ }
+
+ picker.SelectedTextStyle.FontSize = picker.SelectedFontSize;
+ }
+
+ ///
+ /// Method invokes on the picker normal text color changed.
+ ///
+ /// The text style object.
+ /// Property old value.
+ /// Property new value.
+ static void OnNormalTextColorChanged(BindableObject bindable, object oldValue, object newValue)
+ {
+ SfPicker? picker = bindable as SfPicker;
+ if (picker == null)
+ {
+ return;
+ }
+
+ picker.TextStyle.TextColor = picker.NormalTextColor;
+ }
+
+ ///
+ /// Method invokes on the picker normal font size changed.
+ ///
+ /// The text style object.
+ /// Property old value.
+ /// Property new value.
+ static void OnNormalFontSizeChanged(BindableObject bindable, object oldValue, object newValue)
+ {
+ SfPicker? picker = bindable as SfPicker;
+ if (picker == null)
+ {
+ return;
+ }
+
+ picker.TextStyle.FontSize = picker.NormalFontSize;
+ }
+
+ #endregion
+
+ #region Interface Implementation
+
+ ///
+ /// This method is declared only in IParentThemeElement
+ /// and you need to implement this method only in main control.
+ ///
+ /// ResourceDictionary
+ ResourceDictionary IParentThemeElement.GetThemeDictionary()
+ {
+ return new SfPickerStyles();
+ }
+
+ ///
+ /// This method will be called when a theme dictionary
+ /// that contains the value for your control key is merged in application.
+ ///
+ /// The old value.
+ /// The new value.
+ void IThemeElement.OnCommonThemeChanged(string oldTheme, string newTheme)
+ {
+ }
+
+ ///
+ /// This method will be called when users merge a theme dictionary
+ /// that contains value for “SyncfusionTheme” dynamic resource key.
+ ///
+ /// Old theme.
+ /// New theme.
+ void IThemeElement.OnControlThemeChanged(string oldTheme, string newTheme)
+ {
+ }
+
+ #endregion
+
+ #region Events
+
+ ///
+ /// Occurs after the selected index changed on SfPicker.
+ ///
+ public event EventHandler? SelectionChanged;
+
+ #endregion
+ }
+}
\ No newline at end of file
diff --git a/maui/src/Picker/SfTimePicker.cs b/maui/src/Picker/SfTimePicker.cs
new file mode 100644
index 00000000..a09a68d9
--- /dev/null
+++ b/maui/src/Picker/SfTimePicker.cs
@@ -0,0 +1,2383 @@
+using System.Collections.ObjectModel;
+using System.ComponentModel;
+using System.Windows.Input;
+using Syncfusion.Maui.Toolkit.Themes;
+
+namespace Syncfusion.Maui.Toolkit.Picker
+{
+ ///
+ /// Initializes a new instance of the class that represents a control, used to select the time with in specified list of times.
+ ///
+ public class SfTimePicker : PickerBase, IParentThemeElement, IThemeElement
+ {
+ #region Fields
+
+ ///
+ /// Holds the hour column information.
+ ///
+ PickerColumn _hourColumn;
+
+ ///
+ /// Holds the minute column information.
+ ///
+ PickerColumn _minuteColumn;
+
+ ///
+ /// Holds the second column information.
+ ///
+ PickerColumn _secondColumn;
+
+ ///
+ /// Holds the meridiem column information.
+ ///
+ PickerColumn _meridiemColumn;
+
+ ///
+ /// Holds the picker column collection.
+ ///
+ ObservableCollection _columns;
+
+ #endregion
+
+ #region Bindable Properties
+
+ ///
+ /// Identifies the dependency property.
+ ///
+ ///
+ /// The identifier for dependency property.
+ ///
+ public static readonly BindableProperty HeaderViewProperty =
+ BindableProperty.Create(
+ nameof(HeaderView),
+ typeof(PickerHeaderView),
+ typeof(SfTimePicker),
+ defaultValueCreator: bindable => new PickerHeaderView(),
+ propertyChanged: OnHeaderViewChanged);
+
+ ///
+ /// Identifies the dependency property.
+ ///
+ ///
+ /// The identifier for dependency property.
+ ///
+ public static readonly BindableProperty ColumnHeaderViewProperty =
+ BindableProperty.Create(
+ nameof(ColumnHeaderView),
+ typeof(TimePickerColumnHeaderView),
+ typeof(SfTimePicker),
+ defaultValueCreator: bindable => new TimePickerColumnHeaderView(),
+ propertyChanged: OnColumnHeaderViewChanged);
+
+ ///
+ /// Identifies the dependency property.
+ ///
+ ///
+ /// The identifier for dependency property.
+ ///
+ public static readonly BindableProperty SelectedTimeProperty =
+ BindableProperty.Create(
+ nameof(SelectedTime),
+ typeof(TimeSpan?),
+ typeof(SfTimePicker),
+ defaultValueCreator: bindable => GetDefaultTimeSpan(),
+ propertyChanged: OnSelectedTimePropertyChanged);
+
+ ///
+ /// Identifies the dependency property.
+ ///
+ ///
+ /// The identifier for dependency property.
+ ///
+ public static readonly BindableProperty HourIntervalProperty =
+ BindableProperty.Create(
+ nameof(HourInterval),
+ typeof(int),
+ typeof(SfTimePicker),
+ 1,
+ propertyChanged: OnHourIntervalPropertyChanged);
+
+ ///
+ /// Identifies the dependency property.
+ ///
+ ///
+ /// The identifier for dependency property.
+ ///
+ public static readonly BindableProperty MinuteIntervalProperty =
+ BindableProperty.Create(
+ nameof(MinuteInterval),
+ typeof(int),
+ typeof(SfTimePicker),
+ 1,
+ propertyChanged: OnMinuteIntervalPropertyChanged);
+
+ ///
+ /// Identifies the dependency property.
+ ///
+ ///
+ /// The identifier for dependency property.
+ ///
+ public static readonly BindableProperty SecondIntervalProperty =
+ BindableProperty.Create(
+ nameof(SecondInterval),
+ typeof(int),
+ typeof(SfTimePicker),
+ 1,
+ propertyChanged: OnSecondIntervalPropertyChanged);
+
+ ///
+ /// Identifies the dependency property.
+ ///
+ ///
+ /// The identifier for dependency property.
+ ///
+ public static readonly BindableProperty FormatProperty =
+ BindableProperty.Create(
+ nameof(Format),
+ typeof(PickerTimeFormat),
+ typeof(SfTimePicker),
+ PickerTimeFormat.HH_mm_ss,
+ propertyChanged: OnFormatPropertyChanged);
+
+ ///
+ /// Identifies the dependency property.
+ ///
+ ///
+ /// The identifier for dependency property.
+ ///
+ public static readonly BindableProperty SelectionChangedCommandProperty =
+ BindableProperty.Create(
+ nameof(SelectionChangedCommand),
+ typeof(ICommand),
+ typeof(SfTimePicker),
+ null);
+
+ ///
+ /// Identifies the dependency property.
+ ///
+ ///
+ /// The identifier for dependency property.
+ ///
+ public static readonly BindableProperty MinimumTimeProperty =
+ BindableProperty.Create(
+ nameof(MinimumTime),
+ typeof(TimeSpan),
+ typeof(SfTimePicker),
+ defaultValueCreator: bindable => TimeSpan.Zero,
+ propertyChanged: OnMinimumTimePropertyChanged);
+
+ ///
+ /// Identifies the dependency property.
+ ///
+ ///
+ /// The identifier for dependency property.
+ ///
+ public static readonly BindableProperty MaximumTimeProperty =
+ BindableProperty.Create(
+ nameof(MaximumTime),
+ typeof(TimeSpan),
+ typeof(SfTimePicker),
+ defaultValueCreator: bindable => new TimeSpan(23, 59, 59),
+ propertyChanged: OnMaximumTimePropertyChanged);
+
+ ///
+ /// Identifies the dependency property.
+ ///
+ ///
+ /// The identifier for dependency property.
+ ///
+ public static readonly BindableProperty BlackoutTimesProperty =
+ BindableProperty.Create(
+ nameof(BlackoutTimes),
+ typeof(ObservableCollection),
+ typeof(SfTimePicker),
+ defaultValueCreator: bindable => new ObservableCollection(),
+ propertyChanged: OnBlackOutTimesPropertyChanged);
+
+ #endregion
+
+ #region Internal Bindable Properties
+
+ ///
+ /// Identifies the bindable property.
+ ///
+ ///
+ /// The identifier for bindable property.
+ ///
+ internal static readonly BindableProperty TimePickerBackgroundProperty =
+ BindableProperty.Create(
+ nameof(TimePickerBackground),
+ typeof(Color),
+ typeof(SfTimePicker),
+ defaultValueCreator: bindable => Color.FromArgb("#EEE8F4"),
+ propertyChanged: OnTimePickerBackgroundChanged);
+
+ ///
+ /// Identifies the dependency property.
+ ///
+ ///
+ /// Identifies the bindable property.
+ ///
+ internal static readonly BindableProperty HeaderBackgroundProperty =
+ BindableProperty.Create(
+ nameof(HeaderBackground),
+ typeof(Brush),
+ typeof(SfTimePicker),
+ defaultValueCreator: bindable => new SolidColorBrush(Color.FromArgb("#F7F2FB")),
+ propertyChanged: OnHeaderBackgroundChanged);
+
+ ///
+ /// Identifies the dependency property.
+ ///
+ ///
+ /// Identifies the bindable property.
+ ///
+ internal static readonly BindableProperty FooterBackgroundProperty =
+ BindableProperty.Create(
+ nameof(FooterBackground),
+ typeof(Brush),
+ typeof(SfTimePicker),
+ defaultValueCreator: bindable => Brush.Transparent,
+ propertyChanged: OnFooterBackgroundChanged);
+
+ ///
+ /// Identifies the dependency property.
+ ///
+ ///
+ /// Identifies the bindable property.
+ ///
+ internal static readonly BindableProperty SelectionBackgroundProperty =
+ BindableProperty.Create(
+ nameof(SelectionBackground),
+ typeof(Brush),
+ typeof(SfTimePicker),
+ defaultValueCreator: bindable => new SolidColorBrush(Color.FromArgb("#6750A4")),
+ propertyChanged: OnSelectionBackgroundChanged);
+
+ ///
+ /// Identifies the dependency property.
+ ///
+ ///
+ /// Identifies the bindable property.
+ ///
+ internal static readonly BindableProperty SelectionStrokeColorProperty =
+ BindableProperty.Create(
+ nameof(SelectionStrokeColor),
+ typeof(Color),
+ typeof(SfTimePicker),
+ defaultValueCreator: bindable => Colors.Transparent,
+ propertyChanged: OnSelectionStrokeColorChanged);
+
+ ///
+ /// Identifies the dependency property.
+ ///
+ ///
+ /// The identifier for dependency property.
+ ///
+ internal static readonly BindableProperty SelectionCornerRadiusProperty =
+ BindableProperty.Create(
+ nameof(SelectionCornerRadius),
+ typeof(CornerRadius),
+ typeof(SfTimePicker),
+ defaultValueCreator: bindable => new CornerRadius(20),
+ propertyChanged: OnSelectionCornerRadiusChanged);
+
+ ///
+ /// Identifies the dependency property.
+ ///
+ ///
+ /// Identifies the bindable property.
+ ///
+ internal static readonly BindableProperty HeaderDividerColorProperty =
+ BindableProperty.Create(
+ nameof(HeaderDividerColor),
+ typeof(Color),
+ typeof(SfTimePicker),
+ defaultValueCreator: bindable => Color.FromArgb("#CAC4D0"),
+ propertyChanged: OnHeaderDividerColorChanged);
+
+ ///
+ /// Identifies the dependency property.
+ ///
+ ///
+ /// Identifies the bindable property.
+ ///
+ internal static readonly BindableProperty FooterDividerColorProperty =
+ BindableProperty.Create(
+ nameof(FooterDividerColor),
+ typeof(Color),
+ typeof(SfTimePicker),
+ defaultValueCreator: bindable => Color.FromArgb("#CAC4D0"),
+ propertyChanged: OnFooterDividerColorChanged);
+
+ ///
+ /// Identifies the dependency property.
+ ///
+ ///
+ /// The identifier for dependency property.
+ ///
+ internal static readonly BindableProperty HeaderTextColorProperty =
+ BindableProperty.Create(
+ nameof(HeaderTextColor),
+ typeof(Color),
+ typeof(SfTimePicker),
+ defaultValueCreator: bindable => Color.FromArgb("#49454F"),
+ propertyChanged: OnHeaderTextColorChanged);
+
+ ///
+ /// Identifies the dependency property.
+ ///
+ ///
+ /// The identifier for dependency property.
+ ///
+ internal static readonly BindableProperty HeaderFontSizeProperty =
+ BindableProperty.Create(
+ nameof(HeaderFontSize),
+ typeof(double),
+ typeof(SfTimePicker),
+ defaultValueCreator: bindable => 16d,
+ propertyChanged: OnHeaderFontSizeChanged);
+
+ ///
+ /// Identifies the dependency property.
+ ///
+ ///
+ /// The identifier for dependency property.
+ ///
+ internal static readonly BindableProperty FooterTextColorProperty =
+ BindableProperty.Create(
+ nameof(FooterTextColor),
+ typeof(Color),
+ typeof(SfTimePicker),
+ defaultValueCreator: bindable => Color.FromArgb("#6750A4"),
+ propertyChanged: OnFooterTextColorChanged);
+
+ ///
+ /// Identifies the dependency property.
+ ///
+ ///
+ /// The identifier for dependency property.
+ ///
+ internal static readonly BindableProperty FooterFontSizeProperty =
+ BindableProperty.Create(
+ nameof(FooterFontSize),
+ typeof(double),
+ typeof(SfTimePicker),
+ defaultValueCreator: bindable => 14d,
+ propertyChanged: OnFooterFontSizeChanged);
+
+ ///
+ /// Identifies the dependency property.
+ ///
+ ///
+ /// The identifier for dependency property.
+ ///
+ internal static readonly BindableProperty SelectedTextColorProperty =
+ BindableProperty.Create(
+ nameof(SelectedTextColor),
+ typeof(Color),
+ typeof(SfTimePicker),
+ defaultValueCreator: bindable => Colors.White,
+ propertyChanged: OnSelectedTextColorChanged);
+
+ ///
+ /// Identifies the dependency property.
+ ///
+ ///
+ /// The identifier for dependency property.
+ ///
+ internal static readonly BindableProperty SelectionTextColorProperty =
+ BindableProperty.Create(
+ nameof(SelectionTextColor),
+ typeof(Color),
+ typeof(SfPicker),
+ defaultValueCreator: bindable => Color.FromArgb("#6750A4"),
+ propertyChanged: OnSelectedTextColorChanged);
+
+ ///
+ /// Identifies the dependency property.
+ ///
+ ///
+ /// The identifier for dependency property.
+ ///
+ internal static readonly BindableProperty SelectedFontSizeProperty =
+ BindableProperty.Create(
+ nameof(SelectedFontSize),
+ typeof(double),
+ typeof(SfTimePicker),
+ defaultValueCreator: bindable => 16d,
+ propertyChanged: OnSelectedFontSizeChanged);
+
+ ///
+ /// Identifies the dependency property.
+ ///
+ ///
+ /// The identifier for dependency property.
+ ///
+ internal static readonly BindableProperty NormalTextColorProperty =
+ BindableProperty.Create(
+ nameof(NormalTextColor),
+ typeof(Color),
+ typeof(SfTimePicker),
+ defaultValueCreator: bindable => Color.FromArgb("#1C1B1F"),
+ propertyChanged: OnNormalTextColorChanged);
+
+ ///
+ /// Identifies the dependency property.
+ ///
+ ///
+ /// The identifier for dependency property.
+ ///
+ internal static readonly BindableProperty NormalFontSizeProperty =
+ BindableProperty.Create(
+ nameof(NormalFontSize),
+ typeof(double),
+ typeof(SfTimePicker),
+ defaultValueCreator: bindable => 16d,
+ propertyChanged: OnNormalFontSizeChanged);
+
+ ///
+ /// Identifies the dependency property.
+ ///
+ ///
+ /// The identifier for dependency property.
+ ///
+ internal static readonly BindableProperty DisabledTextColorProperty =
+ BindableProperty.Create(
+ nameof(DisabledTextColor),
+ typeof(Color),
+ typeof(SfTimePicker),
+ defaultValueCreator: bindable => Color.FromArgb("#611C1B1F"),
+ propertyChanged: OnDisabledTextColorChanged);
+
+ #endregion
+
+ #region Constructor
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ public SfTimePicker()
+ {
+ _hourColumn = new PickerColumn();
+ _minuteColumn = new PickerColumn();
+ _secondColumn = new PickerColumn();
+ _meridiemColumn = new PickerColumn();
+ _columns = new ObservableCollection();
+ Initialize();
+ GeneratePickerColumns();
+ BaseColumns = _columns;
+ SelectionIndexChanged += OnPickerSelectionIndexChanged;
+ BlackoutTimes.CollectionChanged += OnBlackoutTimes_CollectionChanged;
+ BackgroundColor = TimePickerBackground;
+ Dispatcher.Dispatch(() =>
+ {
+ InitializeTheme();
+ });
+ ColumnHeaderView.Parent = this;
+ }
+
+ #endregion
+
+ #region Properties
+
+ ///
+ /// Gets or sets the value of header view. This property can be used to customize the header in SfTimePicker.
+ ///
+ ///
+ /// The following example demonstrates how to customize the header view of SfTimePicker.
+ ///
+ ///
+ ///
+ ///
+ public PickerHeaderView HeaderView
+ {
+ get { return (PickerHeaderView)GetValue(HeaderViewProperty); }
+ set { SetValue(HeaderViewProperty, value); }
+ }
+
+ ///
+ /// Gets or sets the value of column header view. This property can be used to customize the header column in SfTimePicker.
+ ///
+ ///
+ /// The following example demonstrates how to customize the column header view of SfTimePicker.
+ ///
+ ///
+ ///
+ ///
+ public TimePickerColumnHeaderView ColumnHeaderView
+ {
+ get { return (TimePickerColumnHeaderView)GetValue(ColumnHeaderViewProperty); }
+ set { SetValue(ColumnHeaderViewProperty, value); }
+ }
+
+ ///
+ /// Gets or sets the time picker selection time in SfTimePicker.
+ ///
+ /// The default value of is "TimeSpan(DateTime.Now.Hour, DateTime.Now.Minute, DateTime.Now.Second)".
+ ///
+ /// The following examples demonstrate how to set the selected time in SfTimePicker.
+ /// # [XAML](#tab/tabid-1)
+ ///
+ ///
+ /// ]]>
+ ///
+ /// # [C#](#tab/tabid-2)
+ ///
+ /// SfTimePicker timePicker = new SfTimePicker();
+ /// timePicker.SelectedTime = new TimeSpan(14, 30, 0);
+ ///
+ ///
+ public TimeSpan? SelectedTime
+ {
+ get { return (TimeSpan?)GetValue(SelectedTimeProperty); }
+ set { SetValue(SelectedTimeProperty, value); }
+ }
+
+ ///
+ /// Gets or sets the hour interval in SfTimePicker.
+ ///
+ /// The default value of is 1.
+ ///
+ /// The following examples demonstrate how to set the hour interval in SfTimePicker.
+ /// # [XAML](#tab/tabid-3)
+ ///
+ ///
+ /// ]]>
+ ///
+ /// # [C#](#tab/tabid-4)
+ ///
+ /// SfTimePicker timePicker = new SfTimePicker();
+ /// timePicker.HourInterval = 2;
+ ///
+ ///
+ public int HourInterval
+ {
+ get { return (int)GetValue(HourIntervalProperty); }
+ set { SetValue(HourIntervalProperty, value); }
+ }
+
+ ///
+ /// Gets or sets the minute interval in SfTimePicker.
+ ///
+ /// The default value of is 1.
+ ///
+ /// The following examples demonstrate how to set the minute interval in SfTimePicker.
+ /// # [XAML](#tab/tabid-5)
+ ///
+ ///
+ /// ]]>
+ ///
+ /// # [C#](#tab/tabid-6)
+ ///
+ /// SfTimePicker timePicker = new SfTimePicker();
+ /// timePicker.MinuteInterval = 2;
+ ///
+ ///
+ public int MinuteInterval
+ {
+ get { return (int)GetValue(MinuteIntervalProperty); }
+ set { SetValue(MinuteIntervalProperty, value); }
+ }
+
+ ///
+ /// Gets or sets the second interval in SfTimePicker.
+ ///
+ /// The default value of is 1.
+ ///
+ /// The following examples demonstrate how to set the second interval in SfTimePicker.
+ /// # [XAML](#tab/tabid-7)
+ ///
+ ///
+ /// ]]>
+ ///
+ /// # [C#](#tab/tabid-8)
+ ///
+ /// SfTimePicker timePicker = new SfTimePicker();
+ /// timePicker.SecondInterval = 2;
+ ///
+ ///
+ public int SecondInterval
+ {
+ get { return (int)GetValue(SecondIntervalProperty); }
+ set { SetValue(SecondIntervalProperty, value); }
+ }
+
+ ///
+ /// Gets or sets the picker date format in SfTimePicker.
+ ///
+ /// The default value of is .
+ ///
+ /// The following examples demonstrate how to set the time format in SfTimePicker.
+ /// # [XAML](#tab/tabid-9)
+ ///
+ ///
+ /// ]]>
+ ///
+ /// # [C#](#tab/tabid-10)
+ ///
+ /// SfTimePicker timePicker = new SfTimePicker();
+ /// timePicker.Format = PickerTimeFormat.HH_mm;
+ ///
+ ///
+ public PickerTimeFormat Format
+ {
+ get { return (PickerTimeFormat)GetValue(FormatProperty); }
+ set { SetValue(FormatProperty, value); }
+ }
+
+ ///
+ /// Gets or sets the selection changed command in SfTimePicker.
+ ///
+ /// The default value of is null.
+ ///
+ /// The following example demonstrates how to set the selection changed command in SfTimePicker.
+ /// # [XAML](#tab/tabid-11)
+ ///
+ ///
+ ///
+ ///
+ ///
+ /// ]]>
+ /// # [C#](#tab/tabid-12)
+ ///
+ ///
+ public ICommand SelectionChangedCommand
+ {
+ get { return (ICommand)GetValue(SelectionChangedCommandProperty); }
+ set { SetValue(SelectionChangedCommandProperty, value); }
+ }
+
+ ///
+ /// Gets or sets the minimum time in SfTimePicker.
+ ///
+ /// The default value of is .
+ ///
+ /// The following examples demonstrate how to set the minimum time in SfTimePicker.
+ /// # [XAML](#tab/tabid-13)
+ ///
+ ///
+ /// ]]>
+ ///
+ /// # [C#](#tab/tabid-14)
+ ///
+ /// SfTimePicker timePicker = new SfTimePicker();
+ /// timePicker.MinimumTime = new TimeSpan(9, 0, 0);
+ ///
+ ///
+ public TimeSpan MinimumTime
+ {
+ get { return (TimeSpan)GetValue(MinimumTimeProperty); }
+ set { SetValue(MinimumTimeProperty, value); }
+ }
+
+ ///
+ /// Gets or sets the maximum time in SfTimePicker.
+ ///
+ /// The default value of is "TimeSpan(23, 59, 59)".
+ ///
+ /// The following examples demonstrate how to set the maximum time in SfTimePicker.
+ /// # [XAML](#tab/tabid-15)
+ ///
+ ///
+ /// ]]>
+ ///
+ /// # [C#](#tab/tabid-16)
+ ///
+ /// SfTimePicker timePicker = new SfTimePicker();
+ /// timePicker.MaximumTime = new TimeSpan(17, 0, 0);
+ ///
+ ///
+ public TimeSpan MaximumTime
+ {
+ get { return (TimeSpan)GetValue(MaximumTimeProperty); }
+ set { SetValue(MaximumTimeProperty, value); }
+ }
+
+ ///
+ /// Gets or sets the BlackoutTimes in SfTimePicker.
+ ///
+ /// The selection view will not be applicable when setting blackout times.
+ ///
+ /// The following examples demonstrate how to set the blackout times in SfTimePicker.
+ /// # [XAML](#tab/tabid-17)
+ ///
+ ///
+ ///
+ /// 12:28:00
+ /// 12:26:00
+ /// 12:24:00
+ /// 12:22:00
+ /// 12:37:00
+ /// 12:35:00
+ /// 12:33:00
+ /// 12:32:00
+ ///
+ ///
+ /// ]]>
+ ///
+ /// # [C#](#tab/tabid-18)
+ ///
+ /// SfTimePicker picker = new SfTimePicker();
+ /// picker.BlackoutTimes.Add(new TimeSpan(12, 28, 0));
+ /// picker.BlackoutTimes.Add(new TimeSpan(12, 26, 0));
+ /// picker.BlackoutTimes.Add(new TimeSpan(12, 24, 0));
+ /// picker.BlackoutTimes.Add(new TimeSpan(12, 22, 0));
+ /// picker.BlackoutTimes.Add(new TimeSpan(12, 37, 0));
+ /// picker.BlackoutTimes.Add(new TimeSpan(12, 35, 0));
+ /// picker.BlackoutTimes.Add(new TimeSpan(12, 33, 0));
+ /// picker.BlackoutTimes.Add(new TimeSpan(12, 32, 0));
+ ///
+ ///
+ public ObservableCollection BlackoutTimes
+ {
+ get { return (ObservableCollection)GetValue(BlackoutTimesProperty); }
+ set { SetValue(BlackoutTimesProperty, value); }
+ }
+
+ #endregion
+
+ #region Internal Properties
+
+ ///
+ /// Gets or sets the background color of the time picker.
+ ///
+ internal Color TimePickerBackground
+ {
+ get { return (Color)GetValue(TimePickerBackgroundProperty); }
+ set { SetValue(TimePickerBackgroundProperty, value); }
+ }
+
+ ///
+ /// Gets or sets the background of the header view in time picker.
+ ///
+ internal Brush HeaderBackground
+ {
+ get { return (Brush)GetValue(HeaderBackgroundProperty); }
+ set { SetValue(HeaderBackgroundProperty, value); }
+ }
+
+ ///
+ /// Gets or sets the background of the footer view in SfTimePicker.
+ ///
+ internal Brush FooterBackground
+ {
+ get { return (Brush)GetValue(FooterBackgroundProperty); }
+ set { SetValue(FooterBackgroundProperty, value); }
+ }
+
+ ///
+ /// Gets or sets the background of the selection view in SfTimePicker.
+ ///
+ internal Brush SelectionBackground
+ {
+ get { return (Brush)GetValue(SelectionBackgroundProperty); }
+ set { SetValue(SelectionBackgroundProperty, value); }
+ }
+
+ ///
+ /// Gets or sets the stroke color of the selection view in SfTimePicker.
+ ///
+ internal Color SelectionStrokeColor
+ {
+ get { return (Color)GetValue(SelectionStrokeColorProperty); }
+ set { SetValue(SelectionStrokeColorProperty, value); }
+ }
+
+ ///
+ /// Gets or sets the corner radius of the selection view in SfTimePicker.
+ ///
+ internal CornerRadius SelectionCornerRadius
+ {
+ get { return (CornerRadius)GetValue(SelectionCornerRadiusProperty); }
+ set { SetValue(SelectionCornerRadiusProperty, value); }
+ }
+
+ ///
+ /// Gets or sets the color of the header separator line in time picker.
+ ///
+ internal Color HeaderDividerColor
+ {
+ get { return (Color)GetValue(HeaderDividerColorProperty); }
+ set { SetValue(HeaderDividerColorProperty, value); }
+ }
+
+ ///
+ /// Gets or sets the background of the footer separator line background in SfTimePicker.
+ ///
+ internal Color FooterDividerColor
+ {
+ get { return (Color)GetValue(FooterDividerColorProperty); }
+ set { SetValue(FooterDividerColorProperty, value); }
+ }
+
+ ///
+ /// Gets or sets the header text color of the text style.
+ ///
+ internal Color HeaderTextColor
+ {
+ get { return (Color)GetValue(HeaderTextColorProperty); }
+ set { SetValue(HeaderTextColorProperty, value); }
+ }
+
+ ///
+ /// Gets or sets the header font size of the text style.
+ ///
+ internal double HeaderFontSize
+ {
+ get { return (double)GetValue(HeaderFontSizeProperty); }
+ set { SetValue(HeaderFontSizeProperty, value); }
+ }
+
+ ///
+ /// Gets or sets the footer text color of the text style.
+ ///
+ internal Color FooterTextColor
+ {
+ get { return (Color)GetValue(FooterTextColorProperty); }
+ set { SetValue(FooterTextColorProperty, value); }
+ }
+
+ ///
+ /// Gets or sets the footer font size of the text style.
+ ///
+ internal double FooterFontSize
+ {
+ get { return (double)GetValue(FooterFontSizeProperty); }
+ set { SetValue(FooterFontSizeProperty, value); }
+ }
+
+ ///
+ /// Gets or sets the selection text color of the text style.
+ ///
+ ///
+ /// This color applicable for default text display mode.
+ ///
+ internal Color SelectedTextColor
+ {
+ get { return (Color)GetValue(SelectedTextColorProperty); }
+ set { SetValue(SelectedTextColorProperty, value); }
+ }
+
+ ///
+ /// Gets or sets the selection text color of the text style.
+ ///
+ ///
+ /// This color is used for Fade, Shrink and FadeAndShrink mode.
+ ///
+ internal Color SelectionTextColor
+ {
+ get { return (Color)GetValue(SelectionTextColorProperty); }
+ set { SetValue(SelectionTextColorProperty, value); }
+ }
+
+ ///
+ /// Gets or sets the selection font size of the text style.
+ ///
+ internal double SelectedFontSize
+ {
+ get { return (double)GetValue(SelectedFontSizeProperty); }
+ set { SetValue(SelectedFontSizeProperty, value); }
+ }
+
+ ///
+ /// Gets or sets the normal text color of the text style.
+ ///
+ internal Color NormalTextColor
+ {
+ get { return (Color)GetValue(NormalTextColorProperty); }
+ set { SetValue(NormalTextColorProperty, value); }
+ }
+
+ ///
+ /// Gets or sets the normal font size of the text style.
+ ///
+ internal double NormalFontSize
+ {
+ get { return (double)GetValue(NormalFontSizeProperty); }
+ set { SetValue(NormalFontSizeProperty, value); }
+ }
+
+ ///
+ /// Gets or sets the disabled text color of the text style.
+ ///
+ internal Color DisabledTextColor
+ {
+ get { return (Color)GetValue(DisabledTextColorProperty); }
+ set { SetValue(DisabledTextColorProperty, value); }
+ }
+
+ #endregion
+
+ #region Internal Methods
+
+ ///
+ /// Method to reset the picker columns based on the updated format.
+ ///
+ internal void UpdateFormat()
+ {
+ _hourColumn = new PickerColumn();
+ _minuteColumn = new PickerColumn();
+ _secondColumn = new PickerColumn();
+ _meridiemColumn = new PickerColumn();
+ GeneratePickerColumns();
+ BaseColumns = _columns;
+ }
+
+ #endregion
+
+ #region Private Methods
+
+ ///
+ /// Method trigged whenever the base panel selection is changed.
+ ///
+ /// Base picker instance value.
+ /// Selection changed event arguments.
+ void OnPickerSelectionIndexChanged(object? sender, PickerSelectionChangedEventArgs e)
+ {
+ string hourFormat;
+ List formatStringOrder = TimePickerHelper.GetFormatStringOrder(out hourFormat, Format);
+ int changedColumnValue = formatStringOrder[e.ColumnIndex];
+ TimeSpan maxTime = TimePickerHelper.GetValidMaxTime(MinimumTime, MaximumTime);
+ TimeSpan previousTime = new TimeSpan(_previousSelectedDateTime.Hour, _previousSelectedDateTime.Minute, _previousSelectedDateTime.Second);
+ TimeSpan currentTime = SelectedTime ?? previousTime;
+ TimeSpan? previousSelectedTime = TimePickerHelper.GetValidSelectedTime(currentTime, MinimumTime, maxTime);
+ DateTime minimumTime = Convert.ToDateTime(MinimumTime.ToString());
+ DateTime maximumTime = Convert.ToDateTime(maxTime.ToString());
+ DateTime selectedTime = Convert.ToDateTime(previousSelectedTime.ToString());
+ if (previousSelectedTime == null)
+ {
+ return;
+ }
+
+ switch (changedColumnValue)
+ {
+ case 0:
+ {
+ UpdateHourColumn(e, hourFormat, previousSelectedTime, selectedTime, minimumTime, maximumTime);
+ }
+
+ break;
+ case 1:
+ {
+ int minutes = 0;
+ if (_minuteColumn.ItemsSource != null && _minuteColumn.ItemsSource is ObservableCollection minuteCollection && minuteCollection.Count > e.NewValue)
+ {
+ //// Get the minute value based on the selected index changes value.
+ minutes = int.Parse(minuteCollection[e.NewValue]);
+ }
+
+ SetSelectedTime(new TimeSpan(previousSelectedTime.Value.Hours, minutes, previousSelectedTime.Value.Seconds));
+ }
+
+ break;
+ case 2:
+ {
+ int seconds = 0;
+ if (_secondColumn.ItemsSource != null && _secondColumn.ItemsSource is ObservableCollection secondCollection && secondCollection.Count > e.NewValue)
+ {
+ //// Get the seconds value based on the selected index changes value.
+ seconds = int.Parse(secondCollection[e.NewValue]);
+ }
+
+ SetSelectedTime(new TimeSpan(previousSelectedTime.Value.Hours, previousSelectedTime.Value.Minutes, seconds));
+ }
+
+ break;
+ case 3:
+ {
+ UpdateMeridiemColumn(e, hourFormat, previousSelectedTime, selectedTime, minimumTime, maximumTime);
+ }
+
+ break;
+ }
+ }
+
+ ///
+ /// Method to update the hour column based on the selected time value.
+ ///
+ /// Selection changed event arguments.
+ /// The hour format.
+ /// The previous selected time.
+ /// The selected time.
+ /// The minimum time.
+ /// The maximum time.
+ void UpdateHourColumn(PickerSelectionChangedEventArgs e, string hourFormat, TimeSpan? previousSelectedTime, DateTime selectedTime, DateTime minimumTime, DateTime maximumTime)
+ {
+ if (previousSelectedTime == null)
+ {
+ return;
+ }
+
+ int hour = 0;
+ if (_hourColumn.ItemsSource != null && _hourColumn.ItemsSource is ObservableCollection hourCollection && hourCollection.Count > e.NewValue)
+ {
+ //// Get the hour value based on the selected index changes value.
+ hour = int.Parse(hourCollection[e.NewValue]);
+ }
+
+ if (hourFormat == "h" || hourFormat == "hh")
+ {
+ hour = hour == 12 ? 0 : hour;
+ if (previousSelectedTime.Value.Hours >= 12)
+ {
+ hour += 12;
+ }
+ }
+
+ ObservableCollection minutes = TimePickerHelper.GetMinutes(MinuteInterval, hour, selectedTime, minimumTime, maximumTime);
+ ObservableCollection previousMinutes = _minuteColumn.ItemsSource is ObservableCollection previousMinuteCollection ? previousMinuteCollection : new ObservableCollection();
+ if (!PickerHelper.IsCollectionEquals(minutes, previousMinutes))
+ {
+ _minuteColumn.ItemsSource = minutes;
+ }
+
+ int minuteIndex = TimePickerHelper.GetMinuteOrSecondIndex(minutes, previousSelectedTime.Value.Minutes);
+ //// Get the minute value based on the selected index changes value.
+ int minute = int.Parse(minutes[minuteIndex]);
+
+ SetSelectedTime(new TimeSpan(hour, minute, previousSelectedTime.Value.Seconds));
+ }
+
+ ///
+ /// Method to update the meridiem column based on the selected time value.
+ ///
+ /// Selection changed event arguments.
+ /// The hour format.
+ /// The previous selected time.
+ /// The selected time.
+ /// The minimum time.
+ /// The maximum time.
+ void UpdateMeridiemColumn(PickerSelectionChangedEventArgs e, string hourFormat, TimeSpan? previousSelectedTime, DateTime selectedTime, DateTime minimumTime, DateTime maximumTime)
+ {
+ if (previousSelectedTime == null)
+ {
+ return;
+ }
+
+ ObservableCollection meridiemCollection = new ObservableCollection();
+ if (_meridiemColumn.ItemsSource != null && _meridiemColumn.ItemsSource is ObservableCollection meridiems)
+ {
+ meridiemCollection = meridiems;
+ }
+
+ if (meridiemCollection.Count <= e.NewValue)
+ {
+ return;
+ }
+
+ bool isAMSelected = TimePickerHelper.IsAMText(meridiemCollection, e.NewValue);
+ int neededHour = isAMSelected ? 0 : 12;
+
+ TimeSpan selectedDate = new TimeSpan((previousSelectedTime.Value.Hours % 12) + neededHour, previousSelectedTime.Value.Minutes, previousSelectedTime.Value.Seconds);
+ DateTime selectedDates = Convert.ToDateTime(selectedDate.ToString());
+
+ ObservableCollection hours = TimePickerHelper.GetHours(hourFormat, HourInterval, selectedDates, minimumTime, maximumTime);
+ ObservableCollection previousHour = _hourColumn.ItemsSource is ObservableCollection previousHourCollection ? previousHourCollection : new ObservableCollection();
+ if (!PickerHelper.IsCollectionEquals(hours, previousHour))
+ {
+ _hourColumn.ItemsSource = hours;
+ }
+
+ int? hourIndex = TimePickerHelper.GetHourIndex(hourFormat, hours, previousSelectedTime.Value.Hours);
+ if (hourIndex.HasValue)
+ {
+ int hour = int.Parse(hours[hourIndex.Value]);
+ hour = (hour % 12) + neededHour;
+
+ ObservableCollection minutes = TimePickerHelper.GetMinutes(MinuteInterval, hour, selectedTime, minimumTime, maximumTime);
+ ObservableCollection previousMinutes = _minuteColumn.ItemsSource is ObservableCollection previousMinuteCollection ? previousMinuteCollection : new ObservableCollection();
+ if (!PickerHelper.IsCollectionEquals(minutes, previousMinutes))
+ {
+ _minuteColumn.ItemsSource = minutes;
+ }
+
+ int minuteIndex = TimePickerHelper.GetMinuteOrSecondIndex(minutes, previousSelectedTime.Value.Minutes);
+ //// Get the minute value based on the selected index changes value.
+ int minute = int.Parse(minutes[minuteIndex]);
+
+ SetSelectedTime(new TimeSpan(hour, minute, previousSelectedTime.Value.Seconds));
+ }
+ }
+
+ ///
+ /// Method to set the Selected Time value.
+ ///
+ /// The selected time.
+ void SetSelectedTime(TimeSpan selectedTime)
+ {
+ if (!TimePickerHelper.IsSameTimeSpan(selectedTime, SelectedTime))
+ {
+ SelectedTime = selectedTime;
+ }
+ }
+
+ ///
+ /// Method invokes on column header property changed.
+ ///
+ /// Column header view value.
+ /// Property changed arguments.
+ void OnColumnHeaderPropertyChanged(object? sender, PickerPropertyChangedEventArgs e)
+ {
+ if (e.PropertyName == nameof(TimePickerColumnHeaderView.Background))
+ {
+ BaseColumnHeaderView.Background = ColumnHeaderView.Background;
+ }
+ else if (e.PropertyName == nameof(TimePickerColumnHeaderView.Height))
+ {
+ BaseColumnHeaderView.Height = ColumnHeaderView.Height;
+ }
+ else if (e.PropertyName == nameof(TimePickerColumnHeaderView.DividerColor))
+ {
+ BaseColumnHeaderView.DividerColor = ColumnHeaderView.DividerColor;
+ }
+ else if (e.PropertyName == nameof(TimePickerColumnHeaderView.TextStyle))
+ {
+ SetInheritedBindingContext(ColumnHeaderView.TextStyle, BindingContext);
+ BaseColumnHeaderView.TextStyle = ColumnHeaderView.TextStyle;
+ }
+ else if (e.PropertyName == nameof(TimePickerColumnHeaderView.HourHeaderText))
+ {
+ _hourColumn.HeaderText = SfPickerResources.GetLocalizedString(ColumnHeaderView.HourHeaderText);
+ }
+ else if (e.PropertyName == nameof(TimePickerColumnHeaderView.MinuteHeaderText))
+ {
+ _minuteColumn.HeaderText = SfPickerResources.GetLocalizedString(ColumnHeaderView.MinuteHeaderText);
+ }
+ else if (e.PropertyName == nameof(TimePickerColumnHeaderView.SecondHeaderText))
+ {
+ _secondColumn.HeaderText = SfPickerResources.GetLocalizedString(ColumnHeaderView.SecondHeaderText);
+ }
+ else if (e.PropertyName == nameof(TimePickerColumnHeaderView.MeridiemHeaderText))
+ {
+ _meridiemColumn.HeaderText = SfPickerResources.GetLocalizedString(ColumnHeaderView.MeridiemHeaderText);
+ }
+ }
+
+ ///
+ /// Method to generate the hour, minute, second and meridiem columns based on the selected time value.
+ ///
+ void GeneratePickerColumns()
+ {
+ string hourFormat;
+ List formatStringOrder = TimePickerHelper.GetFormatStringOrder(out hourFormat, Format);
+ TimeSpan maxTime = TimePickerHelper.GetValidMaxTime(MinimumTime, MaximumTime);
+ TimeSpan? validSelectedTime = TimePickerHelper.GetValidSelectedTime(SelectedTime, MinimumTime, maxTime);
+ DateTime currentSelectedTime = validSelectedTime != null ? Convert.ToDateTime(validSelectedTime.ToString()) : DateTime.Now;
+ ObservableCollection pickerColumns = new ObservableCollection();
+ foreach (int index in formatStringOrder)
+ {
+ switch (index)
+ {
+ case 0:
+ _hourColumn = GenerateHourColumn(hourFormat, validSelectedTime, currentSelectedTime);
+ _hourColumn.SelectedItem = SelectedTime != null ? PickerHelper.GetSelectedItemDefaultValue(_hourColumn) : null;
+ pickerColumns.Add(_hourColumn);
+ break;
+ case 1:
+ _minuteColumn = GenerateMinuteColumn(validSelectedTime, currentSelectedTime);
+ _minuteColumn.SelectedItem = SelectedTime != null ? PickerHelper.GetSelectedItemDefaultValue(_minuteColumn) : null;
+ pickerColumns.Add(_minuteColumn);
+ break;
+ case 2:
+ _secondColumn = GenerateSecondColumn();
+ _secondColumn.SelectedItem = SelectedTime != null ? PickerHelper.GetSelectedItemDefaultValue(_secondColumn) : null;
+ pickerColumns.Add(_secondColumn);
+ break;
+ case 3:
+ _meridiemColumn = GenerateMeridiemColumn(validSelectedTime, currentSelectedTime);
+ _meridiemColumn.SelectedItem = SelectedTime != null ? PickerHelper.GetSelectedItemDefaultValue(_meridiemColumn) : null;
+ pickerColumns.Add(_meridiemColumn);
+ break;
+ }
+ }
+
+ _columns = pickerColumns;
+ }
+
+ ///
+ /// Method to generate the hour column with items source and selected index based on format.
+ ///
+ /// The hour format.
+ /// The selected time.
+ /// The current date.
+ /// Returns hour column details.
+ PickerColumn GenerateHourColumn(string format, TimeSpan? selectedTime, DateTime? selectedDate)
+ {
+ TimeSpan maxTime = TimePickerHelper.GetValidMaxTime(MinimumTime, MaximumTime);
+ DateTime minimumTime = Convert.ToDateTime(MinimumTime.ToString());
+ DateTime maximumTime = Convert.ToDateTime(maxTime.ToString());
+
+ ObservableCollection hours = TimePickerHelper.GetHours(format, HourInterval, selectedDate, minimumTime, maximumTime);
+
+ int? hourIndex = selectedTime != null ? TimePickerHelper.GetHourIndex(format, hours, selectedTime.Value.Hours) : _previousSelectedDateTime.Hour;
+
+ return new PickerColumn()
+ {
+ ItemsSource = hours,
+ SelectedIndex = hourIndex != null ? (int)hourIndex : _previousSelectedDateTime.Hour,
+ HeaderText = SfPickerResources.GetLocalizedString(ColumnHeaderView.HourHeaderText),
+ };
+ }
+
+ ///
+ /// Method to generate the minute column with items source and selected index based on format.
+ ///
+ /// The selected time.
+ /// The current date.
+ /// Returns minute column details.
+ PickerColumn GenerateMinuteColumn(TimeSpan? selectedTime, DateTime? selectedDate)
+ {
+ TimeSpan maxTime = TimePickerHelper.GetValidMaxTime(MinimumTime, MaximumTime);
+ DateTime minimumTime = Convert.ToDateTime(MinimumTime.ToString());
+ DateTime maximumTime = Convert.ToDateTime(maxTime.ToString());
+ int selectedHour = selectedTime?.Hours ?? _previousSelectedDateTime.Hour;
+ int selectedMinute = selectedTime?.Minutes ?? _previousSelectedDateTime.Minute;
+
+ ObservableCollection minutes = TimePickerHelper.GetMinutes(MinuteInterval, selectedHour, selectedDate, minimumTime, maximumTime);
+ return new PickerColumn
+ {
+ ItemsSource = minutes,
+ SelectedIndex = TimePickerHelper.GetMinuteOrSecondIndex(minutes, selectedMinute),
+ HeaderText = SfPickerResources.GetLocalizedString(ColumnHeaderView.MinuteHeaderText),
+ };
+ }
+
+ ///
+ /// Method to generate the second column with items source and selected index based on format.
+ ///
+ /// Returns second column details.
+ PickerColumn GenerateSecondColumn()
+ {
+ int selectedHour = SelectedTime?.Hours ?? _previousSelectedDateTime.Hour;
+ int selectedMinute = SelectedTime?.Minutes ?? _previousSelectedDateTime.Minute;
+ int selectedSecond = SelectedTime?.Seconds ?? _previousSelectedDateTime.Second;
+
+ ObservableCollection seconds = TimePickerHelper.GetSeconds(SecondInterval, selectedHour, selectedMinute, null, null, null);
+ return new PickerColumn()
+ {
+ ItemsSource = seconds,
+ SelectedIndex = TimePickerHelper.GetMinuteOrSecondIndex(seconds, selectedSecond),
+ HeaderText = SfPickerResources.GetLocalizedString(ColumnHeaderView.SecondHeaderText),
+ };
+ }
+
+ ///
+ /// Method to generate the meridiem column with items source and selected index based on format.
+ ///
+ /// The selected time.
+ /// The current date.
+ /// Returns meridiem column details.
+ PickerColumn GenerateMeridiemColumn(TimeSpan? selectedTime, DateTime? selectedDate)
+ {
+ TimeSpan maxTime = TimePickerHelper.GetValidMaxTime(MinimumTime, MaximumTime);
+ DateTime minimumTime = Convert.ToDateTime(MinimumTime.ToString());
+ DateTime maximumTime = Convert.ToDateTime(maxTime.ToString());
+ int selectedHour = selectedTime?.Hours ?? _previousSelectedDateTime.Hour;
+ ObservableCollection meridiems = TimePickerHelper.GetMeridiem(minimumTime, maximumTime, selectedDate);
+ return new PickerColumn()
+ {
+ ItemsSource = meridiems,
+ SelectedIndex = selectedHour >= 12 ? 1 : 0,
+ HeaderText = SfPickerResources.GetLocalizedString(ColumnHeaderView.MeridiemHeaderText),
+ };
+ }
+
+ ///
+ /// Method to update the selected index value for all the picker column based on the selected time value.
+ ///
+ void UpdateSelectedIndex()
+ {
+ if (SelectedTime == null)
+ {
+ return;
+ }
+
+ string hourFormat;
+ TimePickerHelper.GetFormatStringOrder(out hourFormat, Format);
+ if (_hourColumn.ItemsSource != null && _hourColumn.ItemsSource is ObservableCollection hourCollection && hourCollection.Count > 0)
+ {
+ int? index = TimePickerHelper.GetHourIndex(hourFormat, hourCollection, SelectedTime.Value.Hours);
+ if (index == null)
+ {
+ return;
+ }
+
+ if (_hourColumn.SelectedIndex != index)
+ {
+ _hourColumn.SelectedIndex = (int)index;
+ }
+ }
+
+ if (_minuteColumn.ItemsSource != null && _minuteColumn.ItemsSource is ObservableCollection minuteCollection && minuteCollection.Count > 0)
+ {
+ int index = TimePickerHelper.GetMinuteOrSecondIndex(minuteCollection, SelectedTime.Value.Minutes);
+ if (_minuteColumn.SelectedIndex != index)
+ {
+ _minuteColumn.SelectedIndex = index;
+ }
+ }
+
+ if (_secondColumn.ItemsSource != null && _secondColumn.ItemsSource is ObservableCollection secondCollection && secondCollection.Count > 0)
+ {
+ int index = TimePickerHelper.GetMinuteOrSecondIndex(secondCollection, SelectedTime.Value.Seconds);
+ if (_secondColumn.SelectedIndex != index)
+ {
+ _secondColumn.SelectedIndex = index;
+ }
+ }
+
+ if (_meridiemColumn.ItemsSource != null && _meridiemColumn.ItemsSource is ObservableCollection meridiemCollection && meridiemCollection.Count > 0)
+ {
+ int index = SelectedTime.Value.Hours >= 12 ? 1 : 0;
+ if (_meridiemColumn.SelectedIndex != index)
+ {
+ _meridiemColumn.SelectedIndex = index;
+ }
+ }
+ }
+
+ ///
+ /// Method to update the minimum and maximum time value for all the picker column based on the time value.
+ ///
+ void UpdateMinimumMaximumTime(object oldValue, object newValue)
+ {
+ TimeSpan? oldTime = (TimeSpan)oldValue;
+ TimeSpan? newTime = (TimeSpan)newValue;
+ TimeSpan maxTime = TimePickerHelper.GetValidMaxTime(MinimumTime, MaximumTime);
+ TimeSpan? validSelectedTime = TimePickerHelper.GetValidSelectedTime(SelectedTime, MinimumTime, maxTime);
+ DateTime minimumTime = Convert.ToDateTime(MinimumTime.ToString());
+ DateTime maximumTime = Convert.ToDateTime(maxTime.ToString());
+ DateTime currentSelectedTime = Convert.ToDateTime(validSelectedTime.ToString());
+
+ string hourFormat;
+ List formatStringOrder = TimePickerHelper.GetFormatStringOrder(out hourFormat, Format);
+ int index = formatStringOrder.IndexOf(0);
+ if (index != -1 && oldTime.Value.Hours != newTime.Value.Hours)
+ {
+ _hourColumn = GenerateHourColumn(hourFormat, SelectedTime, currentSelectedTime);
+ int hourIndex = index;
+ _columns[hourIndex] = _hourColumn;
+ }
+
+ index = formatStringOrder.IndexOf(1);
+ if (index != -1 && (currentSelectedTime.Hour == oldTime.Value.Hours || currentSelectedTime.Hour == newTime.Value.Hours))
+ {
+ ObservableCollection minutes = TimePickerHelper.GetMinutes(MinuteInterval, currentSelectedTime.Hour, currentSelectedTime, minimumTime, maximumTime);
+ ObservableCollection previousMinutes = _minuteColumn.ItemsSource is ObservableCollection previousMinuteCollection ? previousMinuteCollection : new ObservableCollection();
+ if (!PickerHelper.IsCollectionEquals(minutes, previousMinutes))
+ {
+ _minuteColumn = new PickerColumn()
+ {
+ ItemsSource = minutes,
+ SelectedIndex = TimePickerHelper.GetMinuteOrSecondIndex(minutes, currentSelectedTime.Minute),
+ HeaderText = SfPickerResources.GetLocalizedString(ColumnHeaderView.MinuteHeaderText),
+ };
+ _columns[index] = _minuteColumn;
+ }
+ }
+
+ index = formatStringOrder.IndexOf(2);
+ if (index != -1 && ((currentSelectedTime.Hour == oldTime.Value.Hours && currentSelectedTime.Minute == oldTime.Value.Minutes) || (currentSelectedTime.Hour == newTime.Value.Hours && currentSelectedTime.Minute == newTime.Value.Minutes)))
+ {
+ ObservableCollection seconds = TimePickerHelper.GetSeconds(SecondInterval, currentSelectedTime.Hour, currentSelectedTime.Minute, null, null, null);
+ ObservableCollection previousSeconds = _secondColumn.ItemsSource is ObservableCollection previousSecondCollection ? previousSecondCollection : new ObservableCollection();
+ if (!PickerHelper.IsCollectionEquals(seconds, previousSeconds))
+ {
+ _secondColumn = new PickerColumn()
+ {
+ ItemsSource = seconds,
+ SelectedIndex = TimePickerHelper.GetMinuteOrSecondIndex(seconds, currentSelectedTime.Second),
+ HeaderText = SfPickerResources.GetLocalizedString(ColumnHeaderView.SecondHeaderText),
+ };
+ _columns[index] = _secondColumn;
+ }
+ }
+
+ index = formatStringOrder.IndexOf(3);
+ if (index != -1 && ((int)(currentSelectedTime.Hour / 12) == (int)(oldTime.Value.Hours / 12) || (int)(currentSelectedTime.Hour / 12) == (int)(newTime.Value.Hours / 12)))
+ {
+ ObservableCollection meridiems = TimePickerHelper.GetMeridiem(minimumTime, maximumTime, currentSelectedTime);
+ ObservableCollection previousCollection = _meridiemColumn.ItemsSource is ObservableCollection previousMeridiemCollection ? previousMeridiemCollection : new ObservableCollection();
+ if (!PickerHelper.IsCollectionEquals(meridiems, previousCollection))
+ {
+ _meridiemColumn = new PickerColumn()
+ {
+ ItemsSource = meridiems,
+ SelectedIndex = currentSelectedTime.Hour >= 12 ? meridiems.Count > 1 ? 1 : 0 : 0,
+ HeaderText = SfPickerResources.GetLocalizedString(ColumnHeaderView.MeridiemHeaderText),
+ };
+ _columns[index] = _meridiemColumn;
+ }
+ }
+ }
+
+ ///
+ /// Method trigged when the black out time collection gets changed.
+ ///
+ /// time picker instance
+ /// collection changed event arguments
+ void OnBlackoutTimes_CollectionChanged(object? sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
+ {
+ if (SelectedTime != null)
+ {
+ TimeSpan currentTime = SelectedTime.Value;
+ while (BlackoutTimes.Any(blackOutTime => TimePickerHelper.IsBlackoutTime(blackOutTime, currentTime)))
+ {
+ currentTime = currentTime.Add(TimeSpan.FromMinutes(1));
+ }
+
+ if (SelectedTime != currentTime)
+ {
+ SelectedTime = currentTime;
+ }
+ }
+ }
+
+ ///
+ /// Need to update the parent for the new value.
+ ///
+ /// The old value.
+ /// The new value.
+ void SetParent(Element? oldValue, Element? newValue)
+ {
+ if (oldValue != null)
+ {
+ oldValue.Parent = null;
+ }
+
+ if (newValue != null)
+ {
+ newValue.Parent = this;
+ }
+ }
+
+ ///
+ /// Method to initialize the theme and to set dynamic resources.
+ ///
+ void InitializeTheme()
+ {
+ ThemeElement.InitializeThemeResources(this, "SfTimePickerTheme");
+ SetDynamicResource(TimePickerBackgroundProperty, "SfTimePickerNormalBackground");
+
+ SetDynamicResource(HeaderBackgroundProperty, "SfTimePickerNormalHeaderBackground");
+ SetDynamicResource(HeaderDividerColorProperty, "SfTimePickerNormalHeaderDividerColor");
+ SetDynamicResource(HeaderTextColorProperty, "SfTimePickerNormalHeaderTextColor");
+ SetDynamicResource(HeaderFontSizeProperty, "SfTimePickerNormalHeaderFontSize");
+
+ SetDynamicResource(FooterBackgroundProperty, "SfTimePickerNormalFooterBackground");
+ SetDynamicResource(FooterDividerColorProperty, "SfTimePickerNormalFooterDividerColor");
+ SetDynamicResource(FooterTextColorProperty, "SfTimePickerNormalFooterTextColor");
+ SetDynamicResource(FooterFontSizeProperty, "SfTimePickerNormalFooterFontSize");
+
+ SetDynamicResource(SelectionBackgroundProperty, "SfTimePickerSelectionBackground");
+ SetDynamicResource(SelectionStrokeColorProperty, "SfTimePickerSelectionStroke");
+ SetDynamicResource(SelectionCornerRadiusProperty, "SfTimePickerSelectionCornerRadius");
+ SetDynamicResource(SelectedTextColorProperty, "SfTimePickerSelectedTextColor");
+ SetDynamicResource(SelectionTextColorProperty, "SfPickerSelectionTextColor");
+ SetDynamicResource(SelectedFontSizeProperty, "SfTimePickerSelectedFontSize");
+
+ SetDynamicResource(NormalTextColorProperty, "SfTimePickerNormalTextColor");
+ SetDynamicResource(NormalFontSizeProperty, "SfTimePickerNormalFontSize");
+
+ SetDynamicResource(DisabledTextColorProperty, "SfTimePickerDisabledTextColor");
+ }
+
+ #endregion
+
+ #region Override Methods
+
+ ///
+ /// Method to wire the events.
+ ///
+ protected override void Initialize()
+ {
+ base.Initialize();
+ BaseColumns = _columns;
+ BaseHeaderView = HeaderView;
+ if (ColumnHeaderView != null)
+ {
+ SetInheritedBindingContext(ColumnHeaderView, BindingContext);
+ ColumnHeaderView.PickerPropertyChanged += OnColumnHeaderPropertyChanged;
+ BaseColumnHeaderView = new PickerColumnHeaderView()
+ {
+ Background = ColumnHeaderView.Background,
+ DividerColor = ColumnHeaderView.DividerColor,
+ Height = ColumnHeaderView.Height,
+ TextStyle = ColumnHeaderView.TextStyle,
+ };
+ }
+ }
+
+ ///
+ /// Method triggers when the property binding context changed.
+ ///
+ protected override void OnBindingContextChanged()
+ {
+ base.OnBindingContextChanged();
+ if (HeaderView != null)
+ {
+ SetInheritedBindingContext(HeaderView, BindingContext);
+ if (HeaderView.TextStyle != null)
+ {
+ SetInheritedBindingContext(HeaderView.TextStyle, BindingContext);
+ }
+
+ if (HeaderView.SelectionTextStyle != null)
+ {
+ SetInheritedBindingContext(HeaderView.SelectionTextStyle, BindingContext);
+ }
+ }
+
+ if (ColumnHeaderView != null)
+ {
+ SetInheritedBindingContext(ColumnHeaderView, BindingContext);
+ if (ColumnHeaderView.TextStyle != null)
+ {
+ SetInheritedBindingContext(ColumnHeaderView.TextStyle, BindingContext);
+ }
+ }
+
+ if (FooterView != null)
+ {
+ SetInheritedBindingContext(FooterView, BindingContext);
+ if (FooterView.TextStyle != null)
+ {
+ SetInheritedBindingContext(FooterView.TextStyle, BindingContext);
+ }
+ }
+
+ if (SelectedTextStyle != null)
+ {
+ SetInheritedBindingContext(SelectedTextStyle, BindingContext);
+ }
+
+ if (TextStyle != null)
+ {
+ SetInheritedBindingContext(TextStyle, BindingContext);
+ }
+
+ if (SelectionView != null)
+ {
+ SetInheritedBindingContext(SelectionView, BindingContext);
+ }
+ }
+
+ ///
+ /// Method triggers when the time picker popup closed.
+ ///
+ /// The event arguments
+ protected override void OnPopupClosed(EventArgs e)
+ {
+ InvokeClosedEvent(this, e);
+ }
+
+ ///
+ /// Method triggers when the time picker popup closing.
+ ///
+ /// The event arguments
+ protected override void OnPopupClosing(CancelEventArgs e)
+ {
+ InvokeClosingEvent(this, e);
+ }
+
+ ///
+ /// Method triggers when the time picker popup opened.
+ ///
+ /// The event arguments
+ protected override void OnPopupOpened(EventArgs e)
+ {
+ InvokeOpenedEvent(this, e);
+ }
+
+ ///
+ /// Method triggers when the time picker ok button clicked.
+ ///
+ /// The event arguments
+ protected override void OnOkButtonClicked(EventArgs e)
+ {
+ InvokeOkButtonClickedEvent(this, e);
+ if (AcceptCommand != null && AcceptCommand.CanExecute(e))
+ {
+ AcceptCommand.Execute(e);
+ }
+ }
+
+ ///
+ /// Method triggers when the time picker cancel button clicked.
+ ///
+ /// The event arguments
+ protected override void OnCancelButtonClicked(EventArgs e)
+ {
+ InvokeCancelButtonClickedEvent(this, e);
+ if (DeclineCommand != null && DeclineCommand.CanExecute(e))
+ {
+ DeclineCommand.Execute(e);
+ }
+ }
+
+ #endregion
+
+ #region Property Changed Methods
+
+ ///
+ /// Method invokes on picker header view changed.
+ ///
+ /// The picker object.
+ /// Old value of the property.
+ /// New value of the property.
+ static void OnHeaderViewChanged(BindableObject bindable, object oldValue, object newValue)
+ {
+ SfTimePicker? picker = bindable as SfTimePicker;
+ if (picker == null)
+ {
+ return;
+ }
+
+ picker.HeaderView.Parent = picker;
+ picker.BaseHeaderView = picker.HeaderView;
+ if (bindable is SfTimePicker timePicker)
+ {
+ timePicker.SetParent(oldValue as Element, newValue as Element);
+ }
+ }
+
+ ///
+ /// Method invokes on picker column header view changed.
+ ///
+ /// The picker object.
+ /// Old value of the property.
+ /// New value of the property.
+ static void OnColumnHeaderViewChanged(BindableObject bindable, object oldValue, object newValue)
+ {
+ SfTimePicker? picker = bindable as SfTimePicker;
+ if (picker == null)
+ {
+ return;
+ }
+
+ if (oldValue is TimePickerColumnHeaderView oldStyle)
+ {
+ oldStyle.PickerPropertyChanged -= picker.OnColumnHeaderPropertyChanged;
+ oldStyle.BindingContext = null;
+ oldStyle.Parent = null;
+ }
+
+ if (newValue is TimePickerColumnHeaderView newStyle)
+ {
+ newStyle.Parent = picker;
+ SetInheritedBindingContext(newStyle, picker.BindingContext);
+ newStyle.PickerPropertyChanged += picker.OnColumnHeaderPropertyChanged;
+ picker.BaseColumnHeaderView = new PickerColumnHeaderView()
+ {
+ Background = newStyle.Background,
+ DividerColor = newStyle.DividerColor,
+ Height = newStyle.Height,
+ TextStyle = newStyle.TextStyle,
+ };
+
+ picker._hourColumn.HeaderText = SfPickerResources.GetLocalizedString(picker.ColumnHeaderView.HourHeaderText);
+ picker._minuteColumn.HeaderText = SfPickerResources.GetLocalizedString(picker.ColumnHeaderView.MinuteHeaderText);
+ picker._secondColumn.HeaderText = SfPickerResources.GetLocalizedString(picker.ColumnHeaderView.SecondHeaderText);
+ picker._meridiemColumn.HeaderText = SfPickerResources.GetLocalizedString(picker.ColumnHeaderView.MeridiemHeaderText);
+ }
+ }
+
+ ///
+ /// Invokes on selection time property changed.
+ ///
+ /// The SfTimePicker object.
+ /// Property old value.
+ /// Property new value.
+ static void OnSelectedTimePropertyChanged(BindableObject bindable, object oldValue, object newValue)
+ {
+ SfTimePicker? picker = bindable as SfTimePicker;
+ if (picker == null)
+ {
+ return;
+ }
+
+ TimeSpan? previousSelectedTime = null;
+ TimeSpan? currentSelectedTime = null;
+ if (oldValue is TimeSpan oldSelectedTime)
+ {
+ previousSelectedTime = oldSelectedTime;
+ //// Prevents Selection changed event from triggering if old value is black out time.
+ if (picker.BlackoutTimes.Any(blackOutTime => TimePickerHelper.IsBlackoutTime(blackOutTime, previousSelectedTime)))
+ {
+ PickerContainer? pickerContainer = picker.GetPickerContainerValue();
+ pickerContainer?.UpdateScrollViewDraw();
+ picker.UpdateSelectedIndex();
+ //// Skip the update and event call by checking if the time is blackout value and within current hour.
+ if (oldSelectedTime.Hours == picker._previousSelectedDateTime.Hour)
+ {
+ return;
+ }
+
+ previousSelectedTime = picker._previousSelectedDateTime.TimeOfDay;
+ }
+
+ picker._previousSelectedDateTime = DateTime.Today.Add(oldSelectedTime);
+ }
+
+ if (newValue is TimeSpan newSelectedTime)
+ {
+ //// Prevents Selection changed event from triggering if new value is black out time.
+ if (picker.BlackoutTimes.Any(blackOutTime => TimePickerHelper.IsBlackoutTime(blackOutTime, newSelectedTime)))
+ {
+ return;
+ }
+
+ currentSelectedTime = newSelectedTime;
+ }
+
+ //// Skip the update and event call while the same time updated with different time value.
+ if (TimePickerHelper.IsSameTimeSpan(previousSelectedTime, currentSelectedTime))
+ {
+ return;
+ }
+
+ if (newValue == null)
+ {
+ picker._hourColumn.SelectedItem = null;
+ picker._minuteColumn.SelectedItem = null;
+ picker._secondColumn.SelectedItem = null;
+ picker._meridiemColumn.SelectedItem = null;
+ picker.SelectionChanged?.Invoke(picker, new TimePickerSelectionChangedEventArgs() { OldValue = previousSelectedTime, NewValue = currentSelectedTime });
+ return;
+ }
+ else
+ {
+ picker._hourColumn.SelectedItem = PickerHelper.GetSelectedItemDefaultValue(picker._hourColumn);
+ picker._minuteColumn.SelectedItem = PickerHelper.GetSelectedItemDefaultValue(picker._minuteColumn);
+ picker._secondColumn.SelectedItem = PickerHelper.GetSelectedItemDefaultValue(picker._secondColumn);
+ picker._meridiemColumn.SelectedItem = PickerHelper.GetSelectedItemDefaultValue(picker._meridiemColumn);
+ PickerContainer? pickerContainer = picker.GetPickerContainerValue();
+ pickerContainer?.UpdateScrollViewDraw();
+ pickerContainer?.InvalidateDrawable();
+ }
+
+ var timePickerSelectionChangedEventArgs = new TimePickerSelectionChangedEventArgs() { OldValue = previousSelectedTime, NewValue = currentSelectedTime };
+ if (picker.SelectionChanged != null)
+ {
+ picker.SelectionChanged?.Invoke(picker, timePickerSelectionChangedEventArgs);
+ }
+
+ if (picker.SelectionChangedCommand != null && picker.SelectionChangedCommand.CanExecute(timePickerSelectionChangedEventArgs))
+ {
+ picker.SelectionChangedCommand.Execute(timePickerSelectionChangedEventArgs);
+ }
+
+ picker.UpdateSelectedIndex();
+ }
+
+ ///
+ /// Method invokes on format property changed.
+ ///
+ /// The SfTimePicker object.
+ /// Property old value.
+ /// Property new value.
+ static void OnFormatPropertyChanged(BindableObject bindable, object oldValue, object newValue)
+ {
+ SfTimePicker? picker = bindable as SfTimePicker;
+ if (picker == null)
+ {
+ return;
+ }
+
+ picker.UpdateFormat();
+ }
+
+ ///
+ /// Method invokes on hour interval property changed.
+ ///
+ /// The SfTimePicker object.
+ /// Property old value.
+ /// Property new value.
+ static void OnHourIntervalPropertyChanged(BindableObject bindable, object oldValue, object newValue)
+ {
+ SfTimePicker? picker = bindable as SfTimePicker;
+ if (picker == null)
+ {
+ return;
+ }
+
+ string hourFormat;
+ //// Get the format string order and check the index.
+ List formatStringOrder = TimePickerHelper.GetFormatStringOrder(out hourFormat, picker.Format);
+ int hourIndex = formatStringOrder.IndexOf(0);
+ if (hourIndex == -1)
+ {
+ return;
+ }
+
+ picker._hourColumn = picker.GenerateHourColumn(hourFormat, picker.SelectedTime, Convert.ToDateTime(picker.SelectedTime.ToString()));
+ //// Replace the hour column with hour interval.
+ picker._columns[hourIndex] = picker._hourColumn;
+ }
+
+ ///
+ /// Method invokes on minute interval property changed.
+ ///
+ /// The SfTimePicker object.
+ /// Property old value.
+ /// Property new value.
+ static void OnMinuteIntervalPropertyChanged(BindableObject bindable, object oldValue, object newValue)
+ {
+ SfTimePicker? picker = bindable as SfTimePicker;
+ if (picker == null)
+ {
+ return;
+ }
+
+ //// Get the format string order and check the index.
+ List formatStringOrder = TimePickerHelper.GetFormatStringOrder(out _, picker.Format);
+ int minuteIndex = formatStringOrder.IndexOf(1);
+ if (minuteIndex == -1)
+ {
+ return;
+ }
+
+ picker._minuteColumn = picker.GenerateMinuteColumn(picker.SelectedTime, Convert.ToDateTime(picker.SelectedTime.ToString()));
+ //// Replace the minute column with minute interval.
+ picker._columns[minuteIndex] = picker._minuteColumn;
+ }
+
+ ///
+ /// Method invokes on second interval property changed.
+ ///
+ /// The SfTimePicker object.
+ /// Property old value.
+ /// Property new value.
+ static void OnSecondIntervalPropertyChanged(BindableObject bindable, object oldValue, object newValue)
+ {
+ SfTimePicker? picker = bindable as SfTimePicker;
+ if (picker == null)
+ {
+ return;
+ }
+
+ //// Get the format string order and check the index.
+ List formatStringOrder = TimePickerHelper.GetFormatStringOrder(out _, picker.Format);
+ int secondIndex = formatStringOrder.IndexOf(2);
+ if (secondIndex == -1)
+ {
+ return;
+ }
+
+ picker._secondColumn = picker.GenerateSecondColumn();
+ //// Replace the second column with second interval.
+ picker._columns[secondIndex] = picker._secondColumn;
+ }
+
+ ///
+ /// Method to get the default selected time span value for SfTimePicker.
+ ///
+ /// Returns the default selected time.
+ static TimeSpan GetDefaultTimeSpan()
+ {
+ DateTime today = DateTime.Now;
+ return new TimeSpan(today.Hour, today.Minute, today.Second);
+ }
+
+ ///
+ /// Method invokes on minimum time property changed.
+ ///
+ /// The SfDatePicker object.
+ /// Property old value.
+ /// Property new value.
+ static void OnMinimumTimePropertyChanged(BindableObject bindable, object oldValue, object newValue)
+ {
+ SfTimePicker? timepicker = bindable as SfTimePicker;
+ if (timepicker == null)
+ {
+ return;
+ }
+
+ TimeSpan minTime = (TimeSpan)newValue;
+ if (minTime.Days != 0)
+ {
+ timepicker.MinimumTime = (TimeSpan)oldValue;
+ return;
+ }
+
+ timepicker.UpdateMinimumMaximumTime(oldValue, newValue);
+ timepicker.UpdateSelectedIndex();
+ }
+
+ ///
+ /// Method invokes on maximum time property changed.
+ ///
+ /// The SfDatePicker object.
+ /// Property old value.
+ /// Property new value.
+ static void OnMaximumTimePropertyChanged(BindableObject bindable, object oldValue, object newValue)
+ {
+ SfTimePicker? timepicker = bindable as SfTimePicker;
+ if (timepicker == null)
+ {
+ return;
+ }
+
+ TimeSpan maxTime = (TimeSpan)newValue;
+ if (maxTime.Days != 0)
+ {
+ timepicker.MaximumTime = (TimeSpan)oldValue;
+ return;
+ }
+
+ timepicker.UpdateMinimumMaximumTime(oldValue, newValue);
+ timepicker.UpdateSelectedIndex();
+ }
+
+ ///
+ /// Method invokes on blackout times property changed.
+ ///
+ /// The sftimepicker object.
+ /// Property old value.
+ /// Property new value.
+ static void OnBlackOutTimesPropertyChanged(BindableObject bindable, object oldValue, object newValue)
+ {
+ SfTimePicker? timepicker = bindable as SfTimePicker;
+ if (timepicker == null)
+ {
+ return;
+ }
+
+ //// Unwires collection changed from old and wires on new collection.
+ ((ObservableCollection)oldValue).CollectionChanged -= timepicker.OnBlackoutTimes_CollectionChanged;
+ ((ObservableCollection)newValue).CollectionChanged += timepicker.OnBlackoutTimes_CollectionChanged;
+
+ if (timepicker.SelectedTime != null)
+ {
+ TimeSpan currentTime = timepicker.SelectedTime.Value;
+ while (timepicker.BlackoutTimes.Any(blackOutTime => TimePickerHelper.IsBlackoutTime(blackOutTime, currentTime)))
+ {
+ currentTime = currentTime.Add(TimeSpan.FromMinutes(1));
+ }
+
+ if (timepicker.SelectedTime != currentTime)
+ {
+ timepicker.SelectedTime = currentTime;
+ }
+ }
+
+ //// Gets picker container value to update the view.
+ PickerContainer? pickerContainer = timepicker.GetPickerContainerValue();
+
+ pickerContainer?.UpdateScrollViewDraw();
+ pickerContainer?.UpdatePickerSelectionView();
+ }
+
+ #endregion
+
+ #region Internal Property Changed Methods
+
+ ///
+ /// called when property changed.
+ ///
+ /// The bindable object.
+ /// The old value.
+ /// The new value.
+ static void OnTimePickerBackgroundChanged(BindableObject bindable, object oldValue, object newValue)
+ {
+ SfTimePicker? picker = bindable as SfTimePicker;
+ if (picker == null)
+ {
+ return;
+ }
+
+ picker.BackgroundColor = picker.TimePickerBackground;
+ }
+
+ ///
+ /// Method invokes on the picker header background changed.
+ ///
+ /// The header settings object.
+ /// Property old value.
+ /// Property new value.
+ static void OnHeaderBackgroundChanged(BindableObject bindable, object oldValue, object newValue)
+ {
+ SfTimePicker? picker = bindable as SfTimePicker;
+ if (picker == null)
+ {
+ return;
+ }
+
+ picker.HeaderView.Background = picker.HeaderBackground;
+ }
+
+ ///
+ /// Method invokes on the picker footer background changed.
+ ///
+ /// The footer settings object.
+ /// Property old value.
+ /// Property new value.
+ static void OnFooterBackgroundChanged(BindableObject bindable, object oldValue, object newValue)
+ {
+ SfTimePicker? picker = bindable as SfTimePicker;
+ if (picker == null)
+ {
+ return;
+ }
+
+ picker.FooterView.Background = picker.FooterBackground;
+ }
+
+ ///
+ /// Method invokes on the picker selection background changed.
+ ///
+ /// The selection settings object.
+ /// Property old value.
+ /// Property new value.
+ static void OnSelectionBackgroundChanged(BindableObject bindable, object oldValue, object newValue)
+ {
+ SfTimePicker? picker = bindable as SfTimePicker;
+ if (picker == null)
+ {
+ return;
+ }
+
+ picker.SelectionView.Background = picker.SelectionBackground;
+ }
+
+ ///
+ /// Method invokes on the picker selection stroke color changed.
+ ///
+ /// The selection settings object.
+ /// Property old value.
+ /// Property new value.
+ static void OnSelectionStrokeColorChanged(BindableObject bindable, object oldValue, object newValue)
+ {
+ SfTimePicker? picker = bindable as SfTimePicker;
+ if (picker == null)
+ {
+ return;
+ }
+
+ picker.SelectionView.Stroke = picker.SelectionStrokeColor;
+ }
+
+ ///
+ /// Method invokes on the picker selection corner radius changed.
+ ///
+ /// The selection settings object.
+ /// Property old value.
+ /// Property new value.
+ static void OnSelectionCornerRadiusChanged(BindableObject bindable, object oldValue, object newValue)
+ {
+ SfTimePicker? picker = bindable as SfTimePicker;
+ if (picker == null)
+ {
+ return;
+ }
+
+ picker.SelectionView.CornerRadius = picker.SelectionCornerRadius;
+ }
+
+ ///
+ /// Method invokes on the picker header separator line background changed.
+ ///
+ /// The header settings object.
+ /// Property old value.
+ /// Property new value.
+ static void OnHeaderDividerColorChanged(BindableObject bindable, object oldValue, object newValue)
+ {
+ SfTimePicker? picker = bindable as SfTimePicker;
+ if (picker == null)
+ {
+ return;
+ }
+
+ picker.HeaderView.DividerColor = picker.HeaderDividerColor;
+ }
+
+ ///
+ /// Method invokes on the picker footer separator line background changed.
+ ///
+ /// The footer settings object.
+ /// Property old value.
+ /// Property new value.
+ static void OnFooterDividerColorChanged(BindableObject bindable, object oldValue, object newValue)
+ {
+ SfTimePicker? picker = bindable as SfTimePicker;
+ if (picker == null)
+ {
+ return;
+ }
+
+ picker.FooterView.DividerColor = picker.FooterDividerColor;
+ }
+
+ ///
+ /// Method invokes on the picker header text color changed.
+ ///
+ /// The header text style object.
+ /// Property old value.
+ /// Property new value.
+ static void OnHeaderTextColorChanged(BindableObject bindable, object oldValue, object newValue)
+ {
+ SfTimePicker? picker = bindable as SfTimePicker;
+ if (picker == null)
+ {
+ return;
+ }
+
+ picker.HeaderView.TextStyle.TextColor = picker.HeaderTextColor;
+ }
+
+ ///
+ /// Method invokes on the picker header font size changed.
+ ///
+ /// The header text style object.
+ /// Property old value.
+ /// Property new value.
+ static void OnHeaderFontSizeChanged(BindableObject bindable, object oldValue, object newValue)
+ {
+ SfTimePicker? picker = bindable as SfTimePicker;
+ if (picker == null)
+ {
+ return;
+ }
+
+ picker.HeaderView.TextStyle.FontSize = picker.HeaderFontSize;
+ }
+
+ ///
+ /// Method invokes on the picker footer text color changed.
+ ///
+ /// The footer text style object.
+ /// Property old value.
+ /// Property new value.
+ static void OnFooterTextColorChanged(BindableObject bindable, object oldValue, object newValue)
+ {
+ SfTimePicker? picker = bindable as SfTimePicker;
+ if (picker == null)
+ {
+ return;
+ }
+
+ picker.FooterView.TextStyle.TextColor = picker.FooterTextColor;
+ }
+
+ ///
+ /// Method invokes on the picker footer font size changed.
+ ///
+ /// The footer text style object.
+ /// Property old value.
+ /// Property new value.
+ static void OnFooterFontSizeChanged(BindableObject bindable, object oldValue, object newValue)
+ {
+ SfTimePicker? picker = bindable as SfTimePicker;
+ if (picker == null)
+ {
+ return;
+ }
+
+ picker.FooterView.TextStyle.FontSize = picker.FooterFontSize;
+ }
+
+ ///
+ /// Method invokes on the picker selection text color changed.
+ ///
+ /// The selection text style object.
+ /// Property old value.
+ /// Property new value.
+ static void OnSelectedTextColorChanged(BindableObject bindable, object oldValue, object newValue)
+ {
+ SfTimePicker? picker = bindable as SfTimePicker;
+ if (picker == null)
+ {
+ return;
+ }
+
+ picker.SelectedTextStyle.TextColor = picker.TextDisplayMode == PickerTextDisplayMode.Default ? picker.SelectedTextColor : picker.SelectionTextColor;
+ }
+
+ ///
+ /// Method invokes on the picker selection font size changed.
+ ///
+ /// The selection text style object.
+ /// Property old value.
+ /// Property new value.
+ static void OnSelectedFontSizeChanged(BindableObject bindable, object oldValue, object newValue)
+ {
+ SfTimePicker? picker = bindable as SfTimePicker;
+ if (picker == null)
+ {
+ return;
+ }
+
+ picker.SelectedTextStyle.FontSize = picker.SelectedFontSize;
+ }
+
+ ///
+ /// Method invokes on the picker normal text color changed.
+ ///
+ /// The text style object.
+ /// Property old value.
+ /// Property new value.
+ static void OnNormalTextColorChanged(BindableObject bindable, object oldValue, object newValue)
+ {
+ SfTimePicker? picker = bindable as SfTimePicker;
+ if (picker == null)
+ {
+ return;
+ }
+
+ picker.TextStyle.TextColor = picker.NormalTextColor;
+ }
+
+ ///
+ /// Method invokes on the picker normal font size changed.
+ ///
+ /// The text style object.
+ /// Property old value.
+ /// Property new value.
+ static void OnNormalFontSizeChanged(BindableObject bindable, object oldValue, object newValue)
+ {
+ SfTimePicker? picker = bindable as SfTimePicker;
+ if (picker == null)
+ {
+ return;
+ }
+
+ picker.TextStyle.FontSize = picker.NormalFontSize;
+ }
+
+ ///
+ /// Method invokes on the picker disabled text color changed.
+ ///
+ /// The text style object.
+ /// Property old value.
+ /// Property new value.
+ static void OnDisabledTextColorChanged(BindableObject bindable, object oldValue, object newValue)
+ {
+ SfTimePicker? picker = bindable as SfTimePicker;
+ if (picker == null)
+ {
+ return;
+ }
+
+ picker.DisabledTextStyle.TextColor = picker.DisabledTextColor;
+ }
+
+ #endregion
+
+ #region Interface Implementation
+
+ ///
+ /// This method is declared only in IParentThemeElement
+ /// and you need to implement this method only in main control.
+ ///
+ /// ResourceDictionary
+ ResourceDictionary IParentThemeElement.GetThemeDictionary()
+ {
+ return new SfTimePickerStyles();
+ }
+
+ ///
+ /// This method will be called when a theme dictionary
+ /// that contains the value for your control key is merged in application.
+ ///
+ /// The old value.
+ /// The new value.
+ void IThemeElement.OnCommonThemeChanged(string oldTheme, string newTheme)
+ {
+ }
+
+ ///
+ /// This method will be called when users merge a theme dictionary
+ /// that contains value for “SyncfusionTheme” dynamic resource key.
+ ///
+ /// Old theme.
+ /// New theme.
+ void IThemeElement.OnControlThemeChanged(string oldTheme, string newTheme)
+ {
+ }
+
+ #endregion
+
+ #region Events
+
+ ///
+ /// Occurs after the selected time is changed on SfTimePicker.
+ ///
+ public event EventHandler? SelectionChanged;
+
+ #endregion
+ }
+}
\ No newline at end of file
diff --git a/maui/src/Picker/Views/FooterLayout/FooterLayout.cs b/maui/src/Picker/Views/FooterLayout/FooterLayout.cs
new file mode 100644
index 00000000..11f93491
--- /dev/null
+++ b/maui/src/Picker/Views/FooterLayout/FooterLayout.cs
@@ -0,0 +1,524 @@
+using Syncfusion.Maui.Toolkit.Graphics.Internals;
+using Syncfusion.Maui.Toolkit.Themes;
+
+namespace Syncfusion.Maui.Toolkit.Picker
+{
+ ///
+ /// This represents a class that contains information about the picker footer layout.
+ ///
+ internal class FooterLayout : SfView, IThemeElement
+ {
+ #region Fields
+
+ ///
+ /// The separator line thickness for footer and picker layout.
+ ///
+ const float StrokeThickness = 1;
+
+ ///
+ /// Defines the padding value between the layout and the button left and right spacing.
+ ///
+ const double LayoutPadding = 10;
+
+ ///
+ /// The action buttons left and right padding.
+ ///
+ const double ButtonLeftAndRightPadding = 30;
+
+ ///
+ /// The buttons top and bottom padding.
+ ///
+ const double ButtonTopandBottomPadding = 20;
+
+ ///
+ /// The footer view info.
+ ///
+ readonly IFooterView _footerViewInfo;
+
+ ///
+ /// The ok button.
+ ///
+ SfIconButton? _confirmButton;
+
+ ///
+ /// The cancel button.
+ ///
+ SfIconButton? _cancelButton;
+
+ ///
+ /// The ok button view.
+ ///
+ SfIconView? _confirmButtonView;
+
+ ///
+ /// The cancel button view.
+ ///
+ SfIconView? _cancelButtonView;
+
+ #endregion
+
+ #region Constructor
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The footer view info.
+ internal FooterLayout(IFooterView footerViewInfo)
+ {
+ DrawingOrder = DrawingOrder.AboveContent;
+ _footerViewInfo = footerViewInfo;
+ AddOrRemoveFooterButtons();
+ ThemeElement.InitializeThemeResources(this, "SfPickerTheme");
+ }
+
+ #endregion
+
+ #region Internal Methods
+
+ ///
+ /// Method to add the confirm and cancel button to the footer layout.
+ ///
+ internal void AddOrRemoveFooterButtons()
+ {
+ if (_footerViewInfo.FooterView.ShowOkButton)
+ {
+ AddConfirmButton();
+ }
+ else
+ {
+ RemoveConfirmButton();
+ }
+
+ AddCancelButton();
+ }
+
+ ///
+ /// Method to remove the confirm and cancel button from the footer layout.
+ ///
+ internal void RemoveFooterButtons()
+ {
+ RemoveConfirmButton();
+ RemoveCancelButton();
+ }
+
+ ///
+ /// Method to update the ok and cancel button text style.
+ ///
+ internal void UpdateButtonTextStyle()
+ {
+ if (_cancelButtonView != null)
+ {
+ _cancelButtonView.UpdateStyle(_footerViewInfo.FooterView.TextStyle);
+ _cancelButtonView.HighlightTextColor = _footerViewInfo.FooterView.TextStyle.TextColor;
+ _cancelButtonView.TextStyle.FontAutoScalingEnabled = _footerViewInfo.FooterView.TextStyle.FontAutoScalingEnabled;
+ }
+
+ if (_confirmButtonView != null)
+ {
+ _confirmButtonView.UpdateStyle(_footerViewInfo.FooterView.TextStyle);
+ _confirmButtonView.HighlightTextColor = _footerViewInfo.FooterView.TextStyle.TextColor;
+ _confirmButtonView.TextStyle.FontAutoScalingEnabled = _footerViewInfo.FooterView.TextStyle.FontAutoScalingEnabled;
+ }
+ }
+
+ ///
+ /// Method to update the separator color.
+ ///
+ internal void UpdateSeparatorColor()
+ {
+ InvalidateDrawable();
+ }
+
+ ///
+ /// Method to update the confirm button text.
+ ///
+ internal void UpdateConfirmButtonText()
+ {
+ if (_confirmButtonView == null)
+ {
+ return;
+ }
+
+ _confirmButtonView.Text = SfPickerResources.GetLocalizedString(_footerViewInfo.FooterView.OkButtonText);
+ SemanticProperties.SetDescription(_confirmButton, _footerViewInfo.FooterView.OkButtonText);
+ //// While changing the confirm button text, the confirm button view is not updated. So, we have invalidated the drawable.
+ _confirmButtonView.InvalidateDrawable();
+ }
+
+ ///
+ /// Method to update the cancel button text.
+ ///
+ internal void UpdateCancelButtonText()
+ {
+ if (_cancelButtonView == null)
+ {
+ return;
+ }
+
+ _cancelButtonView.Text = SfPickerResources.GetLocalizedString(_footerViewInfo.FooterView.CancelButtonText);
+ SemanticProperties.SetDescription(_cancelButton, _footerViewInfo.FooterView.CancelButtonText);
+ //// While changing the cancel button text, the cancel button view is not updated. So, we have invalidated the drawable.
+ _cancelButtonView.InvalidateDrawable();
+ }
+
+ ///
+ /// Method to update the footer style.
+ ///
+ internal void UpdateFooterStyle()
+ {
+ AddOrRemoveFooterButtons();
+ UpdateConfirmButtonText();
+ UpdateCancelButtonText();
+ UpdateButtonTextStyle();
+ }
+
+ #endregion
+
+ #region Private Methods
+
+ ///
+ /// Method to add the confirm button to the footer layout.
+ ///
+ void AddConfirmButton()
+ {
+ if (_confirmButtonView != null)
+ {
+ return;
+ }
+
+ SemanticsNode confirmButtonNode = new SemanticsNode()
+ {
+ Id = 0,
+ Text = SfPickerResources.GetLocalizedString(_footerViewInfo.FooterView.OkButtonText),
+ IsTouchEnabled = true,
+ OnClick = OnConfirmButtonNodeClicked,
+ };
+
+ _confirmButtonView = new SfIconView(SfIcon.TodayButton, _footerViewInfo.FooterView.TextStyle, SfPickerResources.GetLocalizedString(_footerViewInfo.FooterView.OkButtonText), null, Colors.Transparent, isSelected: true, semanticsNode: confirmButtonNode, highlightTextColor: _footerViewInfo.FooterView.TextStyle.TextColor);
+ _confirmButtonView.DrawingOrder = DrawingOrder.AboveContent;
+ _confirmButton = new SfIconButton(_confirmButtonView, isHoveringOnReleased: false);
+ _confirmButton.AutomationId = $"{PickerHelper.GetParentName(_footerViewInfo.FooterView.Parent)} Ok";
+ _confirmButton.Clicked += ConfirmButtonClicked;
+ Children.Add(_confirmButton);
+ }
+
+ ///
+ /// Method to add the cancel button to the footer layout.
+ ///
+ void AddCancelButton()
+ {
+ if (_cancelButtonView != null)
+ {
+ return;
+ }
+
+ SemanticsNode cancelButtonNode = new SemanticsNode()
+ {
+ Id = 0,
+ Text = SfPickerResources.GetLocalizedString(_footerViewInfo.FooterView.CancelButtonText),
+ IsTouchEnabled = true,
+ OnClick = OnCancelButtonNodeClicked,
+ };
+
+ _cancelButtonView = new SfIconView(SfIcon.TodayButton, _footerViewInfo.FooterView.TextStyle, SfPickerResources.GetLocalizedString(_footerViewInfo.FooterView.CancelButtonText), null, Colors.Transparent, isSelected: true, semanticsNode: cancelButtonNode, highlightTextColor: _footerViewInfo.FooterView.TextStyle.TextColor);
+ _cancelButtonView.DrawingOrder = DrawingOrder.AboveContent;
+ _cancelButton = new SfIconButton(_cancelButtonView, isHoveringOnReleased: false);
+ _cancelButton.AutomationId = $"{PickerHelper.GetParentName(_footerViewInfo.FooterView.Parent)} Cancel";
+ _cancelButton.Clicked += CancelButtonClicked;
+ Children.Add(_cancelButton);
+ }
+
+ ///
+ /// Method to remove the confirm button from the footer layout.
+ ///
+ void RemoveConfirmButton()
+ {
+ if (_confirmButton == null)
+ {
+ return;
+ }
+
+ _confirmButton.Clicked -= ConfirmButtonClicked;
+ Remove(_confirmButton);
+ for (int i = _confirmButton.Children.Count - 1; i >= 0; i--)
+ {
+ _confirmButton.RemoveAt(i);
+ }
+
+ if (_confirmButton.Handler != null && _confirmButton.Handler.PlatformView != null)
+ {
+ _confirmButton.Handler.DisconnectHandler();
+ }
+
+ _confirmButton = null;
+ if (_confirmButtonView == null)
+ {
+ return;
+ }
+
+ if (_confirmButtonView.Handler != null && _confirmButtonView.Handler.PlatformView != null)
+ {
+ _confirmButtonView.Handler.DisconnectHandler();
+ }
+
+ _confirmButtonView = null;
+ }
+
+ ///
+ /// Method to remove the cancel button from the footer layout.
+ ///
+ void RemoveCancelButton()
+ {
+ if (_cancelButton == null)
+ {
+ return;
+ }
+
+ _cancelButton.Clicked -= CancelButtonClicked;
+ Remove(_cancelButton);
+ for (int i = _cancelButton.Children.Count - 1; i >= 0; i--)
+ {
+ _cancelButton.RemoveAt(i);
+ }
+
+ if (_cancelButton.Handler != null && _cancelButton.Handler.PlatformView != null)
+ {
+ _cancelButton.Handler.DisconnectHandler();
+ }
+
+ _cancelButton = null;
+ if (_cancelButtonView == null)
+ {
+ return;
+ }
+
+ if (_cancelButtonView.Handler != null && _cancelButtonView.Handler.PlatformView != null)
+ {
+ _cancelButtonView.Handler.DisconnectHandler();
+ }
+
+ _cancelButtonView = null;
+ }
+
+ ///
+ /// Method to handle the Ok button clicked event.
+ ///
+ /// The object.
+ void ConfirmButtonClicked(string obj)
+ {
+ _footerViewInfo.OnConfirmButtonClicked();
+ }
+
+ ///
+ /// Method to handle the Cancel button clicked event.
+ ///
+ /// The object.
+ void CancelButtonClicked(string obj)
+ {
+ _footerViewInfo.OnCancelButtonClicked();
+ }
+
+ ///
+ /// Occurs when confirm button tapped while accessibility enabled.
+ ///
+ /// Confirm button semantic node.
+ void OnConfirmButtonNodeClicked(SemanticsNode node)
+ {
+ _footerViewInfo.OnConfirmButtonClicked();
+ }
+
+ ///
+ /// Occurs when cancel button tapped while accessibility enabled.
+ ///
+ /// Cancel button semantic node.
+ void OnCancelButtonNodeClicked(SemanticsNode node)
+ {
+ _footerViewInfo.OnCancelButtonClicked();
+ }
+
+ #endregion
+
+ #region Override Methods
+
+ ///
+ /// Method to draw the separator line for footer and picker layout.
+ ///
+ /// The canvas.
+ /// The dirtyRect.
+ protected override void OnDraw(ICanvas canvas, RectF dirtyRect)
+ {
+ if (dirtyRect.Width == 0 || dirtyRect.Height == 0)
+ {
+ return;
+ }
+
+ canvas.SaveState();
+ if (_footerViewInfo.FooterView.DividerColor != Colors.Transparent)
+ {
+ canvas.StrokeColor = _footerViewInfo.FooterView.DividerColor;
+ canvas.StrokeSize = StrokeThickness;
+ float lineTopPosition = dirtyRect.Top + (float)(StrokeThickness * 0.5);
+ canvas.DrawLine(dirtyRect.Left, lineTopPosition, dirtyRect.Left + dirtyRect.Width, lineTopPosition);
+ }
+
+ canvas.RestoreState();
+ }
+
+ ///
+ /// Method used to arrange the children with in the bounds.
+ ///
+ /// The size of the layout.
+ /// The layout size.
+ protected override Size ArrangeContent(Rect bounds)
+ {
+ //// It holds the size of the confirm button text size.
+ Size confirmButtonTextSize = Size.Zero;
+ //// It holds the size of the cancel button text size.
+ Size cancelButtonTextSize = Size.Zero;
+ //// It holds the confirm button width with left and right padding.
+ double confirmButtonWidth = 0;
+ //// It holds the cancel button width with left and right padding.
+ double cancelButtonWidth = 0;
+ if (_footerViewInfo.FooterView.ShowOkButton && _confirmButtonView != null)
+ {
+ confirmButtonTextSize = _confirmButtonView.Text.Measure(_footerViewInfo.FooterView.TextStyle);
+ confirmButtonWidth = confirmButtonTextSize.Width + ButtonLeftAndRightPadding;
+ }
+
+ if (_cancelButtonView != null)
+ {
+ cancelButtonTextSize = _cancelButtonView.Text.Measure(_footerViewInfo.FooterView.TextStyle);
+ cancelButtonWidth = cancelButtonTextSize.Width + ButtonLeftAndRightPadding;
+ }
+
+ double buttonHeight = (cancelButtonTextSize.Height > confirmButtonTextSize.Height ? cancelButtonTextSize.Height : confirmButtonTextSize.Height) + ButtonTopandBottomPadding;
+ //// Value 4 denotes the top and bottom 2 padding.
+ double maximumButtonHeight = bounds.Height - 4;
+ if (buttonHeight > maximumButtonHeight)
+ {
+ buttonHeight = maximumButtonHeight;
+ }
+
+ //// It holds the y position of the button.
+ double buttonYPosition = (bounds.Height - buttonHeight) * 0.5;
+ if (_footerViewInfo.IsRTLLayout)
+ {
+ foreach (var child in Children)
+ {
+ if (child == _cancelButton)
+ {
+ //// Example: Width = 500, confirmButtonWidth = 30, cancelButtonWidth= 50. xPosition = 500 - 30 - 50 - 16 - 10 = 394. So that the cancel button draw start from the x position(394).
+ //// From above values. cancelButtonXPosition = 30 + 10 = 40. So that the cancel button draw start from the x position(40).
+ double cancelButtonXPosition = confirmButtonWidth + LayoutPadding;
+ //// The confirm button width is 0 while it is not visible, So need to start the cancel button draw from the layout padding(10).
+ //// xPosition = LayoutPadding(10).
+ //// The confirm button with is not 0 when the confirm button is visible, So need to draw the cancel button, The cancel button draw start from based on the addition of cancelButtonXPosition and layout padding.
+ //// xPosition = 40 + 10 => 50.
+ double xPosition = confirmButtonWidth == 0 ? LayoutPadding : cancelButtonXPosition + LayoutPadding;
+ child.Arrange(new Rect(xPosition, buttonYPosition, cancelButtonWidth, buttonHeight));
+ }
+ else if (child == _confirmButton)
+ {
+ double xPosition = LayoutPadding;
+ child.Arrange(new Rect(xPosition, buttonYPosition, confirmButtonWidth, buttonHeight));
+ }
+ }
+ }
+ else
+ {
+ foreach (var child in Children)
+ {
+ if (child == _cancelButton)
+ {
+ //// Here need to subtract confirm and cancel button width from the total width to render the cancel button.
+ //// Example: Width = 500, confirmButtonWidth = 30, cancelButtonWidth= 50. xPosition = 500 - 30 - 50 - 16 - 10 = 394. So that the cancel button draw start from the x position(394).
+ double cancelButtonXPosition = bounds.Width - confirmButtonWidth - cancelButtonWidth - LayoutPadding;
+ //// While the confirm button is not visible then the confirm button width is 0, the cancel button is drawn from the right side of the layout with layout padding value and cancel button width.
+ //// xPosition = cancelButtonXPosition(394) - LayoutPadding(10) => 384.
+ double xPosition = confirmButtonWidth == 0 ? cancelButtonXPosition : cancelButtonXPosition - LayoutPadding;
+ child.Arrange(new Rect(xPosition, buttonYPosition, cancelButtonWidth, buttonHeight));
+ }
+ else if (child == _confirmButton)
+ {
+ //// From above values. xPosition = 500 - 30 - 10 = 460. So that the ok button draw start from the x position(460).
+ double xPosition = bounds.Width - confirmButtonWidth - LayoutPadding;
+ child.Arrange(new Rect(xPosition, buttonYPosition, confirmButtonWidth, buttonHeight));
+ }
+ }
+ }
+
+ return bounds.Size;
+ }
+
+ ///
+ /// Method used to measure the children based on width and height value.
+ ///
+ /// The maximum width request of the layout.
+ /// The maximum height request of the layout.
+ /// The maximum size of the layout.
+ protected override Size MeasureContent(double widthConstraint, double heightConstraint)
+ {
+ double width = double.IsFinite(widthConstraint) ? widthConstraint : 0;
+ double height = double.IsFinite(heightConstraint) ? heightConstraint : 0;
+ Size confirmButtonTextSize = Size.Zero;
+ Size cancelButtonTextSize = Size.Zero;
+ if (_footerViewInfo.FooterView.ShowOkButton && _confirmButtonView != null)
+ {
+ confirmButtonTextSize = _confirmButtonView.Text.Measure(_footerViewInfo.FooterView.TextStyle);
+ }
+
+ if (_cancelButtonView != null)
+ {
+ cancelButtonTextSize = _cancelButtonView.Text.Measure(_footerViewInfo.FooterView.TextStyle);
+ }
+
+ double buttonHeight = (cancelButtonTextSize.Height > confirmButtonTextSize.Height ? cancelButtonTextSize.Height : confirmButtonTextSize.Height) + ButtonTopandBottomPadding;
+ //// Value 4 denotes the top and bottom 2 padding.
+ double maximumButtonHeight = height - 4;
+ if (buttonHeight > maximumButtonHeight)
+ {
+ buttonHeight = maximumButtonHeight;
+ }
+
+ foreach (SfIconButton child in Children)
+ {
+ if (child == _cancelButton)
+ {
+ child.Measure(cancelButtonTextSize.Width + ButtonLeftAndRightPadding, buttonHeight);
+ }
+ else if (child == _confirmButton)
+ {
+ child.Measure(confirmButtonTextSize.Width + ButtonLeftAndRightPadding, buttonHeight);
+ }
+ }
+
+ return new Size(width, height);
+ }
+
+ #endregion
+
+ #region Interface Implementation
+
+ ///
+ /// This method will be called when a theme dictionary
+ /// that contains the value for your control key is merged in application.
+ ///
+ /// The old value.
+ /// The new value.
+ void IThemeElement.OnCommonThemeChanged(string oldTheme, string newTheme)
+ {
+ }
+
+ ///
+ /// This method will be called when users merge a theme dictionary
+ /// that contains value for “SyncfusionTheme” dynamic resource key.
+ ///
+ /// Old theme.
+ /// New theme.
+ void IThemeElement.OnControlThemeChanged(string oldTheme, string newTheme)
+ {
+ }
+
+ #endregion
+ }
+}
\ No newline at end of file
diff --git a/maui/src/Picker/Views/HeaderLayout/HeaderLayout.cs b/maui/src/Picker/Views/HeaderLayout/HeaderLayout.cs
new file mode 100644
index 00000000..27435a26
--- /dev/null
+++ b/maui/src/Picker/Views/HeaderLayout/HeaderLayout.cs
@@ -0,0 +1,415 @@
+using Syncfusion.Maui.Toolkit.Graphics.Internals;
+
+namespace Syncfusion.Maui.Toolkit.Picker
+{
+ ///
+ /// This represents a class that contains information about the picker header layout.
+ ///
+ internal class HeaderLayout : SfView
+ {
+ #region Fields
+
+ ///
+ /// The separator line thickness.
+ ///
+ const int StrokeThickness = 1;
+
+ ///
+ /// The header view.
+ ///
+ readonly IHeaderView _pickerInfo;
+
+ ///
+ /// Gets or sets the virtual header layout semantic nodes.
+ ///
+ List? _semanticsNodes;
+
+ ///
+ /// Gets or sets the size of the semantic.
+ ///
+ Size _semanticsSize = Size.Zero;
+
+ #endregion
+
+ #region Constructor
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The picker header view details.
+ internal HeaderLayout(IHeaderView pickerInfo)
+ {
+ _pickerInfo = pickerInfo;
+ if (_pickerInfo.HeaderView.Parent != null)
+ {
+ DrawingOrder = DrawingOrder.AboveContent;
+ AutomationId = $"{PickerHelper.GetParentName(_pickerInfo.HeaderView.Parent)} HeaderView";
+ BackgroundColor = Colors.Transparent;
+ }
+ else
+ {
+ DrawingOrder = DrawingOrder.BelowContent;
+ }
+
+ AddChildren();
+ }
+
+ #endregion
+
+ #region Internal Methods
+
+ ///
+ /// Method to update the header view.
+ ///
+ internal void InvalidateHeaderView()
+ {
+ InvalidateDrawable();
+ }
+
+ ///
+ /// Method to update the header date text value.
+ ///
+ internal void UpdateHeaderDateText()
+ {
+ if (Children.Count != 2)
+ {
+ return;
+ }
+
+ if (Children[0] is SfIconButton dateButton && dateButton.EffectsView != null && dateButton.EffectsView.Content is SfIconView iconView)
+ {
+ iconView.Text = _pickerInfo.HeaderView.DateText;
+ SemanticProperties.SetDescription(dateButton, _pickerInfo.HeaderView.DateText);
+ iconView.InvalidateDrawable();
+ }
+ }
+
+ ///
+ /// Method to update the header time text value.
+ ///
+ internal void UpdateHeaderTimeText()
+ {
+ if (Children.Count != 2)
+ {
+ return;
+ }
+
+ if (Children[1] is SfIconButton timeButton && timeButton.EffectsView != null && timeButton.EffectsView.Content is SfIconView iconView)
+ {
+ iconView.Text = _pickerInfo.HeaderView.TimeText;
+ SemanticProperties.SetDescription(timeButton, _pickerInfo.HeaderView.TimeText);
+ iconView.InvalidateDrawable();
+ }
+ }
+
+ ///
+ /// Update icon button text style.
+ ///
+ internal void UpdateIconButtonTextStyle()
+ {
+ if (Children.Count != 2)
+ {
+ return;
+ }
+
+ if (Children[0] is SfIconButton dateButton && dateButton.EffectsView != null && dateButton.EffectsView.Content is SfIconView dateIconView)
+ {
+ dateButton.UpdateStyle(dateIconView.IsTabHighlight ? _pickerInfo.HeaderView.SelectionTextStyle : _pickerInfo.HeaderView.TextStyle);
+ }
+
+ if (Children[1] is SfIconButton timeButton && timeButton.EffectsView != null && timeButton.EffectsView.Content is SfIconView iconView)
+ {
+ timeButton.UpdateStyle(iconView.IsTabHighlight ? _pickerInfo.HeaderView.SelectionTextStyle : _pickerInfo.HeaderView.TextStyle);
+ }
+ }
+
+ ///
+ /// Used to reset the header interaction highlight.
+ ///
+ internal void ResetHeaderHighlight()
+ {
+ OnDateButtonClicked("Date");
+ }
+
+ #endregion
+
+ #region Private Methods
+
+ ///
+ /// Method to draw the button.
+ ///
+ void AddChildren()
+ {
+ if (Children.Count != 0)
+ {
+ return;
+ }
+
+ if (!string.IsNullOrEmpty(_pickerInfo.HeaderView.DateText))
+ {
+ SemanticsNode buttonViewNode = new SemanticsNode()
+ {
+ Id = 0,
+ Text = _pickerInfo.HeaderView.DateText,
+ IsTouchEnabled = true,
+ OnClick = OnDateNodeButtonClicked,
+ };
+
+ SfIconView buttonView = new SfIconView(SfIcon.TodayButton, _pickerInfo.HeaderView.SelectionTextStyle, _pickerInfo.HeaderView.DateText.ToString(), null, Colors.Transparent, isTabHighlight: true, semanticsNode: buttonViewNode);
+ buttonView.DrawingOrder = DrawingOrder.AboveContent;
+ SfIconButton button = new SfIconButton(buttonView);
+ button.AutomationId = $"DateTimePicker DateHeaderView";
+ Add(button);
+ button.Clicked = OnDateButtonClicked;
+ }
+
+ if (!string.IsNullOrEmpty(_pickerInfo.HeaderView.TimeText))
+ {
+ SemanticsNode buttonViewNode = new SemanticsNode()
+ {
+ Id = 0,
+ Text = _pickerInfo.HeaderView.TimeText,
+ IsTouchEnabled = true,
+ OnClick = OnTimeNodeButtonClicked,
+ };
+ SfIconView buttonView = new SfIconView(SfIcon.TodayButton, _pickerInfo.HeaderView.TextStyle, _pickerInfo.HeaderView.TimeText.ToString(), null, Colors.Transparent, semanticsNode: buttonViewNode);
+ buttonView.DrawingOrder = DrawingOrder.AboveContent;
+ SfIconButton button = new SfIconButton(buttonView);
+ button.AutomationId = $"DateTimePicker TimeHeaderView";
+ Add(button);
+ button.Clicked = OnTimeButtonClicked;
+ }
+ }
+
+ ///
+ /// Method to update the time button after clicked.
+ ///
+ /// The button text.
+ void OnTimeButtonClicked(string buttonText)
+ {
+ OnTimeButtonClicked();
+ }
+
+ ///
+ /// Method to update the date button after clicked.
+ ///
+ /// The button text.
+ void OnDateButtonClicked(string buttonText)
+ {
+ OnDateButtonClicked();
+ }
+
+ ///
+ /// Occurs when date button clicked while accessibility enabled.
+ ///
+ /// Date button semantic node.
+ void OnDateNodeButtonClicked(SemanticsNode node)
+ {
+ OnDateButtonClicked();
+ }
+
+ ///
+ /// Occurs when time button clicked while accessibility enabled.
+ ///
+ /// Time button semantic node.
+ void OnTimeNodeButtonClicked(SemanticsNode node)
+ {
+ OnTimeButtonClicked();
+ }
+
+ ///
+ /// Method to update the time button after clicked.
+ ///
+ void OnTimeButtonClicked()
+ {
+ if (Children.Count != 2)
+ {
+ return;
+ }
+
+ if (Children[0] is SfIconButton dateButton && dateButton.EffectsView != null && dateButton.EffectsView.Content is SfIconView dateIconView)
+ {
+ dateButton.UpdateStyle(_pickerInfo.HeaderView.TextStyle);
+ dateIconView.IsTabHighlight = false;
+ }
+
+ if (Children[1] is SfIconButton timeButton && timeButton.EffectsView != null && timeButton.EffectsView.Content is SfIconView iconView)
+ {
+ timeButton.UpdateStyle(_pickerInfo.HeaderView.SelectionTextStyle);
+ iconView.IsTabHighlight = true;
+ }
+
+ _pickerInfo.OnTimeButtonClicked();
+ }
+
+ ///
+ /// Method to update the date button after clicked.
+ ///
+ void OnDateButtonClicked()
+ {
+ if (Children.Count != 2)
+ {
+ return;
+ }
+
+ if (Children[1] is SfIconButton timeButton && timeButton.EffectsView != null && timeButton.EffectsView.Content is SfIconView iconView)
+ {
+ timeButton.UpdateStyle(_pickerInfo.HeaderView.TextStyle);
+ iconView.IsTabHighlight = false;
+ }
+
+ if (Children[0] is SfIconButton dateButton && dateButton.EffectsView != null && dateButton.EffectsView.Content is SfIconView dateIconView)
+ {
+ dateButton.UpdateStyle(_pickerInfo.HeaderView.SelectionTextStyle);
+ dateIconView.IsTabHighlight = true;
+ }
+
+ _pickerInfo.OnDateButtonClicked();
+ }
+
+ #endregion
+
+ #region Override Methods
+
+ ///
+ /// Method to draw the separator line for header and picker layout.
+ ///
+ /// The canvas.
+ /// The dirtyRect.
+ protected override void OnDraw(ICanvas canvas, RectF dirtyRect)
+ {
+ if (dirtyRect.Width == 0 || dirtyRect.Height == 0)
+ {
+ return;
+ }
+
+ canvas.SaveState();
+ float height = dirtyRect.Height;
+ float width = dirtyRect.Width;
+ float xPosition = dirtyRect.Left;
+ float yPosition = dirtyRect.Top;
+ bool isDividerEnabled = _pickerInfo.HeaderView.DividerColor != Colors.Transparent;
+ //// To avoid the separator line overlapping with the text, the height of the rectangle is reduced by the stroke thickness.
+ Rect rectangle = new Rect(xPosition, yPosition, width, height - (isDividerEnabled ? StrokeThickness : 0));
+ Color fillColor = _pickerInfo.HeaderView.Background.ToColor();
+ if (fillColor != Colors.Transparent)
+ {
+ canvas.FillColor = fillColor;
+ canvas.FillRectangle(rectangle);
+ }
+
+ if (!string.IsNullOrEmpty(_pickerInfo.HeaderView.Text))
+ {
+ string headerText = PickerHelper.TrimText(_pickerInfo.HeaderView.Text, width, _pickerInfo.HeaderView.TextStyle);
+ SemanticProperties.SetDescription(this, headerText);
+ canvas.DrawText(headerText, rectangle, HorizontalAlignment.Center, VerticalAlignment.Center, _pickerInfo.HeaderView.TextStyle);
+ }
+
+ if (isDividerEnabled)
+ {
+ canvas.StrokeColor = _pickerInfo.HeaderView.DividerColor;
+ canvas.StrokeSize = StrokeThickness;
+ float lineBottomPosition = height - (float)(StrokeThickness * 0.5);
+ //// To avoid the separator line overlapping with the text, the height of the rectangle is reduced by the stroke thickness.
+ canvas.DrawLine(xPosition, lineBottomPosition, width, lineBottomPosition);
+ }
+
+ canvas.RestoreState();
+ }
+
+ ///
+ /// Method used to arrange the children with in the bounds.
+ ///
+ /// The size of the view.
+ /// The view size.
+ protected override Size ArrangeContent(Rect bounds)
+ {
+ if (Children.Count == 0)
+ {
+ return base.ArrangeContent(bounds);
+ }
+
+ double width = bounds.Width / 2;
+ //// Stroke thickness denotes the below divider line space.
+ double height = bounds.Height - StrokeThickness;
+ bool isRTL = _pickerInfo.IsRTLLayout;
+ double buttonXPosition = 0;
+ if (isRTL)
+ {
+ buttonXPosition = width;
+ }
+
+ foreach (var child in Children)
+ {
+ child.Arrange(new Rect(buttonXPosition, 0, width, height));
+ if (isRTL)
+ {
+ buttonXPosition -= width;
+ }
+ else
+ {
+ buttonXPosition += width;
+ }
+ }
+
+ return bounds.Size;
+ }
+
+ ///
+ /// Method used to measure the children based on width and height value.
+ ///
+ /// The maximum width request of the view.
+ /// The maximum height request of the view.
+ /// The maximum size of the view.
+ protected override Size MeasureContent(double widthConstraint, double heightConstraint)
+ {
+ if (Children.Count == 0)
+ {
+ return base.MeasureContent(widthConstraint, heightConstraint);
+ }
+
+ double width = double.IsFinite(widthConstraint) ? widthConstraint : 0;
+ double height = double.IsFinite(heightConstraint) ? heightConstraint : 0;
+ double buttonWidth = width / 2;
+ //// Stroke thickness denotes the below divider line space.
+ double buttonHeight = height - 1;
+ foreach (var child in Children)
+ {
+ child.Measure(buttonWidth, buttonHeight);
+ }
+
+ return new Size(width, height);
+ }
+
+ ///
+ /// Method to create the semantics node for header layout.
+ ///
+ /// The width.
+ /// The height.
+ /// Returns semantic virtual view.
+ protected override List? GetSemanticsNodesCore(double width, double height)
+ {
+ Size newSize = new Size(width, height);
+
+ _semanticsNodes = new List();
+ _semanticsSize = newSize;
+ bool isDividerEnabled = _pickerInfo.HeaderView.DividerColor != Colors.Transparent;
+ //// To avoid the separator line overlapping with the text, the height of the rectangle is reduced by the stroke thickness.
+ Rect rectangle = new Rect(0, 0, width, height - (isDividerEnabled ? StrokeThickness : 0));
+ if (!string.IsNullOrEmpty(_pickerInfo.HeaderView.Text))
+ {
+ SemanticsNode node = new SemanticsNode()
+ {
+ Text = _pickerInfo.HeaderView.Text,
+ Bounds = rectangle,
+ IsTouchEnabled = true,
+ };
+ _semanticsNodes.Add(node);
+ }
+
+ return _semanticsNodes;
+ }
+
+ #endregion
+ }
+}
\ No newline at end of file
diff --git a/maui/src/Picker/Views/PickerLayout/ColumnHeaderLayout.cs b/maui/src/Picker/Views/PickerLayout/ColumnHeaderLayout.cs
new file mode 100644
index 00000000..34282e8a
--- /dev/null
+++ b/maui/src/Picker/Views/PickerLayout/ColumnHeaderLayout.cs
@@ -0,0 +1,175 @@
+using Syncfusion.Maui.Toolkit.Graphics.Internals;
+
+namespace Syncfusion.Maui.Toolkit.Picker
+{
+ ///
+ /// This represents a class that contains information about the picker column header layout.
+ ///
+ internal class ColumnHeaderLayout : SfView
+ {
+ #region Fields
+
+ ///
+ /// The picker view info.
+ ///
+ readonly IColumnHeaderView _columnHeaderInfo;
+
+ ///
+ /// The column header text.
+ ///
+ string _columnHeaderText;
+
+ ///
+ /// Gets or sets the virtual column header layout semantic nodes.
+ ///
+ List? _semanticsNodes;
+
+ ///
+ /// Gets or sets the size of the semantic.
+ ///
+ Size _semanticsSize = Size.Zero;
+
+ #endregion
+
+ #region Constructor
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The picker column header view details.
+ /// The column header text value.
+ internal ColumnHeaderLayout(IColumnHeaderView columnHeaderInfo, string headerText)
+ {
+ DrawingOrder = DrawingOrder.BelowContent;
+ _columnHeaderInfo = columnHeaderInfo;
+ _columnHeaderText = headerText;
+ }
+
+ #endregion
+
+ #region Internal Methods
+
+ ///
+ /// Method to trigger the picker column header draw.
+ ///
+ internal void TriggerColumnHeaderDraw()
+ {
+ InvalidateDrawable();
+ }
+
+ ///
+ /// Method to update the column header text.
+ ///
+ /// The column header text.
+ internal void UpdateColumnHeaderText(string columnHeaderText)
+ {
+ if (_columnHeaderText == columnHeaderText)
+ {
+ return;
+ }
+
+ _columnHeaderText = columnHeaderText;
+ InvalidateDrawable();
+ }
+
+ #endregion
+
+ #region Private Methods
+
+ ///
+ /// Method to trim the text based on available width.
+ ///
+ /// The text to trim.
+ /// The available width.
+ /// The text style.
+ /// Returns the text for the available width.
+ static string TrimText(string text, double width, PickerTextStyle textStyle)
+ {
+ if (width <= 0)
+ {
+ return string.Empty;
+ }
+
+ Size textSize = text.Measure(textStyle);
+ if (textSize.Width < width)
+ {
+ return text;
+ }
+
+ string textTrim = text;
+ while ((int)textSize.Width + 1 > width)
+ {
+ if (textTrim.Length == 0)
+ {
+ break;
+ }
+
+ textTrim = textTrim.Substring(0, textTrim.Length - 1);
+ textSize = (textTrim + "..").Measure((float)textStyle.FontSize, textStyle.FontAttributes, textStyle.FontFamily);
+ }
+
+ return textTrim + "..";
+ }
+
+ #endregion
+
+ #region Override Methods
+
+ ///
+ /// Method to draw the separator line for column header and picker layout.
+ ///
+ /// The canvas.
+ /// The dirtyRectangle.
+ protected override void OnDraw(ICanvas canvas, RectF dirtyRect)
+ {
+ if (dirtyRect.Width == 0 || dirtyRect.Height == 0)
+ {
+ return;
+ }
+
+ canvas.SaveState();
+ float width = dirtyRect.Width;
+ Rect rectangle = new Rect(0, 0, width, dirtyRect.Height);
+ Color fillColor = _columnHeaderInfo.ColumnHeaderView.Background.ToColor();
+ if (fillColor != Colors.Transparent)
+ {
+ canvas.FillColor = fillColor;
+ canvas.FillRectangle(rectangle);
+ }
+
+ string? headerText = TrimText(_columnHeaderText, width, _columnHeaderInfo.ColumnHeaderView.TextStyle);
+ if (!string.IsNullOrEmpty(headerText))
+ {
+ canvas.DrawText(headerText, rectangle, HorizontalAlignment.Center, VerticalAlignment.Center, _columnHeaderInfo.ColumnHeaderView.TextStyle);
+ }
+
+ canvas.RestoreState();
+ }
+
+ ///
+ /// Method to create the semantics node for header layout.
+ ///
+ /// The width.
+ /// The height.
+ /// Returns semantic virtual view.
+ protected override List? GetSemanticsNodesCore(double width, double height)
+ {
+ Size newSize = new Size(width, height);
+
+ _semanticsNodes = new List();
+ _semanticsSize = newSize;
+ Rect rectangle = new Rect(0, 0, width, height);
+ SemanticsNode node = new SemanticsNode()
+ {
+ Id = 0,
+ Text = _columnHeaderText,
+ Bounds = rectangle,
+ IsTouchEnabled = true,
+ };
+ _semanticsNodes.Add(node);
+ return _semanticsNodes;
+ }
+
+ #endregion
+ }
+}
\ No newline at end of file
diff --git a/maui/src/Picker/Views/PickerLayout/PickerContainer.cs b/maui/src/Picker/Views/PickerLayout/PickerContainer.cs
new file mode 100644
index 00000000..3bdce8a6
--- /dev/null
+++ b/maui/src/Picker/Views/PickerLayout/PickerContainer.cs
@@ -0,0 +1,572 @@
+using System.Collections.ObjectModel;
+using System.Collections.Specialized;
+using System.Diagnostics.CodeAnalysis;
+using Syncfusion.Maui.Toolkit.Internals;
+
+namespace Syncfusion.Maui.Toolkit.Picker
+{
+ ///
+ /// This represents a class that contains information about the picker container.
+ ///
+ internal class PickerContainer : SfView
+ {
+ #region Fields
+
+ ///
+ /// The stroke thickness.
+ ///
+ const float StrokeThickness = 1;
+
+ ///
+ /// The picker info.
+ ///
+ readonly IPicker _pickerInfo;
+
+ #endregion
+
+ #region Constructor
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The picker info.
+ internal PickerContainer(IPicker pickerInfo)
+ {
+ DrawingOrder = DrawingOrder.BelowContent;
+ _pickerInfo = pickerInfo;
+ }
+
+ #endregion
+
+ #region Internal Methods
+
+ ///
+ /// Method to update the Picker keyboard interaction.
+ ///
+ /// The keyboard event args.
+ /// The column index.
+ internal void PickerRightLeftKeys(KeyEventArgs args, int columnIndex)
+ {
+ int columnCount = Children.Count;
+ if (args.Key == KeyboardKey.Right)
+ {
+ columnIndex = (columnIndex + 1) % columnCount;
+ }
+ else if (args.Key == KeyboardKey.Left)
+ {
+ if (columnIndex == 0)
+ {
+ columnIndex = columnCount - 1;
+ }
+ else
+ {
+ columnIndex = Math.Abs(columnIndex - 1);
+ columnIndex = columnIndex == 0 ? columnIndex : columnIndex % columnCount;
+ }
+ }
+
+ PickerLayout? pickerLayout = Children[columnIndex] as PickerLayout;
+ pickerLayout?.UpdateKeyboardLayoutFocus();
+ }
+
+ ///
+ /// Method to update the selected index changed.
+ ///
+ /// The updated column index.
+ internal void UpdateSelectedIndexValue(int columnIndex)
+ {
+ if (columnIndex >= Children.Count)
+ {
+ return;
+ }
+
+ PickerLayout? pickerLayout = Children[columnIndex] as PickerLayout;
+ if (pickerLayout == null)
+ {
+ return;
+ }
+
+ pickerLayout.UpdateSelectedIndexValue();
+ }
+
+ ///
+ /// Method to update the items source changed.
+ ///
+ /// The updated column index.
+ [RequiresUnreferencedCode("The GetPropertyValue method is not trim compatible")]
+ internal void UpdateItemsSource(int columnIndex)
+ {
+ if (columnIndex >= Children.Count)
+ {
+ return;
+ }
+
+ PickerLayout? pickerLayout = Children[columnIndex] as PickerLayout;
+ if (pickerLayout == null)
+ {
+ return;
+ }
+
+ pickerLayout.UpdateItemSource(false);
+ }
+
+ ///
+ /// Method to invokes while the columns collection changed.
+ ///
+ /// The collection changed event arguments.
+ [RequiresUnreferencedCode("The GetPropertyValue method is not trim compatible")]
+ internal void OnColumnsCollectionChanged(NotifyCollectionChangedEventArgs e)
+ {
+ if (Children.Count == 0)
+ {
+ TriggerPickerContainerMeasure();
+ UpdatePickerSelectionViewDraw();
+ return;
+ }
+
+ if (e.Action == NotifyCollectionChangedAction.Add)
+ {
+ if (e.NewStartingIndex == -1)
+ {
+ return;
+ }
+
+ PickerColumn column = _pickerInfo.Columns[e.NewStartingIndex];
+ Insert(e.NewStartingIndex, new PickerLayout(_pickerInfo, column));
+ TriggerPickerContainerMeasure();
+ UpdatePickerSelectionViewDraw();
+ }
+ else if (e.Action == NotifyCollectionChangedAction.Remove)
+ {
+ if (e.OldStartingIndex == -1)
+ {
+ return;
+ }
+
+ Remove((View)Children[e.OldStartingIndex]);
+ UpdatePickerSelectionViewDraw();
+ TriggerPickerContainerMeasure();
+ }
+ else if (e.Action == NotifyCollectionChangedAction.Replace)
+ {
+ if (e.NewStartingIndex == -1)
+ {
+ return;
+ }
+
+ PickerLayout? pickerLayout = Children[e.NewStartingIndex] as PickerLayout;
+ if (pickerLayout != null)
+ {
+ PickerColumn column = _pickerInfo.Columns[e.NewStartingIndex];
+ pickerLayout.UpdateColumnData(column);
+ pickerLayout.UpdateItemSource(true);
+ pickerLayout.UpdateSelectedIndexValue();
+ }
+
+ TriggerPickerContainerMeasure();
+ }
+ else if (e.Action == NotifyCollectionChangedAction.Move)
+ {
+ if (e.NewStartingIndex == -1)
+ {
+ return;
+ }
+
+ PickerLayout? pickerLayout = Children[e.OldStartingIndex] as PickerLayout;
+ if (pickerLayout != null)
+ {
+ Remove(pickerLayout);
+ Insert(e.NewStartingIndex, pickerLayout);
+ }
+
+ TriggerPickerContainerMeasure();
+ }
+ else if (e.Action == NotifyCollectionChangedAction.Reset)
+ {
+ ResetPickerColumns(_pickerInfo.Columns);
+ }
+ }
+
+ ///
+ /// Method to the update the picker container.
+ ///
+ /// The new columns details.
+ [RequiresUnreferencedCode("The GetPropertyValue method is not trim compatible")]
+ internal void ResetPickerColumns(ObservableCollection newColumns)
+ {
+ int columnsCount = newColumns.Count;
+ if (columnsCount <= Children.Count)
+ {
+ for (int index = 0; index < columnsCount; index++)
+ {
+ PickerColumn pickerColumn = newColumns[index];
+ PickerLayout? pickerLayout = Children[index] as PickerLayout;
+ if (pickerLayout != null)
+ {
+ pickerLayout.UpdateColumnData(pickerColumn);
+ pickerLayout.UpdateColumnHeaderText();
+ pickerLayout.UpdateItemSource(true);
+ }
+ }
+
+ for (int index = Children.Count - 1; index >= columnsCount; index--)
+ {
+ Remove((View)Children[index]);
+ }
+ }
+ else
+ {
+ for (int index = 0; index < Children.Count; index++)
+ {
+ PickerColumn pickerColumn = newColumns[index];
+ PickerLayout? pickerLayout = Children[index] as PickerLayout;
+ if (pickerLayout != null)
+ {
+ pickerLayout.UpdateColumnData(pickerColumn);
+ pickerLayout.UpdateColumnHeaderText();
+ pickerLayout.UpdateItemSource(true);
+ }
+ }
+
+ for (int index = Children.Count; index < columnsCount; index++)
+ {
+ PickerColumn pickerColumn = newColumns[index];
+ Add(new PickerLayout(_pickerInfo, pickerColumn));
+ }
+ }
+
+ TriggerPickerContainerMeasure();
+ UpdatePickerSelectionViewDraw();
+ }
+
+ ///
+ /// Method to update the scroll view draw.
+ ///
+ internal void UpdateScrollViewDraw()
+ {
+ foreach (var child in Children)
+ {
+ if (child is PickerLayout pickerLayout)
+ {
+ pickerLayout.UpdateScrollViewDraw();
+ }
+ }
+ }
+
+ ///
+ /// Method to update the picker item template changes.
+ ///
+ internal void UpdateItemTemplate()
+ {
+ foreach (var child in Children)
+ {
+ if (child is PickerLayout pickerLayout)
+ {
+ pickerLayout.UpdateItemTemplate();
+ }
+ }
+ }
+
+ ///
+ /// Method to update the column header height.
+ ///
+ internal void UpdateColumnHeaderHeight()
+ {
+ //// Need to update the picker layout while change the column header height. Because , the picker layout height is based on the column header height.
+ foreach (PickerLayout pickerLayout in Children)
+ {
+ pickerLayout.UpdateColumnHeaderHeight();
+ }
+
+ //// After update the picker layout height, need to update the picker container height.
+ TriggerPickerContainerMeasure();
+ UpdatePickerSelectionViewDraw();
+ }
+
+ ///
+ /// Method to update the picker column header draw.
+ ///
+ internal void UpdateColumnHeaderDraw()
+ {
+ //// Need to update the picker layout drawing when changing the column header text style, background color, because the column header drawing is within the picker layout.
+ foreach (PickerLayout pickerLayout in Children)
+ {
+ pickerLayout.UpdateColumnHeaderDraw();
+ }
+ }
+
+ ///
+ /// Method to update the column divider color.
+ ///
+ internal void UpdateColumnDividerColor()
+ {
+ //// Need to update the picker layout drawing when changing the column divider color, because the column divider drawing is within the picker layout.
+ foreach (PickerLayout pickerLayout in Children)
+ {
+ pickerLayout.UpdateColumnDividerColor();
+ }
+ }
+
+ ///
+ /// Method to update the column header divider color.
+ ///
+ internal void UpdateColumnHeaderDividerColor()
+ {
+ foreach (PickerLayout pickerLayout in Children)
+ {
+ pickerLayout.UpdateColumnHeaderDividerColor();
+ }
+ }
+
+ ///
+ /// Method to update the item height.
+ ///
+ internal void UpdateItemHeight()
+ {
+ foreach (PickerLayout pickerLayout in Children)
+ {
+ pickerLayout.UpdateItemHeight();
+ }
+
+ UpdatePickerSelectionViewDraw();
+ }
+
+ ///
+ /// Method to update the picker column width.
+ ///
+ internal void UpdatePickerColumnWidth()
+ {
+ TriggerPickerContainerMeasure();
+ UpdatePickerSelectionViewDraw();
+ }
+
+ ///
+ /// Method to used to update the header text.
+ ///
+ /// The column index.
+ internal void UpdateHeaderText(int columnIndex)
+ {
+ if (columnIndex >= Children.Count)
+ {
+ return;
+ }
+
+ PickerLayout? pickerLayout = Children[columnIndex] as PickerLayout;
+ if (pickerLayout == null)
+ {
+ return;
+ }
+
+ pickerLayout.UpdateColumnHeaderText();
+ }
+
+ ///
+ /// Method to update the picker selection view.
+ ///
+ internal void UpdatePickerSelectionView()
+ {
+ UpdatePickerSelectionViewDraw();
+ }
+
+ #endregion
+
+ #region Private Methods
+
+ ///
+ /// Method to update the picker selection view draw.
+ ///
+ void UpdatePickerSelectionViewDraw()
+ {
+ InvalidateDrawable();
+ }
+
+ ///
+ /// Method to add children for the current layout.
+ ///
+ [UnconditionalSuppressMessage("Trimming", "IL2026:Members annotated with 'RequiresUnreferencedCodeAttribute' require dynamic access otherwise can break functionality when trimming application code", Justification = "")]
+ void GenerateChildren()
+ {
+ foreach (PickerColumn item in _pickerInfo.Columns)
+ {
+ Add(new PickerLayout(_pickerInfo, item));
+ }
+ }
+
+ ///
+ /// Method to get the total assigned width and default column width.
+ ///
+ /// The total width.
+ /// Returns total assigned width and default column width.
+ Point GetDefaultColumnWidth(double totalWidth)
+ {
+ double assignedWidth = 0;
+ int unAssignedColumnCount = 0;
+ foreach (PickerColumn column in _pickerInfo.Columns)
+ {
+ if (column.Width == -1)
+ {
+ unAssignedColumnCount++;
+ }
+ else
+ {
+ assignedWidth += column.Width <= -1 ? 0 : column.Width;
+ }
+ }
+
+ assignedWidth = assignedWidth > totalWidth ? totalWidth : assignedWidth;
+ double unAssignedWidth = totalWidth - assignedWidth;
+ assignedWidth += unAssignedColumnCount > 0 ? unAssignedWidth : 0;
+ double defaultColumnWidth = unAssignedWidth / unAssignedColumnCount;
+ //// Here the assigned width is total assigned and default column width is un assigned per column width.
+ return new Point(assignedWidth, defaultColumnWidth);
+ }
+
+ ///
+ /// Method to trigger the picker container measure.
+ ///
+ void TriggerPickerContainerMeasure()
+ {
+ InvalidateMeasure();
+ }
+
+ #endregion
+
+ #region Override Methods
+
+ ///
+ /// Method to draw the selection UI.
+ ///
+ /// The canvas.
+ /// The dirty rectangle.
+ protected override void OnDraw(ICanvas canvas, RectF dirtyRectangle)
+ {
+ //// The picker selection view is drawn only when the columns count greater than 0.
+ if (_pickerInfo.Columns.Count == 0 || !(_pickerInfo.TextDisplayMode == PickerTextDisplayMode.Default))
+ {
+ return;
+ }
+
+ for (int index = 0; index < _pickerInfo.Columns.Count; index++)
+ {
+ if (_pickerInfo.Columns[index].SelectedItem == null || _pickerInfo.Columns[index].SelectedIndex <= -1)
+ {
+ return;
+ }
+ }
+
+ canvas.SaveState();
+ //// The item source height.
+ double itemHeight = _pickerInfo.ItemHeight;
+ double columnHeaderHeight = _pickerInfo.ColumnHeaderView.Height;
+ //// The view port item count is calculated based on the view port height.
+ //// Assume the view port height is 100 and item height is 50, then the view port item count is 2.
+ double viewPortItemCount = Math.Round((dirtyRectangle.Height - columnHeaderHeight) / itemHeight);
+ //// The selectionIndex count is calculated based on the view port item count.
+ //// Assume the view port item count is 2, then the top count is 0.
+ int selectionIndex = (int)Math.Ceiling(viewPortItemCount / 2) - 1;
+
+ if (selectionIndex < 0)
+ {
+ canvas.RestoreState();
+ return;
+ }
+
+ //// The top padding is calculated based on the top count and item height.
+ //// Assume the selectionIndex is 0 and item height is 50, then the top padding is 0.
+ double yPosition = (selectionIndex * itemHeight) + columnHeaderHeight;
+ float xPosition = dirtyRectangle.Left;
+ float width = dirtyRectangle.Width;
+ float totalColumnWidth = (float)GetDefaultColumnWidth(width).X;
+ if (totalColumnWidth == 0)
+ {
+ canvas.RestoreState();
+ return;
+ }
+
+ float leftPadding = (float)(width - totalColumnWidth) * 0.5f;
+ xPosition += leftPadding;
+
+ //// Calculate the selection highlight rectangle based on padding value.
+ Thickness padding = _pickerInfo.SelectionView.Padding;
+ xPosition += (float)padding.Left;
+ totalColumnWidth -= (float)padding.HorizontalThickness;
+ yPosition += padding.Top;
+ itemHeight -= padding.VerticalThickness;
+ Rect selectionRectangle = new Rect(xPosition, yPosition, totalColumnWidth, itemHeight);
+ CornerRadius cornerRadius = _pickerInfo.SelectionView.CornerRadius;
+
+ Color fillColor = _pickerInfo.SelectionView.Background.ToColor();
+ if (fillColor != Colors.Transparent)
+ {
+ canvas.FillColor = fillColor;
+ canvas.FillRoundedRectangle(selectionRectangle, cornerRadius.TopLeft, cornerRadius.TopRight, cornerRadius.BottomLeft, cornerRadius.BottomRight);
+ }
+
+ if (_pickerInfo.SelectionView.Stroke != Colors.Transparent)
+ {
+ canvas.StrokeColor = _pickerInfo.SelectionView.Stroke;
+ canvas.StrokeSize = StrokeThickness;
+ float strokeOffset = StrokeThickness * 0.5f;
+ selectionRectangle = new Rect(xPosition + strokeOffset, yPosition + strokeOffset, totalColumnWidth - StrokeThickness, itemHeight - StrokeThickness);
+ canvas.DrawRoundedRectangle(selectionRectangle, cornerRadius.TopLeft, cornerRadius.TopRight, cornerRadius.BottomLeft, cornerRadius.BottomRight);
+ }
+
+ canvas.RestoreState();
+ }
+
+ ///
+ /// Method used to arrange the children with in the bounds.
+ ///
+ /// The size of the layout.
+ /// The layout size.
+ protected override Size ArrangeContent(Rect bounds)
+ {
+ Point columnWidthInfo = GetDefaultColumnWidth(bounds.Width);
+ double defaultColumnWidth = columnWidthInfo.Y;
+ double totalColumnWidth = columnWidthInfo.X;
+ int columnCount = _pickerInfo.Columns.Count;
+ double xPosition = (bounds.Width - totalColumnWidth) * 0.5;
+ for (int index = 0; index < columnCount; index++)
+ {
+ int actualIndex = _pickerInfo.IsRTLLayout ? columnCount - 1 - index : index;
+ var child = Children[actualIndex];
+ double columnWidth = _pickerInfo.Columns[actualIndex].Width;
+ columnWidth = columnWidth == -1 ? defaultColumnWidth : (columnWidth < -1 ? 0 : columnWidth);
+ child.Arrange(new Rect(xPosition, 0, columnWidth, bounds.Height));
+ xPosition += columnWidth;
+ }
+
+ return bounds.Size;
+ }
+
+ ///
+ /// Method to measures child elements size in a container, picker layout is measured with given column width and height constraints.
+ ///
+ /// The maximum width request of the layout.
+ /// The maximum height request of the layout.
+ /// The maximum size of the layout.
+ protected override Size MeasureContent(double widthConstraint, double heightConstraint)
+ {
+ if (Children.Count <= 0)
+ {
+ GenerateChildren();
+ }
+
+ Point columnWidthInfo = GetDefaultColumnWidth(widthConstraint);
+ double defaultColumnWidth = columnWidthInfo.Y;
+
+ int childCount = Math.Min(_pickerInfo.Columns.Count, Children.Count);
+ for (int index = 0; index < childCount; index++)
+ {
+ var child = Children[index];
+ double columnWidth = _pickerInfo.Columns[index].Width;
+ columnWidth = columnWidth == -1 ? defaultColumnWidth : (columnWidth < -1 ? 0 : columnWidth);
+ child.Measure(columnWidth, heightConstraint);
+ }
+
+ return new Size(widthConstraint, heightConstraint);
+ }
+
+ #endregion
+ }
+}
\ No newline at end of file
diff --git a/maui/src/Picker/Views/PickerLayout/PickerDrawableView.cs b/maui/src/Picker/Views/PickerLayout/PickerDrawableView.cs
new file mode 100644
index 00000000..938ac4ec
--- /dev/null
+++ b/maui/src/Picker/Views/PickerLayout/PickerDrawableView.cs
@@ -0,0 +1,295 @@
+using System.Collections.ObjectModel;
+using Syncfusion.Maui.Toolkit.Graphics.Internals;
+
+namespace Syncfusion.Maui.Toolkit.Picker;
+
+///
+/// Holds the picker items drawing based on virtualization.
+///
+internal class PickerDrawableView : SfDrawableView
+{
+ #region Fields
+
+ ///
+ /// The selected index holds the current selected index based on the current scroll position.
+ /// It is used to restricted the unwanted drawing of the item source element while scrolling.
+ ///
+ int _selectedIndex = 0;
+
+ ///
+ /// The items source.
+ ///
+ ObservableCollection _itemsSource;
+
+ ///
+ /// The items source collection based on size.
+ ///
+ readonly ObservableCollection _sizeBasedItemsSource;
+
+ ///
+ /// The drawn width used to calculate the items source based on drawn width value.
+ ///
+ double _drawnWidth = 0;
+
+ ///
+ /// The picker view info.
+ ///
+ readonly IPickerLayout _pickerLayoutInfo;
+
+ ///
+ /// Method to get the current scroll view starting index.
+ ///
+ readonly Func _getStartingIndex;
+
+ ///
+ /// The picker view info.
+ ///
+ readonly PickerView _pickerView;
+
+ #endregion
+
+ #region Constructor
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The picker layout info.
+ /// The picker view info.
+ /// The items source.
+ /// Method to get the current scroll view starting index.
+ internal PickerDrawableView(IPickerLayout pickerLayoutInfo, PickerView pickerView, ObservableCollection itemsSource, Func getStartingIndex)
+ {
+ _pickerView = pickerView;
+ _pickerLayoutInfo = pickerLayoutInfo;
+ _itemsSource = itemsSource;
+ _getStartingIndex = getStartingIndex;
+ _sizeBasedItemsSource = new ObservableCollection();
+ }
+
+ #endregion
+
+ #region Internal Methods
+
+ ///
+ /// Method to update the selected index.
+ ///
+ /// The index.
+ internal void UpdateSelectedIndexValue(int index)
+ {
+ if (index == _selectedIndex)
+ {
+ return;
+ }
+
+ _selectedIndex = index;
+ InvalidateDrawable();
+ }
+
+ ///
+ /// Method to update the selected index on animation end.
+ ///
+ /// The index.
+ internal void UpdateSelectedIndexOnAnimationEnd(int index)
+ {
+ _selectedIndex = index;
+ InvalidateDrawable();
+ }
+
+ ///
+ /// Method to update the items source.
+ ///
+ /// The items source.
+ internal void UpdateItemsSource(ObservableCollection itemsSource)
+ {
+ _itemsSource = itemsSource;
+ _sizeBasedItemsSource.Clear();
+ InvalidateDrawable();
+ }
+
+ ///
+ /// Method to update the picker view draw.
+ ///
+ internal void UpdatePickerViewDraw()
+ {
+ InvalidateDrawable();
+ }
+
+ #endregion
+
+ #region Private Methods
+
+ ///
+ /// Used for checking whether value is disabled or not.
+ ///
+ /// Current value of picker column.
+ /// Current value is selected or not.
+ /// Returns true or false based on disabled values.
+ bool UpdateBlackoutStyle(string currentValue, bool isSelected)
+ {
+ //// Since black out selection not applicable.
+ if (_pickerLayoutInfo.PickerInfo is SfPicker)
+ {
+ return false;
+ }
+
+ if (_pickerLayoutInfo.PickerInfo is SfTimePicker timePicker)
+ {
+ //// Used to check the given time is black out time or not.
+ if (_pickerLayoutInfo.Column.HeaderText == SfPickerResources.GetLocalizedString(timePicker.ColumnHeaderView.MinuteHeaderText))
+ {
+ TimeSpan selectedTime = timePicker.SelectedTime ?? timePicker._previousSelectedDateTime.TimeOfDay;
+ return timePicker.BlackoutTimes.Any(blackOutTime => blackOutTime.Hours == selectedTime.Hours && blackOutTime.Minutes == int.Parse(currentValue));
+ }
+ }
+
+ if (_pickerLayoutInfo.PickerInfo is SfDatePicker datePicker)
+ {
+ //// Used to check the given date is black out date or not.
+ if (_pickerLayoutInfo.Column.HeaderText == SfPickerResources.GetLocalizedString(datePicker.ColumnHeaderView.DayHeaderText))
+ {
+ DateTime selectedDate = datePicker.SelectedDate ?? datePicker._previousSelectedDateTime;
+ return datePicker.BlackoutDates.Any(blackOutDate => DatePickerHelper.IsBlackoutDate(false, currentValue, blackOutDate, selectedDate));
+ }
+ }
+
+ if (_pickerLayoutInfo.PickerInfo is SfDateTimePicker dateTimePicker)
+ {
+ DateTime selectedDateTime = dateTimePicker.SelectedDate ?? dateTimePicker._previousSelectedDateTime;
+
+ //// Used to check the given date time is black out date time or not.
+ if (_pickerLayoutInfo.Column.HeaderText == SfPickerResources.GetLocalizedString(dateTimePicker.ColumnHeaderView.DayHeaderText))
+ {
+ return dateTimePicker.BlackoutDateTimes.Any(blackOutDateTime => DatePickerHelper.IsBlackoutDate(false, currentValue, blackOutDateTime, selectedDateTime) && blackOutDateTime.TimeOfDay == TimeSpan.Zero);
+ }
+
+ if (_pickerLayoutInfo.Column.HeaderText == SfPickerResources.GetLocalizedString(dateTimePicker.ColumnHeaderView.MinuteHeaderText))
+ {
+ return dateTimePicker.BlackoutDateTimes.Any(blackOutDateTime => DatePickerHelper.IsBlackoutDate(true, currentValue, blackOutDateTime, selectedDateTime) && blackOutDateTime.Hour == selectedDateTime.Hour && blackOutDateTime.Minute == int.Parse(currentValue));
+ }
+ }
+
+ return false;
+ }
+
+ #endregion
+
+ #region Override Methods
+
+ ///
+ /// Method to draw the item source element inside the scroll view.
+ ///
+ /// The canvas.
+ /// The dirty rectangle.
+ protected override void OnDraw(ICanvas canvas, RectF dirtyRect)
+ {
+ if (dirtyRect.Width == 0 || dirtyRect.Height == 0)
+ {
+ return;
+ }
+
+ int padding = 5;
+ if (_drawnWidth != dirtyRect.Width || _sizeBasedItemsSource.Count == 0)
+ {
+ _drawnWidth = dirtyRect.Width;
+ _sizeBasedItemsSource.Clear();
+ double maxTextWidth = _drawnWidth - padding;
+ foreach (string value in _itemsSource)
+ {
+ //// Initially checks that the given value is need to be trimmed by using font size and text length.
+ string unSelectedText = value.Length * (_pickerLayoutInfo.PickerInfo.TextStyle.FontSize * 0.6) > maxTextWidth ? PickerHelper.TrimText(value, maxTextWidth, _pickerLayoutInfo.PickerInfo.TextStyle) : value;
+ string selectedText = value.Length * (_pickerLayoutInfo.PickerInfo.SelectedTextStyle.FontSize * 0.6) > maxTextWidth ? PickerHelper.TrimText(value, maxTextWidth, _pickerLayoutInfo.PickerInfo.SelectedTextStyle) : value;
+
+ string textToAdd = unSelectedText.Length < selectedText.Length ? unSelectedText : selectedText;
+ _sizeBasedItemsSource.Add(textToAdd);
+ }
+ }
+
+ canvas.SaveState();
+ //// The item height.
+ double itemHeight = _pickerLayoutInfo.PickerInfo.ItemHeight;
+ double yPosition = 0;
+ int startIndex = _getStartingIndex();
+
+ float minimumThresholdFontSize = 8;
+ var unselectedTextStyle = _pickerLayoutInfo.PickerInfo.TextStyle;
+ var selectedTextStyle = _pickerLayoutInfo.PickerInfo.SelectedTextStyle;
+ int maxDistance = (int)(_pickerView.GetPickerItemViewPortCount() / 2) + 1;
+
+ //// Draw item source item based on the item source collection. And change the text style based on selected item and selected index.
+ for (int i = startIndex; i < _sizeBasedItemsSource.Count; i++)
+ {
+ Rect rectangle = new Rect(0, yPosition, dirtyRect.Width, itemHeight);
+ PickerTextStyle textStyle;
+ PickerTextStyle blackoutStyle = _pickerLayoutInfo.PickerInfo.DisabledTextStyle;
+ blackoutStyle.FontSize = unselectedTextStyle.FontSize;
+
+ if (i == _selectedIndex)
+ {
+ PickerTextStyle defaultSelectedStyle = new PickerTextStyle { FontSize = selectedTextStyle.FontSize, TextColor = selectedTextStyle.TextColor, FontFamily = selectedTextStyle.FontFamily, FontAttributes = selectedTextStyle.FontAttributes, FontAutoScalingEnabled = selectedTextStyle.FontAutoScalingEnabled };
+ if (_pickerLayoutInfo.Column.SelectedItem == null || _pickerLayoutInfo.Column.SelectedIndex <= -1)
+ {
+ defaultSelectedStyle = _pickerLayoutInfo.PickerInfo.TextStyle;
+ }
+
+ canvas.DrawText(_sizeBasedItemsSource[i], rectangle, HorizontalAlignment.Center, VerticalAlignment.Center, defaultSelectedStyle);
+ }
+ else
+ {
+ int distance = Math.Abs(i - _selectedIndex);
+
+ if (distance <= maxDistance)
+ {
+ switch (_pickerLayoutInfo.PickerInfo.TextDisplayMode)
+ {
+ case PickerTextDisplayMode.Default:
+ textStyle = _pickerLayoutInfo.PickerInfo.TextStyle;
+ canvas.DrawText(_sizeBasedItemsSource[i], rectangle, HorizontalAlignment.Center, VerticalAlignment.Center, UpdateBlackoutStyle(_sizeBasedItemsSource[i], false) ? blackoutStyle : textStyle);
+ break;
+
+ case PickerTextDisplayMode.Fade:
+ {
+ PickerTextStyle fadeStyle = new PickerTextStyle() { TextColor = unselectedTextStyle.TextColor, FontSize = unselectedTextStyle.FontSize, FontAttributes = unselectedTextStyle.FontAttributes, FontFamily = unselectedTextStyle.FontFamily, FontAutoScalingEnabled = unselectedTextStyle.FontAutoScalingEnabled };
+ float opacity = 1.0f - (distance / (float)maxDistance);
+ fadeStyle.TextColor = fadeStyle.TextColor.WithAlpha(opacity);
+ canvas.DrawText(_sizeBasedItemsSource[i], rectangle, HorizontalAlignment.Center, VerticalAlignment.Center, UpdateBlackoutStyle(_sizeBasedItemsSource[i], false) ? blackoutStyle : fadeStyle);
+ }
+
+ break;
+
+ case PickerTextDisplayMode.Shrink:
+ {
+ PickerTextStyle shrinkStyle = new PickerTextStyle() { TextColor = unselectedTextStyle.TextColor, FontSize = unselectedTextStyle.FontSize, FontAttributes = unselectedTextStyle.FontAttributes, FontFamily = unselectedTextStyle.FontFamily, FontAutoScalingEnabled = unselectedTextStyle.FontAutoScalingEnabled };
+ float size = (float)(shrinkStyle.FontSize - (distance / (float)maxDistance * (shrinkStyle.FontSize - minimumThresholdFontSize)));
+ shrinkStyle.FontSize = Math.Max(minimumThresholdFontSize, size);
+ canvas.DrawText(_sizeBasedItemsSource[i], rectangle, HorizontalAlignment.Center, VerticalAlignment.Center, UpdateBlackoutStyle(_sizeBasedItemsSource[i], false) ? blackoutStyle : shrinkStyle);
+ }
+
+ break;
+
+ case PickerTextDisplayMode.FadeAndShrink:
+ {
+ PickerTextStyle fadeandShrinkStyle = new PickerTextStyle() { TextColor = unselectedTextStyle.TextColor, FontSize = unselectedTextStyle.FontSize, FontAttributes = unselectedTextStyle.FontAttributes, FontFamily = unselectedTextStyle.FontFamily, FontAutoScalingEnabled = unselectedTextStyle.FontAutoScalingEnabled };
+ float opacity = 1.0f - (distance / (float)maxDistance);
+ float size = (float)(fadeandShrinkStyle.FontSize - (distance / (float)maxDistance * (fadeandShrinkStyle.FontSize - minimumThresholdFontSize)));
+ fadeandShrinkStyle.FontSize = Math.Max(minimumThresholdFontSize, size);
+ fadeandShrinkStyle.TextColor = fadeandShrinkStyle.TextColor.WithAlpha(opacity);
+ canvas.DrawText(_sizeBasedItemsSource[i], rectangle, HorizontalAlignment.Center, VerticalAlignment.Center, UpdateBlackoutStyle(_sizeBasedItemsSource[i], false) ? blackoutStyle : fadeandShrinkStyle);
+ }
+
+ break;
+ }
+ }
+ }
+
+ yPosition += itemHeight;
+ if (yPosition >= dirtyRect.Height)
+ {
+ break;
+ }
+ }
+
+ canvas.RestoreState();
+ }
+
+ #endregion
+}
\ No newline at end of file
diff --git a/maui/src/Picker/Views/PickerLayout/PickerLayout.cs b/maui/src/Picker/Views/PickerLayout/PickerLayout.cs
new file mode 100644
index 00000000..560dbebe
--- /dev/null
+++ b/maui/src/Picker/Views/PickerLayout/PickerLayout.cs
@@ -0,0 +1,436 @@
+using System.Collections;
+using System.Collections.ObjectModel;
+using System.Diagnostics.CodeAnalysis;
+using System.Reflection;
+
+namespace Syncfusion.Maui.Toolkit.Picker;
+
+///
+/// This represents a class that contains information about the picker layout.
+///
+internal class PickerLayout : SfView, IPickerLayout
+{
+ #region Fields
+
+ ///
+ /// The separator line thickness.
+ ///
+ const int StrokeThickness = 1;
+
+ ///
+ /// The picker view info.
+ ///
+ readonly IPicker _pickerViewInfo;
+
+ ///
+ /// The picker scroll view.
+ ///
+ PickerScrollView _pickerScrollView;
+
+ ///
+ /// The picker column.
+ ///
+ PickerColumn _column;
+
+ ///
+ /// The items source.
+ ///
+ ObservableCollection _itemsSource;
+
+ #endregion
+
+ #region Constructor
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The picker view info.
+ /// The picker column.
+ [RequiresUnreferencedCode("The GetPropertyValue method is not trim compatible")]
+ internal PickerLayout(IPicker pickerViewInfo, PickerColumn pickerColumn)
+ {
+ _pickerViewInfo = pickerViewInfo;
+ _column = pickerColumn;
+ _itemsSource = new ObservableCollection();
+ UpdateItemSource(false);
+ DrawingOrder = DrawingOrder.AboveContent;
+ ColumnHeaderLayout columnHeaderLayout = new ColumnHeaderLayout(_pickerViewInfo, _column.HeaderText);
+ Add(columnHeaderLayout);
+ _pickerScrollView = new PickerScrollView(this, _pickerViewInfo, _itemsSource);
+ Add(_pickerScrollView);
+ }
+
+ #endregion
+
+ #region Public Properties
+
+ ///
+ /// Gets the pickerViewInfo variable of IPickerLayout to the IPickerView property.
+ ///
+ IPickerView IPickerLayout.PickerInfo => _pickerViewInfo;
+
+ ///
+ /// Gets the column variable of IPickerLayout to the IPickerView property.
+ ///
+ PickerColumn IPickerLayout.Column => _column;
+
+ ///
+ /// Gets the scroll position of the scroll view.
+ ///
+ double IPickerLayout.ScrollOffset => _pickerScrollView.ScrollY;
+
+ #endregion
+
+ #region Internal Methods
+
+ ///
+ /// Method to update the column header height.
+ ///
+ internal void UpdateColumnHeaderHeight()
+ {
+ TriggerPickerLayoutMeasure();
+ TriggerPickerLayoutDraw();
+ }
+
+ ///
+ /// Method to update the column divider color.
+ ///
+ internal void UpdateColumnDividerColor()
+ {
+ TriggerPickerLayoutDraw();
+ }
+
+ ///
+ /// Method to update the column header draw.
+ ///
+ internal void UpdateColumnHeaderDraw()
+ {
+ foreach (var child in Children)
+ {
+ if (child is ColumnHeaderLayout columnHeaderLayout)
+ {
+ columnHeaderLayout.TriggerColumnHeaderDraw();
+ }
+ }
+ }
+
+ ///
+ /// Method to update the column header divider color.
+ ///
+ internal void UpdateColumnHeaderDividerColor()
+ {
+ TriggerPickerLayoutDraw();
+ }
+
+ ///
+ /// Method to update the item height.
+ ///
+ internal void UpdateItemHeight()
+ {
+ foreach (var child in Children)
+ {
+ if (child is PickerScrollView pickerScrollView)
+ {
+ pickerScrollView.UpdateItemHeight();
+ }
+ }
+ }
+
+ ///
+ /// Method to update the picker item template changes.
+ ///
+ internal void UpdateItemTemplate()
+ {
+ foreach (var child in Children)
+ {
+ if (child is PickerScrollView pickerScrollView)
+ {
+ pickerScrollView.UpdateItemTemplate();
+ }
+ }
+ }
+
+ ///
+ /// Method to update the column header text.
+ ///
+ internal void UpdateColumnHeaderText()
+ {
+ foreach (var child in Children)
+ {
+ if (child is ColumnHeaderLayout columnHeaderLayout)
+ {
+ columnHeaderLayout.UpdateColumnHeaderText(_column.HeaderText);
+ }
+ }
+ }
+
+ ///
+ /// Method to updater the scroll view draw.
+ ///
+ internal void UpdateScrollViewDraw()
+ {
+ foreach (var child in Children)
+ {
+ if (child is PickerScrollView pickerScrollView)
+ {
+ pickerScrollView.InvalidatePickerViewDraw();
+ }
+ }
+ }
+
+ ///
+ /// Method to update the picker column data.
+ ///
+ /// The column data.
+ internal void UpdateColumnData(PickerColumn column)
+ {
+ _column = column;
+ }
+
+ ///
+ /// Method to update the item source.
+ ///
+ /// Used to reset the picker scroll view.
+ [RequiresUnreferencedCode("The GetPropertyValue method is not trim compatible")]
+ internal void UpdateItemSource(bool isNeedResetColumn)
+ {
+ ObservableCollection dataSource = new ObservableCollection();
+ if (_column.ItemsSource != null && _column.ItemsSource is ICollection collection)
+ {
+ if (!string.IsNullOrEmpty(_column.DisplayMemberPath))
+ {
+ PropertyInfo? property = null;
+ foreach (var item in collection)
+ {
+ if (property == null)
+ {
+ property = item.GetType().GetProperty(_column.DisplayMemberPath);
+ }
+
+ if (property == null)
+ {
+ continue;
+ }
+
+ string? value = property.GetValue(item) as string;
+ if (value != null)
+ {
+ dataSource.Add(value);
+ }
+ }
+ }
+
+ if (dataSource.Count == 0)
+ {
+ foreach (var item in collection)
+ {
+ string? text = item?.ToString();
+ if (text != null)
+ {
+ dataSource.Add(text);
+ }
+ }
+ }
+ }
+
+ if (PickerHelper.IsCollectionEquals(_itemsSource, dataSource))
+ {
+ return;
+ }
+
+ _itemsSource = dataSource;
+ if (_pickerScrollView == null)
+ {
+ return;
+ }
+
+ if (isNeedResetColumn)
+ {
+ Remove(_pickerScrollView);
+ _pickerScrollView.Dispose();
+ _pickerScrollView = new PickerScrollView(this, _pickerViewInfo, _itemsSource);
+ Add(_pickerScrollView);
+ }
+ else
+ {
+ _pickerScrollView.UpdateItemsSource(_itemsSource);
+ }
+
+ TriggerPickerLayoutMeasure();
+ }
+
+ ///
+ /// Method to update the selected index changed.
+ ///
+ internal void UpdateSelectedIndexValue()
+ {
+ if (_pickerScrollView == null)
+ {
+ return;
+ }
+
+ if (_pickerScrollView.ContentSize != Size.Zero)
+ {
+ _pickerScrollView.UpdateSelectedIndexValue();
+ }
+ }
+
+ ///
+ /// Method to update the focus for the keyboard interaction.
+ ///
+ internal void UpdateKeyboardLayoutFocus()
+ {
+ foreach (var child in Children)
+ {
+ if (child is PickerScrollView pickerScrollView)
+ {
+ pickerScrollView.UpdateKeyboardViewFocus();
+ }
+ }
+ }
+
+ #endregion
+
+ #region Private Methods
+
+ ///
+ /// Method to update the picker layout draw.
+ ///
+ void TriggerPickerLayoutDraw()
+ {
+ InvalidateDrawable();
+ }
+
+ ///
+ /// Method to update the picker layout measure.
+ ///
+ void TriggerPickerLayoutMeasure()
+ {
+ InvalidateMeasure();
+ }
+
+ #endregion
+
+ #region Override Methods
+
+ ///
+ /// Method used to arrange the children with in the bounds.
+ ///
+ /// The size of the layout.
+ /// The layout size.
+ protected override Size ArrangeContent(Rect bounds)
+ {
+ double width = bounds.Width;
+ double height = bounds.Height;
+ double headerColumHeight = _pickerViewInfo.ColumnHeaderView.Height;
+ foreach (var child in Children)
+ {
+ if (child is ColumnHeaderLayout)
+ {
+ child.Arrange(new Rect(0, 0, width, headerColumHeight));
+ }
+ else if (child is PickerScrollView)
+ {
+ double childWidth = width - StrokeThickness;
+ childWidth = childWidth < 0 ? 0 : childWidth;
+ double childHeight = height - headerColumHeight;
+ childHeight = childHeight < 0 ? 0 : childHeight;
+ child.Arrange(new Rect(0, headerColumHeight, childWidth, childHeight));
+ }
+ }
+
+ return bounds.Size;
+ }
+
+ ///
+ /// Method used to measure the children based on width and height value.
+ ///
+ /// The maximum width request of the layout.
+ /// The maximum height request of the layout.
+ /// The maximum size of the layout.
+ protected override Size MeasureContent(double widthConstraint, double heightConstraint)
+ {
+ //// The header column height.
+ double headerColumHeight = _pickerViewInfo.ColumnHeaderView.Height;
+ foreach (var child in Children)
+ {
+ if (child is ColumnHeaderLayout)
+ {
+ child.Measure(widthConstraint, headerColumHeight);
+ }
+ else if (child is PickerScrollView)
+ {
+ double childWidth = widthConstraint - StrokeThickness;
+ childWidth = childWidth < 0 ? 0 : childWidth;
+ double childHeight = heightConstraint - headerColumHeight;
+ childHeight = childHeight < 0 ? 0 : childHeight;
+ child.Measure(childWidth, childHeight);
+ }
+ }
+
+ return new Size(widthConstraint, heightConstraint);
+ }
+
+ ///
+ /// Method to draw the selection background and border for picker view.
+ ///
+ /// The canvas.
+ /// The dirty rectangle.
+ protected override void OnDraw(ICanvas canvas, RectF dirtyRectangle)
+ {
+ float width = dirtyRectangle.Width;
+ float height = dirtyRectangle.Height;
+ float headerColumHeight = (float)_pickerViewInfo.ColumnHeaderView.Height;
+ float topPosition = dirtyRectangle.Top + headerColumHeight;
+ float leftPosition = dirtyRectangle.Left;
+ canvas.SaveState();
+
+ //// The columns count is 1 then no need to draw the column separator line.
+ if (_pickerViewInfo.ColumnDividerColor != Colors.Transparent && _column._columnIndex < _pickerViewInfo.Columns.Count - 1)
+ {
+ //// To draw the separator line between the column headers.
+ canvas.StrokeColor = _pickerViewInfo.ColumnDividerColor;
+ canvas.StrokeSize = StrokeThickness;
+ float strokeThickness = (float)(StrokeThickness * 0.5);
+ float columnRightPosition = width - strokeThickness;
+ columnRightPosition = _pickerViewInfo.IsRTLLayout ? strokeThickness : columnRightPosition;
+ canvas.DrawLine(columnRightPosition, topPosition, columnRightPosition, height);
+ }
+
+ if (_pickerViewInfo.ColumnHeaderView.DividerColor != Colors.Transparent && headerColumHeight > 0)
+ {
+ //// To draw the separator line at bottom of the column header.
+ canvas.StrokeColor = _pickerViewInfo.ColumnHeaderView.DividerColor;
+ canvas.StrokeSize = StrokeThickness;
+ float columnHeaderBottomPosition = topPosition;
+ canvas.DrawLine(leftPosition, columnHeaderBottomPosition, width, columnHeaderBottomPosition);
+ }
+
+ canvas.RestoreState();
+ }
+
+ #endregion
+
+ #region Interface Implementation
+
+ ///
+ /// Method to update the selected index.
+ ///
+ /// The tapped index.
+ /// Check whether is initial loading or not.
+ void IPickerLayout.UpdateSelectedIndexValue(int tappedIndex, bool isInitialLoading)
+ {
+ if (tappedIndex >= _itemsSource.Count)
+ {
+ tappedIndex = _itemsSource.Count - 1;
+ }
+ else if (tappedIndex < 0)
+ {
+ tappedIndex = 0;
+ }
+
+ _pickerViewInfo.UpdateSelectedIndexValue(tappedIndex, _column._columnIndex, isInitialLoading);
+ }
+
+ #endregion
+}
\ No newline at end of file
diff --git a/maui/src/Picker/Views/PickerLayout/PickerScrollView.cs b/maui/src/Picker/Views/PickerLayout/PickerScrollView.cs
new file mode 100644
index 00000000..13bcc786
--- /dev/null
+++ b/maui/src/Picker/Views/PickerLayout/PickerScrollView.cs
@@ -0,0 +1,716 @@
+using System.Collections.ObjectModel;
+using System.ComponentModel;
+using Syncfusion.Maui.Toolkit.Internals;
+
+namespace Syncfusion.Maui.Toolkit.Picker
+{
+ ///
+ /// This represents a class that contains information about the picker scroll view.
+ ///
+ internal class PickerScrollView : SfPickerView
+ {
+ #region Fields
+
+ ///
+ /// The picker view info.
+ ///
+ readonly IPickerLayout _pickerLayoutInfo;
+
+ ///
+ /// The picker info.
+ ///
+ readonly IPicker _pickerInfo;
+
+ ///
+ /// The picker view .
+ ///
+ readonly PickerView _pickerView;
+
+ ///
+ /// Holds the measure height value because on windows, while change mode from default(resize the height) to dialog native change, base measure automatically change the scroll position, so it moves to other positions, so checking the content size before the scroll to fix the issue.
+ ///
+ double _availableHeight = 0;
+
+#if __ANDROID__
+
+ ///
+ /// Touch end position value on scroll end.
+ ///
+ double _touchEndPosition = -1;
+
+ ///
+ /// Timer used to trigger scroll end based on item height.
+ ///
+ IDispatcherTimer? _timer;
+#endif
+
+ #endregion
+
+ #region Constructor
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The picker layout info.
+ /// The picker view info.
+ /// The items source.
+ internal PickerScrollView(IPickerLayout pickerLayoutInfo, IPicker pickerViewInfo, ObservableCollection itemsSource)
+ {
+ _pickerInfo = pickerViewInfo;
+ _pickerLayoutInfo = pickerLayoutInfo;
+ //// No need to show the scroll bar in picker control so that the scroll bar visibility set to be as never.
+ VerticalScrollBarVisibility = ScrollBarVisibility.Never;
+ _pickerView = new PickerView(_pickerLayoutInfo, pickerViewInfo, itemsSource);
+ //// This event is triggered while the scroll view is scrolling.
+ //// Using this event to update the selected and unselected text style when the scroll view is scrolling.
+ Scrolled += OnViewScrolling;
+ Content = _pickerView;
+ PropertyChanged += OnScrollPropertyChanged;
+ }
+
+ #endregion
+
+ #region Internal Methods
+
+ ///
+ /// Method to update the selected index changed.
+ ///
+ internal void UpdateSelectedIndexValue()
+ {
+ if (!_pickerLayoutInfo.PickerInfo.IsValidParent)
+ {
+ Dispatcher.Dispatch(() =>
+ {
+#if __ANDROID__
+ RequestLayout();
+#endif
+ UpdateSelectedIndex();
+ });
+ }
+ else
+ {
+#if __ANDROID__
+ RequestLayout();
+#endif
+ UpdateSelectedIndex();
+ }
+ }
+
+ ///
+ /// Method to update the items source.
+ ///
+ /// The items source.
+ internal void UpdateItemsSource(ObservableCollection itemsSource)
+ {
+ _pickerView.UpdateItemsSource(itemsSource);
+ InvalidateMeasure();
+ if (!_pickerLayoutInfo.PickerInfo.IsValidParent)
+ {
+ Dispatcher.Dispatch(() =>
+ {
+#if __ANDROID__
+ RequestLayout();
+#endif
+ UpdateSelectedIndex();
+ });
+ }
+ else
+ {
+#if __ANDROID__
+ RequestLayout();
+#endif
+ UpdateSelectedIndex();
+ }
+ }
+
+ ///
+ /// Method to update the picker scroll view.
+ ///
+ internal void InvalidatePickerViewDraw()
+ {
+ _pickerView.InvalidatePickerViewDraw();
+ }
+
+ ///
+ /// Method to update the item height.
+ ///
+ internal void UpdateItemHeight()
+ {
+ _pickerView.UpdateItemHeight();
+ TriggerPickerScrollViewMeasure();
+ double itemHeight = _pickerLayoutInfo.PickerInfo.ItemHeight;
+ int itemsCount = PickerHelper.GetItemsCount(_pickerLayoutInfo.Column.ItemsSource);
+ int selectedIndex = PickerHelper.GetValidSelectedIndex(_pickerLayoutInfo.Column.SelectedIndex, itemsCount);
+ ScrollToAsync(0, selectedIndex * itemHeight, false);
+ }
+
+ ///
+ /// Method to update the picker item template changes.
+ ///
+ internal void UpdateItemTemplate()
+ {
+ _pickerView.UpdateItemTemplate();
+ }
+
+ ///
+ /// Unwire the events.
+ ///
+ internal void Dispose()
+ {
+ Scrolled -= OnViewScrolling;
+ PropertyChanged -= OnScrollPropertyChanged;
+ if (Content != null)
+ {
+ Content = null;
+ }
+ }
+
+ ///
+ /// Method to update the keyboard interaction.
+ ///
+ internal void UpdateKeyboardViewFocus()
+ {
+ _pickerView.UpdatePickerFocus();
+ }
+
+ #endregion
+
+ #region Private Methods
+
+ ///
+ /// Method to call scroll view is scrolling.
+ ///
+ /// The sender.
+ /// The scroll event args.
+ void OnViewScrolling(object? sender, ScrolledEventArgs e)
+ {
+ double itemHeight = _pickerLayoutInfo.PickerInfo.ItemHeight;
+ //// Assume the scrollY position is 109 and the item height is 20, While using Math.Round then the selectionIndex is 109 / 20 = 5.4 => 5(SelectionIndex).
+ //// Assume the scrollY position is 110 and the item height is 20, While using Math.Round then the selectionIndex is 110 / 20 = 5.5 => 6(SelectionIndex).
+ int selectionIndex = (int)Math.Round(e.ScrollY / itemHeight);
+ int itemsCount = PickerHelper.GetItemsCount(_pickerLayoutInfo.Column.ItemsSource);
+ selectionIndex = PickerHelper.GetValidSelectedIndex(selectionIndex, itemsCount);
+ if (UpdateBlackoutSelection())
+ {
+ return;
+ }
+
+ _pickerView.UpdateSelectedIndexValue(selectionIndex);
+ }
+
+#if __ANDROID__
+ ///
+ /// Triggered while the touch completed.
+ ///
+ void OnTouchCompleted()
+ {
+ double itemHeight = _pickerLayoutInfo.PickerInfo.ItemHeight;
+ if (_timer == null)
+ {
+ _timer = Dispatcher.CreateTimer();
+ _timer.Interval = TimeSpan.FromMilliseconds(50);
+ }
+
+ _timer.Tick += (s, e) =>
+ {
+ if (_touchEndPosition == ScrollY)
+ {
+ double selectionIndex = Math.Round(_touchEndPosition / itemHeight);
+ //// From above example the selectionIndex is 5. So that the current item position is 5 * 20 = 100.
+ double currentItemPosition = selectionIndex * itemHeight;
+ //// From above example the scrollEndPosition is 109. So that the positionDifference is 109 - 100 = 9.
+ double positionDifference = _touchEndPosition - currentItemPosition;
+ //// For windows, While using custom animation for windows, triggers scroll event. So that default scroll to async method is used.
+ //// For other than windows, While scroll view fast scrolling and we started new scrolling before end of the previous scrolling then the scroll view is not scrolled to the exact position.
+ // Method to update the position based on animation value.
+ void UpdateProperty(double value)
+ {
+ //// From above example the positionDifference is 9. So that the center position is 109 - (9 * 1) = 100. So that the scroll position is 100.
+ double centerPosition = _touchEndPosition - (positionDifference * value);
+ //// From above example the center position is 100. So that the scroll position is 100.
+ //// The scroll to async does not trigger the scroll event. Because in android we handled scroll using touch event.
+ //// In iOS and Mac we handled scroll end using drag end event.
+ ScrollToAsync(0, centerPosition, false);
+ }
+
+ void Finished(double value, bool isFinished)
+ {
+ _pickerLayoutInfo.UpdateSelectedIndexValue((int)selectionIndex);
+ }
+
+ if (_pickerLayoutInfo.PickerInfo.IsValidParent)
+ {
+ //// Animation duration is 100 milliseconds for smooth animation.
+ AnimationExtensions.Animate(this, "Scrolled", UpdateProperty, 0, 1, 16, 100U, Easing.Linear, Finished);
+ }
+ else
+ {
+ ScrollToAsync(0, currentItemPosition, false);
+ RequestLayout();
+ _pickerLayoutInfo.UpdateSelectedIndexValue((int)selectionIndex);
+ }
+
+ _timer.Stop();
+ _timer = null;
+ }
+ else
+ {
+ _touchEndPosition = ScrollY;
+ }
+ };
+
+ _timer.Start();
+ }
+
+ ///
+ /// Method used to request layout while the scroll view is scrolled. because the
+ /// scroll view does not scroll properly while the previous scroll does not end correctly.
+ /// Example, if dialog is closed while the scroll view scrolling on fast fling then the
+ /// scroll view does not scroll to the exact position on first time due to incomplete previous scroll.
+ ///
+ void RequestLayout()
+ {
+ if (Handler != null && Handler.PlatformView != null && Handler.PlatformView is NativeCustomScrolLayout scrolLayout)
+ {
+ scrolLayout.RequestLayout();
+ }
+ }
+#endif
+
+ ///
+ /// Method to update the selected index changed while the scroll view content size is greater than item source needed size.
+ ///
+ void UpdateSelectedIndex()
+ {
+ double itemHeight = _pickerLayoutInfo.PickerInfo.ItemHeight;
+ //// While scrolled the scroll position is updated in UI but not update selected index. After scroll position updated in UI then the selected index changed based on the scroll position and its property changed event will triggered.
+ //// In this case no need to update the scroll position again.
+ int scrolledIndex = (int)Math.Round(ScrollY / itemHeight);
+ int itemsCount = PickerHelper.GetItemsCount(_pickerLayoutInfo.Column.ItemsSource);
+ int selectedIndex = PickerHelper.GetValidSelectedIndex(_pickerLayoutInfo.Column.SelectedIndex, itemsCount);
+ if (selectedIndex == scrolledIndex)
+ {
+ return;
+ }
+
+ double totalContentSize = (itemsCount * itemHeight) + DesiredSize.Height - itemHeight;
+ double scrollPosition = selectedIndex * itemHeight;
+ double contentHeight = Math.Floor(ContentSize.Height);
+ if (contentHeight < Math.Floor(totalContentSize) && contentHeight - DesiredSize.Height <= Math.Ceiling(scrollPosition))
+ {
+ return;
+ }
+
+ //// No need to animate the scroll position while changing the selected index value.
+ ScrollToAsync(0, scrollPosition, false);
+ }
+
+ ///
+ /// Method to update the selected scroll position on initial scrolling and content size changed.
+ ///
+ void UpdateSelectedIndexPosition()
+ {
+ if (UpdateBlackoutSelection())
+ {
+ return;
+ }
+
+ double itemHeight = _pickerLayoutInfo.PickerInfo.ItemHeight;
+ int itemsCount = PickerHelper.GetItemsCount(_pickerLayoutInfo.Column.ItemsSource);
+ int selectedIndex = PickerHelper.GetValidSelectedIndex(_pickerLayoutInfo.Column.SelectedIndex, itemsCount);
+ //// Check if any column selected index is initially set to -1 or less than -1, and change the other column selected index as -1.
+ if (_pickerLayoutInfo.Column.SelectedIndex <= -1)
+ {
+ for (int index = 0; index < _pickerInfo.Columns.Count; index++)
+ {
+ _pickerInfo.Columns[index].SelectedIndex = -1;
+ }
+ }
+ //// Check the selected item is null and selected index has default value and change the selected index value.
+ else if (_pickerLayoutInfo.Column.SelectedIndex == 0 && _pickerLayoutInfo.Column.SelectedItem == null)
+ {
+ if (_pickerLayoutInfo.Column.Parent == null)
+ {
+ _pickerLayoutInfo.Column.SelectedIndex = -1;
+ }
+ }
+ //// Check the default value of selected index and update the index based on selected item.
+ else if (_pickerLayoutInfo.Column.SelectedIndex == 0 && _pickerLayoutInfo.Column.SelectedItem != null)
+ {
+ int valueCount = PickerHelper.GetSelectedItemIndex(_pickerLayoutInfo.Column);
+ selectedIndex = PickerHelper.GetValidSelectedIndex(valueCount, itemsCount);
+ _pickerLayoutInfo.Column.SelectedIndex = selectedIndex;
+ }
+
+ double newScrollPosition = selectedIndex * itemHeight;
+ //// Checking position rather than index because switching to default mode and change screen height and again move into dialog mode then the selected item does not shown correctly center of selection highlight.
+ if (newScrollPosition == ScrollY)
+ {
+ return;
+ }
+
+ //// No need to animate the scroll position while changing the selected index value.
+ ScrollToAsync(0, newScrollPosition, false);
+ }
+
+ ///
+ /// Triggered whenever the scroll view property changed. Used to scroll after the content size changed.
+ ///
+ /// The scroll view instance.
+ /// Property changed event argument.
+ void OnScrollPropertyChanged(object? sender, PropertyChangedEventArgs e)
+ {
+ if (e.PropertyName != nameof(ContentSize))
+ {
+ return;
+ }
+
+ double itemHeight = _pickerLayoutInfo.PickerInfo.ItemHeight;
+ int itemsCount = PickerHelper.GetItemsCount(_pickerLayoutInfo.Column.ItemsSource);
+ double totalContentSize = (itemsCount * itemHeight) + DesiredSize.Height - itemHeight;
+ double availableContentSize = Math.Floor(ContentSize.Height);
+ if (availableContentSize - 1 > totalContentSize || availableContentSize + 1 < totalContentSize)
+ {
+ return;
+ }
+
+ Dispatcher.Dispatch(() =>
+ {
+ UpdateSelectedIndexPosition();
+ });
+ }
+
+ ///
+ /// Method to Check and update selection based on blackout value.
+ ///
+ bool UpdateBlackoutSelection()
+ {
+ //// Since black out selection not applicable.
+ if (_pickerLayoutInfo.PickerInfo is SfPicker)
+ {
+ return false;
+ }
+
+ //// Checks the current selected time is black out date or not to allow scrolling.
+ if (_pickerLayoutInfo.PickerInfo is SfDatePicker datePicker)
+ {
+ if (datePicker.SelectedDate != null && datePicker.BlackoutDates.Any(blackOutDate => blackOutDate.Date == datePicker.SelectedDate.Value.Date))
+ {
+ //// If the selected date is blackout date and is within the current month it reverts to previous selected date.
+ //// In else case, it will increment the selected date by one day until it finds a non-blackout date.
+ //// also if it reaches the end of the month it will get reset to the start of the month.
+ if (datePicker.SelectedDate.Value.Month == datePicker._previousSelectedDateTime.Date.Month && datePicker.SelectedDate.Value.Year == datePicker._previousSelectedDateTime.Date.Year)
+ {
+ datePicker.SelectedDate = datePicker._previousSelectedDateTime.Date;
+ }
+ else
+ {
+ DateTime selectedDate = datePicker.SelectedDate.Value.AddDays(datePicker.DayInterval);
+ selectedDate = selectedDate > datePicker.MaximumDate ? datePicker.MaximumDate : selectedDate;
+ bool isDayReset = false;
+ while (datePicker.BlackoutDates.Any(blackOutDate => blackOutDate.Date == selectedDate.Date))
+ {
+ //// Increase the date by day interval.
+ selectedDate = selectedDate.AddDays(datePicker.DayInterval);
+
+ if (selectedDate.Month != datePicker.SelectedDate.Value.Month)
+ {
+ //// To avoid the infinite loop while the blackout date is set for the whole month.
+ if (!isDayReset)
+ {
+ break;
+ }
+
+ //// Reset the date to the start of the month based on minimum Date.
+ selectedDate = new DateTime(selectedDate.Year, selectedDate.Month, datePicker.MinimumDate.Month == selectedDate.Month ? datePicker.MinimumDate.Day : 1);
+ isDayReset = true;
+ }
+
+ //// Reset the date to the start of the month based on maximum Date.
+ if (selectedDate > datePicker.MaximumDate)
+ {
+ selectedDate = new DateTime(selectedDate.Year, selectedDate.Month, datePicker.MinimumDate.Month == selectedDate.Month ? datePicker.MinimumDate.Day : 1);
+ }
+ }
+
+ datePicker.SelectedDate = selectedDate;
+ }
+
+ return true;
+ }
+ }
+
+ //// Checks the current selected time is black out time or not to allow scrolling.
+ if (_pickerLayoutInfo.PickerInfo is SfTimePicker timePicker)
+ {
+ if (timePicker.SelectedTime != null && timePicker.BlackoutTimes.Any(blackOutTime => blackOutTime.Hours == timePicker.SelectedTime.Value.Hours && blackOutTime.Minutes == timePicker.SelectedTime.Value.Minutes))
+ {
+ //// If the selected time is blackout time and is within the current hour it reverts to previous selected time.
+ //// In else case, it will increment the selected time by one minute until it finds a non-blackout time.
+ //// also if it reaches the end of the hour it will get reset to the start of the hour.
+ if (timePicker.SelectedTime.Value.Hours == timePicker._previousSelectedDateTime.Hour)
+ {
+ timePicker.SelectedTime = timePicker._previousSelectedDateTime.TimeOfDay;
+ }
+ else
+ {
+ TimeSpan selectedTime = timePicker.SelectedTime.Value.Add(TimeSpan.FromMinutes(timePicker.MinuteInterval));
+ selectedTime = selectedTime > timePicker.MaximumTime ? timePicker.MaximumTime : selectedTime;
+ bool isHourReset = false;
+ while (timePicker.BlackoutTimes.Any(blackOutTime => blackOutTime.Hours == selectedTime.Hours && blackOutTime.Minutes == selectedTime.Minutes))
+ {
+ selectedTime = selectedTime.Add(TimeSpan.FromMinutes(timePicker.MinuteInterval));
+ if (selectedTime.Hours != timePicker.SelectedTime.Value.Hours)
+ {
+ //// To avoid the infinite loop while the blackout time is set for the whole hour.
+ if (isHourReset)
+ {
+ break;
+ }
+
+ //// Reset the time to the start of the hour based on minimum time.
+ selectedTime = new TimeSpan(selectedTime.Hours, timePicker.MinimumTime.Hours == selectedTime.Hours ? timePicker.MinimumTime.Hours : 0, selectedTime.Seconds);
+ isHourReset = true;
+ }
+
+ //// Reset the time to the start of the hour based on maximum time.
+ if (selectedTime > timePicker.MaximumTime)
+ {
+ selectedTime = new TimeSpan(selectedTime.Hours, timePicker.MinimumTime.Hours == selectedTime.Hours ? timePicker.MinimumTime.Hours : 0, selectedTime.Seconds);
+ }
+ }
+
+ timePicker.SelectedTime = selectedTime;
+ }
+
+ return true;
+ }
+ }
+
+ //// Checks the current selected time is black out datetime or not to allow scrolling.
+ if (_pickerLayoutInfo.PickerInfo is SfDateTimePicker dateTimePicker)
+ {
+ bool isTimeSpanAtZero = false;
+ if (dateTimePicker.SelectedDate != null && dateTimePicker.BlackoutDateTimes.Any(blackOutDateTime => DatePickerHelper.IsBlackoutDateTime(blackOutDateTime, dateTimePicker.SelectedDate, out isTimeSpanAtZero)))
+ {
+ //// For calculating the blackout date time, if the whole date is black out value the if case will get executed.
+ //// If only the time is black out value the else case will get executed
+ if (isTimeSpanAtZero)
+ {
+ //// If the selected date time is blackout value and is within the current month it reverts to previous selected date time.
+ //// In else case, it will increment the selected date time by one day until it finds a non-blackout date time.
+ //// also if it reaches the end of the month it will get reset to the start of the month.
+ if (dateTimePicker.SelectedDate.Value.Month == dateTimePicker._previousSelectedDateTime.Month && dateTimePicker.SelectedDate.Value.Year == dateTimePicker._previousSelectedDateTime.Year)
+ {
+ dateTimePicker.SelectedDate = dateTimePicker._previousSelectedDateTime;
+ }
+ else
+ {
+ DateTime selectedDate = dateTimePicker.SelectedDate.Value.AddDays(dateTimePicker.DayInterval);
+ selectedDate = selectedDate > dateTimePicker.MaximumDate ? dateTimePicker.MaximumDate : selectedDate;
+ bool isDayReset = false;
+ while (dateTimePicker.BlackoutDateTimes.Any(blackOutDateTime => DatePickerHelper.IsBlackoutDateTime(blackOutDateTime, selectedDate, out bool isTimeSpanZero)))
+ {
+ selectedDate = selectedDate.AddDays(dateTimePicker.DayInterval);
+ if (selectedDate.Month != dateTimePicker.SelectedDate.Value.Month)
+ {
+ //// To avoid the infinite loop while the blackout date is set for the whole month.
+ if (isDayReset)
+ {
+ break;
+ }
+
+ selectedDate = new DateTime(selectedDate.Year, selectedDate.Month, dateTimePicker.MinimumDate.Month == dateTimePicker.MinimumDate.Month ? dateTimePicker.MinimumDate.Day : 1);
+ isDayReset = true;
+ }
+
+ //// Reset the date to the start of the month based on maximum date.
+ if (selectedDate > dateTimePicker.MaximumDate)
+ {
+ selectedDate = new DateTime(selectedDate.Year, selectedDate.Month, dateTimePicker.MinimumDate.Month == dateTimePicker.MinimumDate.Month ? dateTimePicker.MinimumDate.Day : 1);
+ }
+ }
+
+ dateTimePicker.SelectedDate = selectedDate;
+ }
+ }
+ else
+ {
+ //// If the selected date time is blackout value and is within the current hour it reverts to previous selected time.
+ //// In else case, it will increment the selected date time by one minute until it finds a non-blackout date time.
+ //// also if it reaches the end of the hour it will get reset to the start of the hour.
+ if (dateTimePicker.SelectedDate.Value.Hour == dateTimePicker._previousSelectedDateTime.Hour)
+ {
+ dateTimePicker.SelectedDate = dateTimePicker._previousSelectedDateTime;
+ }
+ else
+ {
+ DateTime selectedTime = dateTimePicker.SelectedDate.Value.AddMinutes(dateTimePicker.MinuteInterval);
+ selectedTime = selectedTime > dateTimePicker.MaximumDate ? dateTimePicker.MaximumDate : selectedTime;
+ bool isHourReset = false;
+ while (dateTimePicker.BlackoutDateTimes.Any(blackOutDateTime => DatePickerHelper.IsBlackoutDateTime(blackOutDateTime, selectedTime, out bool isTimeSpanZero)))
+ {
+ selectedTime = selectedTime.AddMinutes(dateTimePicker.MinuteInterval);
+ if (selectedTime.Hour != dateTimePicker.SelectedDate.Value.Hour)
+ {
+ //// To avoid the infinite loop while the blackout time is set for the whole hour.
+ if (isHourReset)
+ {
+ break;
+ }
+
+ selectedTime = new DateTime(selectedTime.Year, selectedTime.Month, selectedTime.Day, selectedTime.Hour, dateTimePicker.MinimumDate.Hour == selectedTime.Hour ? dateTimePicker.MinimumDate.Hour : 0, selectedTime.Second);
+ isHourReset = true;
+ }
+
+ //// Reset the time to the start of the hour based on maximum time.
+ if (selectedTime > dateTimePicker.MaximumDate)
+ {
+ selectedTime = new DateTime(selectedTime.Year, selectedTime.Month, selectedTime.Day, selectedTime.Hour, dateTimePicker.MinimumDate.Hour == selectedTime.Hour ? dateTimePicker.MinimumDate.Hour : 0, selectedTime.Second);
+ }
+ }
+
+ dateTimePicker.SelectedDate = selectedTime;
+ }
+ }
+
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ ///
+ /// Method to update the picker scroll view measure.
+ ///
+ void TriggerPickerScrollViewMeasure()
+ {
+ InvalidateMeasure();
+ }
+
+ #endregion
+
+ #region Internal Override Methods
+
+ ///
+ /// Method to check the scroll view scroll started through interaction.
+ ///
+ internal override void OnPickerViewScrollStart()
+ {
+#if __ANDROID__
+ if (_timer != null)
+ {
+ _timer.Stop();
+ _timer = null;
+ }
+#endif
+ }
+
+ ///
+ /// Method to update the scroll view scroll position when the scroll ends.
+ ///
+ /// The scroll end position.
+ internal override void OnPickerViewScrollEnd(double scrollEndPosition)
+ {
+#if __ANDROID__
+ _touchEndPosition = scrollEndPosition;
+ OnTouchCompleted();
+ return;
+#else
+ double itemHeight = _pickerLayoutInfo.PickerInfo.ItemHeight;
+ int itemsCount = PickerHelper.GetItemsCount(_pickerLayoutInfo.Column.ItemsSource);
+ double totalContentSize = (itemsCount * itemHeight) + _availableHeight - itemHeight;
+ //// On windows, while change mode from default(resize the height) to dialog native change, base measure automatically change the scroll position, so it moves to other positions, so checking the content size before the scroll to fix the issue.
+ double availableContentSize = Math.Floor(ContentSize.Height);
+ if (availableContentSize - 1 > totalContentSize || availableContentSize + 1 < totalContentSize)
+ {
+ return;
+ }
+
+ if (UpdateBlackoutSelection())
+ {
+ return;
+ }
+
+ //// Assume the scrollEndPosition is 109 and the item height is 20, While using Math.Round then the selectionIndex is 109 / 20 = 5.4 => 5(SelectionIndex).
+ //// Assume the scrollEndPosition is 110 and the item height is 20, While using Math.Round then the selectionIndex is 110 / 20 = 5.5 => 6(SelectionIndex).
+ double selectionIndex = Math.Round(scrollEndPosition / itemHeight);
+ //// From above example the selectionIndex is 5. So that the current item position is 5 * 20 = 100.
+ double currentItemPosition = selectionIndex * itemHeight;
+ //// From above example the scrollEndPosition is 109. So that the positionDifference is 109 - 100 = 9.
+ double positionDifference = scrollEndPosition - currentItemPosition;
+ //// While scroll view fast scrolling and we started new scrolling before end of the previous scrolling then the scroll view is not scrolled to the exact position.
+ //// Method to update the position based on animation value.
+ void UpdateProperty(double value)
+ {
+ //// From above example the positionDifference is 9. So that the center position is 109 - (9 * 1) = 100. So that the scroll position is 100.
+ double centerPosition = scrollEndPosition - (positionDifference * value);
+ //// From above example the center position is 100. So that the scroll position is 100.
+ //// The scroll to async does not trigger the scroll event. Because in android we handled scroll using touch event.
+ //// In iOS and Mac we handled scroll end using drag end event.
+ ScrollToAsync(0, centerPosition, false);
+ }
+
+ void Finished(double value, bool isFinished)
+ {
+ _pickerLayoutInfo.UpdateSelectedIndexValue((int)selectionIndex);
+ _pickerView.UpdateSelectedIndexOnAnimationEnd((int)selectionIndex);
+ }
+
+ //// Animation duration is 100 milliseconds for smooth animation.
+ AnimationExtensions.Animate(this, "Scrolled", UpdateProperty, 0, 1, 16, 100U, Easing.Linear, Finished);
+#endif
+ }
+
+ #endregion
+
+ #region Override Methods
+
+ ///
+ /// Method to measures child elements size in a container, scroll view is measured with given width and height constraints.
+ ///
+ /// The maximum width request of the layout.
+ /// The maximum height request of the layout.
+ /// Returns size of the scroll view.
+ protected override Size MeasureOverride(double widthConstraint, double heightConstraint)
+ {
+#if MACCATALYST || IOS
+ if (DesiredSize.Width == widthConstraint && DesiredSize.Height == heightConstraint)
+ {
+ return new Size(widthConstraint, heightConstraint);
+ }
+#endif
+
+ bool isContentViewPortChanged = _pickerView.UpdatePickerViewPortHeight(heightConstraint);
+ _availableHeight = heightConstraint;
+ if (isContentViewPortChanged)
+ {
+ //// If the content view port size changed then the content measure will not triggered. So that the content measure is triggered manually.
+ Content.Handler?.GetDesiredSize(widthConstraint, heightConstraint);
+ }
+
+ base.MeasureOverride(widthConstraint, heightConstraint);
+ DesiredSize = new Size(widthConstraint, heightConstraint);
+
+ double itemHeight = _pickerLayoutInfo.PickerInfo.ItemHeight;
+ int itemsCount = PickerHelper.GetItemsCount(_pickerLayoutInfo.Column.ItemsSource);
+ double totalContentSize = (itemsCount * itemHeight) + DesiredSize.Height - itemHeight;
+ double availableContentSize = Math.Floor(ContentSize.Height);
+ //// Need to scroll selected index value while content size is same, content size will be assigned while handling visibility.
+ //// Eg., if user shown the picker and dynamically change the visibility and update the selected index then the scroll view does not update the UI,
+ //// so update the scroll position while the UI visible. Measure will trigger while the UI visibility changed.
+ if (availableContentSize - 1 <= totalContentSize && availableContentSize + 1 >= totalContentSize)
+ {
+ Dispatcher.Dispatch(() =>
+ {
+ UpdateSelectedIndexPosition();
+ });
+ }
+
+ return new Size(widthConstraint, heightConstraint);
+ }
+
+ #endregion
+ }
+}
\ No newline at end of file
diff --git a/maui/src/Picker/Views/PickerLayout/PickerView.cs b/maui/src/Picker/Views/PickerLayout/PickerView.cs
new file mode 100644
index 00000000..d4ec9164
--- /dev/null
+++ b/maui/src/Picker/Views/PickerLayout/PickerView.cs
@@ -0,0 +1,744 @@
+using System.Collections.ObjectModel;
+using Syncfusion.Maui.Toolkit.Internals;
+using Syncfusion.Maui.Toolkit.Graphics.Internals;
+
+namespace Syncfusion.Maui.Toolkit.Picker
+{
+ ///
+ /// This represents a class that contains information about the picker view.
+ ///
+ internal class PickerView : SfView, ITapGestureListener, IKeyboardListener
+ {
+ #region Fields
+
+ ///
+ /// The picker view info.
+ ///
+ readonly IPickerLayout _pickerLayoutInfo;
+
+ ///
+ /// The picker info.
+ ///
+ readonly IPicker _pickerViewInfo;
+
+ ///
+ /// The picker view port height.
+ /// Here -2 values is used to restrict the initial viewportSize assigning.
+ ///
+ double _pickerViewPortHeight = -2;
+
+ ///
+ /// The selected index holds the current selected index based on the current scroll position.
+ /// It is used to restricted the unwanted drawing of the item source element while scrolling.
+ ///
+ int _selectedIndex = 0;
+
+ ///
+ /// This initial node top position is used to store the starting position of first picker item of y axis to generate the picker item semantic node.
+ ///
+ double _initialNodeTopPosition = 0;
+
+ ///
+ /// The items source.
+ ///
+ ObservableCollection _itemsSource;
+
+ ///
+ /// Holds the picker item drawing view based on picker view height.
+ ///
+ PickerDrawableView? _pickerDrawableView;
+
+ ///
+ /// Gets or sets the virtual picker items semantic nodes.
+ ///
+ List? _semanticsNodes;
+
+ ///
+ /// Gets or sets the size of the semantic.
+ ///
+ Size _semanticsSize = Size.Zero;
+
+ #endregion
+
+ #region Constructor
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The picker layout info.
+ /// The items source.
+ /// The picker info.
+ internal PickerView(IPickerLayout pickerLayoutInfo, IPicker pickerViewInfo, ObservableCollection itemsSource)
+ {
+ this.AddGestureListener(this);
+ this.AddKeyboardListener(this);
+ _pickerLayoutInfo = pickerLayoutInfo;
+ _pickerViewInfo = pickerViewInfo;
+ _itemsSource = itemsSource;
+#if __IOS__ || __MACCATALYST__
+ DrawingOrder = DrawingOrder.BelowContent;
+#else
+ DrawingOrder = DrawingOrder.AboveContentWithTouch;
+#endif
+#if WINDOWS
+ //// If the SfView drawing canvas size exceeds MaximumBitmapSizeInPixels when adding more items,
+ //// an OS limitation with CanvasImageSource size (refer: https://github.com/dotnet/maui/issues/3785)
+ //// requires restricting the draw function when semantics or accessibility are used on SfView. This prevents OS limitation issues on the Windows platform.
+ //// For accessibility, SfView should be enabled with AboveContentWithTouch to add a native user control and override the AutomationPeer.
+ //// Virtualization isn't possible because automation peers must be added initially to access scrollable content.
+ //// Since accessibility highlights are managed by native framework automation peers, SfView canvas drawing is unnecessary.
+ IsCanvasNeeded = false;
+#endif
+ if (_pickerLayoutInfo.PickerInfo.ItemTemplate != null)
+ {
+ GeneratePickerItemsTemplate();
+ }
+ else
+ {
+ _pickerDrawableView = new PickerDrawableView(pickerLayoutInfo, this, itemsSource, GetStartingIndex);
+ Add(_pickerDrawableView);
+ }
+
+ Focus();
+ }
+
+ #endregion
+
+ #region Internal Methods
+
+ ///
+ /// Method to update the view port height while scroll view height is changed.
+ ///
+ /// The view port height.
+ /// Return true while the view port size changed.
+ internal bool UpdatePickerViewPortHeight(double viewPortHeight)
+ {
+ bool isSizeUpdated = _pickerViewPortHeight != -2;
+ if (_pickerViewPortHeight == viewPortHeight)
+ {
+ return false;
+ }
+
+ _pickerViewPortHeight = viewPortHeight;
+ return isSizeUpdated;
+ }
+
+ ///
+ /// Method to update the selected index.
+ ///
+ /// The index.
+ internal void UpdateSelectedIndexValue(int index)
+ {
+ if (index == _selectedIndex)
+ {
+ return;
+ }
+
+ _selectedIndex = index;
+
+ if (_pickerLayoutInfo.PickerInfo.ItemTemplate != null)
+ {
+ return;
+ }
+
+ int maximumViewPortCount = GetMaximumViewPort();
+ double maximumViewPortHeight = maximumViewPortCount * _pickerLayoutInfo.PickerInfo.ItemHeight;
+ if (maximumViewPortHeight + GetViewPortHeight() - _pickerLayoutInfo.PickerInfo.ItemHeight < Height)
+ {
+ ArrangeContent(new Rect(0, 0, Width, Height));
+ }
+
+ _pickerDrawableView?.UpdateSelectedIndexValue(index);
+ }
+
+ ///
+ /// Method to update the selected index on animation end.
+ ///
+ /// The index.
+ internal void UpdateSelectedIndexOnAnimationEnd(int index)
+ {
+ _selectedIndex = index;
+ if (_pickerLayoutInfo.PickerInfo.ItemTemplate != null)
+ {
+ return;
+ }
+
+ int maximumViewPortCount = GetMaximumViewPort();
+ double maximumViewPortHeight = maximumViewPortCount * _pickerLayoutInfo.PickerInfo.ItemHeight;
+ if (maximumViewPortHeight + GetViewPortHeight() - _pickerLayoutInfo.PickerInfo.ItemHeight < Height)
+ {
+ ArrangeContent(new Rect(0, 0, Width, Height));
+ }
+
+ _pickerDrawableView?.UpdateSelectedIndexOnAnimationEnd(index);
+ }
+
+ ///
+ /// Method to update the items source.
+ ///
+ /// The items source.
+ internal void UpdateItemsSource(ObservableCollection itemsSource)
+ {
+ _itemsSource = itemsSource;
+ if (_pickerLayoutInfo.PickerInfo.ItemTemplate == null)
+ {
+ _pickerDrawableView?.UpdateItemsSource(itemsSource);
+ InvalidateMeasure();
+ }
+ else
+ {
+ DataTemplate template = _pickerLayoutInfo.PickerInfo.ItemTemplate;
+ if (template is DataTemplateSelector)
+ {
+ Children.Clear();
+ GeneratePickerItemsTemplate();
+ InvalidateMeasure();
+ }
+ else
+ {
+ //// Here we are using the childCount variable to update binding context for only the needed views.
+ int childCount = 0;
+ if (_itemsSource.Count > Children.Count)
+ {
+ //// Here we are storing the count of the child before adding the new views.
+ //// And using this to update the binding context for the old views.
+ childCount = Children.Count;
+ for (int i = Children.Count; i < _itemsSource.Count; i++)
+ {
+ PickerItemDetails itemDetails = GetColumnDetails(i);
+ View view = PickerHelper.CreateTemplateView(template, itemDetails);
+ Add(view);
+ }
+ }
+ else if (_itemsSource.Count < Children.Count)
+ {
+ for (int i = Children.Count - 1; i >= _itemsSource.Count; i--)
+ {
+ Children.RemoveAt(i);
+ }
+
+ //// Here we are storing the count of the child after removing the views and using it to update the binding context for the old views.
+ childCount = Children.Count;
+ }
+
+ for (int i = 0; i < childCount; i++)
+ {
+ View view = (View)Children[i];
+ PickerItemDetails itemDetails = GetColumnDetails(i);
+ view.BindingContext = itemDetails;
+ }
+
+ InvalidateMeasure();
+ }
+ }
+ }
+
+ ///
+ /// Method to update the picker item template changes.
+ ///
+ internal void UpdateItemTemplate()
+ {
+ if (_pickerLayoutInfo.PickerInfo.ItemTemplate != null)
+ {
+ if (_pickerDrawableView != null)
+ {
+ Remove(_pickerDrawableView);
+ }
+
+ Children.Clear();
+ GeneratePickerItemsTemplate();
+ InvalidateMeasure();
+ }
+ else
+ {
+ Children.Clear();
+ _pickerDrawableView = new PickerDrawableView(_pickerLayoutInfo, this, _itemsSource, GetStartingIndex);
+ Add(_pickerDrawableView);
+ InvalidateMeasure();
+ }
+ }
+
+ ///
+ /// Method to redraw the scroll view.
+ ///
+ internal void InvalidatePickerViewDraw()
+ {
+ _pickerDrawableView?.UpdatePickerViewDraw();
+ }
+
+ ///
+ /// Method to update the picker item height.
+ ///
+ internal void UpdateItemHeight()
+ {
+ _pickerDrawableView?.UpdatePickerViewDraw();
+ InvalidateMeasure();
+ }
+
+ ///
+ /// Method to update the measure the picker view.
+ ///
+ internal void TriggerPickerViewMeasure()
+ {
+ InvalidateMeasure();
+ }
+
+ ///
+ /// Method to update the Focus for the keyboard interaction.
+ ///
+ internal void UpdatePickerFocus()
+ {
+ Focus();
+ }
+
+ ///
+ /// Method to get the picker item view port count.
+ ///
+ /// Returns the picker item count.
+ internal double GetPickerItemViewPortCount()
+ {
+ var itemHeight = _pickerLayoutInfo.PickerInfo.ItemHeight;
+ return Math.Round(GetViewPortHeight() / itemHeight);
+ }
+
+ #endregion
+
+ #region Private Methods
+
+#if __IOS__ || __MACCATALYST__
+
+ ///
+ /// Method to set the focus for layout changed to other view. Focus removes while new view generation or inner view gets focus.
+ /// Hence used delay to set focus.
+ ///
+ /// The focus delay in milliseconds.
+ async void SetFocus(int delay)
+ {
+ await Task.Delay(delay);
+ Focus();
+ }
+
+#endif
+
+ ///
+ /// Get the starting index.
+ ///
+ /// Return the starting index.
+ int GetStartingIndex()
+ {
+ //// The item height.
+ double itemHeight = _pickerLayoutInfo.PickerInfo.ItemHeight;
+ //// The view port item count is calculated based on the view port height and item height.
+ //// Assume the view port height is 109 and the item height is 20, While using Math.Round then the view port item count is 109 / 20 = 5.4 => 5(view port item count).
+ //// Assume the view port height is 110 and the item height is 20, While using Math.Round then the view port item count is 110 / 20 = 5.5 => 6(view port item count).
+ double viewPortItemCount = Math.Round(GetViewPortHeight() / itemHeight);
+ //// The maximum view port count is 3 times the view port item count because larger view port leads to crash with larger canvas size.
+ int maximumViewPortCount = (int)(viewPortItemCount * 3);
+ //// Calculate the last index based on item source count and maximum view port count.
+ double lastIndex = _itemsSource.Count - maximumViewPortCount;
+ lastIndex = lastIndex < 0 ? 0 : lastIndex;
+ //// Get the current scrolled item based on scroll offset.
+ int selectedIndex = (int)(_pickerLayoutInfo.ScrollOffset / itemHeight);
+ //// Calculate the virtualized drawable view start index based on view port count and selected index.
+ double startingIndex = selectedIndex - viewPortItemCount;
+ startingIndex = startingIndex < 0 ? 0 : startingIndex;
+ startingIndex = startingIndex > lastIndex ? lastIndex : startingIndex;
+ return (int)startingIndex;
+ }
+
+ ///
+ /// Get the maximum view port count.
+ ///
+ /// Return the maximum view port count.
+ int GetMaximumViewPort()
+ {
+ //// The item height.
+ double itemHeight = _pickerLayoutInfo.PickerInfo.ItemHeight;
+ //// The view port item count is calculated based on the view port height and item height.
+ //// Assume the view port height is 109 and the item height is 20, While using Math.Round then the view port item count is 109 / 20 = 5.4 => 5(view port item count).
+ //// Assume the view port height is 110 and the item height is 20, While using Math.Round then the view port item count is 110 / 20 = 5.5 => 6(view port item count).
+ double viewPortItemCount = Math.Round(GetViewPortHeight() / itemHeight);
+ //// The maximum view port count is 3 times the view port item count because larger view port leads to crash with larger canvas size.
+ int maximumViewPortCount = (int)(viewPortItemCount * 3);
+ return maximumViewPortCount;
+ }
+
+ ///
+ /// Method to generate the picker items template.
+ ///
+ void GeneratePickerItemsTemplate()
+ {
+ DataTemplate template = _pickerLayoutInfo.PickerInfo.ItemTemplate;
+ if (template is DataTemplateSelector itemTemplateSelector)
+ {
+ for (int index = 0; index < _itemsSource.Count; index++)
+ {
+ PickerItemDetails itemDetails = GetColumnDetails(index);
+ DataTemplate templateSelector = itemTemplateSelector.SelectTemplate(itemDetails, this);
+ CreatePickerTemplateView(templateSelector, itemDetails);
+ }
+ }
+ else
+ {
+ for (int index = 0; index < _itemsSource.Count; index++)
+ {
+ PickerItemDetails itemDetails = GetColumnDetails(index);
+ CreatePickerTemplateView(template, itemDetails);
+ }
+ }
+ }
+
+ ///
+ /// Method to create a template view for the picker items.
+ ///
+ /// The template.
+ /// The item details.
+ void CreatePickerTemplateView(DataTemplate template, PickerItemDetails itemDetails)
+ {
+ var itemTemplateView = PickerHelper.CreateTemplateView(template, itemDetails);
+ Add(itemTemplateView);
+ }
+
+ ///
+ /// Method to get the item details.
+ ///
+ /// The index.
+ /// The item details based on the index.
+ PickerItemDetails GetColumnDetails(int index)
+ {
+ PickerItemDetails columnDetails = new PickerItemDetails(_itemsSource[index]);
+ return columnDetails;
+ }
+
+ ///
+ /// Method to get the valid view port height.
+ ///
+ /// Returns view port height.
+ double GetViewPortHeight()
+ {
+ if (_pickerViewPortHeight <= 0)
+ {
+ return 0;
+ }
+
+ return _pickerViewPortHeight;
+ }
+
+ #endregion
+
+ #region Override Methods
+
+ ///
+ /// Method to measures child elements size in a container, item source item is measured with given width and height constraints.
+ ///
+ /// The maximum width request of the layout.
+ /// The maximum height request of the layout.
+ /// Returns the maximum size of the scroll view size.
+ protected override Size MeasureContent(double widthConstraint, double heightConstraint)
+ {
+ //// The item height.
+ double itemHeight = _pickerLayoutInfo.PickerInfo.ItemHeight;
+ if (_pickerLayoutInfo.PickerInfo.ItemTemplate != null)
+ {
+ foreach (var child in Children)
+ {
+ child.Measure(widthConstraint, itemHeight);
+ }
+ }
+
+ //// The total picker height is calculated based on the item source count and item height.
+ //// Assume the item source count is 5 and item height is 20, then the total picker height is 5 * 20 = 100.
+ double totalPickerHeight = _itemsSource.Count * itemHeight;
+ double viewPortHeight = GetViewPortHeight();
+ //// Total height of the layout based on top and bottom empty space.
+ double totalHeight = totalPickerHeight + viewPortHeight - itemHeight;
+
+ //// The view port item count is calculated based on the view port height and item height.
+ double viewPortItemCount = Math.Round(viewPortHeight / itemHeight);
+ //// The maximum view port count is 3 times the view port item count because larger view port leads to crash with larger canvas size.
+ int maximumViewPortCount = (int)(viewPortItemCount * 3);
+ //// The maximum items height based on the maximum view port count.
+ double pickerHeight = maximumViewPortCount * itemHeight;
+ foreach (var child in Children)
+ {
+ if (!(child is PickerDrawableView))
+ {
+ continue;
+ }
+
+ if (totalHeight > pickerHeight)
+ {
+ child.Measure(widthConstraint, pickerHeight);
+ }
+ else
+ {
+ child.Measure(widthConstraint, totalPickerHeight);
+ }
+ }
+
+ //// Need to add the empty space to the picker view. So by adding the picker view port height and it subtracted by single item height then we get total picker scroll view height.
+ //// Need to show space before 0th item and after last item for showing the selected item at middle position.
+ //// So that the total picker scroll view height is calculated by adding the picker view port height and it subtracted by single item height.
+ DesiredSize = new Size(widthConstraint, totalHeight);
+ return new Size(widthConstraint, totalHeight);
+ }
+
+ ///
+ /// Method used to arrange the children with in the bounds.
+ ///
+ /// The size of the layout.
+ /// The layout size.
+ protected override Size ArrangeContent(Rect bounds)
+ {
+ //// The item height.
+ double itemHeight = _pickerLayoutInfo.PickerInfo.ItemHeight;
+ //// The view port item count is calculated based on the view port height and item height.
+ //// Assume the view port height is 109 and the item height is 20, While using Math.Round then the view port item count is 109 / 20 = 5.4 => 5(view port item count).
+ //// Assume the view port height is 110 and the item height is 20, While using Math.Round then the view port item count is 110 / 20 = 5.5 => 6(view port item count).
+ double viewPortItemCount = Math.Round(GetViewPortHeight() / itemHeight);
+ //// The maximum view port count is 3 times the view port item count because larger view port leads to crash with larger canvas size.
+ int maximumViewPortCount = (int)(viewPortItemCount * 3);
+ //// The maximum items height based on the maximum view port count.
+ double pickerHeight = maximumViewPortCount * itemHeight;
+ //// The drawingStartIndex is calculated based on the view port item count.
+ //// From above example the view port item count is 5, then the drawingStartIndex is 2. While using the Math.Ceiling the drawingStartIndex is 5/2 = 2.5 => 3.
+ int drawingStartIndex = (int)Math.Ceiling(viewPortItemCount / 2) - 1;
+ //// The start position is calculated based on drawingStartIndex value.
+ //// From above example the drawingStartIndex is 2 and item height is 20, then the start y position is 40.
+ double yPosition;
+
+#if ANDROID || WINDOWS
+ yPosition = _pickerLayoutInfo.Column.SelectedIndex <= -1
+ ? (drawingStartIndex + 1) * itemHeight
+ : drawingStartIndex * itemHeight;
+#else
+ yPosition = drawingStartIndex * itemHeight;
+#endif
+ //// Calculate the last index based on item source count and maximum view port count.
+ double lastIndex = _itemsSource.Count - maximumViewPortCount;
+ lastIndex = lastIndex < 0 ? 0 : lastIndex;
+ double maxPosition = (lastIndex * itemHeight) + yPosition;
+ foreach (var child in Children)
+ {
+ if (child is PickerDrawableView)
+ {
+ if (lastIndex != 0)
+ {
+ //// Get the current scrolled item based on scroll offset.
+ int selectedIndex = (int)(_pickerLayoutInfo.ScrollOffset / itemHeight);
+ //// Calculate the virtualized drawable view start index based on view port count and selected index.
+ double startingIndex = selectedIndex - viewPortItemCount;
+ startingIndex = startingIndex < 0 ? 0 : startingIndex;
+ //// Does not need to move after the last index value.
+ startingIndex = startingIndex > lastIndex ? lastIndex : startingIndex;
+ double startPosition = startingIndex * itemHeight;
+ double topPosition = startPosition + yPosition;
+ if (topPosition > maxPosition)
+ {
+ topPosition = maxPosition;
+ }
+
+ _initialNodeTopPosition = topPosition;
+ child.Arrange(new Rect(0, topPosition, bounds.Width, pickerHeight));
+ }
+ else
+ {
+ //// Arrange the drawable view only for item source items without top and bottom empty spacing.
+ child.Arrange(new Rect(0, yPosition, bounds.Width, _itemsSource.Count * itemHeight));
+ _initialNodeTopPosition = yPosition;
+ }
+ }
+ else
+ {
+ child.Arrange(new Rect(0, yPosition, bounds.Width, itemHeight));
+ yPosition += itemHeight;
+ }
+ }
+
+ return bounds.Size;
+ }
+
+ ///
+ /// Method to create the semantics node for each items in the picker items.
+ ///
+ /// The width.
+ /// The height.
+ /// Returns semantic virtual view.
+ protected override List? GetSemanticsNodesCore(double width, double height)
+ {
+ Size newSize = new Size(width, height);
+
+ _semanticsNodes = new List();
+ _semanticsSize = newSize;
+ //// The item height.
+ double itemHeight = _pickerLayoutInfo.PickerInfo.ItemHeight;
+ double yPosition = _initialNodeTopPosition;
+ int startIndex = GetStartingIndex();
+ //// Draw item source item based on the item source collection.
+ for (int i = startIndex; i < _itemsSource.Count; i++)
+ {
+ Rect rectangle = new Rect(0, yPosition, width, itemHeight);
+ //// The yPosition is adjusted based on the item height.
+ //// Assume the item height is 20, then the yPosition is 40. So that the next item is drawn from 60.
+ yPosition += itemHeight;
+ if (yPosition >= height)
+ {
+ break;
+ }
+
+ SemanticsNode node = new SemanticsNode()
+ {
+ Text = _itemsSource[i],
+ Bounds = rectangle,
+ Id = i,
+ };
+ _semanticsNodes.Add(node);
+ }
+
+ return _semanticsNodes;
+ }
+
+ ///
+ /// Method handle the scroll position of the picker while accessibility enabled.
+ ///
+ /// The semantic node.
+ protected override void ScrollToCore(SemanticsNode node)
+ {
+ if (Parent != null && Parent != null && Parent is PickerScrollView)
+ {
+ PickerScrollView scrollView = (PickerScrollView)Parent;
+ double scrollHeight = scrollView.Height;
+ double nodeHeight = node.Bounds.Height;
+ double nodeTopPosition = node.Bounds.Top;
+ double scrollYPosition = scrollView.ScrollY;
+ if ((nodeTopPosition > scrollYPosition && nodeTopPosition < scrollYPosition + scrollHeight) ||
+ (nodeTopPosition + nodeHeight > scrollYPosition && nodeTopPosition + nodeHeight < scrollYPosition + scrollHeight))
+ {
+ return;
+ }
+
+ double maxScrollPosition = scrollView.ContentSize.Height - scrollView.Height;
+ if (maxScrollPosition > node.Bounds.Top)
+ {
+ scrollView.ScrollToAsync(0, node.Bounds.Top, false);
+ }
+ else
+ {
+ scrollView.ScrollToAsync(0, maxScrollPosition, false);
+ }
+ }
+ }
+
+ #endregion
+
+ #region Interface Implementation
+
+#if __IOS__ || __MACCATALYST__
+
+ ///
+ /// Gets a value indicating whether the view can become the first responder to listen the keyboard actions.
+ ///
+ /// This property will be considered only in maccatalyst and iOS Platform.
+ bool IKeyboardListener.CanBecomeFirstResponder
+ {
+ get
+ {
+ return true;
+ }
+ }
+
+#endif
+
+ ///
+ /// Method to handle the tap event.
+ ///
+ /// The tap event arguments.
+ void ITapGestureListener.OnTap(TapEventArgs e)
+ {
+#if __IOS__ || __MACCATALYST__
+ SetFocus(10);
+#endif
+ //// The item height.
+ double itemHeight = _pickerLayoutInfo.PickerInfo.ItemHeight;
+ //// The view port item count is calculated based on the view port height and item height.
+ //// Assume the view port height is 109 and the item height is 20, While using Math.Round then the view port item count is 109 / 20 = 5.4 => 5(view port item count).
+ //// Assume the view port height is 110 and the item height is 20, While using Math.Round then the view port item count is 110 / 20 = 5.5 => 6(view port item count).
+ double viewPortItemCount = Math.Round(GetViewPortHeight() / itemHeight);
+ //// The drawingStartIndex is calculated based on the view port item count.
+ //// From above example the view port item count is 5, then the drawingStartIndex is 2. While using the Math.Ceiling the drawingStartIndex is 5/2 = 2.5 => 3.
+ int drawingStartIndex = (int)Math.Ceiling(viewPortItemCount / 2) - 1;
+ //// The firstItemYPosition is calculated based on drawingStartIndex value.
+ //// From above example the drawingStartIndex is 2 and item height is 20, then the yPosition is 40.
+ double firstItemYPosition = drawingStartIndex * itemHeight;
+ //// The selected index.
+ int selectedIndex;
+ //// The selected index is calculated based on the tap point.
+ //// If the tap point is less than firstItemYPosition then need to move the scroll view to first item.
+ //// It means tapped point is before the first item then need to render first item as selected item.
+ if (e.TapPoint.Y < firstItemYPosition)
+ {
+ selectedIndex = 0;
+ }
+ //// The tapped point is after the last item then need to render last item as selected item.
+ else if (e.TapPoint.Y > Height - firstItemYPosition - itemHeight)
+ {
+ selectedIndex = _itemsSource.Count;
+ }
+ else
+ {
+ //// Assume tap y position is 100 and first item y position is 50 and item height is 10.
+ //// Then the selected index is 5 => (100 - 50) / 10.
+ selectedIndex = (int)((e.TapPoint.Y - firstItemYPosition) / itemHeight);
+ }
+
+ _pickerLayoutInfo.UpdateSelectedIndexValue(selectedIndex);
+ }
+
+ ///
+ /// Method to handle the keyboard interaction.
+ ///
+ /// The keyboard event args.
+ void IKeyboardListener.OnKeyDown(KeyEventArgs args)
+ {
+ if (args.Key == KeyboardKey.Tab)
+ {
+ Focus();
+ }
+ else if (args.Key == KeyboardKey.Up)
+ {
+ _pickerLayoutInfo.UpdateSelectedIndexValue(_selectedIndex - 1);
+ }
+ else if (args.Key == KeyboardKey.Down)
+ {
+ _pickerLayoutInfo.UpdateSelectedIndexValue(_selectedIndex + 1);
+ }
+ else if (args.Key == KeyboardKey.Right || args.Key == KeyboardKey.Left)
+ {
+ Unfocus();
+ PickerContainer? pickerContainer = Parent.Parent.Parent as PickerContainer;
+ pickerContainer?.PickerRightLeftKeys(args, _pickerLayoutInfo.Column._columnIndex);
+ }
+ else if (args.Key == KeyboardKey.Enter)
+ {
+ if (_pickerViewInfo.IsOpen == true)
+ {
+ _pickerViewInfo.IsOpen = false;
+ }
+ }
+ else if (args.Key == KeyboardKey.Escape)
+ {
+ _pickerViewInfo.IsOpen = false;
+ }
+ }
+
+ ///
+ /// The keyboard interaction override method.
+ ///
+ /// The keyboard event args.
+ void IKeyboardListener.OnKeyUp(KeyEventArgs args)
+ {
+ }
+
+ #endregion
+ }
+}
\ No newline at end of file
diff --git a/maui/src/Popup/Helpers/PopupExtension/PopupExtension.Android.cs b/maui/src/Popup/Helpers/PopupExtension/PopupExtension.Android.cs
index aaf7f0b9..1aec71f9 100644
--- a/maui/src/Popup/Helpers/PopupExtension/PopupExtension.Android.cs
+++ b/maui/src/Popup/Helpers/PopupExtension/PopupExtension.Android.cs
@@ -363,34 +363,66 @@ internal static int GetWindowInsets(string position)
/// Specifies whether the popup is open or not.
internal static void Blur(MauiView view, SfPopup popup, bool isopen)
{
- if (OperatingSystem.IsAndroidVersionAtLeast(31) && Shader.TileMode.Clamp is not null && popup.GetBlurRadius() > 0)
+ ClearBlurViews(popup);
+
+ if (OperatingSystem.IsAndroidVersionAtLeast(31) && Shader.TileMode.Clamp is not null && popup.GetBlurRadius() > 0 && SfWindowOverlay.ViewList is not null)
{
+ popup._blurredViews = new List();
+ bool hasModalPage = false;
if (IPlatformApplication.Current is not null && IPlatformApplication.Current.Application is Microsoft.Maui.Controls.Application application &&
application.Windows is not null && application.Windows.Count > 0)
{
Microsoft.Maui.Controls.Window window = application.Windows[0];
- bool hasModalPage = window is not null && window.Page is not null && window.Page.Navigation is not null
+ hasModalPage = window is not null && window.Page is not null && window.Page.Navigation is not null
&& window.Page.Navigation.ModalStack is not null && window.Navigation.ModalStack.Count > 0;
- // Applies blur effect to the top page in modal stack when the modal page is displayed.
- if (hasModalPage)
+ // In the case of multiple popups, if none of the popups in the view list have a blur effect, we need to apply the blur to the main view.
+ if (!SfWindowOverlay.ViewList.Any(view => view.HasBlurMode))
{
- Page? mainPage = PopupExtension.GetMainPage();
- if (mainPage is not null && mainPage.Handler is not null && mainPage.Handler.PlatformView is PlatformView pageView)
+ // Applies blur effect to the top page in modal stack when the modal page is displayed.
+ if (hasModalPage)
+ {
+ Page? mainPage = PopupExtension.GetMainPage();
+ if (mainPage is not null && mainPage.Handler is not null && mainPage.Handler.PlatformView is PlatformView pageView)
+ {
+ popup._blurredViews.Add(pageView);
+ pageView.SetRenderEffect(RenderEffect.CreateBlurEffect(popup.GetBlurRadius(), popup.GetBlurRadius(), Shader.TileMode.Clamp));
+ }
+ }
+ else
{
- popup._blurTarget = pageView;
- popup._blurTarget.SetRenderEffect(RenderEffect.CreateBlurEffect(popup.GetBlurRadius(), popup.GetBlurRadius(), Shader.TileMode.Clamp));
- return;
+ ViewGroup? platformRootview = WindowOverlayHelper._platformRootView;
+ if (platformRootview is not null && platformRootview.GetChildAt(0) is PlatformView blurTarget)
+ {
+ // Applies blur effect to target view.
+ popup._blurredViews.Add(blurTarget);
+ blurTarget.SetRenderEffect(RenderEffect.CreateBlurEffect(popup.GetBlurRadius(), popup.GetBlurRadius(), Shader.TileMode.Clamp));
+ }
}
}
- }
- ViewGroup? platformRootview = WindowOverlayHelper._platformRootView;
- if (platformRootview is not null && platformRootview.ChildCount > 0 && platformRootview.GetChildAt(0) is PlatformView blurTarget)
- {
- // Applies blur effect to target view.
- popup._blurTarget = blurTarget;
- popup._blurTarget.SetRenderEffect(RenderEffect.CreateBlurEffect(popup.GetBlurRadius(), popup.GetBlurRadius(), Shader.TileMode.Clamp));
+ // Applying Blur for nested popup.
+ foreach (WindowOverlayStack windowOverlay in SfWindowOverlay.ViewList.SkipLast(1).Reverse())
+ {
+ if (windowOverlay is not null)
+ {
+ windowOverlay.SetRenderEffect(RenderEffect.CreateBlurEffect(popup.GetBlurRadius(), popup.GetBlurRadius(), Shader.TileMode.Clamp));
+ popup._blurredViews.Add(windowOverlay);
+ if (windowOverlay.HasBlurMode)
+ {
+ break; // If the HasBlurMode is enabled, the blurring of previous views will be handled.
+ }
+ }
+ else
+ {
+ continue;
+ }
+ }
+
+ if (popup._popupOverlay is not null && popup._popupOverlay._overlayStack is not null)
+ {
+ popup._popupOverlay._overlayStack.HasBlurMode = true;
+ }
}
}
}
@@ -404,10 +436,20 @@ internal static void ClearBlurViews(SfPopup popup)
if (OperatingSystem.IsAndroidVersionAtLeast(31))
{
// Clears the blur effect for all views listed in BlurredViews.
- if (popup._blurTarget is not null)
+ if (popup._blurredViews is not null)
{
- popup._blurTarget.SetRenderEffect(null);
- popup._blurTarget = null;
+ foreach (var blurredView in popup._blurredViews)
+ {
+ blurredView.SetRenderEffect(null);
+ }
+
+ if (popup._popupOverlay is not null && popup._popupOverlay._overlayStack is not null)
+ {
+ popup._popupOverlay._overlayStack.HasBlurMode = false;
+ }
+
+ popup._blurredViews.Clear();
+ popup._blurredViews = null;
}
}
}
@@ -432,6 +474,9 @@ internal static void CalculateRelativePoint(PopupView popupView, MauiView relati
PlatformView relativeView = relative.ToPlatform(relative.Handler.MauiContext);
+ absoluteX *= WindowOverlayHelper._density;
+ absoluteY *= WindowOverlayHelper._density;
+
var popupViewWidth = popupView._popup._popupViewWidth * WindowOverlayHelper._density;
var popupViewHeight = popupView._popup._popupViewHeight * WindowOverlayHelper._density;
@@ -443,8 +488,8 @@ internal static void CalculateRelativePoint(PopupView popupView, MauiView relati
var top = decorViewFrame is not null && decorViewFrame.Top > 0 ? decorViewFrame.Top : 0;
var left = decorViewFrame is not null && decorViewFrame.Left > 0 ? decorViewFrame.Left : 0;
- // Adds the absolute points to the location of the relative view.
- location[0] += (int)(absoluteX - left);
+ // Adding the absolute points to the Relative View's location, if Flow direction is RTL means we need to move in opposite direction.
+ location[0] += (popupView._popup._isRTL ? -(int)absoluteX : (int)absoluteX) - left;
if (GetAttributes() is WindowManagerLayoutParams attributes && (attributes.Flags & WindowManagerFlags.Fullscreen) is WindowManagerFlags.Fullscreen)
{
location[1] += (int)absoluteY;
@@ -580,40 +625,41 @@ static void CalculateXPosition(PopupView popupView, PopupRelativePosition positi
{
if (popupView._popup._isRTL)
{
- relativeX = Math.Max(location[0] - (2 * absoluteX) - popupViewWidth, 0);
+ relativeX = location[0] + widthOfRelativeView;
}
else
{
- relativeX = location[0] - popupViewWidth > 0 ? location[0] - popupViewWidth : 0;
+ relativeX = location[0] - popupViewWidth;
}
}
else if (position == PopupRelativePosition.AlignToRightOf || position == PopupRelativePosition.AlignTopRight || position == PopupRelativePosition.AlignBottomRight)
{
if (popupView._popup._isRTL)
{
- relativeX = Math.Max(location[0] - (2 * absoluteX) + widthOfRelativeView - popupViewWidth, 0);
+ relativeX = location[0] - popupViewWidth;
// In the RTL case, if the button's width request exceeds the screen size, the popup is not displayed correctly within the view.
relativeX = popupView._popup.ValidatePopupPosition(relativeX, popupViewWidth, screenWidth);
}
else
{
- relativeX = location[0] + widthOfRelativeView + popupViewWidth < screenWidth ? location[0] + widthOfRelativeView : screenWidth - popupViewWidth;
+ relativeX = location[0] + widthOfRelativeView;
}
}
else
{
if (popupView._popup._isRTL)
{
- relativeX = Math.Max(location[0] - (2 * absoluteX) + widthOfRelativeView - popupViewWidth, 0);
+ relativeX = location[0] + widthOfRelativeView - popupViewWidth;
relativeX = popupView._popup.ValidatePopupPosition(relativeX, popupViewWidth, screenWidth);
}
else
{
- relativeX = location[0] + popupViewWidth < screenWidth ? location[0] : screenWidth - popupViewWidth;
- relativeX = popupView._popup.ValidatePopupPosition(relativeX, popupViewWidth, screenWidth);
+ relativeX = location[0];
}
}
+
+ relativeX = popupView._popup.ValidatePopupPosition(relativeX, popupViewWidth, screenWidth);
}
///
@@ -633,17 +679,19 @@ static void CalculateYPosition(PopupView popupView, PopupRelativePosition positi
{
if (position == PopupRelativePosition.AlignTop || position == PopupRelativePosition.AlignTopLeft || position == PopupRelativePosition.AlignTopRight)
{
- relativeY = Math.Max(statusBarHeight + actionBarHeight, location[1] - popupViewHeight);
+ relativeY = location[1] - popupViewHeight;
}
else if (position == PopupRelativePosition.AlignBottom || position == PopupRelativePosition.AlignBottomLeft || position == PopupRelativePosition.AlignBottomRight)
{
- relativeY = location[1] + heightOfRelativeView + popupViewHeight < screenHeight ? location[1] + heightOfRelativeView : screenHeight - popupViewHeight;
+ relativeY = location[1] + heightOfRelativeView;
}
else
{
// When the button's height exceeds the screen size, the popup is not displayed correctly within the view.
- relativeY = Math.Max(statusBarHeight + actionBarHeight, location[1] + popupViewHeight < screenHeight ? location[1] : screenHeight - popupViewHeight);
+ relativeY = location[1];
}
+
+ relativeY = popupView._popup.ValidatePopupPosition(relativeY, popupViewHeight, screenHeight, statusBarHeight + (!popupView._popup.IgnoreActionBar ? actionBarHeight : 0));
}
///
diff --git a/maui/src/Popup/Helpers/PopupExtension/PopupExtension.Windows.cs b/maui/src/Popup/Helpers/PopupExtension/PopupExtension.Windows.cs
index 075977d8..590c314b 100644
--- a/maui/src/Popup/Helpers/PopupExtension/PopupExtension.Windows.cs
+++ b/maui/src/Popup/Helpers/PopupExtension/PopupExtension.Windows.cs
@@ -220,10 +220,6 @@ internal static void UpdateEffectVisualSize(SfPopup popup)
internal static void CalculateRelativePoint(PopupView popupView, MauiView relative, PopupRelativePosition position, double absoluteX, double absoluteY, ref double relativeX, ref double relativeY)
{
var isRelativeViewRTL = false;
- if (((relative as IVisualElementController).EffectiveFlowDirection & EffectiveFlowDirection.RightToLeft) == EffectiveFlowDirection.RightToLeft)
- {
- isRelativeViewRTL = true;
- }
var rootView = WindowOverlayHelper._platformRootView;
if (rootView is null || relative.Handler is null || relative.Handler.MauiContext is null)
@@ -244,8 +240,8 @@ internal static void CalculateRelativePoint(PopupView popupView, MauiView relati
location[1] = transform.TransformPoint(new PlatformPoint(0, 0)).Y;
// Adds absolute points to the location of the relative view.
- location[0] += absoluteX;
- location[1] += absoluteY;
+ location[0] = location[0] + (popupView._popup._isRTL ? -absoluteX : absoluteX);
+ location[1] = location[1] + absoluteY;
var screenHeight = GetScreenHeight();
var screenWidth = GetScreenWidth();
@@ -267,72 +263,55 @@ static void CalculateXPosition(PopupView popupView, PopupRelativePosition positi
{
if (popupView._popup._isRTL)
{
- if (isRelativeViewRTL)
- {
- relativeX = Math.Max(location[0] - (2 * absoluteX) - widthOfRelativeView - popupViewWidth, 0);
- }
- else
- {
- relativeX = Math.Max(location[0] - (2 * absoluteX) - popupViewWidth, 0);
- }
+ relativeX = location[0] + widthOfRelativeView;
}
else
{
- relativeX = location[0] - popupViewWidth > 0 ? location[0] - popupViewWidth : 0;
+ relativeX = location[0] - popupViewWidth;
}
}
else if (position == PopupRelativePosition.AlignToRightOf || position == PopupRelativePosition.AlignTopRight || position == PopupRelativePosition.AlignBottomRight)
{
if (popupView._popup._isRTL)
{
- if (isRelativeViewRTL)
- {
- relativeX = Math.Max(location[0] - (2 * absoluteX) - popupViewWidth, 0);
- }
- else
- {
- relativeX = Math.Max(location[0] - (2 * absoluteX) + widthOfRelativeView - popupViewWidth, 0);
- }
+ relativeX = location[0] - popupViewWidth;
}
else
{
- relativeX = location[0] + widthOfRelativeView + popupViewWidth < screenWidth ? location[0] + widthOfRelativeView : screenWidth - popupViewWidth;
+ relativeX = location[0] + widthOfRelativeView;
}
}
else
{
if (popupView._popup._isRTL)
{
- if (isRelativeViewRTL)
- {
- relativeX = Math.Max(location[0] - (2 * absoluteX) - popupViewWidth, 0);
- }
- else
- {
- relativeX = Math.Max(location[0] - (2 * absoluteX) + widthOfRelativeView - popupViewWidth, 0);
- }
+ relativeX = location[0] + widthOfRelativeView - popupViewWidth;
}
else
{
- relativeX = location[0] + popupViewWidth < screenWidth ? location[0] : screenWidth - popupViewWidth;
+ relativeX = location[0];
}
}
+
+ relativeX = popupView._popup.ValidatePopupPosition(relativeX, popupViewWidth, screenWidth);
}
static void CalculateYPosition(PopupView popupView, PopupRelativePosition position, ref double relativeY, double popupViewHeight, double[] location, float heightOfRelativeView, float screenHeight)
{
if (position == PopupRelativePosition.AlignTop || position == PopupRelativePosition.AlignTopLeft || position == PopupRelativePosition.AlignTopRight)
{
- relativeY = Math.Max(GetStatusBarHeight() + GetActionBarHeight(popupView._popup.IgnoreActionBar), location[1] - popupViewHeight);
+ relativeY = location[1] - popupViewHeight;
}
else if (position == PopupRelativePosition.AlignBottom || position == PopupRelativePosition.AlignBottomLeft || position == PopupRelativePosition.AlignBottomRight)
{
- relativeY = location[1] + heightOfRelativeView + popupViewHeight < screenHeight ? location[1] + heightOfRelativeView : screenHeight - popupViewHeight;
+ relativeY = location[1] + heightOfRelativeView;
}
else
{
- relativeY = location[1] + popupViewHeight < screenHeight ? location[1] : screenHeight - popupViewHeight;
+ relativeY = location[1];
}
+
+ relativeY = popupView._popup.ValidatePopupPosition(relativeY, popupViewHeight, screenHeight, GetStatusBarHeight() + (!popupView._popup.IgnoreActionBar ? GetActionBarHeight(popupView._popup.IgnoreActionBar) : 0));
}
static void BlurEffect(UIElement blurElement, SfPopup popup, IReadOnlyList? popupList)
diff --git a/maui/src/Popup/PopupFooter.cs b/maui/src/Popup/PopupFooter.cs
index 63c8e118..b180048b 100644
--- a/maui/src/Popup/PopupFooter.cs
+++ b/maui/src/Popup/PopupFooter.cs
@@ -285,10 +285,12 @@ void Initialize()
_footerView = new SfGrid();
_footerView.Style = new Style(typeof(SfGrid));
_acceptButton = new SfButton() { IsVisible = false };
+ _acceptButton.AutomationId = "PopupAcceptButton";
_acceptButton.Style = new Style(typeof(SfButton));
_acceptButton.Text = SfPopupResources.GetLocalizedString("AcceptButtonText");
_acceptButton.Clicked += OnAcceptButtonClicked;
_declineButton = new SfButton() { IsVisible = false };
+ _declineButton.AutomationId = "PopupDeclineButton";
_declineButton.Style = new Style(typeof(SfButton));
_declineButton.Text = SfPopupResources.GetLocalizedString("DeclineButtonText");
_declineButton.Clicked += OnDeclineButtonClicked;
diff --git a/maui/src/Popup/SfPopup/SfPopup.Android.cs b/maui/src/Popup/SfPopup/SfPopup.Android.cs
index daac1042..3239f5af 100644
--- a/maui/src/Popup/SfPopup/SfPopup.Android.cs
+++ b/maui/src/Popup/SfPopup/SfPopup.Android.cs
@@ -21,7 +21,7 @@ public partial class SfPopup
///
/// List to store the views blurred by this popup.
///
- internal PlatformView? _blurTarget;
+ internal List? _blurredViews;
///
/// Backing field to store the decorView content.
diff --git a/maui/src/Popup/SfPopup/SfPopup.cs b/maui/src/Popup/SfPopup/SfPopup.cs
index 20051e08..c4a958d5 100644
--- a/maui/src/Popup/SfPopup/SfPopup.cs
+++ b/maui/src/Popup/SfPopup/SfPopup.cs
@@ -1582,7 +1582,7 @@ public void Show(double xPosition, double yPosition)
}
else
{
- _positionYPoint = ValidatePopupPosition(yPosition, _popupViewHeight, screenHeight - PopupExtension.GetStatusBarHeight() - PopupExtension.GetActionBarHeight(IgnoreActionBar)) + PopupExtension.GetStatusBarHeight() + PopupExtension.GetActionBarHeight(IgnoreActionBar);
+ _positionYPoint = ValidatePopupPosition(yPosition, _popupViewHeight, screenHeight - PopupExtension.GetStatusBarHeight() - (IgnoreActionBar ? 0 : PopupExtension.GetSafeAreaHeight("Top")) - PopupExtension.GetActionBarHeight(IgnoreActionBar)) + PopupExtension.GetStatusBarHeight() + PopupExtension.GetActionBarHeight(IgnoreActionBar) + (IgnoreActionBar ? 0 : PopupExtension.GetSafeAreaHeight("Top"));
}
if (!IsOpen)
@@ -2055,14 +2055,15 @@ internal void ResetPopupBasedOnDynamicContentSize()
///
/// Validates the position of the popup view.
///
- /// The point at which the layout is expected.
- /// The actual size of the view.
- /// The available size of the view.
+ /// The Point calculated to position the popup.
+ /// The actual size of the view.
+ /// The available size of the view.
+ /// Indicates the starting bounds of the application.
/// Returns the validated position of the popup view.
- internal double ValidatePopupPosition(double point, double actualSize, double availableSize)
+ internal double ValidatePopupPosition(double point, double popupViewSize, double availableScreenSize, double screenStartingPoint = 0)
{
// When the button's width request exceeds the screen size, the popup is not displayed correctly.
- return Math.Max(point + actualSize > availableSize ? availableSize - actualSize : point, 0);
+ return Math.Max(point + popupViewSize > availableScreenSize ? availableScreenSize - popupViewSize : point, screenStartingPoint);
}
#endregion
@@ -2167,13 +2168,29 @@ void DisplayPopup()
// Need to test if sizing has already been called when laying out relative to the view.
CalculatePopupViewWidth();
CalculatePopupViewHeight();
-
+#if WINDOWS
+ // IsViewLoaded will be true after the position popup.
+ // It will be set during the initialization of the child view.
+ // This flag is used to determine whether the calculations for size have been done with or without the child.
+ bool isChildViewInitialized = false;
+ if (_popupView is not null)
+ {
+ isChildViewInitialized = _popupView.IsViewLoaded;
+ }
+#endif
PositionPopupView();
ApplyOverlayBackground();
// When setting AutoSizeMode, the Popup MessageView is measured after being added, so the height, width, and position need to be recalculated.
UpdatePopupView();
+#if WINDOWS
+ // When setting AutoSizeMode, the measure happened without child initialization and is not called after calculating the width, so remeasuring is required; otherwise, the OnDraw dirtyRect will have the wrong width.
+ if (_popupView is not null && AutoSizeMode is not PopupAutoSizeMode.None && !IsFullScreen && !isChildViewInitialized)
+ {
+ _popupView.Measure(double.PositiveInfinity, double.PositiveInfinity);
+ }
+#endif
if (!RaisePopupOpeningEvent())
{
ApplyContainerAnimation();
@@ -2380,7 +2397,7 @@ void PositionPopupView()
}
}
- if (StartY == -1)
+ if (StartY == -1 || CanShowPopupInFullScreen)
{
// TODO when IsFullScreen is true, popup need to position from below statusbar.
// when IsFullScreen is false, popup need to position from below actionbar.
@@ -2398,7 +2415,14 @@ void PositionPopupView()
}
else
{
- y = (screenHeight - _popupViewHeight) / 2;
+ if (((screenHeight - _popupViewHeight) / 2) < statusBarHeight)
+ {
+ y = statusBarHeight;
+ }
+ else
+ {
+ y = (screenHeight - _popupViewHeight) / 2;
+ }
}
}
else
@@ -2505,7 +2529,7 @@ void CalculatePopupViewWidth()
}
}
- _popupViewWidth = Math.Min(_popupViewWidth, PopupExtension.GetScreenWidth() - (PopupExtension.GetSafeAreaHeight("Left") + PopupExtension.GetSafeAreaHeight("Right")));
+ _popupViewWidth = Math.Min(_popupViewWidth + PopupStyle.GetStrokeThickness(), PopupExtension.GetScreenWidth() - (PopupExtension.GetSafeAreaHeight("Left") + PopupExtension.GetSafeAreaHeight("Right")));
}
double GetFinalWidth(View template)
@@ -2612,7 +2636,7 @@ void CalculatePopupViewHeight()
AppliedBodyHeight = (screenHeight - (AppliedHeaderHeight + AppliedFooterHeight + safeAreaHeightAtTop + safeAreaHeightAtBottom + statusBarHeight + actionBarHeight));
}
- _popupViewHeight = AppliedHeaderHeight + AppliedBodyHeight + AppliedFooterHeight;
+ _popupViewHeight = AppliedHeaderHeight + AppliedBodyHeight + AppliedFooterHeight + strokeThickness;
// _popupViewHeight is not updated when the content size is increased in runtime when the keyboard is in open.
if (_popupViewHeightBeforeKeyboardInView != 0 && _keyboardPoints != 0)
@@ -2623,7 +2647,7 @@ void CalculatePopupViewHeight()
else
{
// When changing the device orientation from landscape to portrait, the popup, which was shrunk in landscape, does not return to its default height in portrait.
- _popupViewHeight = GetPopupViewDefaultHeight();
+ _popupViewHeight = GetPopupViewDefaultHeight() + strokeThickness;
}
}
@@ -3155,7 +3179,7 @@ void SetParent()
{
_popupOverlayContainer.Parent = page;
- // _popupOverlayContainer visibility is set to false after dismissing the popup.
+ // _popupOverlayContainer visibility is set to false after dismissing the popup.
// _popupOverlayContainer will be set as the parent to popupView here from DisplayPopup().
// Due to Maui 9.0.50 changes [https://github.com/dotnet/maui/pull/20154],
// the IsVisible property of _popupView is set to false when reopening with the same instance of the popup, since _popupOverlayContainer visibility will now be false.
@@ -3203,7 +3227,7 @@ Easing GetAnimationEasing()
///
void ApplyContainerAnimation()
{
- if (_isContainerAnimationInProgress || _popupOverlayContainer is null || AnimationMode is PopupAnimationMode.None)
+ if (_isContainerAnimationInProgress || _popupOverlayContainer is null || AnimationMode is PopupAnimationMode.None || !CanAnimate())
{
return;
}
@@ -3226,9 +3250,15 @@ void ApplyPopupAnimation()
{
return;
}
+
+ if (!CanAnimate())
+ {
+ ProcessAnimationCompleted(_popupView);
+ return;
+ }
#if IOS
// Native configuration for Scale and Translation will get skipped if Parent is null and Frame is Zero.
- if (AnimationMode is not PopupAnimationMode.None && _popupView is IView popupView)
+ if (_popupView is IView popupView)
{
popupView.Frame = new Rect(_popupXPosition, _popupYPosition, _popupViewHeight, _popupViewWidth);
}
@@ -3303,10 +3333,6 @@ void ApplyPopupAnimation()
#endif
SlideAnimate(_popupView, IsOpen ? startTranslate : endTranslate, IsOpen ? endTranslate : startTranslate);
break;
-
- case PopupAnimationMode.None:
- ProcessAnimationCompleted(_popupView);
- break;
}
}
@@ -3405,6 +3431,15 @@ void ProcessAnimationCompleted(View view)
RaisePopupEvent();
}
+ ///
+ /// Checks if the animation should proceed based on the animation duration and mode.
+ ///
+ /// Returns true if the animation duration is greater than 0 and the animation mode is not None, indicating that the animation should proceed. Returns false otherwise.
+ private bool CanAnimate()
+ {
+ return AnimationDuration > 0 && AnimationMode is not PopupAnimationMode.None;
+ }
+
///
/// Reset the _popupView Animated Properties.
///
@@ -3429,27 +3464,25 @@ void ResetAnimatedProperties()
///
void AbortPopupViewAnimation()
{
- if (AnimationMode is PopupAnimationMode.None)
- {
- return;
- }
-
- if (_popupView.AnimationIsRunning("ZoomAnimation"))
- {
- _popupView.AbortAnimation("ZoomAnimation");
- }
- else if (_popupView.AnimationIsRunning("FadeAnimation"))
- {
- _popupView.AbortAnimation("FadeAnimation");
- }
- else if (_popupView.AnimationIsRunning("SlideAnimation"))
+ if (CanAnimate())
{
- _popupView.AbortAnimation("SlideAnimation");
- }
+ if (_popupView.AnimationIsRunning("ZoomAnimation"))
+ {
+ _popupView.AbortAnimation("ZoomAnimation");
+ }
+ else if (_popupView.AnimationIsRunning("FadeAnimation"))
+ {
+ _popupView.AbortAnimation("FadeAnimation");
+ }
+ else if (_popupView.AnimationIsRunning("SlideAnimation"))
+ {
+ _popupView.AbortAnimation("SlideAnimation");
+ }
- if (_popupOverlayContainer.AnimationIsRunning("ContainerFadeAnimation"))
- {
- _popupOverlayContainer.AbortAnimation("ContainerFadeAnimation");
+ if (_popupOverlayContainer.AnimationIsRunning("ContainerFadeAnimation"))
+ {
+ _popupOverlayContainer.AbortAnimation("ContainerFadeAnimation");
+ }
}
}
diff --git a/maui/src/Popup/SfPopup/SfPopup.iOS.cs b/maui/src/Popup/SfPopup/SfPopup.iOS.cs
index 24c787b6..50354a29 100644
--- a/maui/src/Popup/SfPopup/SfPopup.iOS.cs
+++ b/maui/src/Popup/SfPopup/SfPopup.iOS.cs
@@ -188,6 +188,10 @@ internal void ApplyNativePopupViewClip()
var maskLayer = new CAShapeLayer { Frame = view.Bounds, Path = maskPath.CGPath };
view.Layer.Mask = maskLayer;
}
+ else
+ {
+ view.Layer.Mask = null;
+ }
}
}
diff --git a/maui/src/Popup/Style/PopupStyle.cs b/maui/src/Popup/Style/PopupStyle.cs
index 77269f11..9774e007 100644
--- a/maui/src/Popup/Style/PopupStyle.cs
+++ b/maui/src/Popup/Style/PopupStyle.cs
@@ -1149,7 +1149,7 @@ internal void SetThemeProperties(SfPopup popup)
ApplyDynamicResource(HeaderBackgroundProperty, "SfPopupNormalHeaderBackground");
ApplyDynamicResource(HeaderTextColorProperty, "SfPopupNormalHeaderTextColor");
ApplyDynamicResource(MessageBackgroundProperty, "SfPopupNormalMessageBackground");
- ApplyDynamicResource(MessageTextColorProperty, "SfpopupNormalMessageTextColor");
+ ApplyDynamicResource(MessageTextColorProperty, "SfPopupNormalMessageTextColor");
ApplyDynamicResource(FooterBackgroundProperty, "SfPopupNormalFooterBackground");
ApplyDynamicResource(AcceptButtonBackgroundProperty, "SfPopupNormalAcceptButtonBackground");
ApplyDynamicResource(AcceptButtonTextColorProperty, "SfPopupNormalAcceptButtonTextColor");
diff --git a/maui/src/ProgressBar/Base/ProgressBarBase.cs b/maui/src/ProgressBar/Base/ProgressBarBase.cs
new file mode 100644
index 00000000..12b94343
--- /dev/null
+++ b/maui/src/ProgressBar/Base/ProgressBarBase.cs
@@ -0,0 +1,1433 @@
+using System.Collections.ObjectModel;
+using System.Collections.Specialized;
+using Syncfusion.Maui.Toolkit.Graphics.Internals;
+
+namespace Syncfusion.Maui.Toolkit.ProgressBar
+{
+ ///
+ /// Base class of and .
+ /// It contains common logic of track, progress, etc that help to visualize the data.
+ ///
+ public abstract class ProgressBarBase : SfView
+ {
+ #region Fields
+
+ ///
+ /// Field used to skip the property changed when the is changed from .
+ ///
+ bool _skipProgressUpdate = false;
+
+ #endregion
+
+ #region Bindable Properties
+
+ ///
+ /// Identifies the bindable property.
+ ///
+ ///
+ /// The identifier for bindable property.
+ ///
+ public static readonly BindableProperty MinimumProperty =
+ BindableProperty.Create(
+ nameof(Minimum),
+ typeof(double),
+ typeof(ProgressBarBase),
+ 0d,
+ propertyChanged: OnMinimumPropertyChanged);
+
+ ///
+ /// Identifies the bindable property.
+ ///
+ ///
+ /// The identifier for bindable property.
+ ///
+ public static readonly BindableProperty MaximumProperty =
+ BindableProperty.Create(
+ nameof(Maximum),
+ typeof(double),
+ typeof(ProgressBarBase),
+ 100d,
+ propertyChanged: OnMaximumPropertyChanged);
+
+ ///
+ /// Identifies the bindable property.
+ ///
+ ///
+ /// The identifier for bindable property.
+ ///
+ public static readonly BindableProperty TrackFillProperty =
+ BindableProperty.Create(
+ nameof(TrackFill),
+ typeof(Brush),
+ typeof(ProgressBarBase),
+ null,
+ defaultValueCreator: bindable => new SolidColorBrush(Color.FromArgb("#E7E0EC")),
+ propertyChanged: OnFillPropertyChanged);
+
+ ///
+ /// Identifies the bindable property.
+ ///
+ ///
+ /// The identifier for bindable property.
+ ///
+ public static readonly BindableProperty ProgressProperty =
+ BindableProperty.Create(
+ nameof(Progress),
+ typeof(double),
+ typeof(ProgressBarBase),
+ 0d,
+ propertyChanged: OnProgressPropertyChanged);
+
+ ///
+ /// Identifies the bindable property.
+ ///
+ ///
+ /// The identifier for bindable property.
+ ///
+ public static readonly BindableProperty ProgressFillProperty =
+ BindableProperty.Create(
+ nameof(ProgressFill),
+ typeof(Brush),
+ typeof(ProgressBarBase),
+ null,
+ defaultValueCreator: bindable => new SolidColorBrush(Color.FromArgb("#6750A4")),
+ propertyChanged: OnFillPropertyChanged);
+
+ ///
+ /// Identifies the bindable property.
+ ///
+ ///
+ /// The identifier for bindable property.
+ ///
+ public static readonly BindableProperty SegmentCountProperty =
+ BindableProperty.Create(
+ nameof(SegmentCount),
+ typeof(int),
+ typeof(ProgressBarBase),
+ 1,
+ propertyChanged: OnSegmentPropertiesChanged);
+
+ ///
+ /// Identifies the bindable property.
+ ///
+ ///
+ /// The identifier for bindable property.
+ ///
+ public static readonly BindableProperty SegmentGapWidthProperty =
+ BindableProperty.Create(
+ nameof(SegmentGapWidth),
+ typeof(double),
+ typeof(ProgressBarBase),
+ 3d,
+ propertyChanged: OnSegmentPropertiesChanged);
+
+ ///
+ /// Identifies the bindable property.
+ ///
+ ///
+ /// The identifier for bindable property.
+ ///
+ public static readonly BindableProperty IsIndeterminateProperty =
+ BindableProperty.Create(
+ nameof(IsIndeterminate),
+ typeof(bool),
+ typeof(ProgressBarBase),
+ false,
+ propertyChanged: OnIndeterminatePropertiesChanged);
+
+ ///
+ /// Identifies the bindable property.
+ ///
+ ///
+ /// The identifier for bindable property.
+ ///
+ public static readonly BindableProperty IndeterminateIndicatorWidthFactorProperty =
+ BindableProperty.Create(
+ nameof(IndeterminateIndicatorWidthFactor),
+ typeof(double), typeof(ProgressBarBase),
+ 0.25d,
+ coerceValue: (bindable, value) =>
+ {
+ return Math.Clamp((double)value, 0, 1);
+ },
+ propertyChanged: OnIndeterminatePropertiesChanged);
+
+ ///
+ /// Identifies the bindable property.
+ ///
+ ///
+ /// The identifier for bindable property.
+ ///
+ public static readonly BindableProperty IndeterminateAnimationEasingProperty =
+ BindableProperty.Create(
+ nameof(IndeterminateAnimationEasing),
+ typeof(Easing),
+ typeof(ProgressBarBase),
+ Easing.Linear,
+ propertyChanged: OnIndeterminatePropertiesChanged);
+
+ ///
+ /// Identifies the bindable property.
+ ///
+ ///
+ /// The identifier for bindable property.
+ ///
+ public static readonly BindableProperty IndeterminateAnimationDurationProperty =
+ BindableProperty.Create(
+ nameof(IndeterminateAnimationDuration),
+ typeof(double),
+ typeof(ProgressBarBase),
+ 3000d,
+ propertyChanged: OnIndeterminatePropertiesChanged);
+
+ ///
+ /// Identifies the bindable property.
+ ///
+ ///
+ /// The identifier for bindable property.
+ ///
+ public static readonly BindableProperty AnimationDurationProperty =
+ BindableProperty.Create(
+ nameof(AnimationDuration),
+ typeof(double),
+ typeof(ProgressBarBase),
+ 1000d,
+ BindingMode.Default, null);
+
+ ///
+ /// Identifies the bindable property.
+ ///
+ ///
+ /// The identifier for bindable property.
+ ///
+ public static readonly BindableProperty AnimationEasingProperty =
+ BindableProperty.Create(
+ nameof(AnimationEasing),
+ typeof(Easing),
+ typeof(ProgressBarBase),
+ Easing.Linear,
+ BindingMode.Default,
+ null);
+
+ ///
+ /// Identifies the bindable property.
+ ///
+ ///
+ /// The identifier for bindable property.
+ ///
+ public static readonly BindableProperty GradientStopsProperty =
+ BindableProperty.Create(
+ nameof(GradientStops),
+ typeof(ObservableCollection),
+ typeof(ProgressBarBase),
+ null,
+ propertyChanged: OnGradientPropertyChanged);
+
+ #endregion
+
+ #region Constructor
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ public ProgressBarBase()
+ {
+ GradientStops = new ObservableCollection();
+ DrawingOrder = DrawingOrder.BelowContent;
+ ValidateMinimumMaximum();
+ }
+
+ #endregion
+
+ #region Properties
+
+ ///
+ /// Gets or sets the minimum possible value of the progress bar. The progress bar range starts from this value.
+ ///
+ ///
+ /// The minimum possible value of the progress bar. The default value is 0.
+ ///
+ ///
+ ///
+ /// # [XAML](#tab/tabid-1)
+ /// Snippet for
+ ///
+ /// ]]>
+ /// Snippet for
+ ///
+ /// ]]>
+ /// # [C#](#tab/tabid-2)
+ /// Snippet for
+ ///
+ /// Snippet for
+ ///
+ /// ***
+ ///
+ public double Minimum
+ {
+ get { return (double)GetValue(MinimumProperty); }
+ set { SetValue(MinimumProperty, value); }
+ }
+
+ ///
+ /// Gets or sets the maximum possible value of the progress bar. The progress bar ends at this value.
+ ///
+ ///
+ /// The highest possible value of the progress bar. The default value is 100.
+ ///
+ ///
+ /// # [XAML](#tab/tabid-1)
+ /// Snippet for
+ ///
+ /// ]]>
+ /// Snippet for
+ ///
+ /// ]]>
+ /// # [C#](#tab/tabid-2)
+ /// Snippet for
+ ///
+ /// Snippet for
+ ///
+ /// ***
+ ///
+ public double Maximum
+ {
+ get { return (double)GetValue(MaximumProperty); }
+ set { SetValue(MaximumProperty, value); }
+ }
+
+ ///
+ /// Gets or sets the brush that paints the interior area of the track.
+ ///
+ ///
+ /// TrackFill specifies how the track is painted.
+ ///
+ ///
+ /// # [XAML](#tab/tabid-1)
+ /// Snippet for
+ ///
+ /// ]]>
+ /// Snippet for
+ ///
+ /// ]]>
+ /// # [C#](#tab/tabid-2)
+ /// Snippet for
+ ///
+ /// Snippet for
+ ///
+ /// ***
+ ///
+ public Brush TrackFill
+ {
+ get { return (Brush)GetValue(TrackFillProperty); }
+ set { SetValue(TrackFillProperty, value); }
+ }
+
+ ///
+ /// Gets or sets the value that specifies the current value for the progress.
+ ///
+ ///
+ /// The default value is 0.
+ ///
+ ///
+ ///
+ /// # [XAML](#tab/tabid-1)
+ /// Snippet for
+ ///
+ /// ]]>
+ /// Snippet for
+ ///
+ /// ]]>
+ /// # [C#](#tab/tabid-2)
+ /// Snippet for
+ ///
+ /// Snippet for
+ ///
+ /// ***
+ ///
+ public double Progress
+ {
+ get { return (double)GetValue(ProgressProperty); }
+ set { SetValue(ProgressProperty, value); }
+ }
+
+ ///
+ /// Gets or sets the brush that paints the interior area of the progress.
+ ///
+ ///
+ /// ProgressFill specifies how the progress is painted.
+ ///
+ ///
+ /// # [XAML](#tab/tabid-1)
+ /// Snippet for
+ ///
+ /// ]]>
+ /// Snippet for
+ ///
+ /// ]]>
+ /// # [C#](#tab/tabid-2)
+ /// Snippet for
+ ///
+ /// Snippet for
+ ///
+ /// ***
+ ///
+ public Brush ProgressFill
+ {
+ get { return (Brush)GetValue(ProgressFillProperty); }
+ set { SetValue(ProgressFillProperty, value); }
+ }
+
+ ///
+ /// Gets or sets the value that determine the segments count of progress bar.
+ ///
+ ///
+ /// The default value is 1.
+ ///
+ ///
+ /// # [XAML](#tab/tabid-1)
+ /// Snippet for
+ ///
+ /// ]]>
+ /// Snippet for
+ ///
+ /// ]]>
+ /// # [C#](#tab/tabid-2)
+ /// Snippet for
+ ///
+ /// Snippet for
+ ///
+ /// ***
+ ///
+ public int SegmentCount
+ {
+ get { return (int)GetValue(SegmentCountProperty); }
+ set { SetValue(SegmentCountProperty, value); }
+ }
+
+ ///
+ /// Gets or sets the value that determine the gap between the segments.
+ ///
+ ///
+ /// The default value is 3.
+ ///
+ ///
+ ///
+ /// # [XAML](#tab/tabid-1)
+ /// Snippet for
+ ///
+ /// ]]>
+ /// Snippet for
+ ///
+ /// ]]>
+ /// # [C#](#tab/tabid-2)
+ /// Snippet for
+ ///
+ /// Snippet for
+ ///
+ /// ***
+ ///
+ public double SegmentGapWidth
+ {
+ get { return (double)GetValue(SegmentGapWidthProperty); }
+ set { SetValue(SegmentGapWidthProperty, value); }
+ }
+
+ ///
+ /// Gets or sets a value indicating whether the progress bar is in indeterminate state or not.
+ ///
+ ///
+ /// true if it is indeterminate; otherwise, false.The default value is false.
+ ///
+ ///
+ /// # [XAML](#tab/tabid-1)
+ /// Snippet for
+ ///
+ /// ]]>
+ /// Snippet for
+ ///
+ /// ]]>
+ /// # [C#](#tab/tabid-2)
+ /// Snippet for
+ ///
+ /// Snippet for
+ ///
+ /// ***
+ ///
+ public bool IsIndeterminate
+ {
+ get { return (bool)GetValue(IsIndeterminateProperty); }
+ set { SetValue(IsIndeterminateProperty, value); }
+ }
+
+ ///
+ /// Gets or sets the value that specifies width of the indeterminate indicator.
+ ///
+ ///
+ /// It ranges from 0 to 1. The default value is 0.25d.
+ ///
+ ///
+ /// # [XAML](#tab/tabid-1)
+ /// Snippet for
+ ///
+ /// ]]>
+ /// Snippet for
+ ///
+ /// ]]>
+ /// # [C#](#tab/tabid-2)
+ /// Snippet for
+ ///
+ /// Snippet for
+ ///
+ /// ***
+ ///
+ public double IndeterminateIndicatorWidthFactor
+ {
+ get { return (double)GetValue(IndeterminateIndicatorWidthFactorProperty); }
+ set { SetValue(IndeterminateIndicatorWidthFactorProperty, value); }
+ }
+
+ ///
+ /// Gets or sets a value that specifies the easing effect for indeterminate animation.
+ ///
+ ///
+ /// The default value is .
+ ///
+ ///
+ /// # [XAML](#tab/tabid-1)
+ /// Snippet for
+ ///
+ /// ]]>
+ /// Snippet for
+ ///
+ /// ]]>
+ /// # [C#](#tab/tabid-2)
+ /// Snippet for
+ ///
+ /// Snippet for
+ ///
+ /// ***
+ ///
+ public Easing IndeterminateAnimationEasing
+ {
+ get { return (Easing)GetValue(IndeterminateAnimationEasingProperty); }
+ set { SetValue(IndeterminateAnimationEasingProperty, value); }
+ }
+
+ ///
+ /// Gets or sets a value that specifies the indeterminate animation duration in milliseconds.
+ ///
+ ///
+ /// The default value is 3000 milliseconds.
+ ///
+ ///
+ /// # [XAML](#tab/tabid-1)
+ /// Snippet for
+ ///
+ /// ]]>
+ /// Snippet for
+ ///
+ /// ]]>
+ /// # [C#](#tab/tabid-2)
+ /// Snippet for
+ ///
+ /// Snippet for
+ ///
+ /// ***
+ ///
+ public double IndeterminateAnimationDuration
+ {
+ get { return (double)GetValue(IndeterminateAnimationDurationProperty); }
+ set { SetValue(IndeterminateAnimationDurationProperty, value); }
+ }
+
+ ///
+ /// Gets or sets a value that specifies the progress animation duration in milliseconds.
+ ///
+ ///
+ /// The default value is 1000 milliseconds.
+ ///
+ ///
+ /// # [XAML](#tab/tabid-1)
+ /// Snippet for
+ ///
+ /// ]]>
+ /// Snippet for
+ ///
+ /// ]]>
+ /// # [C#](#tab/tabid-2)
+ /// Snippet for
+ ///
+ /// Snippet for
+ ///
+ /// ***
+ ///
+ public double AnimationDuration
+ {
+ get { return (double)GetValue(AnimationDurationProperty); }
+ set { SetValue(AnimationDurationProperty, value); }
+ }
+
+ ///
+ /// Gets or sets a value that specifies the easing effect for progress animation.
+ ///
+ ///
+ /// The default value is .
+ ///
+ ///
+ /// # [XAML](#tab/tabid-1)
+ /// Snippet for
+ ///
+ /// ]]>
+ /// Snippet for
+ ///
+ /// ]]>
+ /// # [C#](#tab/tabid-2)
+ /// Snippet for
+ ///
+ /// Snippet for
+ ///
+ /// ***
+ ///
+ public Easing AnimationEasing
+ {
+ get { return (Easing)GetValue(AnimationEasingProperty); }
+ set { SetValue(AnimationEasingProperty, value); }
+ }
+
+ ///
+ /// Gets or sets a collection of to fill the gradient brush to the progress.
+ ///
+ ///
+ /// Gradient effect is not supported for segmented circular progress bar.
+ ///
+ ///
+ /// A collection of the objects associated with the brush, each of which specifies a color and an offset along the axis.
+ /// The default is an empty collection.
+ ///
+ ///
+ /// # [XAML](#tab/tabid-1)
+ /// Snippet for
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ /// ]]>
+ /// Snippet for
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ /// ]]>
+ /// # [C#](#tab/tabid-2)
+ /// Snippet for
+ ///
+ /// Snippet for
+ ///
+ /// ***
+ ///
+ public ObservableCollection GradientStops
+ {
+ get { return (ObservableCollection)GetValue(GradientStopsProperty); }
+ set { SetValue(GradientStopsProperty, value); }
+ }
+
+ ///
+ /// Backing field to store track and progress path.
+ ///
+ internal PathF? TrackPath, ProgressPath;
+
+ ///
+ /// Gets or sets the value to store actual minimum value.
+ ///
+ internal double ActualMinimum { get; set; }
+
+ ///
+ /// Gets or sets the value to store actual maximum value
+ ///
+ internal double ActualMaximum { get; set; }
+
+ ///
+ /// Gets or sets the value to store the available size of the progress bar.
+ ///
+ internal Size AvailableSize { get; set; }
+
+ ///
+ /// Gets or sets the value to identify whether to animate progress.
+ ///
+ internal bool CanAnimate { get; set; }
+
+ ///
+ /// Gets or sets the actual progress.
+ ///
+ internal double ActualProgress { get; set; }
+
+ ///
+ /// Gets or sets the value to identify whether Indeterminate Animation is aborted.
+ ///
+ internal bool IsIndeterminateAnimationAborted { get; set; }
+
+ #endregion
+
+ #region Public Methods
+
+ ///
+ /// Sets with corresponding animation duration and easing effects.
+ ///
+ /// value.
+ /// Duration to animate the progress. If didn't passed the duration, then it takes .
+ /// Easing effect for the animation. If didn't passed the Easing, then it takes .
+ ///
+ /// # [XAML](#tab/tabid-1)
+ /// Snippet for
+ ///
+ /// ]]>
+ /// Snippet for
+ ///
+ /// ]]>
+ /// # [C#](#tab/tabid-2)
+ /// Snippet for
+ ///
+ /// Snippet for
+ ///
+ /// ***
+ ///
+ public void SetProgress(double progress, double? animationDuration = null, Easing? easing = null)
+ {
+ _skipProgressUpdate = true;
+ Progress = progress;
+ _skipProgressUpdate = false;
+ var duration = animationDuration ?? AnimationDuration;
+ CanAnimate = duration > 0;
+ CreateProgressPath(duration, easing);
+ if (duration <= 0)
+ {
+ RaiseProgressChanged();
+ RaiseProgressCompleted();
+ InvalidateDrawable();
+ }
+ }
+
+ #endregion
+
+ #region Internal Methods
+
+ ///
+ /// To create a track path.
+ ///
+ internal abstract void CreateTrackPath();
+
+ ///
+ /// To create a progress path.
+ ///
+ /// The animation duration.
+ /// The easing effect.
+ internal abstract void CreateProgressPath(double? animationDuration = null, Easing? easing = null);
+
+ ///
+ /// To draw a progress.
+ ///
+ /// The canvas.
+ internal abstract void DrawProgress(ICanvas canvas);
+
+ ///
+ /// To create indeterminate animation.
+ ///
+ internal abstract void CreateIndeterminateAnimation();
+
+ ///
+ /// To create gradient effect.
+ ///
+ internal abstract void CreateGradient();
+
+ ///
+ /// To draw a track.
+ ///
+ /// The canvas.
+ internal void DrawTrack(ICanvas canvas)
+ {
+ if (TrackPath != null)
+ {
+ canvas.SaveState();
+ canvas.SetFillPaint(TrackFill, TrackPath.Bounds);
+ canvas.FillPath(TrackPath);
+ canvas.RestoreState();
+ }
+ }
+
+ ///
+ /// Converts progress value to factor value.
+ ///
+ /// The value to convert as factor.
+ /// The minimum value.
+ /// The maximum value.
+ /// The factor of the provided value.
+ internal double ValueToFactor(double value, double? minimum = null, double? maximum = null)
+ {
+ double min = minimum ?? ActualMinimum;
+ double max = maximum ?? ActualMaximum;
+ double factor = (value - min) / (max - min);
+ return Math.Clamp(factor, 0, 1);
+ }
+
+ ///
+ /// To create progress animation.
+ ///
+ /// The animation duration.
+ /// The easing effect.
+ internal void CreateProgressAnimation(double animationDuration, Easing easingEffect)
+ {
+ var progress = Math.Clamp(Progress, ActualMinimum, ActualMaximum);
+ if (ActualProgress != progress)
+ {
+ AnimationExtensions.Animate(
+ this,
+ "ProgressAnimation",
+ OnProgressAnimationUpdate,
+ ActualProgress,
+ progress,
+ 16,
+ (uint)animationDuration,
+ easingEffect,
+ OnProgressAnimationFinished);
+ }
+ }
+
+ #endregion
+
+ #region Private Methods
+
+ ///
+ /// To validate the minimum and maximum.
+ ///
+ void ValidateMinimumMaximum()
+ {
+ (double, double) actualValues = Utility.ValidateMinimumMaximumValue(Minimum, Maximum);
+ ActualMinimum = actualValues.Item1;
+ ActualMaximum = actualValues.Item2;
+ }
+
+ ///
+ /// Common logic to perform when or changed.
+ ///
+ void OnMinMaxChanged()
+ {
+ ValidateMinimumMaximum();
+ UpdateProgressBar();
+ InvalidateDrawable();
+ }
+
+ ///
+ /// To raise progress changed event.
+ ///
+ void RaiseProgressChanged()
+ {
+ if (!IsIndeterminate)
+ {
+ ProgressChanged?.Invoke(this, new ProgressValueEventArgs
+ {
+ CurrentProgress = Progress
+ });
+ }
+ }
+
+ ///
+ /// To raise progress completed event.
+ ///
+ void RaiseProgressCompleted()
+ {
+ if (!IsIndeterminate && Progress >= ActualMaximum)
+ {
+ ProgressCompleted?.Invoke(this, new ProgressValueEventArgs
+ {
+ CurrentProgress = Progress,
+ });
+ }
+ }
+
+ ///
+ /// Called when animation progress finished.
+ ///
+ void OnProgressAnimationFinished(double value, bool isCompleted)
+ {
+ AnimationExtensions.AbortAnimation(this, "ProgressAnimation");
+ CanAnimate = false;
+ RaiseProgressChanged();
+ RaiseProgressCompleted();
+ }
+
+ ///
+ /// To update progress animation value.
+ ///
+ /// Represents animation value.
+ void OnProgressAnimationUpdate(double value)
+ {
+ ActualProgress = value;
+ CreateProgressPath();
+ InvalidateDrawable();
+ }
+
+ ///
+ /// Called when collection changes.
+ ///
+ /// The sender object.
+ /// The NotifyCollectionChangedEventArgs.
+ void GradientStops_CollectionChanged(object? sender, NotifyCollectionChangedEventArgs e)
+ {
+ e.ApplyCollectionChanges((obj, index, _) => { AddGradientStop(obj, index); if (obj is ProgressGradientStop) { ((ProgressGradientStop)obj).Parent = this; } }, (obj, index) => { RemoveGradientStop(obj, index); if (obj is ProgressGradientStop) { ((ProgressGradientStop)obj).Parent = null; } }, () => { ResetGradientStop(e); });
+ if (!AvailableSize.IsZero)
+ {
+ CreateGradient();
+ InvalidateDrawable();
+ }
+ }
+
+ ///
+ /// Handle's the gradient stop property changed.
+ ///
+ /// The sender object.
+ /// The event argument.
+ void GradientStops_PropertyChanged(object? sender, System.ComponentModel.PropertyChangedEventArgs e)
+ {
+ if (e.PropertyName == ProgressGradientStop.ColorProperty.PropertyName ||
+ e.PropertyName == ProgressGradientStop.ValueProperty.PropertyName)
+ {
+ CreateGradient();
+ InvalidateDrawable();
+ }
+ }
+
+ ///
+ /// To add gradient stop.
+ ///
+ /// The gradient stop object.
+ /// The index value.
+ void AddGradientStop(object gradientStop, int index)
+ {
+ if (gradientStop is ProgressGradientStop newGradientStop)
+ {
+ newGradientStop.PropertyChanged -= GradientStops_PropertyChanged;
+ newGradientStop.PropertyChanged += GradientStops_PropertyChanged;
+ }
+ }
+
+ ///
+ /// To remove gradient stop.
+ ///
+ /// The gradient stop object
+ /// The index value
+ void RemoveGradientStop(object gradientStop, int index)
+ {
+ if (gradientStop is ProgressGradientStop oldGradientStop)
+ {
+ oldGradientStop.PropertyChanged -= GradientStops_PropertyChanged;
+ }
+ }
+
+ ///
+ /// To clear gradient stops collection.
+ ///
+ /// The collection changed event arguments.
+ void ResetGradientStop(NotifyCollectionChangedEventArgs e)
+ {
+ if (e.OldItems != null)
+ {
+ foreach (var item in e.OldItems)
+ {
+ if (item is ProgressGradientStop gradientStop)
+ {
+ gradientStop.PropertyChanged -= GradientStops_PropertyChanged;
+ gradientStop.Parent = null;
+ }
+ }
+ }
+
+ if (e.NewItems != null)
+ {
+ foreach (var item in e.NewItems)
+ {
+ if (item is ProgressGradientStop gradientStop)
+ {
+ gradientStop.PropertyChanged += GradientStops_PropertyChanged;
+ gradientStop.Parent = this;
+ }
+ }
+ }
+ }
+
+ #endregion
+
+ #region Override Methods
+
+ ///
+ /// Draws the progress bar.
+ ///
+ /// The canvas.
+ /// The dirty rect.
+ protected override void OnDraw(ICanvas canvas, RectF dirtyRect)
+ {
+ base.OnDraw(canvas, dirtyRect);
+ canvas.SaveState();
+ DrawProgressBar(canvas);
+ canvas.RestoreState();
+ }
+
+
+ ///
+ /// Invoked whenever the binding context of the View changes.
+ ///
+ protected override void OnBindingContextChanged()
+ {
+ base.OnBindingContextChanged();
+ if (GradientStops != null)
+ {
+ foreach (var gradientStop in GradientStops)
+ {
+ SetInheritedBindingContext(gradientStop, BindingContext);
+ }
+ }
+ }
+
+ #endregion
+
+ #region Property Changed
+
+ ///
+ /// Called when property changed.
+ ///
+ /// The bindable object.
+ /// The old value.
+ /// The new value.
+ static void OnMinimumPropertyChanged(BindableObject bindable, object oldValue, object newValue)
+ {
+ if (bindable is ProgressBarBase progressBar)
+ {
+ progressBar.OnMinMaxChanged();
+ }
+ }
+
+ ///
+ /// Called when property changed.
+ ///
+ /// The bindable object.
+ /// The old value.
+ /// The new value.
+ static void OnMaximumPropertyChanged(BindableObject bindable, object oldValue, object newValue)
+ {
+ if (bindable is ProgressBarBase progressBar)
+ {
+ progressBar.OnMinMaxChanged();
+ progressBar.RaiseProgressCompleted();
+ }
+ }
+
+ ///
+ /// Called when or property changed.
+ ///
+ /// The bindable object.
+ /// The old value.
+ /// The new value.
+ static void OnFillPropertyChanged(BindableObject bindable, object oldValue, object newValue)
+ {
+ if (bindable is ProgressBarBase progressBar && !progressBar.AvailableSize.IsZero)
+ {
+ progressBar.InvalidateDrawable();
+ }
+ }
+
+ ///
+ /// Called when property changed.
+ ///
+ /// The bindable object.
+ /// The old value.
+ /// The new value.
+ static void OnProgressPropertyChanged(BindableObject bindable, object oldValue, object newValue)
+ {
+ if (bindable is ProgressBarBase progressBar)
+ {
+ progressBar.CanAnimate = progressBar.AnimationDuration > 0;
+ if (!progressBar.AvailableSize.IsZero)
+ {
+ progressBar.ActualProgress = (double)oldValue;
+ if (progressBar._skipProgressUpdate)
+ {
+ return;
+ }
+
+ progressBar.CreateProgressPath();
+ if (progressBar.AnimationDuration <= 0)
+ {
+ progressBar.RaiseProgressChanged();
+ progressBar.RaiseProgressCompleted();
+ progressBar.InvalidateDrawable();
+ }
+ }
+ }
+ }
+
+ ///
+ /// Called when property changed.
+ ///
+ /// The bindable object.
+ /// The old value.
+ /// The new value.
+ static void OnSegmentPropertiesChanged(BindableObject bindable, object oldValue, object newValue)
+ {
+ if (bindable is ProgressBarBase progressBar && !progressBar.AvailableSize.IsZero)
+ {
+ progressBar.UpdateProgressBar();
+ progressBar.InvalidateDrawable();
+ }
+ }
+
+ ///
+ /// Called when the properties associated with indeterminate effect is changed.
+ ///
+ /// The BindableObject.
+ /// Old value.
+ /// New value.
+ static void OnIndeterminatePropertiesChanged(BindableObject bindable, object oldValue, object newValue)
+ {
+ if (bindable is ProgressBarBase progressBar && !progressBar.AvailableSize.IsZero)
+ {
+ AnimationExtensions.AbortAnimation(progressBar, "IndeterminateAnimation");
+ progressBar.UpdateProgressBar();
+ progressBar.InvalidateDrawable();
+ }
+ }
+
+ ///
+ /// Called when property changes.
+ ///
+ /// The bindable object.
+ /// The old value.
+ /// The new value.
+ static void OnGradientPropertyChanged(BindableObject bindable, object oldValue, object newValue)
+ {
+ if (bindable is ProgressBarBase progressBar)
+ {
+ if (oldValue != null && oldValue is ObservableCollection oldGradientStops)
+ {
+ oldGradientStops.CollectionChanged -= progressBar.GradientStops_CollectionChanged;
+ foreach (ProgressGradientStop gradientStop in oldGradientStops)
+ {
+ gradientStop.PropertyChanged -= progressBar.GradientStops_PropertyChanged;
+ gradientStop.Parent = null;
+ }
+ }
+
+ if (newValue != null && newValue is ObservableCollection newGradientStops)
+ {
+ newGradientStops.CollectionChanged += progressBar.GradientStops_CollectionChanged;
+ foreach (ProgressGradientStop gradientStop in newGradientStops)
+ {
+ gradientStop.Parent = progressBar;
+ SetInheritedBindingContext(gradientStop, progressBar.BindingContext);
+ gradientStop.PropertyChanged += progressBar.GradientStops_PropertyChanged;
+ }
+ }
+
+ if (!progressBar.AvailableSize.IsZero)
+ {
+ progressBar.CreateGradient();
+ progressBar.InvalidateDrawable();
+ }
+ }
+ }
+
+ #endregion
+
+ #region Virtual Methods
+
+ ///
+ /// To draw a progress bar elements.
+ ///
+ /// The drawing canvas.
+ internal virtual void DrawProgressBar(ICanvas canvas)
+ {
+ DrawTrack(canvas);
+ }
+
+ ///
+ /// To update the progress bar.
+ ///
+ internal virtual void UpdateProgressBar()
+ {
+ if (!AvailableSize.IsZero)
+ {
+ CreateTrackPath();
+ if (IsIndeterminate
+ && IndeterminateAnimationDuration > 0
+ && IndeterminateIndicatorWidthFactor > 0
+ && !AnimationExtensions.AnimationIsRunning(this, "IndeterminateAnimation"))
+ {
+ CreateIndeterminateAnimation();
+ }
+ else
+ {
+ CreateProgressPath();
+ }
+ }
+ }
+
+ #endregion
+
+ #region Events
+
+ ///
+ /// The value change event occurs when is changed.
+ ///
+ ///
+ /// # [XAML](#tab/tabid-1)
+ /// This snippet shows how to hook ProgressChanged event for
+ ///
+ /// ]]>
+ /// This snippet shows how to hook ProgressChanged event for
+ ///
+ /// ]]>
+ /// # [C#](#tab/tabid-2)
+ /// Snippet for
+ ///
+ /// Snippet for
+ ///
+ /// ***
+ ///
+ public event EventHandler? ProgressChanged;
+
+ ///
+ /// The progress completed event occurs when attains .
+ ///
+ ///
+ /// # [XAML](#tab/tabid-1)
+ /// This snippet shows how to hook ProgressCompleted event for
+ ///
+ /// ]]>
+ /// This snippet shows how to hook ProgressCompleted event for
+ ///
+ /// ]]>
+ /// # [C#](#tab/tabid-2)
+ /// Snippet for
+ ///
+ /// Snippet for
+ ///
+ /// ***
+ ///
+ public event EventHandler? ProgressCompleted;
+
+ #endregion
+ }
+}
\ No newline at end of file
diff --git a/maui/src/ProgressBar/Enums.cs b/maui/src/ProgressBar/Enums.cs
new file mode 100644
index 00000000..4985b218
--- /dev/null
+++ b/maui/src/ProgressBar/Enums.cs
@@ -0,0 +1,44 @@
+namespace Syncfusion.Maui.Toolkit.ProgressBar
+{
+ ///
+ /// Specifies the size units in the circular progress bar.
+ ///
+ public enum SizeUnit
+ {
+ ///
+ /// Indicates to treat the provided value as pixel.
+ ///
+ Pixel,
+
+ ///
+ /// Indicates to treat the provided value as factor.
+ ///
+ Factor
+ }
+
+ ///
+ /// Specifies the corner style for .
+ ///
+ public enum CornerStyle
+ {
+ ///
+ /// Flat does not apply the rounded corner on both side
+ ///
+ BothFlat,
+
+ ///
+ /// Curve apply the rounded corner on both side.
+ ///
+ BothCurve,
+
+ ///
+ /// Curve apply the rounded corner on end(right) side.
+ ///
+ EndCurve,
+
+ ///
+ /// Curve apply the rounded corner on start(left) side.
+ ///
+ StartCurve
+ }
+}
diff --git a/maui/src/ProgressBar/Events/ProgressValueEventArgs.cs b/maui/src/ProgressBar/Events/ProgressValueEventArgs.cs
new file mode 100644
index 00000000..44e782b4
--- /dev/null
+++ b/maui/src/ProgressBar/Events/ProgressValueEventArgs.cs
@@ -0,0 +1,109 @@
+namespace Syncfusion.Maui.Toolkit.ProgressBar
+{
+ ///
+ /// Provides event data for the and event.
+ ///
+ ///
+ /// # [XAML](#tab/tabid-1)
+ /// This snippet shows how to hook ProgressCompleted event for
+ ///
+ /// ]]>
+ /// This snippet shows how to hook ProgressCompleted event for
+ ///
+ /// ]]>
+ /// # [C#](#tab/tabid-2)
+ /// Snippet for
+ ///
+ /// Snippet for
+ ///
+ /// ***
+ ///
+ public class ProgressValueEventArgs : EventArgs
+ {
+ ///
+ /// Gets the current progress.
+ ///
+ ///
+ /// The current progress value.
+ ///
+ ///
+ /// # [XAML](#tab/tabid-1)
+ /// This snippet shows how to hook ProgressCompleted event for
+ ///
+ /// ]]>
+ /// This snippet shows how to hook ProgressCompleted event for
+ ///
+ /// ]]>
+ /// # [C#](#tab/tabid-2)
+ /// Snippet for
+ ///
+ /// Snippet for
+ ///
+ /// ***
+ ///
+ public double Progress
+ {
+ get
+ {
+ return CurrentProgress;
+ }
+ }
+
+ ///
+ /// Gets or sets the current progress value.
+ ///
+ internal double CurrentProgress { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/maui/src/ProgressBar/Gradient/CircularProgressBarArcInfo.cs b/maui/src/ProgressBar/Gradient/CircularProgressBarArcInfo.cs
new file mode 100644
index 00000000..e23524be
--- /dev/null
+++ b/maui/src/ProgressBar/Gradient/CircularProgressBarArcInfo.cs
@@ -0,0 +1,48 @@
+namespace Syncfusion.Maui.Toolkit.ProgressBar
+{
+ ///
+ /// Represents information about the arcs that rendered in progress bar.
+ ///
+ internal struct CircularProgressBarArcInfo
+ {
+ ///
+ /// Gets or sets arc bounds top left position value of arc.
+ ///
+ internal PointF TopLeft { get; set; }
+
+ ///
+ /// Gets or sets arc bounds bottom right position value of arc.
+ ///
+ internal PointF BottomRight { get; set; }
+
+ ///
+ /// Gets or sets arc start angle.
+ ///
+ internal float StartAngle { get; set; }
+
+ ///
+ /// Gets or sets arc end angle.
+ ///
+ internal float EndAngle { get; set; }
+
+ ///
+ /// Gets or sets arc path instance, that holds shapes of arc.
+ ///
+ internal PathF ArcPath { get; set; }
+
+ ///
+ /// Gets or sets paint, that represents arc fill color.
+ ///
+ internal Paint FillPaint { get; set; }
+
+ ///
+ /// Gets or sets inner arc start radius.
+ ///
+ internal double InnerStartRadius { get; set; }
+
+ ///
+ /// Gets or sets inner arc end radius.
+ ///
+ internal double InnerEndRadius { get; set; }
+ }
+}
diff --git a/maui/src/ProgressBar/Gradient/ProgressGradientStop.cs b/maui/src/ProgressBar/Gradient/ProgressGradientStop.cs
new file mode 100644
index 00000000..2e58c526
--- /dev/null
+++ b/maui/src/ProgressBar/Gradient/ProgressGradientStop.cs
@@ -0,0 +1,248 @@
+using Syncfusion.Maui.Toolkit.Themes;
+
+namespace Syncfusion.Maui.Toolkit.ProgressBar
+{
+ ///
+ /// Represents class.
+ ///
+ ///
+ /// # [XAML](#tab/tabid-1)
+ /// Snippet for
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ /// ]]>
+ /// Snippet for
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ /// ]]>
+ /// # [C#](#tab/tabid-2)
+ /// Snippet for
+ ///
+ /// Snippet for
+ ///
+ /// ***
+ ///
+ public class ProgressGradientStop : Element, IThemeElement
+ {
+ #region Fields
+
+ ///
+ /// Backing field to store the actual value of gradient stop.
+ ///
+ double _actualValue;
+
+ #endregion
+
+ #region Bindable Properties
+
+ ///
+ /// Identifies the bindable property.
+ ///
+ ///
+ /// The identifier for bindable property.
+ ///
+ public static readonly BindableProperty ColorProperty =
+ BindableProperty.Create(
+ nameof(Color),
+ typeof(Color),
+ typeof(ProgressGradientStop),
+ Colors.Transparent);
+
+ ///
+ /// Identifies the bindable property.
+ ///
+ ///
+ /// The identifier for bindable property.
+ ///
+ public static readonly BindableProperty ValueProperty =
+ BindableProperty.Create(
+ nameof(Value),
+ typeof(double),
+ typeof(ProgressGradientStop),
+ 0d,
+ propertyChanged: OnValuePropertyChanged);
+
+ #endregion
+
+ #region Constructor
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ public ProgressGradientStop()
+ {
+ ThemeElement.InitializeThemeResources(this, "SfProgressBarTheme");
+ }
+
+ #endregion
+
+ #region Properties
+
+ ///
+ /// Gets or sets the color that describes the gradient color value.
+ ///
+ ///
+ /// The default color is transparent.
+ ///
+ ///
+ /// # [XAML](#tab/tabid-1)
+ /// Snippet for
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ /// ]]>
+ /// Snippet for
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ /// ]]>
+ /// # [C#](#tab/tabid-2)
+ /// Snippet for
+ ///
+ /// Snippet for
+ ///
+ /// ***
+ ///
+ public Color Color
+ {
+ get { return (Color)GetValue(ColorProperty); }
+ set { SetValue(ColorProperty, value); }
+ }
+
+ ///
+ /// Gets or sets the value that describes the gradient value.
+ ///
+ ///
+ /// The default value is 0.
+ ///
+ ///
+ /// # [XAML](#tab/tabid-1)
+ /// Snippet for
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ /// ]]>
+ /// Snippet for
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ /// ]]>
+ /// # [C#](#tab/tabid-2)
+ /// Snippet for
+ ///
+ /// Snippet for
+ ///
+ /// ***
+ ///
+ public double Value
+ {
+ get { return (double)GetValue(ValueProperty); }
+ set { SetValue(ValueProperty, value); }
+ }
+
+ ///
+ /// Gets or sets the value for actual value of gradient stop
+ ///
+ internal double ActualValue
+ {
+ get
+ {
+ return _actualValue;
+ }
+
+ set
+ {
+ if (_actualValue != value)
+ {
+ _actualValue = value;
+ }
+ }
+ }
+
+ #endregion
+
+ #region Property Changed
+
+ ///
+ /// Called when the value of gauge gradient stop is changed
+ ///
+ /// The bindable object
+ /// Represents old value
+ /// Represents new value
+ private static void OnValuePropertyChanged(BindableObject bindable, object oldValue, object newValue)
+ {
+ if (bindable is ProgressGradientStop gradientStop)
+ {
+ gradientStop.ActualValue = gradientStop.Value;
+ }
+ }
+
+ void IThemeElement.OnControlThemeChanged(string oldTheme, string newTheme) { }
+
+ void IThemeElement.OnCommonThemeChanged(string oldTheme, string newTheme) { }
+
+ #endregion
+ }
+}
diff --git a/maui/src/ProgressBar/SfCircularProgressBar/SfCircularProgressBar.cs b/maui/src/ProgressBar/SfCircularProgressBar/SfCircularProgressBar.cs
new file mode 100644
index 00000000..f07e12c4
--- /dev/null
+++ b/maui/src/ProgressBar/SfCircularProgressBar/SfCircularProgressBar.cs
@@ -0,0 +1,2024 @@
+using Microsoft.Maui.Animations;
+using Syncfusion.Maui.Toolkit.Themes;
+using GradientStop = Microsoft.Maui.Graphics.PaintGradientStop;
+
+namespace Syncfusion.Maui.Toolkit.ProgressBar
+{
+ ///
+ /// Represents class, which is a UI control that displays a circular progress indicator.
+ /// This control allows for visualizing the progress of an operation through a customizable bar
+ /// It supports features such as determinate and indeterminate progress modes,
+ /// secondary progress indication, customizable appearance including track and progress colors,
+ /// segment divisions, corner radius customization, and animation effects.
+ ///
+ ///
+ /// # [XAML](#tab/tabid-1)
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ /// ]]>
+ /// # [C#](#tab/tabid-2)
+ ///
+ /// ***
+ ///
+ [ContentProperty(nameof(Content))]
+ public class SfCircularProgressBar : ProgressBarBase, IParentThemeElement
+ {
+ #region Fields
+
+ ///
+ /// Backing field to store actual sweep angle of progress bar.
+ /// ie., Calculated actual sweep angle after validating start and end angle.
+ ///
+ double _actualSweepAngle;
+
+ ///
+ /// Backing field to store actual start angle of progress bar.
+ /// ie., Validated the customer provided value and stored in it and this value should be used in all calculations.
+ ///
+ double _actualStartAngle;
+
+ ///
+ /// Backing field to store actual end angle of progress bar.
+ /// ie., Validated the customer provided value and stored in it and this value should be used in all calculations.
+ ///
+ double _actualEndAngle;
+
+ ///
+ /// Backing field to store progress bar center.
+ ///
+ PointF _center;
+
+ ///
+ /// Backing field to store progress bar arrange size.
+ ///
+ Size _arrangeSize = Size.Zero;
+
+ ///
+ /// Backing field to store radius of progress bar.
+ ///
+ double _radius;
+
+ ///
+ /// Backing field to store the animation start value.
+ ///
+ double _animationStart;
+
+ ///
+ /// Backing field to store the animation end value.
+ ///
+ double _animationEnd;
+
+ ///
+ /// Backing field to store gradient arc path list.
+ ///
+ List? _gradientArcPaths;
+
+ #endregion
+
+ #region Bindable Properties
+
+ ///
+ /// Identifies the bindable property.
+ ///
+ ///
+ /// The identifier for bindable property.
+ ///
+ public static readonly BindableProperty StartAngleProperty =
+ BindableProperty.Create(
+ nameof(StartAngle),
+ typeof(double),
+ typeof(SfCircularProgressBar),
+ 270d,
+ propertyChanged: OnAnglePropertyChanged);
+
+ ///
+ /// Identifies the bindable property.
+ ///
+ ///
+ /// The identifier for bindable property.
+ ///
+ public static readonly BindableProperty EndAngleProperty =
+ BindableProperty.Create(
+ nameof(EndAngle),
+ typeof(double),
+ typeof(SfCircularProgressBar),
+ 630d,
+ propertyChanged: OnAnglePropertyChanged);
+
+ ///
+ /// Identifies the bindable property.
+ ///
+ ///
+ /// The identifier for bindable property.
+ ///
+ public static readonly BindableProperty TrackRadiusFactorProperty =
+ BindableProperty.Create(
+ nameof(TrackRadiusFactor),
+ typeof(double),
+ typeof(SfCircularProgressBar), 0.9d,
+ coerceValue: (bindable, value) =>
+ {
+ return Math.Clamp((double)value, 0, 1);
+ },
+ propertyChanged: OnTrackPropertiesChanged);
+
+ ///
+ /// Identifies the bindable property.
+ ///
+ ///
+ /// The identifier for bindable property.
+ ///
+ public static readonly BindableProperty TrackThicknessProperty =
+ BindableProperty.Create(
+ nameof(TrackThickness),
+ typeof(double),
+ typeof(SfCircularProgressBar),
+ 5d,
+ propertyChanged: OnTrackPropertiesChanged);
+
+ ///
+ /// Identifies the bindable property.
+ ///
+ ///
+ /// The identifier for bindable property.
+ ///
+ public static readonly BindableProperty ThicknessUnitProperty =
+ BindableProperty.Create(
+ nameof(ThicknessUnit),
+ typeof(SizeUnit),
+ typeof(SfCircularProgressBar),
+ SizeUnit.Pixel,
+ propertyChanged: OnThicknessUnitChanged);
+
+ ///
+ /// Identifies the bindable property.
+ ///
+ ///
+ /// The identifier for bindable property.
+ ///
+ public static readonly BindableProperty ProgressRadiusFactorProperty =
+ BindableProperty.Create(
+ nameof(ProgressRadiusFactor),
+ typeof(double),
+ typeof(SfCircularProgressBar),
+ 0.9d,
+ coerceValue: (bindable, value) =>
+ {
+ return Math.Clamp((double)value, 0, 1);
+ },
+ propertyChanged: OnProgressPropertiesChanged);
+
+ ///
+ /// Identifies the bindable property.
+ ///
+ ///
+ /// The identifier for bindable property.
+ ///
+ public static readonly BindableProperty ProgressThicknessProperty =
+ BindableProperty.Create(
+ nameof(ProgressThickness),
+ typeof(double),
+ typeof(SfCircularProgressBar),
+ 5d,
+ propertyChanged: OnProgressPropertiesChanged);
+
+ ///
+ /// Identifies the bindable property.
+ ///
+ ///
+ /// The identifier for bindable property.
+ ///
+ public static readonly BindableProperty TrackCornerStyleProperty =
+ BindableProperty.Create(
+ nameof(TrackCornerStyle),
+ typeof(CornerStyle),
+ typeof(SfCircularProgressBar),
+ CornerStyle.BothFlat,
+ propertyChanged: OnTrackCornerStylePropertyChanged);
+
+ ///
+ /// Identifies the bindable property.
+ ///
+ ///
+ /// The identifier for bindable property.
+ ///
+ public static readonly BindableProperty ProgressCornerStyleProperty =
+ BindableProperty.Create(
+ nameof(ProgressCornerStyle),
+ typeof(CornerStyle),
+ typeof(SfCircularProgressBar),
+ CornerStyle.BothFlat, propertyChanged: OnProgressCornerStylePropertyChanged);
+
+ ///
+ /// Identifies the bindable property.
+ ///
+ ///
+ /// The identifier for bindable property.
+ ///
+ public static readonly BindableProperty ContentProperty =
+ BindableProperty.Create(
+ nameof(Content),
+ typeof(View),
+ typeof(SfCircularProgressBar),
+ null,
+ propertyChanged: OnContentPropertyChanged);
+
+ ///
+ /// Identifies the bindable property.
+ ///
+ ///
+ /// The identifier for bindable property.
+ ///
+ internal static readonly BindableProperty CircularProgressBarBackgroundProperty =
+ BindableProperty.Create(
+ nameof(CircularProgressBarBackground),
+ typeof(Color),
+ typeof(SfCircularProgressBar),
+ defaultValueCreator: bindable => Color.FromArgb("#FFFBFE"),
+ propertyChanged: OnCircularProgressBarBackgroundChanged);
+
+ #endregion
+
+ #region Constructor
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ ///
+ /// # [XAML](#tab/tabid-1)
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ /// ]]>
+ /// # [C#](#tab/tabid-2)
+ ///
+ /// ***
+ ///
+ public SfCircularProgressBar()
+ {
+ ValidateStartEndAngle();
+ ThemeElement.InitializeThemeResources(this, "SfCircularProgressBarTheme");
+ BackgroundColor = CircularProgressBarBackground;
+ // TASK-886910: SfLinearProgressBar gets frozen when switching between tabs in TabBar
+ Loaded += OnProgressBarLoaded;
+ Unloaded += OnProgressBarUnloaded;
+ }
+
+ #endregion
+
+ #region Properties
+
+ ///
+ /// Gets or sets a value that specifies the of the progress bar.
+ ///
+ ///
+ /// It defines the start angle of the progress bar. The default value is 270.
+ ///
+ ///
+ /// # [XAML](#tab/tabid-1)
+ ///
+ /// ]]>
+ /// # [C#](#tab/tabid-2)
+ ///
+ /// ***
+ ///
+ public double StartAngle
+ {
+ get { return (double)GetValue(StartAngleProperty); }
+ set { SetValue(StartAngleProperty, value); }
+ }
+
+ ///
+ /// Gets or sets a value that specifies the of the progress bar.
+ ///
+ ///
+ /// It defines the end angle of the progress bar. The default value is 630.
+ ///
+ ///
+ /// # [XAML](#tab/tabid-1)
+ ///
+ /// ]]>
+ /// # [C#](#tab/tabid-2)
+ ///
+ /// ***
+ ///
+ public double EndAngle
+ {
+ get { return (double)GetValue(EndAngleProperty); }
+ set { SetValue(EndAngleProperty, value); }
+ }
+
+ ///
+ /// Gets or sets a value that specifies the outer radius factor of the track.
+ ///
+ ///
+ /// This value ranges from 0 to 1. It defines the radius factor of the track. The default value is 0.9.
+ ///
+ ///
+ /// # [XAML](#tab/tabid-1)
+ ///
+ /// ]]>
+ /// # [C#](#tab/tabid-2)
+ ///
+ /// ***
+ ///
+ public double TrackRadiusFactor
+ {
+ get { return (double)GetValue(TrackRadiusFactorProperty); }
+ set { SetValue(TrackRadiusFactorProperty, value); }
+ }
+
+ ///
+ /// Gets or sets a value that specifies the thickness of track in circular progress bar.
+ /// You can specify value either in logical pixel or radius factor using the property.
+ ///
+ ///
+ /// The default value is 5.
+ ///
+ ///
+ /// # [XAML](#tab/tabid-1)
+ ///
+ /// ]]>
+ /// # [C#](#tab/tabid-2)
+ ///
+ /// ***
+ ///
+ public double TrackThickness
+ {
+ get { return (double)GetValue(TrackThicknessProperty); }
+ set { SetValue(TrackThicknessProperty, value); }
+ }
+
+ ///
+ /// Gets or sets enum value that indicates to calculate the track and progress thickness either in logical pixel or radius factor.
+ ///
+ ///
+ /// One of the enumeration that specifies how the thickness unit value is considered.
+ /// The default mode is .
+ ///
+ ///
+ /// # [XAML](#tab/tabid-1)
+ ///
+ /// ]]>
+ /// # [C#](#tab/tabid-2)
+ ///
+ /// ***
+ ///
+ public SizeUnit ThicknessUnit
+ {
+ get { return (SizeUnit)GetValue(ThicknessUnitProperty); }
+ set { SetValue(ThicknessUnitProperty, value); }
+ }
+
+ ///
+ /// Gets or sets a value that specifies the outer radius factor of the progress.
+ ///
+ ///
+ /// It defines the radius factor of the progress. The default value is 0.9.
+ ///
+ ///
+ /// # [XAML](#tab/tabid-1)
+ ///
+ /// ]]>
+ /// # [C#](#tab/tabid-2)
+ ///
+ /// ***
+ ///
+ public double ProgressRadiusFactor
+ {
+ get { return (double)GetValue(ProgressRadiusFactorProperty); }
+ set { SetValue(ProgressRadiusFactorProperty, value); }
+ }
+
+ ///
+ /// Gets or sets a value that specifies the thickness for the progress.
+ /// You can specify value either in logical pixel or radius factor using the property.
+ ///
+ ///
+ /// The default value is 5.
+ ///
+ ///
+ /// # [XAML](#tab/tabid-1)
+ ///
+ /// ]]>
+ /// # [C#](#tab/tabid-2)
+ ///
+ /// ***
+ ///
+ public double ProgressThickness
+ {
+ get { return (double)GetValue(ProgressThicknessProperty); }
+ set { SetValue(ProgressThicknessProperty, value); }
+ }
+
+ ///
+ /// Gets or sets the value that specifies the corner style of the track.
+ ///
+ ///
+ /// One of the enumeration values that specifies the corner style of the track in progress bar.
+ /// The default is .
+ ///
+ ///
+ /// # [XAML](#tab/tabid-1)
+ ///
+ /// ]]>
+ /// # [C#](#tab/tabid-2)
+ ///
+ /// ***
+ ///
+ public CornerStyle TrackCornerStyle
+ {
+ get { return (CornerStyle)GetValue(TrackCornerStyleProperty); }
+ set { SetValue(TrackCornerStyleProperty, value); }
+ }
+
+ ///
+ /// Gets or sets the value that specifies the corner style of the progress.
+ ///
+ ///
+ /// One of the enumeration values that specifies the corner style of the progress in progress bar.
+ /// The default is .
+ ///
+ ///
+ /// # [XAML](#tab/tabid-1)
+ ///
+ /// ]]>
+ /// # [C#](#tab/tabid-2)
+ ///
+ /// ***
+ ///
+ public CornerStyle ProgressCornerStyle
+ {
+ get { return (CornerStyle)GetValue(ProgressCornerStyleProperty); }
+ set { SetValue(ProgressCornerStyleProperty, value); }
+ }
+
+ ///
+ /// Gets or sets a any view to display in the center of circular progress bar.
+ ///
+ ///
+ /// An object that contains the progress bar visual content. The default value is null.
+ ///
+ ///
+ /// # [XAML](#tab/tabid-1)
+ ///
+ ///
+ ///
+ ///
+ ///
+ /// ]]>
+ /// # [C#](#tab/tabid-2)
+ ///
+ /// ***
+ ///
+ public View Content
+ {
+ get { return (View)GetValue(ContentProperty); }
+ set { SetValue(ContentProperty, value); }
+ }
+
+ ///
+ /// Gets or sets the background color of the circular progress bar.
+ ///
+ ///
+ /// # [XAML](#tab/tabid-1)
+ ///
+ /// ]]>
+ /// # [C#](#tab/tabid-2)
+ ///
+ /// ***
+ ///
+ internal Color CircularProgressBarBackground
+ {
+ get { return (Color)GetValue(CircularProgressBarBackgroundProperty); }
+ set { SetValue(CircularProgressBarBackgroundProperty, value); }
+ }
+
+ #endregion
+
+ #region Private Methods
+
+ ///
+ /// To create gradient arc segments.
+ ///
+ /// Thre gradient stop list.
+ /// The inner start radius.
+ /// The inner end radius.
+ /// The range start.
+ /// The range end.
+ /// The progress bar arc info.
+ List? CreateGradientArcSegments(
+ List gradientStops,
+ double innerStartRadius,
+ double innerEndRadius,
+ double rangeStart,
+ double rangeEnd)
+ {
+ gradientStops = Utility.UpdateGradientStopCollection(gradientStops, rangeStart, rangeEnd);
+ var gradientRange = Utility.GetGradientRange(gradientStops, rangeStart, rangeEnd);
+ double tempInnerStart = innerStartRadius;
+ double tempInnerEnd = innerEndRadius;
+ List arcInfoCollection = new List();
+ for (int i = 0; i < gradientRange.Count - 1; i++)
+ {
+ double startAngle = ValueToAngle(gradientRange[i]);
+ double endAngle = ValueToAngle(gradientRange[i + 1]);
+ double offset = 0.5; // Added .5 degree at the end angle to avoid line difference.
+ endAngle += i < gradientRange.Count - 2 ? offset : 0;
+ double rangeMidAngle = Math.Abs(endAngle - startAngle) / 2;
+ Color color1 = gradientStops[i].Color;
+ Color color2 = gradientStops[i + 1].Color;
+ for (int j = 0; j < gradientStops.Count - 1; j++)
+ {
+ if (gradientStops[j].ActualValue <= gradientRange[i] && gradientStops[j + 1].ActualValue > gradientRange[i])
+ {
+ double offset1 = (gradientRange[i] - gradientStops[j].ActualValue) / (gradientStops[j + 1].ActualValue - gradientStops[j].ActualValue);
+ color1 = gradientStops[j].Color.Lerp(gradientStops[j + 1].Color, offset1);
+ double offset2 = (gradientRange[i + 1] - gradientStops[j].ActualValue) / (gradientStops[j + 1].ActualValue - gradientStops[j].ActualValue);
+ color2 = gradientStops[j].Color.Lerp(gradientStops[j + 1].Color, offset2);
+ }
+ }
+
+ if (innerStartRadius != innerEndRadius)
+ {
+ double innerOffsetFraction = (tempInnerEnd - tempInnerStart) / (rangeEnd - rangeStart);
+ double rangeStartOffset = rangeStart * innerOffsetFraction;
+ innerEndRadius = tempInnerStart + (gradientRange[i + 1] * innerOffsetFraction) - rangeStartOffset;
+ innerStartRadius = tempInnerStart + (gradientRange[i] * innerOffsetFraction) - rangeStartOffset;
+ }
+
+ if (rangeMidAngle <= 90)
+ {
+ CircularProgressBarArcInfo arcInfo = CreateGradientArcSegment(startAngle, endAngle, color1, color2, innerStartRadius, innerEndRadius);
+ arcInfoCollection.Add(arcInfo);
+ }
+ else
+ {
+ Color midColor = color1.Lerp(color2, 0.5);
+ double midValue = gradientRange[i] + ((gradientRange[i + 1] - gradientRange[i]) / 2);
+ var midAngle = ValueToAngle(midValue);
+ CircularProgressBarArcInfo arcInfo = CreateGradientArcSegment(startAngle, midAngle + 0.25, color1, midColor, innerStartRadius, innerEndRadius);
+ arcInfoCollection.Add(arcInfo);
+ arcInfo = CreateGradientArcSegment(midAngle, endAngle, midColor, color2, innerStartRadius, innerEndRadius);
+ arcInfoCollection.Add(arcInfo);
+ }
+ }
+
+ return arcInfoCollection;
+ }
+
+ ///
+ /// To create gradient arc segment.
+ ///
+ /// The start angle.
+ /// The end angle.
+ /// The start color.
+ /// The end color.
+ /// The inner start radius.
+ /// The inner end radius.
+ /// The circular progress bar arc info.
+ CircularProgressBarArcInfo CreateGradientArcSegment(double startAngle, double endAngle, Color color1, Color color2, double innerStartRadius, double innerEndRadius)
+ {
+ Point startPoint, endPoint;
+ CalculateGradientArcOffset(startAngle, endAngle, out startPoint, out endPoint);
+ LinearGradientPaint gradient = new LinearGradientPaint()
+ {
+ StartPoint = startPoint,
+ EndPoint = endPoint
+ };
+
+ gradient.GradientStops = new GradientStop[]
+ {
+ new GradientStop( 0.1f ,color1),
+ new GradientStop(0.9f, color2)
+ };
+
+ CircularProgressBarArcInfo gaugeArcInfo = new CircularProgressBarArcInfo()
+ {
+ FillPaint = gradient,
+ ArcPath = new PathF(),
+ StartAngle = (float)startAngle,
+ EndAngle = (float)endAngle,
+ InnerStartRadius = innerStartRadius,
+ InnerEndRadius = innerEndRadius
+ };
+
+ return gaugeArcInfo;
+ }
+
+ ///
+ /// Calculate gradient arc offset.
+ ///
+ /// The start angle.
+ /// The end angle.
+ /// The start point.
+ /// The end point.
+ void CalculateGradientArcOffset(double startAngle, double endAngle, out Point startPoint, out Point endPoint)
+ {
+ var start = startAngle > 360 ? startAngle % 360 : startAngle;
+ start += 0.5;// Added .5 degree at the end angle to avoid line difference.
+ var end = endAngle > 360 ? endAngle % 360 : endAngle;
+ if (start >= 0 && start < 90)
+ {
+ startPoint = new Point(1, 0.5);
+ endPoint = new Point(0, 0.5);
+ }
+ else if (start >= 90 && start < 180)
+ {
+ startPoint = new Point(0.5, 1);
+ endPoint = new Point(0.5, 0);
+ }
+ else if (start >= 180 && start < 270)
+ {
+ if (end >= 180 && end < 270)
+ {
+ startPoint = new Point(0.5, 1);
+ endPoint = new Point(0.5, 0);
+ }
+ else
+ {
+ startPoint = new Point(0, 0.5);
+ endPoint = new Point(1, 0.5);
+ }
+ }
+ else
+ {
+ startPoint = new Point(0.5, 0);
+ endPoint = new Point(0.5, 1);
+ }
+ }
+
+ ///
+ /// To create gradient arc.
+ ///
+ /// The outer arc top left.
+ /// The outer arc bottom right.
+ /// The inner arc top left.
+ /// The inner arc bottom right.
+ /// The outer radius.
+ /// The inner radius.
+ /// The inner radius.
+ /// The inner radius.
+ void CreateGradientArc(
+ PointF outerArcTopLeft,
+ PointF outerArcBottomRight,
+ PointF innerArcTopLeft, PointF innerArcBottomRight,
+ float outerRadius,
+ float innerRadius,
+ Point? startCurveCapCenter = null,
+ Point? endCurveCapCenter = null)
+ {
+ double halfWidth = (outerRadius - innerRadius) / 2;
+ double cornerRadiusAngle = Utility.CornerRadiusAngle(_radius, halfWidth);
+ if (_gradientArcPaths != null)
+ {
+ for (int i = 0; i < _gradientArcPaths.Count; i++)
+ {
+ CircularProgressBarArcInfo arcInfo = _gradientArcPaths[i];
+
+ // Create gradient arc path.
+ if (i == 0 && startCurveCapCenter != null)
+ {
+ arcInfo.StartAngle += (float)cornerRadiusAngle;
+ }
+
+ if (endCurveCapCenter != null)
+ {
+ float endCornerAngle = (float)cornerRadiusAngle;
+ if (i == _gradientArcPaths.Count - 1)
+ {
+ arcInfo.EndAngle -= endCornerAngle;
+ if (i > 0 && arcInfo.EndAngle < arcInfo.StartAngle)
+ arcInfo.StartAngle = arcInfo.EndAngle;
+ }
+
+ if (i == _gradientArcPaths.Count - 2 && (_gradientArcPaths[i + 1].EndAngle - endCornerAngle) < arcInfo.EndAngle)
+ {
+ arcInfo.EndAngle = _gradientArcPaths[i + 1].EndAngle - endCornerAngle;
+ }
+ }
+
+ CreateFilledArc(
+ arcInfo.ArcPath,
+ outerArcTopLeft,
+ outerArcBottomRight,
+ innerArcTopLeft,
+ innerArcBottomRight,
+ arcInfo.StartAngle,
+ arcInfo.EndAngle,
+ outerRadius,
+ innerRadius);
+
+ // Append circle in edge for corner style.
+ if (i == 0 && startCurveCapCenter != null)
+ {
+ arcInfo.ArcPath.AppendCircle(
+ (float)startCurveCapCenter.Value.X,
+ (float)startCurveCapCenter.Value.Y,
+ (float)halfWidth);
+ }
+
+ if (i == _gradientArcPaths.Count - 1 && endCurveCapCenter != null)
+ {
+ arcInfo.ArcPath.AppendCircle(
+ (float)endCurveCapCenter.Value.X,
+ (float)endCurveCapCenter.Value.Y,
+ (float)halfWidth);
+ }
+ }
+ }
+ }
+
+ ///
+ /// To update indeterminate animation value.
+ ///
+ /// Represents animation value.
+ void OnIndeterminateAnimationUpdate(double value)
+ {
+ _animationStart = value;
+ _animationEnd = value + 360 * IndeterminateIndicatorWidthFactor;
+ _animationStart %= 360;
+ _animationEnd %= 360;
+ _animationEnd = (_animationEnd - _animationStart) % 360 == 0
+ ? _animationEnd - 0.01
+ : _animationEnd;
+ while (_animationEnd < _animationStart)
+ {
+ _animationEnd += 360;
+ }
+
+ CreateProgressPath();
+ InvalidateDrawable();
+ }
+
+ ///
+ /// Calculate the progress percentage.
+ ///
+ /// The progress percentage.
+ /// The current progress value.
+ /// The segment minimum value.
+ /// The segment maximum value.
+ double CalculateProgressPercentage(double indicatorValue, double min, double max)
+ {
+ if (max > min)
+ {
+ double delta = max - min;
+ indicatorValue -= min;
+ if (delta > 0 && indicatorValue > 0)
+ {
+ double percent = indicatorValue / delta * 100;
+ return percent < 100 ? percent : 100;
+ }
+ }
+
+ return 0;
+ }
+
+ ///
+ /// To create range or progress path.
+ ///
+ /// True to create track path; otherwise progress path.
+ /// The animation duration.
+ void CreatePath(bool isTrack, double? animationDuration = null)
+ {
+ animationDuration ??= AnimationDuration;
+ float outerRadius, innerRadius;
+ double actualEndValue = 0, actualStartAngle, actualEndAngle, midRadius, halfWidth, actualSweepAngle;
+ PointF outerArcTopLeft, outerArcBottomRight, innerArcTopLeft, innerArcBottomRight;
+ CornerStyle cornerStyle;
+ if (isTrack)
+ {
+ TrackPath = new PathF();
+ InitializeTrackPath(out actualStartAngle, out actualEndAngle, out actualSweepAngle, out cornerStyle);
+ }
+ else
+ {
+ ProgressPath = new PathF();
+ actualEndValue = Math.Clamp(animationDuration > 0 ? ActualProgress : ActualProgress = Progress, ActualMinimum, ActualMaximum);
+ cornerStyle = ProgressCornerStyle;
+
+ if (ActualMinimum == actualEndValue && !IsIndeterminate)
+ {
+ ProgressPath = null;
+ _gradientArcPaths = null;
+ return;
+ }
+
+ InitializeProgressPath(out actualStartAngle, out actualEndAngle, out actualSweepAngle, actualEndValue);
+ }
+
+ outerRadius = GetRadiusFromFactor(isTrack ? TrackRadiusFactor : ProgressRadiusFactor, SizeUnit.Factor);
+ innerRadius = outerRadius - GetRadiusFromFactor(isTrack ? TrackThickness : ProgressThickness, ThicknessUnit);
+ innerRadius = innerRadius < 0 ? 0 : innerRadius;
+ halfWidth = (outerRadius - innerRadius) / 2;
+ midRadius = outerRadius == 0 ? 0 : outerRadius - halfWidth;
+
+ // Calculating outer arc bounds.
+ outerArcTopLeft = GetArcTopLeft(outerRadius);
+ outerArcBottomRight = GetArcBottomRight(outerRadius);
+
+ // Calculating inner arc bounds.
+ innerArcTopLeft = GetArcTopLeft(innerRadius);
+ innerArcBottomRight = GetArcBottomRight(innerRadius);
+ Point? endCurveCapCenter = null;
+ Point? startCurveCapCenter = null;
+ double cornerRadiusAngle = Utility.CornerRadiusAngle(_radius, halfWidth);
+ if (cornerStyle == CornerStyle.StartCurve || cornerStyle == CornerStyle.BothCurve && actualSweepAngle != 359.99)
+ {
+ if (actualStartAngle + cornerRadiusAngle < actualEndAngle)
+ {
+ actualStartAngle += cornerRadiusAngle;
+ startCurveCapCenter = CalculateCapCenter(actualStartAngle, midRadius);
+ }
+ }
+
+ if (cornerStyle == CornerStyle.EndCurve || cornerStyle == CornerStyle.BothCurve && actualSweepAngle != 359.99)
+ {
+ if (actualEndAngle - cornerRadiusAngle > actualStartAngle)
+ {
+ actualEndAngle -= cornerRadiusAngle;
+ endCurveCapCenter = CalculateCapCenter(actualEndAngle, midRadius);
+ }
+ }
+
+ if (SegmentCount > 1 && !IsIndeterminate)
+ {
+ CreateSegmentedPath(
+ isTrack, outerArcTopLeft,
+ outerArcBottomRight,
+ innerArcTopLeft,
+ innerArcBottomRight,
+ outerRadius,
+ innerRadius);
+ }
+ else
+ {
+ if (!isTrack && !IsIndeterminate && GradientStops != null && GradientStops.Count > 0)
+ {
+ ProgressPath = null;
+ _gradientArcPaths = CreateGradientArcSegments(GradientStops.ToList(), innerRadius,
+ innerRadius, ActualMinimum, actualEndValue);
+ CreateGradientArc(
+ outerArcTopLeft,
+ outerArcBottomRight,
+ innerArcTopLeft,
+ innerArcBottomRight,
+ outerRadius,
+ innerRadius,
+ startCurveCapCenter,
+ endCurveCapCenter);
+ }
+ else
+ {
+ if (!isTrack)
+ {
+ _gradientArcPaths = null;
+ }
+
+ CreateFilledArc(isTrack ? TrackPath : ProgressPath,
+ outerArcTopLeft,
+ outerArcBottomRight,
+ innerArcTopLeft,
+ innerArcBottomRight,
+ actualStartAngle,
+ actualEndAngle,
+ outerRadius,
+ innerRadius);
+
+ // For 360 angle, fill arc not displaying. To resolve the issue drawn 359.99 angle arc and small gap filled by circle.
+ if (actualSweepAngle == 359.99)
+ {
+ Point vector = Utility.AngleToVector(actualStartAngle);
+ PointF point = new PointF((float)(_center.X + (midRadius * vector.X)),
+ (float)(_center.Y + (midRadius * vector.Y)));
+ (isTrack ? TrackPath : ProgressPath)?.AppendCircle(point, (float)halfWidth);
+ }
+ }
+
+ if (startCurveCapCenter != null)
+ {
+ (isTrack ? TrackPath : ProgressPath)?.AppendCircle(
+ (float)startCurveCapCenter.Value.X,
+ (float)startCurveCapCenter.Value.Y,
+ (float)halfWidth);
+ }
+
+ if (endCurveCapCenter != null)
+ {
+ (isTrack ? TrackPath : ProgressPath)?.AppendCircle(
+ (float)endCurveCapCenter.Value.X,
+ (float)endCurveCapCenter.Value.Y,
+ (float)halfWidth);
+ }
+ }
+ }
+
+ ///
+ /// Initializes the track path properties of the progress bar.
+ ///
+ /// The actual start angle for rendering the track path.
+ /// The actual end angle for rendering the track path.
+ /// The actual sweep angle for the track path.
+ /// Specifies the corner style of the track.
+ ///
+ /// If the progress bar is in an indeterminate state, this method resets the angles to cover the entire circle and sets the corner style to flat.
+ ///
+ void InitializeTrackPath(out double actualStartAngle, out double actualEndAngle, out double actualSweepAngle, out CornerStyle cornerStyle)
+ {
+ actualStartAngle = _actualStartAngle;
+ actualEndAngle = _actualEndAngle;
+ actualSweepAngle = _actualSweepAngle;
+ cornerStyle = TrackCornerStyle;
+ if (IsIndeterminate)
+ {
+ actualStartAngle = 0;
+ actualEndAngle = 359.99;
+ actualSweepAngle = 359.99;
+ cornerStyle = CornerStyle.BothFlat;
+ }
+ }
+
+ ///
+ /// Initializes the progress path properties of the progress bar.
+ ///
+ /// The actual start angle for rendering the progress path.
+ /// The actual end angle for rendering the progress path.
+ /// The actual sweep angle for the progress path.
+ /// The value used to calculate the actual end angle of the progress.
+ ///
+ /// If the progress bar is in an indeterminate state, this method calculates the angles and sweep factor based on the animation settings.
+ ///
+ void InitializeProgressPath(out double actualStartAngle, out double actualEndAngle, out double actualSweepAngle, double actualEndValue)
+ {
+ actualStartAngle = ValueToAngle(ActualMinimum);
+ actualEndAngle = ValueToAngle(actualEndValue);
+ if (IsIndeterminate)
+ {
+ actualStartAngle = 0;
+ if (IndeterminateIndicatorWidthFactor <= 0)
+ {
+ actualEndAngle = 0;
+ }
+ else if (IndeterminateIndicatorWidthFactor >= 1)
+ {
+ actualEndAngle = 359.99;
+ }
+ else
+ {
+ actualStartAngle = _animationStart;
+ actualEndAngle = _animationEnd;
+ }
+ }
+
+ actualSweepAngle = Utility.CalculateSweepAngle(actualStartAngle, actualEndAngle);
+ }
+
+ ///
+ /// To calculate cap center.
+ ///
+ /// The angle.
+ /// The mid radius.
+ ///
+ Point CalculateCapCenter(double angle, double midRadius)
+ {
+ Point vector = Utility.AngleToVector(angle);
+ return new Point(_center.X + (midRadius * vector.X),
+ _center.Y + (midRadius * vector.Y));
+ }
+
+ ///
+ /// To create segmented path for range and progress.
+ ///
+ /// True to create range path; otherwise progress path.
+ /// The top left point of outer arc.
+ /// The bottom right point of outer arc.
+ /// The top left point of inner arc.
+ /// the bottom right point of inner arc.
+ /// The outer radius of arc.
+ /// The inner radius of arc.
+ void CreateSegmentedPath(bool isTrack, Point outerArcTopLeft, Point outerArcBottomRight, Point innerArcTopLeft, Point innerArcBottomRight, double outerRadius, double innerRadius)
+ {
+ double halfWidth = (outerRadius - innerRadius) / 2;
+ double midRadius = outerRadius == 0 ? 0 : outerRadius - halfWidth;
+ int actualSegmentCount = SegmentCount > 1 ? SegmentCount : 1;
+ double actualGapWidth = SegmentGapWidth > 0 ? SegmentGapWidth : 0;
+ float angleOfSlice = (float)(Math.Abs(_actualSweepAngle) > (actualGapWidth * actualSegmentCount) ? (_actualSweepAngle - (actualGapWidth * actualSegmentCount)) / actualSegmentCount : 0);
+ float sliceAngle = _actualSweepAngle == 359.99 ? 360 / actualSegmentCount : (float)(_actualSweepAngle / actualSegmentCount);
+ double progressAngle = _actualStartAngle + (_actualSweepAngle / 100 * CalculateProgressPercentage(AnimationDuration > 0 ? ActualProgress : ActualProgress = Progress, ActualMinimum, ActualMaximum));
+ int segment = isTrack ? actualSegmentCount : (int)Math.Ceiling(Math.Round((progressAngle - _actualStartAngle) / sliceAngle, 2));
+ double actualStartAngle = _actualStartAngle + actualGapWidth / 2;
+ double range = (ActualMaximum - ActualMinimum) / actualSegmentCount;
+ double min, max = 0;
+ CornerStyle cornerStyle;
+ for (int i = 0; i < segment; i++)
+ {
+ double segmentStartAngle = actualStartAngle;
+ double segmentEndAngle = segmentStartAngle + angleOfSlice;
+ actualStartAngle = segmentEndAngle + actualGapWidth;
+ cornerStyle = TrackCornerStyle;
+ if (!isTrack)
+ {
+ double sweepAngle = segmentEndAngle - segmentStartAngle;
+ min = i == 0 ? ActualMinimum : max;
+ max = min + range;
+ segmentEndAngle = segmentStartAngle + (sweepAngle / 100 * CalculateProgressPercentage(AnimationDuration > 0 ? ActualProgress : ActualProgress = Progress, min, max));
+ cornerStyle = ProgressCornerStyle;
+ }
+
+ Point? endCurveCapCenter = null;
+ Point? startCurveCapCenter = null;
+ double cornerRadiusAngle = Utility.CornerRadiusAngle(_radius, halfWidth);
+
+ if (i == 0)
+ {
+ if (cornerStyle == CornerStyle.StartCurve || cornerStyle == CornerStyle.BothCurve)
+ {
+ if (segmentStartAngle + cornerRadiusAngle < segmentEndAngle)
+ {
+ segmentStartAngle += cornerRadiusAngle;
+ startCurveCapCenter = CalculateCapCenter(segmentStartAngle, midRadius);
+ }
+ }
+
+ if (startCurveCapCenter != null)
+ {
+ (isTrack ? TrackPath : ProgressPath)?.AppendCircle((float)startCurveCapCenter.Value.X, (float)startCurveCapCenter.Value.Y, (float)halfWidth);
+ }
+ }
+
+ if (i == segment - 1)
+ {
+ if (cornerStyle == CornerStyle.EndCurve || cornerStyle == CornerStyle.BothCurve)
+ {
+ if (segmentEndAngle - cornerRadiusAngle > segmentStartAngle)
+ {
+ segmentEndAngle -= cornerRadiusAngle;
+ endCurveCapCenter = CalculateCapCenter(segmentEndAngle, midRadius);
+ }
+ }
+
+ if (endCurveCapCenter != null)
+ {
+ (isTrack ? TrackPath : ProgressPath)?.AppendCircle((float)endCurveCapCenter.Value.X, (float)endCurveCapCenter.Value.Y, (float)halfWidth);
+ }
+ }
+
+ CreateFilledArc(isTrack ? TrackPath : ProgressPath, outerArcTopLeft, outerArcBottomRight, innerArcTopLeft, innerArcBottomRight,
+ segmentStartAngle, segmentEndAngle, outerRadius, innerRadius);
+
+ }
+ }
+
+ ///
+ /// Provides top left point of arc.
+ ///
+ /// The radius.
+ /// Top left point of arc.
+ Point GetArcTopLeft(float radius)
+ {
+ return new PointF(_center.X - radius, _center.Y - radius);
+ }
+
+ ///
+ /// Provides bottom right point of arc.
+ ///
+ /// The radius.
+ /// The bottom right point of arc.
+ Point GetArcBottomRight(float radius)
+ {
+ return new PointF(_center.X + radius, _center.Y + radius);
+ }
+
+ ///
+ /// Get radius from factor value.
+ ///
+ /// The thickness.
+ /// The size unit.
+ /// The radius.
+ float GetRadiusFromFactor(double thickness, SizeUnit sizeUnit)
+ {
+ if (sizeUnit == SizeUnit.Factor)
+ {
+ thickness = Math.Clamp(thickness, 0, 1) * _radius;
+ }
+
+ thickness = thickness < 0 ? 0 : thickness;
+ return (float)thickness;
+ }
+
+ ///
+ /// To create closed arc.
+ ///
+ /// The rendering path.
+ /// The outer arc top left.
+ /// The outer arc bottom right.
+ /// The inner arc top left.
+ /// The inner arc bottom right.
+ /// The start angle.
+ /// The end angle.
+ /// The outer radius of arc.
+ /// The inner radius of arc.
+ void CreateFilledArc(PathF? pathF, PointF outerArcTopLeft, PointF outerArcBottomRight,
+ PointF innerArcTopLeft, PointF innerArcBottomRight,
+ double startAngle, double endAngle, double outerRadius, double innerRadius)
+ {
+ if (pathF == null)
+ {
+ return;
+ }
+
+ if (startAngle > endAngle)
+ {
+ double temp = endAngle;
+ endAngle = startAngle;
+ startAngle = temp;
+ }
+#if WINDOWS
+ //TODO : Here we moved the path before adding shapes to already closed path.
+ //We don't need this move for other platforms. We reported this problem in below link.
+ //https://github.com/dotnet/maui/issues/3507
+ //Once the problem resolved, we need to remove this part.
+
+ var startAngleVector = Utility.AngleToVector(startAngle);
+ var startPoint = new PointF((float)(_center.X + (outerRadius * startAngleVector.X)),
+ (float)(_center.Y + (outerRadius * startAngleVector.Y)));
+ pathF.MoveTo(startPoint);
+#endif
+ pathF.AddArc(outerArcTopLeft, outerArcBottomRight, -(float)startAngle, -(float)endAngle, true);
+ Point vector = Utility.AngleToVector(endAngle);
+ PointF point = new PointF((float)(_center.X + (innerRadius * vector.X)),
+ (float)(_center.Y + (innerRadius * vector.Y)));
+
+ // Draw line to inner arc end angle.
+ pathF.LineTo(point);
+
+ // Draw inner arc.
+ pathF.AddArc(innerArcTopLeft, innerArcBottomRight, -(float)endAngle, -(float)startAngle, false);
+
+ vector = Utility.AngleToVector(startAngle);
+ point = new PointF((float)(_center.X + (outerRadius * vector.X)),
+ (float)(_center.Y + (outerRadius * vector.Y)));
+
+ // Draw line to outer arc start angle.
+ pathF.LineTo(point);
+
+ // Close the path.
+ pathF.Close();
+ }
+
+ ///
+ /// To validate start and end angle.
+ ///
+ void ValidateStartEndAngle()
+ {
+ var start = double.IsNaN(StartAngle) ? 0 : StartAngle > 360 ? StartAngle % 360 : StartAngle;
+ var end = double.IsNaN(EndAngle) ? 0 : EndAngle > 360 ? EndAngle % 360 : EndAngle;
+ _actualStartAngle = start;
+ end = (end - start) % 360 == 0 ? end - 0.01 : end;
+ while (end < start)
+ {
+ end += 360;
+ }
+
+ _actualEndAngle = end;
+ _actualSweepAngle = Utility.CalculateSweepAngle(_actualStartAngle, _actualEndAngle);
+ }
+
+ ///
+ /// To calculate the radius.
+ ///
+ void CalculateRadius()
+ {
+ if (AvailableSize.IsZero)
+ {
+ return;
+ }
+
+ _radius = Math.Min(AvailableSize.Width, AvailableSize.Height) * 0.5;
+ _center = GetCenter(_radius);
+ double diff;
+ double centerYDiff = Math.Abs((AvailableSize.Height / 2) - _center.Y);
+ double centerXDiff = Math.Abs((AvailableSize.Width / 2) - _center.X);
+ if (AvailableSize.Width > AvailableSize.Height)
+ {
+ diff = centerYDiff / 2;
+ double radius = (AvailableSize.Height / 2) + diff;
+
+ if ((AvailableSize.Width / 2) < radius)
+ {
+ double actualDiff = (AvailableSize.Width / 2) - (AvailableSize.Height / 2);
+ diff = actualDiff * 0.7f;
+ }
+ }
+ else
+ {
+ diff = centerXDiff / 2;
+ double radius = (AvailableSize.Width / 2) + diff;
+
+ if (AvailableSize.Height / 2 < radius)
+ {
+ double actualDiff = (AvailableSize.Height / 2) - (AvailableSize.Width / 2);
+ diff = actualDiff * 0.7f;
+ }
+ }
+
+ _radius += diff;
+ }
+
+ ///
+ /// To measure and align content.
+ ///
+ void AlignCustomContent()
+ {
+ if (Content != null)
+ {
+ Size minSize = Content.Measure(_radius * 2, _radius * 2);
+ AbsoluteLayout.SetLayoutBounds(Content, new Rect(_center.X - minSize.Width / 2, _center.Y - minSize.Height / 2, minSize.Width, minSize.Height));
+ }
+ }
+
+ ///
+ /// To calculate the center point.
+ ///
+ /// The radius.
+ /// The center point.
+ PointF GetCenter(double radius)
+ {
+ Point centerPoint;
+ centerPoint = new Point(AvailableSize.Width * 0.5d, AvailableSize.Height * 0.5d);
+ if (_actualSweepAngle == 359.99 || IsIndeterminate)
+ {
+ return centerPoint;
+ }
+
+ double startAngle = _actualStartAngle;
+ double endAngle = _actualEndAngle;
+ var arraySize = ((Math.Max(Math.Abs((int)startAngle / 90), Math.Abs((int)endAngle / 90)) + 1) * 2) + 1;
+ double[] regions = new double[arraySize];
+ int arrayIndex = 0;
+ for (int i = -(arraySize / 2); i < (arraySize / 2) + 1; i++)
+ {
+ regions[arrayIndex] = i * 90;
+ arrayIndex++;
+ }
+
+ List region = new List();
+ if (startAngle < endAngle)
+ {
+ for (int i = 0; i < regions.Length; i++)
+ {
+ if (regions[i] > startAngle && regions[i] < endAngle)
+ {
+ region.Add((int)((regions[i] % 360) < 0 ? (regions[i] % 360) + 360 : (regions[i] % 360)));
+ }
+ }
+ }
+ else
+ {
+ for (int i = 0; i < regions.Length; i++)
+ {
+ if (regions[i] < startAngle && regions[i] > endAngle)
+ {
+ region.Add((int)((regions[i] % 360) < 0 ? (regions[i] % 360) + 360 : (regions[i] % 360)));
+ }
+ }
+ }
+
+ return CalculateActualCenter(centerPoint, region, radius);
+ }
+
+ ///
+ /// Calculate actual center for progress bar.
+ ///
+ /// The center point.
+ /// List of regions.
+ /// The Radius.
+ /// Returns actual center point.
+ PointF CalculateActualCenter(Point centerPoint, List region, double radius)
+ {
+ var startRadian = 2 * Math.PI * _actualStartAngle / 360;
+ var endRadian = 2 * Math.PI * _actualEndAngle / 360;
+ Point startPoint = new Point(centerPoint.X + (radius * Math.Cos(startRadian)), centerPoint.Y + (radius * Math.Sin(startRadian)));
+ Point endPoint = new Point(centerPoint.X + (radius * Math.Cos(endRadian)), centerPoint.Y + (radius * Math.Sin(endRadian)));
+ Point actualCenter = centerPoint;
+ switch (region.Count)
+ {
+ case 0:
+ var longX = Math.Abs(centerPoint.X - startPoint.X) > Math.Abs(centerPoint.X - endPoint.X) ? startPoint.X : endPoint.X;
+ var longY = Math.Abs(centerPoint.Y - startPoint.Y) > Math.Abs(centerPoint.Y - endPoint.Y) ? startPoint.Y : endPoint.Y;
+ var midPoint = new Point(Math.Abs(centerPoint.X + longX) / 2, Math.Abs(centerPoint.Y + longY) / 2);
+ actualCenter.X = centerPoint.X + (centerPoint.X - midPoint.X);
+ actualCenter.Y = centerPoint.Y + (centerPoint.Y - midPoint.Y);
+ break;
+
+ case 1:
+ midPoint = CalculateRegionMidPoint(startPoint, endPoint, centerPoint, region[0]);
+ actualCenter.X = centerPoint.X + ((centerPoint.X - midPoint.X) >= radius ? 0 : (centerPoint.X - midPoint.X));
+ actualCenter.Y = centerPoint.Y + ((centerPoint.Y - midPoint.Y) >= radius ? 0 : (centerPoint.Y - midPoint.Y));
+ break;
+
+ case 2:
+ midPoint = CalculateRegionMidPoint(startPoint, endPoint, centerPoint, region[0], region[1]);
+ actualCenter.X = centerPoint.X + (midPoint.X == 0 ? 0 : (centerPoint.X - midPoint.X) >= radius ? 0 : (centerPoint.X - midPoint.X));
+ actualCenter.Y = centerPoint.Y + (midPoint.Y == 0 ? 0 : (centerPoint.Y - midPoint.Y) >= radius ? 0 : (centerPoint.Y - midPoint.Y));
+ break;
+
+ case 3:
+ midPoint = CalculateRegionMidPoint(startPoint, endPoint, centerPoint, region[0], region[1], region[2]);
+ actualCenter.X = centerPoint.X + (midPoint.X == 0 ? 0 : (centerPoint.X - midPoint.X) >= radius ? 0 : (centerPoint.X - midPoint.X));
+ actualCenter.Y = centerPoint.Y + (midPoint.Y == 0 ? 0 : (centerPoint.Y - midPoint.Y) >= radius ? 0 : (centerPoint.Y - midPoint.Y));
+ break;
+ }
+
+ return actualCenter;
+ }
+
+ ///
+ /// Calculate region mid point for center calculation.
+ ///
+ /// The start point.
+ /// The end point.
+ /// The center point.
+ /// The region.
+ /// The mid point for center calculation.
+ Point CalculateRegionMidPoint(Point startPoint, Point endPoint, Point centerPoint, int region)
+ {
+ Point point1 = new Point(), point2 = new Point();
+ var maxRadian = 2 * Math.PI * region / 360;
+ var maxPoint = new Point(centerPoint.X + (_radius * Math.Cos(maxRadian)), centerPoint.Y + (_radius * Math.Sin(maxRadian)));
+ switch (region)
+ {
+ case 270:
+ point1 = new Point(startPoint.X, maxPoint.Y);
+ point2 = new Point(endPoint.X, centerPoint.Y);
+ break;
+ case 0:
+ case 360:
+ point1 = new Point(centerPoint.X, endPoint.Y);
+ point2 = new Point(maxPoint.X, startPoint.Y);
+ break;
+ case 90:
+ point1 = new Point(endPoint.X, centerPoint.Y);
+ point2 = new Point(startPoint.X, maxPoint.Y);
+ break;
+ case 180:
+ point1 = new Point(maxPoint.X, startPoint.Y);
+ point2 = new Point(centerPoint.X, endPoint.Y);
+ break;
+ }
+
+ return new Point((point1.X + point2.X) / 2, (point1.Y + point2.Y) / 2);
+ }
+
+ ///
+ /// Calculate region mid point for center calculation.
+ ///
+ /// The start point.
+ /// The end point.
+ /// The center point.
+ /// The region 1.
+ /// The region 2.
+ /// The mid point for center calculation.
+ Point CalculateRegionMidPoint(Point startPoint, Point endPoint, Point centerPoint, int region1, int region2)
+ {
+ Point point1, point2;
+ var minRadian = 2 * Math.PI * region1 / 360;
+ var maxRadian = 2 * Math.PI * region2 / 360;
+ var maxPoint = new Point(centerPoint.X + (_radius * Math.Cos(maxRadian)), centerPoint.Y + (_radius * Math.Sin(maxRadian)));
+ Point minPoint = new Point(centerPoint.X + (_radius * Math.Cos(minRadian)), centerPoint.Y + (_radius * Math.Sin(minRadian)));
+ if ((region1 == 0 && region2 == 90) || (region1 == 180 && region2 == 270))
+ {
+ point1 = new Point(minPoint.X, maxPoint.Y);
+ }
+ else
+ {
+ point1 = new Point(maxPoint.X, minPoint.Y);
+ }
+
+ if (region1 == 0 || region1 == 180)
+ {
+ point2 = new Point(Utility.GetMinMaxValue(startPoint, endPoint, region1), Utility.GetMinMaxValue(startPoint, endPoint, region2));
+ }
+ else
+ {
+ point2 = new Point(Utility.GetMinMaxValue(startPoint, endPoint, region2), Utility.GetMinMaxValue(startPoint, endPoint, region1));
+ }
+
+ return new Point(Math.Abs(point1.X - point2.X) / 2 >= _radius ? 0 : (point1.X + point2.X) / 2, y: Math.Abs(point1.Y - point2.Y) / 2 >= _radius ? 0 : (point1.Y + point2.Y) / 2);
+ }
+
+ ///
+ /// Calculate region mid point for center calculation.
+ ///
+ /// The start point.
+ /// The end point.
+ /// The center point.
+ /// The region 1.
+ /// The region 2.
+ /// The region 3.
+ /// The mid point for center calculation.
+ Point CalculateRegionMidPoint(Point startPoint, Point endPoint, Point centerPoint, int region1, int region2, int region3)
+ {
+ float region0Radian = (float)(2 * Math.PI * region1 / 360);
+ float region1Radian = (float)(2 * Math.PI * region2 / 360);
+ float region2Radian = (float)(2 * Math.PI * region3 / 360);
+ Point region0Point = new Point((float)(centerPoint.X + (_radius * Math.Cos(region0Radian))), (float)(centerPoint.Y + (_radius * Math.Sin(region0Radian))));
+ Point region1Point = new Point((float)(centerPoint.X + (_radius * Math.Cos(region1Radian))), (float)(centerPoint.Y + (_radius * Math.Sin(region1Radian))));
+ Point region2Point = new Point((float)(centerPoint.X + (_radius * Math.Cos(region2Radian))), (float)(centerPoint.Y + (_radius * Math.Sin(region2Radian))));
+ Point regionPoint1 = new Point(), regionPoint2 = new Point();
+ switch (region3)
+ {
+ case 0:
+ case 360:
+ regionPoint1 = new Point(region0Point.X, region1Point.Y);
+ regionPoint2 = new Point(region2Point.X, Math.Max(startPoint.Y, endPoint.Y));
+ break;
+ case 90:
+ regionPoint1 = new Point(Math.Min(startPoint.X, endPoint.X), region0Point.Y);
+ regionPoint2 = new Point(region1Point.X, region2Point.Y);
+ break;
+ case 180:
+ regionPoint1 = new Point(region2Point.X, Math.Min(startPoint.Y, endPoint.Y));
+ regionPoint2 = new Point(region0Point.X, region1Point.Y);
+ break;
+ case 270:
+ regionPoint1 = new Point(region1Point.X, region2Point.Y);
+ regionPoint2 = new Point(Math.Max(startPoint.X, endPoint.X), region0Point.Y);
+ break;
+ }
+
+ return new Point(Math.Abs(regionPoint1.X - regionPoint2.X) / 2 >= _radius ? 0 : (regionPoint1.X + regionPoint2.X) / 2,
+ Math.Abs(regionPoint1.Y - regionPoint2.Y) / 2 >= _radius ? 0 : (regionPoint1.Y + regionPoint2.Y) / 2);
+ }
+
+ ///
+ /// Converts progress value to its respective angle.
+ ///
+ /// The value.
+ /// Angle of the given value.
+ double ValueToAngle(double value)
+ {
+ return FactorToAngle(ValueToFactor(value));
+ }
+
+ ///
+ /// Converts factor value to angle.
+ ///
+ /// Input factor value.
+ /// Returns the angle value.
+ double FactorToAngle(double factor)
+ {
+ return _actualStartAngle + (factor * _actualSweepAngle);
+ }
+
+ ///
+ /// Gets the theme dictionary for the circular progress bar.
+ ///
+ ///
+ /// A containing the theme resources for the circular progress bar.
+ ///
+ ResourceDictionary IParentThemeElement.GetThemeDictionary()
+ {
+ return new SfCircularProgressBarStyles();
+ }
+
+ ///
+ /// Invoked when the control theme changes.
+ ///
+ /// The old theme name.
+ /// The new theme name.
+ void IThemeElement.OnControlThemeChanged(string oldTheme, string newTheme)
+ {
+ SetDynamicResource(CircularProgressBarBackgroundProperty, "SfCircularProgressBarBackground");
+ }
+
+ ///
+ /// Invoked when the common theme changes.
+ ///
+ /// The old theme name.
+ /// The new theme name.
+ void IThemeElement.OnCommonThemeChanged(string oldTheme, string newTheme)
+ {
+ // No implementation required for common theme changes.
+ }
+
+
+ ///
+ /// Triggered when the view is loaded.
+ ///
+ /// Sender.
+ /// Event argument.
+ void OnProgressBarLoaded(object? sender, EventArgs e)
+ {
+ if (IsIndeterminate
+ && IndeterminateAnimationDuration > 0
+ && IndeterminateIndicatorWidthFactor > 0
+ && !AnimationExtensions.AnimationIsRunning(this, "IndeterminateAnimation")
+ && IsIndeterminateAnimationAborted)
+ {
+ CreateIndeterminateAnimation();
+ IsIndeterminateAnimationAborted = false;
+ Loaded -= OnProgressBarLoaded;
+ }
+ }
+
+ ///
+ /// Triggered while the view removed from main visual tree.
+ ///
+ /// Sender.
+ /// Event argument.
+ void OnProgressBarUnloaded(object? sender, EventArgs e)
+ {
+ AnimationExtensions.AbortAnimation(this, "IndeterminateAnimation");
+ // TASK-886910: SfLinearProgressBar gets frozen when switching between tabs in TabBar
+ IsIndeterminateAnimationAborted = true;
+ Unloaded -= OnProgressBarUnloaded;
+ }
+
+ #endregion
+
+ #region Protected Override Methods
+
+ ///
+ /// Arrange the child elements.
+ ///
+ /// The bounds.
+ /// Return child element size.
+ protected override Size ArrangeContent(Rect bounds)
+ {
+ if (AvailableSize.Width > 0 && AvailableSize.Height > 0)
+ {
+ if (AvailableSize.Width != _arrangeSize.Width ||
+ AvailableSize.Height != _arrangeSize.Height)
+ {
+ _arrangeSize = AvailableSize;
+ UpdateProgressBar();
+ }
+ }
+
+ return base.ArrangeContent(bounds);
+ }
+
+ ///
+ /// Measures the size of layout required for child elements.
+ ///
+ /// The widthConstraint.
+ /// The heightConstraint.
+ /// Return child element size.
+ protected override Size MeasureContent(double widthConstraint, double heightConstraint)
+ {
+ base.MeasureContent(widthConstraint, heightConstraint);
+ var width = double.IsPositiveInfinity(widthConstraint) ? 100d : widthConstraint;
+ var height = double.IsPositiveInfinity(heightConstraint) ? 100d : heightConstraint;
+ if (width > 0 && height > 0)
+ {
+ GetAvailableSize(width, height);
+ }
+
+ return AvailableSize;
+ }
+
+ void GetAvailableSize(double width, double height)
+ {
+ if (HeightRequest > 0)
+ {
+ height = HeightRequest;
+ }
+ else
+ {
+ if (MaximumHeightRequest > 0 && height > MaximumHeightRequest)
+ {
+ height = MaximumHeightRequest;
+ }
+
+ if (MinimumHeightRequest > 0 && height < MinimumHeightRequest)
+ {
+ height = MinimumHeightRequest;
+ }
+ }
+
+ if (WidthRequest > 0)
+ {
+ width = WidthRequest;
+ }
+ else
+ {
+ if (MaximumWidthRequest > 0 && width > MaximumWidthRequest)
+ {
+ width = MaximumWidthRequest;
+ }
+
+ if (MinimumWidthRequest > 0 && width < MinimumWidthRequest)
+ {
+ width = MinimumWidthRequest;
+ }
+ }
+
+ AvailableSize = new Size(width, height);
+ }
+
+ #endregion
+
+ #region Internal Override Methods
+
+ ///
+ /// To update progress bar and its visual elements.
+ ///
+ internal override void UpdateProgressBar()
+ {
+ CalculateRadius();
+ AlignCustomContent();
+ base.UpdateProgressBar();
+ }
+
+ ///
+ /// To create a track path.
+ ///
+ internal override void CreateTrackPath()
+ {
+ CreatePath(true);
+ }
+
+ ///
+ /// To create a progress path.
+ ///
+ /// The animation duration.
+ /// The easing.
+ internal override void CreateProgressPath(double? animationDuration = null, Easing? easing = null)
+ {
+ double duration = animationDuration ?? AnimationDuration;
+ var ease = easing ?? AnimationEasing;
+ if (CanAnimate && !IsIndeterminate && duration > 0)
+ {
+ CanAnimate = false;
+ CreateProgressAnimation(duration, ease);
+ }
+ else
+ {
+ CreatePath(false, duration);
+ }
+ }
+
+ ///
+ /// To animate progress for indeterminate state.
+ ///
+ internal override void CreateIndeterminateAnimation()
+ {
+ if (IndeterminateIndicatorWidthFactor < 1)
+ {
+ AnimationExtensions.Animate(this,
+ "IndeterminateAnimation",
+ OnIndeterminateAnimationUpdate,
+ 0,
+ 360,
+ 16,
+ (uint)IndeterminateAnimationDuration,
+ IndeterminateAnimationEasing,
+ null,
+ () => IsIndeterminate);
+ }
+ else
+ {
+ CreateProgressPath();
+ }
+ }
+
+ ///
+ /// To draw a circular progress bar elements such as track, progress, etc.
+ ///
+ /// The drawing canvas.
+ internal override void DrawProgressBar(ICanvas canvas)
+ {
+ base.DrawProgressBar(canvas);
+ DrawProgress(canvas);
+ }
+
+ ///
+ /// To draw a progress.
+ ///
+ /// The canvas.
+ internal override void DrawProgress(ICanvas canvas)
+ {
+ canvas.SaveState();
+ if (_gradientArcPaths != null && _gradientArcPaths.Count > 0 && !IsIndeterminate && SegmentCount <= 1)
+ {
+ foreach (var path in _gradientArcPaths)
+ {
+ canvas.SetFillPaint(path.FillPaint, path.ArcPath.Bounds);
+ canvas.FillPath(path.ArcPath);
+ }
+ }
+ else if (ProgressPath != null)
+ {
+ canvas.SetFillPaint(ProgressFill, ProgressPath.Bounds);
+ canvas.FillPath(ProgressPath);
+ }
+
+ canvas.RestoreState();
+ }
+
+ ///
+ /// To create gradient fill for circular progress path.
+ ///
+ internal override void CreateGradient()
+ {
+ CreateProgressPath();
+ }
+
+ #endregion
+
+ #region Property Changed Methods
+
+ ///
+ /// Called when or property changed.
+ ///
+ /// The bindable object.
+ /// The old value.
+ /// The new value.
+ static void OnAnglePropertyChanged(BindableObject bindable, object oldValue, object newValue)
+ {
+ if (bindable is SfCircularProgressBar circularProgressBar)
+ {
+ circularProgressBar.ValidateStartEndAngle();
+ if (!circularProgressBar.AvailableSize.IsZero)
+ {
+ circularProgressBar.UpdateProgressBar();
+ circularProgressBar.InvalidateDrawable();
+ }
+ }
+ }
+
+ ///
+ /// Called when property changed.
+ ///
+ /// The bindable object.
+ /// The old value.
+ /// The new value.
+ static void OnThicknessUnitChanged(BindableObject bindable, object oldValue, object newValue)
+ {
+ if (bindable is SfCircularProgressBar circularProgressBar && !circularProgressBar.AvailableSize.IsZero)
+ {
+ circularProgressBar.UpdateProgressBar();
+ circularProgressBar.InvalidateDrawable();
+ }
+ }
+
+ ///
+ /// Called when or property changed.
+ ///
+ /// The bindable object.
+ /// The old value.
+ /// The new value.
+ static void OnTrackPropertiesChanged(BindableObject bindable, object oldValue, object newValue)
+ {
+ if (bindable is SfCircularProgressBar circularProgressBar && !circularProgressBar.AvailableSize.IsZero)
+ {
+ circularProgressBar.CreateTrackPath();
+ circularProgressBar.InvalidateDrawable();
+ }
+ }
+
+ ///
+ /// Called when or property changed.
+ ///
+ /// The bindable object.
+ /// The old value.
+ /// The new value.
+ static void OnProgressPropertiesChanged(BindableObject bindable, object oldValue, object newValue)
+ {
+ if (bindable is SfCircularProgressBar circularProgressBar && !circularProgressBar.AvailableSize.IsZero)
+ {
+ circularProgressBar.CreateProgressPath();
+ circularProgressBar.InvalidateDrawable();
+ }
+ }
+
+ ///
+ /// Called when property changed.
+ ///
+ /// The bindable object.
+ /// The old value.
+ /// The new value.
+ static void OnTrackCornerStylePropertyChanged(BindableObject bindable, object oldValue, object newValue)
+ {
+ if (bindable is SfCircularProgressBar circularProgressBar && !circularProgressBar.AvailableSize.IsZero)
+ {
+ circularProgressBar.CreatePath(true);
+ circularProgressBar.InvalidateDrawable();
+ }
+ }
+
+ ///
+ /// Called when property changed.
+ ///
+ /// The bindable object.
+ /// The old value.
+ /// The new value.
+ static void OnProgressCornerStylePropertyChanged(BindableObject bindable, object oldValue, object newValue)
+ {
+ if (bindable is SfCircularProgressBar circularProgressBar && !circularProgressBar.AvailableSize.IsZero)
+ {
+ circularProgressBar.CreatePath(false);
+ circularProgressBar.InvalidateDrawable();
+ }
+ }
+
+ ///
+ /// Called when property changed.
+ ///
+ /// The BindableObject.
+ /// Old value.
+ /// New value.
+ static void OnContentPropertyChanged(BindableObject bindable, object oldValue, object newValue)
+ {
+ if (bindable is SfCircularProgressBar circularProgressBar)
+ {
+ if (oldValue is View oldView && circularProgressBar.Children.Contains(oldView))
+ {
+ circularProgressBar.Remove(oldView);
+ }
+
+ if (newValue is View newView && !circularProgressBar.Children.Contains(newView))
+ {
+ circularProgressBar.Add(newView);
+ if (!circularProgressBar.AvailableSize.IsZero)
+ {
+ circularProgressBar.AlignCustomContent();
+ }
+ }
+ }
+ }
+
+ ///
+ /// called when property changed.
+ ///
+ /// The bindable object.
+ /// The old value.
+ /// The new value.
+ static void OnCircularProgressBarBackgroundChanged(BindableObject bindable, object oldValue, object newValue)
+ {
+ if (bindable is SfCircularProgressBar circularProgressBar)
+ {
+ circularProgressBar.BackgroundColor = circularProgressBar.CircularProgressBarBackground;
+ }
+ }
+
+ #endregion
+ }
+}
diff --git a/maui/src/ProgressBar/SfLinearProgressBar/SfLinearProgressBar.cs b/maui/src/ProgressBar/SfLinearProgressBar/SfLinearProgressBar.cs
new file mode 100644
index 00000000..e077eb1e
--- /dev/null
+++ b/maui/src/ProgressBar/SfLinearProgressBar/SfLinearProgressBar.cs
@@ -0,0 +1,1323 @@
+using Syncfusion.Maui.Toolkit.Themes;
+
+namespace Syncfusion.Maui.Toolkit.ProgressBar
+{
+ ///
+ /// Represents class, which is a UI control that displays a linear progress indicator.
+ /// This control allows for visualizing the progress of an operation through a customizable bar that fills
+ /// from left to right. It supports features such as determinate and indeterminate progress modes,
+ /// secondary progress indication, customizable appearance including track and progress colors,
+ /// segment divisions, corner radius customization, and animation effects.
+ ///
+ ///
+ /// # [XAML](#tab/tabid-1)
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ /// ]]>
+ /// # [C#](#tab/tabid-2)
+ ///
+ /// ***
+ ///
+ public class SfLinearProgressBar : ProgressBarBase, IParentThemeElement
+ {
+ #region Fields
+ ///
+ /// Backing field to store a secondary progress path instance.
+ ///
+ PathF? _secondProgressPath;
+
+ ///
+ /// Backing field to store the indeterminate animation value.
+ ///
+ double _indeterminateAnimationValue;
+
+ ///
+ /// Backing field to store the secondary progress.
+ ///
+ double _actualSecondaryProgress;
+
+ ///
+ /// Backing field to store linear gradient brush value.
+ ///
+ LinearGradientBrush? _linearGradientBrush;
+
+ ///
+ /// Backing field to store value to identify whether to animate secondary progress.
+ ///
+ bool _canAnimateSecondaryProgress;
+
+ #endregion
+
+ #region Bindable Properties
+
+ ///
+ /// Identifies the bindable property.
+ ///
+ ///
+ /// The identifier for bindable property.
+ ///
+ public static readonly BindableProperty TrackHeightProperty =
+ BindableProperty.Create(
+ nameof(TrackHeight),
+ typeof(double),
+ typeof(SfLinearProgressBar),
+ 5d,
+ propertyChanged: OnHeightPropertyChanged);
+
+ ///
+ /// Identifies the bindable property.
+ ///
+ ///
+ /// The identifier for bindable property.
+ ///
+ public static readonly BindableProperty TrackCornerRadiusProperty =
+ BindableProperty.Create(
+ nameof(TrackCornerRadius),
+ typeof(CornerRadius),
+ typeof(SfLinearProgressBar),
+ null,
+ defaultValueCreator: bindable => new CornerRadius(0d),
+ propertyChanged: OnTrackCornerRadiusChanged);
+
+ ///
+ /// Identifies the bindable property.
+ ///
+ ///
+ /// The identifier for bindable property.
+ ///
+ public static readonly BindableProperty ProgressHeightProperty =
+ BindableProperty.Create(
+ nameof(ProgressHeight),
+ typeof(double),
+ typeof(SfLinearProgressBar),
+ 5d,
+ propertyChanged: OnHeightPropertyChanged);
+
+ ///
+ /// Identifies the bindable property.
+ ///
+ ///
+ /// The identifier for bindable property.
+ ///
+ public static readonly BindableProperty ProgressCornerRadiusProperty =
+ BindableProperty.Create(
+ nameof(ProgressCornerRadius),
+ typeof(CornerRadius),
+ typeof(SfLinearProgressBar),
+ null,
+ defaultValueCreator: bindable => new CornerRadius(0d),
+ propertyChanged: OnProgressCornerRadiusChanged);
+
+ ///
+ /// Identifies the bindable property.
+ ///
+ ///
+ /// The identifier for bindable property.
+ ///
+ public static readonly BindableProperty SecondaryProgressProperty =
+ BindableProperty.Create(
+ nameof(SecondaryProgress),
+ typeof(double),
+ typeof(SfLinearProgressBar),
+ 0d,
+ propertyChanged: OnSecondaryProgressPropertyChanged);
+
+ ///
+ /// Identifies the bindable property.
+ ///
+ ///
+ /// The identifier for bindable property.
+ ///
+ public static readonly BindableProperty SecondaryProgressFillProperty =
+ BindableProperty.Create(
+ nameof(SecondaryProgressFill),
+ typeof(Brush),
+ typeof(SfLinearProgressBar),
+ null,
+ defaultValueCreator: bindable => new SolidColorBrush(Color.FromArgb("#806750a4")),
+ propertyChanged: OnSecondaryProgressFillChanged);
+
+ ///
+ /// Identifies the bindable property.
+ ///
+ ///
+ /// The identifier for bindable property.
+ ///
+ public static readonly BindableProperty SecondaryAnimationDurationProperty =
+ BindableProperty.Create(
+ nameof(SecondaryAnimationDuration),
+ typeof(double),
+ typeof(SfLinearProgressBar),
+ 1500d,
+ BindingMode.Default,
+ null);
+
+ ///
+ /// Identifies the bindable property.
+ ///
+ ///
+ /// The identifier for bindable property.
+ ///
+ public static readonly BindableProperty SecondaryProgressHeightProperty =
+ BindableProperty.Create(
+ nameof(SecondaryProgressHeight),
+ typeof(double),
+ typeof(SfLinearProgressBar),
+ 5d,
+ propertyChanged: OnHeightPropertyChanged);
+
+ ///
+ /// Identifies the bindable property.
+ ///
+ ///
+ /// The identifier for bindable property.
+ ///
+ public static readonly BindableProperty SecondaryProgressCornerRadiusProperty =
+ BindableProperty.Create(
+ nameof(SecondaryProgressCornerRadius),
+ typeof(CornerRadius),
+ typeof(SfLinearProgressBar),
+ null,
+ defaultValueCreator: bindable => new CornerRadius(0d),
+ propertyChanged: OnSecondaryProgressCornerRadiusChanged);
+
+ ///
+ /// Identifies the bindable property.
+ ///
+ ///
+ /// The identifier for bindable property.
+ ///
+ public static readonly BindableProperty ProgressPaddingProperty =
+ BindableProperty.Create(
+ nameof(ProgressPadding),
+ typeof(double),
+ typeof(SfLinearProgressBar),
+ 0d,
+ propertyChanged: OnProgressPaddingPropertyChanged);
+
+ ///
+ /// Identifies the bindable property.
+ ///
+ ///
+ /// The identifier for bindable property.
+ ///
+ internal static readonly BindableProperty LinearProgressBarBackgroundProperty =
+ BindableProperty.Create(
+ nameof(LinearProgressBarBackground),
+ typeof(Color),
+ typeof(SfLinearProgressBar),
+ defaultValueCreator: bindable => Color.FromArgb("#FFFBFE"),
+ propertyChanged: OnLinearProgressBarBackgroundChanged);
+
+ #endregion
+
+ #region Constructor
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ public SfLinearProgressBar()
+ {
+ ThemeElement.InitializeThemeResources(this, "SfLinearProgressBarTheme");
+ BackgroundColor = LinearProgressBarBackground;
+ // TASK-886910: SfLinearProgressBar gets frozen when switching between tabs in TabBar
+ Loaded += OnProgressBarLoaded;
+ Unloaded += OnProgressBarUnloaded;
+ }
+
+ #endregion
+
+ #region Properties
+
+ ///
+ /// Gets or sets a value to determine the corner radius of the track.
+ ///
+ ///
+ /// The corner radius of the track.
+ ///
+ ///
+ /// # [XAML](#tab/tabid-1)
+ ///
+ /// ]]>
+ /// # [C#](#tab/tabid-2)
+ ///
+ /// ***
+ ///
+ public CornerRadius TrackCornerRadius
+ {
+ get { return (CornerRadius)GetValue(TrackCornerRadiusProperty); }
+ set { SetValue(TrackCornerRadiusProperty, value); }
+ }
+
+ ///
+ /// Gets or sets a value to determine the track height.
+ ///
+ ///
+ /// The height of the track. The default value is 5.
+ ///
+ ///
+ /// # [XAML](#tab/tabid-1)
+ ///
+ /// ]]>
+ /// # [C#](#tab/tabid-2)
+ ///
+ /// ***
+ ///
+ public double TrackHeight
+ {
+ get { return (double)GetValue(TrackHeightProperty); }
+ set { SetValue(TrackHeightProperty, value); }
+ }
+
+ ///
+ /// Gets or sets a value to determine the corner radius of the progress.
+ ///
+ ///
+ /// The corner radius of the progress.
+ ///
+ ///
+ /// # [XAML](#tab/tabid-1)
+ ///
+ /// ]]>
+ /// # [C#](#tab/tabid-2)
+ ///
+ /// ***
+ ///
+ public CornerRadius ProgressCornerRadius
+ {
+ get { return (CornerRadius)GetValue(ProgressCornerRadiusProperty); }
+ set { SetValue(ProgressCornerRadiusProperty, value); }
+ }
+
+ ///
+ /// Gets or sets a value to determine the progress height.
+ ///
+ ///
+ /// The height of the progress. The default value is 5.
+ ///
+ ///
+ /// # [XAML](#tab/tabid-1)
+ ///
+ /// ]]>
+ /// # [C#](#tab/tabid-2)
+ ///
+ /// ***
+ ///
+ public double ProgressHeight
+ {
+ get { return (double)GetValue(ProgressHeightProperty); }
+ set { SetValue(ProgressHeightProperty, value); }
+ }
+
+ ///
+ /// Gets or sets the secondary progress value for the .
+ ///
+ ///
+ /// The default value is 0.
+ ///
+ ///
+ /// # [XAML](#tab/tabid-1)
+ ///
+ /// ]]>
+ /// # [C#](#tab/tabid-2)
+ ///
+ /// ***
+ ///
+ public double SecondaryProgress
+ {
+ get { return (double)GetValue(SecondaryProgressProperty); }
+ set { SetValue(SecondaryProgressProperty, value); }
+ }
+
+ ///
+ /// Gets or sets the brush that paints the interior area of the secondary progress.
+ ///
+ ///
+ /// SecondaryProgressFill specifies how the secondary progress is painted.
+ ///
+ ///
+ /// # [XAML](#tab/tabid-1)
+ ///
+ /// ]]>
+ /// # [C#](#tab/tabid-2)
+ ///
+ /// ***
+ ///
+ public Brush SecondaryProgressFill
+ {
+ get { return (Brush)GetValue(SecondaryProgressFillProperty); }
+ set { SetValue(SecondaryProgressFillProperty, value); }
+ }
+
+ ///
+ /// Gets or sets a value that specifies the secondary progress animation duration in milliseconds.
+ ///
+ ///
+ /// The default value is 1500 milliseconds.
+ ///
+ ///
+ /// # [XAML](#tab/tabid-1)
+ ///
+ /// ]]>
+ /// # [C#](#tab/tabid-2)
+ ///
+ /// ***
+ ///
+ public double SecondaryAnimationDuration
+ {
+ get { return (double)GetValue(SecondaryAnimationDurationProperty); }
+ set { SetValue(SecondaryAnimationDurationProperty, value); }
+ }
+
+ ///
+ /// Gets or sets a value to determine the secondary progress height.
+ ///
+ ///
+ /// The height of the secondary progress. The default value is 5.
+ ///
+ ///
+ /// # [XAML](#tab/tabid-1)
+ ///
+ /// ]]>
+ /// # [C#](#tab/tabid-2)
+ ///
+ /// ***
+ ///
+ public double SecondaryProgressHeight
+ {
+ get { return (double)GetValue(SecondaryProgressHeightProperty); }
+ set { SetValue(SecondaryProgressHeightProperty, value); }
+ }
+
+ ///
+ /// Gets or sets a value to determine the corner radius of the secondary progress.
+ ///
+ ///
+ /// The corner radius of the secondary progress.
+ ///
+ ///
+ /// # [XAML](#tab/tabid-1)
+ ///
+ /// ]]>
+ /// # [C#](#tab/tabid-2)
+ ///
+ /// ***
+ ///
+ public CornerRadius SecondaryProgressCornerRadius
+ {
+ get { return (CornerRadius)GetValue(SecondaryProgressCornerRadiusProperty); }
+ set { SetValue(SecondaryProgressCornerRadiusProperty, value); }
+ }
+
+ ///
+ /// Gets or sets the value that specifies the padding to be applied to the progress or the secondary progress indicator.
+ ///
+ ///
+ /// The padding of the progress or the secondary progress. The default value is 0.
+ ///
+ ///
+ /// The padding will be applied only at the start and end of the Progress or SecondaryProgress indicator.
+ /// To adjust the top and bottom of the indicator ProgressHeight or SecondaryProgressHeight is considered.
+ ///
+ ///
+ /// # [XAML](#tab/tabid-1)
+ ///
+ /// ]]>
+ /// # [C#](#tab/tabid-2)
+ ///
+ /// ***
+ ///
+ public double ProgressPadding
+ {
+ get { return (double)GetValue(ProgressPaddingProperty); }
+ set { SetValue(ProgressPaddingProperty, value); }
+ }
+
+ ///
+ /// Gets or sets the background color of the linear progress bar.
+ ///
+ ///
+ /// # [XAML](#tab/tabid-1)
+ ///
+ /// ]]>
+ /// # [C#](#tab/tabid-2)
+ ///
+ /// ***
+ ///
+ internal Color LinearProgressBarBackground
+ {
+ get { return (Color)GetValue(LinearProgressBarBackgroundProperty); }
+ set { SetValue(LinearProgressBarBackgroundProperty, value); }
+ }
+
+ #endregion
+
+ #region Private Methods
+
+ #region Segment Methods
+
+ ///
+ /// To create a secondary progress.
+ ///
+ void CreateSecondaryProgressPath()
+ {
+ if (!AvailableSize.IsZero)
+ {
+ if (_canAnimateSecondaryProgress && !IsIndeterminate && SecondaryAnimationDuration > 0)
+ {
+ _canAnimateSecondaryProgress = false;
+ CreateSecondaryProgressAnimation();
+ }
+ else
+ {
+ CreatePath("SecondaryProgress");
+ }
+ }
+ }
+
+ ///
+ /// To create the path.
+ ///
+ /// The corresponding element.
+ /// The animation duration.
+ void CreatePath(string element, double? animationDuration = null)
+ {
+ animationDuration ??= AnimationDuration;
+ PathF path = new PathF();
+ double height = 0, width = 0,
+ actualGapWidth = SegmentGapWidth > 0 ? SegmentGapWidth : 0, y = 0, x = 0;
+ CornerRadius actualCornerRadius = 0;
+ int count = 0,
+ actualSegmentCount = SegmentCount > 0 ? SegmentCount : 0;
+ double topLeftRadius, topRightRadius, bottomLeftRadius, bottomRightRadius,
+ segmentWidth = CalculateSegmentWidth();
+ double padding = 0;
+ if (ProgressPadding > 0)
+ {
+ if (ProgressPadding < ((segmentWidth == 0 ? AvailableSize.Width : segmentWidth) / 2))
+ {
+ padding = ProgressPadding;
+ }
+ else
+ {
+ padding = (segmentWidth == 0 ? AvailableSize.Width : segmentWidth) / 2;
+ }
+
+ //Padding has to be applied at both the ends (left and right) of progress or secondary progress indicator.
+ padding *= 2;
+ }
+
+ switch (element)
+ {
+ case "Track":
+ path = TrackPath = new PathF();
+ height = TrackHeight > 0 ? TrackHeight : 0d;
+ width = (float)AvailableSize.Width;
+ y = (GetControlHeight() - height) / 2;
+ actualCornerRadius = TrackCornerRadius;
+ count = IsIndeterminate ? 1 : actualSegmentCount;
+ break;
+ case "Progress":
+ SetupProgressPathParameters(ref path, ref height, ref width, ref y, ref x, ref actualCornerRadius, ref count, padding, segmentWidth, animationDuration);
+ if (IsIndeterminate)
+ {
+ CalculateIndeterminateProgressWidth(IndeterminateIndicatorWidthFactor, AvailableSize.Width, _indeterminateAnimationValue, ref x, ref width);
+ }
+
+ break;
+ case "SecondaryProgress":
+ path = _secondProgressPath = new PathF();
+ CreateSecondaryProgressPath(path, ref height, ref width, ref y, ref x, ref actualCornerRadius, ref count, padding, segmentWidth);
+ break;
+ }
+
+ topLeftRadius = actualCornerRadius.TopLeft > 0 ? actualCornerRadius.TopLeft : 0;
+ topRightRadius = actualCornerRadius.TopRight > 0 ? actualCornerRadius.TopRight : 0;
+ bottomLeftRadius = actualCornerRadius.BottomLeft > 0 ? actualCornerRadius.BottomLeft : 0;
+ bottomRightRadius = actualCornerRadius.BottomRight > 0 ? actualCornerRadius.BottomRight : 0;
+ actualCornerRadius = new CornerRadius(topLeftRadius, topRightRadius, bottomLeftRadius, bottomRightRadius);
+ if (actualSegmentCount > 1 && count > 1 && actualGapWidth != 0 && !IsIndeterminate)
+ {
+ CreateSegmentedPath(count, width, path, (float)y, (float)height, (float)segmentWidth, actualCornerRadius);
+ }
+ else
+ {
+ path.AppendRoundedRectangle((float)x, (float)y, (float)width, (float)height, (float)topLeftRadius, (float)topRightRadius, (float)bottomLeftRadius, (float)bottomRightRadius);
+ }
+ }
+
+ ///
+ /// To create the segmented path.
+ ///
+ /// The segment count.
+ /// The segment width.
+ /// The path.
+ /// The y position.
+ /// The height.
+ /// The height.
+ /// The corner radius.
+ void CreateSegmentedPath(int count, double width, PathF path, float y, float height, float segmentWidth, CornerRadius cornerRadius)
+ {
+ float init = 0, x, pathWidth, indicatorWidth, segmentHeight = height;
+ float actualGapWidth = SegmentGapWidth > 0 ? (float)SegmentGapWidth : 0;
+ float padding = (float)(ProgressPadding > 0 ? (ProgressPadding < (segmentWidth / 2) ? ProgressPadding : (segmentWidth / 2)) : 0);
+ segmentWidth = path != TrackPath ? segmentWidth - (2 * padding) : segmentWidth;
+ int t = 0;
+ for (int i = 0; i < count; i++)
+ {
+ x = init + (i > 0 ? actualGapWidth : 0f);
+ init = init + segmentWidth + (i > 0 ? actualGapWidth : 0f);
+ indicatorWidth = (float)(width + (i * actualGapWidth));
+ if (path != TrackPath)
+ {
+ t += 2;
+ x += (i == 0 ? padding : (2 * padding));
+ init += (i == 0 ? padding : (2 * padding));
+ indicatorWidth += ((t * padding) - padding);
+ }
+
+ pathWidth = init > indicatorWidth ? indicatorWidth - x : segmentWidth;
+ path.AppendRoundedRectangle(x, y, pathWidth, segmentHeight, (float)cornerRadius.TopLeft, (float)cornerRadius.TopRight, (float)cornerRadius.BottomLeft, (float)cornerRadius.BottomRight);
+ }
+ }
+
+ ///
+ /// Creates the path for the secondary progress indicator. This method calculates the dimensions and position based on the specified parameters and updates the path object.
+ ///
+ /// The PathF object to which the secondary progress path will be applied.
+ /// Reference to the height of the secondary progress path.
+ /// Reference to the width of the secondary progress path.
+ /// Reference to the y-coordinate for positioning the secondary progress path.
+ /// Reference to the x-coordinate for positioning the secondary progress path.
+ /// Reference to the corner radius for the secondary progress path.
+ /// Reference to the number of segments in the secondary progress path.
+ /// The padding to be applied to the secondary progress path.
+ /// The segment width for the secondary progress path.
+ void CreateSecondaryProgressPath(PathF path, ref double height, ref double width, ref double y, ref double x, ref CornerRadius actualCornerRadius, ref int count, double padding, double segmentWidth)
+ {
+ if (!IsIndeterminate)
+ {
+ height = SecondaryProgressHeight > 0 ? SecondaryProgressHeight : 0d;
+ width = (float)CalculateIndicatorWidth(Math.Clamp(SecondaryAnimationDuration > 0 ? _actualSecondaryProgress : _actualSecondaryProgress = SecondaryProgress, ActualMinimum, ActualMaximum), padding);
+ y = (GetControlHeight() - height) / 2;
+ //"x" is the starting point of indicator, hence we need to consider padding value to be applied only at the start.
+ x = padding / 2;
+ actualCornerRadius = IsIndeterminate ? 1 : SecondaryProgressCornerRadius;
+ count = (int)Math.Ceiling(width / (segmentWidth - padding));
+ }
+ }
+
+ ///
+ /// Sets up the basic parameters for the progress path.
+ ///
+ /// The path to be assigned.
+ /// The height of the progress.
+ /// The width of the progress.
+ /// The y position of the progress.
+ /// The x position of the progress.
+ /// The corner radius of the progress.
+ /// The count of segments.
+ /// The padding value.
+ /// The width of each segment.
+ /// The animation duration.
+ void SetupProgressPathParameters(ref PathF path, ref double height, ref double width, ref double y,
+ ref double x, ref CornerRadius actualCornerRadius, ref int count,
+ double padding, double segmentWidth, double? animationDuration)
+ {
+ path = ProgressPath = new PathF();
+ height = ProgressHeight > 0 ? ProgressHeight : 0d;
+ width = (float)CalculateIndicatorWidth(Math.Clamp(animationDuration > 0 ? ActualProgress : ActualProgress = Progress, ActualMinimum, ActualMaximum), padding);
+ y = (GetControlHeight() - height) / 2;
+ //"x" is the starting point of indicator, hence we need to consider padding value to be applied only at the start.
+ x = padding / 2;
+ actualCornerRadius = ProgressCornerRadius;
+ count = (int)Math.Ceiling(width / (segmentWidth - padding));
+ }
+
+ // Second new method - for calculating indeterminate progress width
+ ///
+ /// Calculates the width for indeterminate progress mode.
+ ///
+ /// The indeterminate indicator width factor.
+ /// The available width.
+ /// The current indeterminate animation value.
+ /// The x position (will be updated).
+ /// The width (will be updated).
+ void CalculateIndeterminateProgressWidth(double indeterminateIndicatorWidthFactor, double availableWidth,
+ double indeterminateAnimationValue, ref double x, ref double width)
+ {
+ if (indeterminateIndicatorWidthFactor <= 0)
+ {
+ width = 0;
+ }
+ else
+ {
+ double indeterminateWidth = availableWidth * indeterminateIndicatorWidthFactor;
+ if (indeterminateWidth > indeterminateAnimationValue)
+ {
+ width = indeterminateAnimationValue;
+ }
+ else
+ {
+ if (indeterminateAnimationValue > availableWidth)
+ {
+ indeterminateWidth = availableWidth - (indeterminateAnimationValue - indeterminateWidth);
+ _indeterminateAnimationValue = availableWidth;
+ }
+
+ x = indeterminateAnimationValue - indeterminateWidth;
+ width = indeterminateWidth;
+ }
+
+ width = width > 0 ? width : 0;
+ }
+ }
+
+ #endregion
+
+ #region Animation Methods
+
+ ///
+ /// To update inderterminate animation value.
+ ///
+ /// Represents animation value.
+ void OnIndeterminateAnimationUpdate(double value)
+ {
+ _indeterminateAnimationValue = value;
+ CreateProgressPath();
+ InvalidateDrawable();
+ }
+
+ ///
+ /// Animation of the secondary progress.
+ ///
+ void CreateSecondaryProgressAnimation()
+ {
+ var secondaryProgress = Math.Clamp(SecondaryProgress, ActualMinimum, ActualMaximum);
+ if (_actualSecondaryProgress != secondaryProgress)
+ {
+ AnimationExtensions.Animate(this, "SecondaryProgressAnimation", OnSecondaryAnimationUpdate,
+ _actualSecondaryProgress, secondaryProgress, 16, (uint)SecondaryAnimationDuration,
+ AnimationEasing, OnSecondaryAnimationFinished);
+ }
+ }
+
+ ///
+ /// To update secondary animation value.
+ ///
+ /// Represents animation value.
+ void OnSecondaryAnimationUpdate(double value)
+ {
+ _actualSecondaryProgress = value;
+ CreateSecondaryProgressPath();
+ InvalidateDrawable();
+ }
+
+ ///
+ /// Called when secondary animation finished.
+ ///
+ void OnSecondaryAnimationFinished(double value, bool isCompleted)
+ {
+ AnimationExtensions.AbortAnimation(this, "SecondaryProgressAnimation");
+ _canAnimateSecondaryProgress = false;
+ }
+
+ #endregion
+
+ #region Drawing Methods
+
+ ///
+ /// To draw a secondary progress bar.
+ ///
+ /// The canvas.
+ void DrawSecondaryProgress(ICanvas canvas)
+ {
+ if (_secondProgressPath != null)
+ {
+ canvas.SaveState();
+ canvas.SetFillPaint(SecondaryProgressFill, _secondProgressPath.Bounds);
+ canvas.FillPath(_secondProgressPath);
+ canvas.RestoreState();
+ }
+ }
+
+ #endregion
+
+ ///
+ /// To get the width of the progress bar..
+ ///
+ /// The value to be convert as width.
+ /// The paddng value.
+ /// Width for the given value.
+ private double GetWidthFromValue(double value, double padding)
+ {
+ return ValueToFactor(value) * (AvailableSize.Width - padding);
+ }
+
+ ///
+ /// Refresh the width of the progress.
+ ///
+ /// Return segment width.
+ private double CalculateSegmentWidth()
+ {
+ double segmentWidth = 0;
+ double actualGapWidth = SegmentGapWidth > 0 ? SegmentGapWidth : 0;
+ int actualSegmentCount = SegmentCount > 0 ? SegmentCount : 0;
+ if (actualSegmentCount >= 1)
+ {
+ segmentWidth = (AvailableSize.Width - (actualGapWidth * (actualSegmentCount - 1))) / actualSegmentCount;
+ }
+
+ return segmentWidth < 0 ? 0 : segmentWidth;
+ }
+
+ ///
+ /// Calculates the width of the indicator.
+ ///
+ /// The indicator width.
+ /// The value.
+ /// The padding value.
+ private double CalculateIndicatorWidth(double value, double padding)
+ {
+ double width = AvailableSize.Width - (SegmentCount * padding);
+ double actualGapWidth = SegmentGapWidth > 0 ? SegmentGapWidth : 0;
+ int actualSegmentCount = SegmentCount > 0 ? SegmentCount : 0;
+ if (actualSegmentCount >= 1)
+ {
+ width -= actualGapWidth * (actualSegmentCount - 1);
+ return width * ValueToFactor(value);
+ }
+
+ return GetWidthFromValue(value, padding);
+ }
+
+ ///
+ /// To get the control height.
+ ///
+ /// The control height.
+ private double GetControlHeight()
+ {
+ double actualTrackHeight = TrackHeight > 0 ? TrackHeight : 0;
+ double actualProgressHeight = ProgressHeight > 0 ? ProgressHeight : 0;
+ double actualSecondaryProgressHeight = SecondaryProgressHeight > 0 ? SecondaryProgressHeight : 0;
+ return Math.Max(actualTrackHeight, Math.Max(actualProgressHeight, actualSecondaryProgressHeight));
+ }
+
+ ///
+ /// Gets the theme dictionary for the .
+ ///
+ ///
+ /// A containing the theme resources for the .
+ ///
+ ResourceDictionary IParentThemeElement.GetThemeDictionary()
+ {
+ return new SfLinearProgressBarStyles();
+ }
+
+ ///
+ /// Invoked when the control theme changes.
+ ///
+ /// The name of the old theme.
+ /// The name of the new theme.
+ void IThemeElement.OnControlThemeChanged(string oldTheme, string newTheme)
+ {
+ SetDynamicResource(LinearProgressBarBackgroundProperty, "SfLinearProgressBarBackground");
+ }
+
+ ///
+ /// Invoked when the common theme changes.
+ ///
+ /// The name of the old theme.
+ /// The name of the new theme.
+ void IThemeElement.OnCommonThemeChanged(string oldTheme, string newTheme)
+ {
+ // No implementation required for common theme changes.
+ }
+
+ ///
+ /// Triggered when the view is loaded.
+ ///
+ /// Sender.
+ /// Event argument.
+ void OnProgressBarLoaded(object? sender, EventArgs e)
+ {
+ if (IsIndeterminate
+ && IndeterminateAnimationDuration > 0
+ && IndeterminateIndicatorWidthFactor > 0
+ && !AnimationExtensions.AnimationIsRunning(this, "IndeterminateAnimation")
+ && IsIndeterminateAnimationAborted)
+ {
+ CreateIndeterminateAnimation();
+ IsIndeterminateAnimationAborted = false;
+ Loaded -= OnProgressBarLoaded;
+ }
+ }
+
+ ///
+ /// Triggered while the view removed from main visual tree.
+ ///
+ /// Sender.
+ /// Event argument.
+ void OnProgressBarUnloaded(object? sender, EventArgs e)
+ {
+ AnimationExtensions.AbortAnimation(this, "IndeterminateAnimation");
+ // TASK-886910: SfLinearProgressBar gets frozen when switching between tabs in TabBar
+ IsIndeterminateAnimationAborted = true;
+ Unloaded -= OnProgressBarUnloaded;
+ }
+
+ #endregion
+
+ #region Protected Override Methods
+
+ ///
+ /// Measures the size in layout required for child elements.
+ ///
+ /// The widthConstraint.
+ /// The heightConstraint.
+ /// Return child element size.
+ protected override Size MeasureContent(double widthConstraint, double heightConstraint)
+ {
+ if (double.IsInfinity(widthConstraint))
+ {
+ widthConstraint = 350d;
+ }
+
+ if (double.IsInfinity(heightConstraint))
+ {
+ heightConstraint = GetControlHeight();
+ }
+
+ AvailableSize = new Size(widthConstraint, heightConstraint);
+ UpdateProgressBar();
+ return AvailableSize;
+ }
+
+ #endregion
+
+ #region Internal Override Methods
+
+ ///
+ /// To create a track path.
+ ///
+ internal override void CreateTrackPath()
+ {
+ CreatePath("Track");
+ }
+
+ ///
+ /// To create a progress path.
+ ///
+ /// The animation duration.
+ /// The easing effect.
+ internal override void CreateProgressPath(double? animationDuration = null, Easing? easing = null)
+ {
+ double duration = animationDuration ?? AnimationDuration;
+ var ease = easing ?? AnimationEasing;
+ if (CanAnimate && !IsIndeterminate && duration > 0)
+ {
+ CanAnimate = false;
+ CreateProgressAnimation(duration, ease);
+ }
+ else
+ {
+ CreatePath("Progress", duration);
+ CreateGradient();
+ }
+ }
+
+ ///
+ /// To update the linear progress bar elements such as track, secondary progress, progress, etc.
+ ///
+ internal override void UpdateProgressBar()
+ {
+ base.UpdateProgressBar();
+ CreateSecondaryProgressPath();
+ }
+
+ ///
+ /// To animate progress for indeterminate state.
+ ///
+ internal override void CreateIndeterminateAnimation()
+ {
+ double indicatorwidth = AvailableSize.Width * IndeterminateIndicatorWidthFactor;
+ AnimationExtensions.Animate(
+ this,
+ "IndeterminateAnimation",
+ OnIndeterminateAnimationUpdate,
+ 0,
+ AvailableSize.Width + indicatorwidth,
+ 16,
+ (uint)IndeterminateAnimationDuration,
+ IndeterminateAnimationEasing,
+ null,
+ () => IsIndeterminate);
+ }
+
+ ///
+ /// To draw a linear progress bar elements such as track, secondary progress, progress, etc.
+ ///
+ /// The drawing canvas.
+ internal override void DrawProgressBar(ICanvas canvas)
+ {
+ base.DrawProgressBar(canvas);
+ DrawSecondaryProgress(canvas);
+ DrawProgress(canvas);
+ }
+
+ ///
+ /// To draw a progress.
+ ///
+ /// The canvas.
+ internal override void DrawProgress(ICanvas canvas)
+ {
+ if (ProgressPath != null)
+ {
+ canvas.SaveState();
+ if (_linearGradientBrush != null && !IsIndeterminate)
+ {
+ canvas.SetFillPaint(_linearGradientBrush, ProgressPath.Bounds);
+ }
+ else
+ {
+ canvas.SetFillPaint(ProgressFill, ProgressPath.Bounds);
+ }
+
+ canvas.FillPath(ProgressPath);
+ canvas.RestoreState();
+ }
+ }
+
+ ///
+ /// To create linear gradient for progress.
+ ///
+ internal override void CreateGradient()
+ {
+
+ if (GradientStops == null || GradientStops.Count == 0 || IsIndeterminate)
+ {
+ _linearGradientBrush = null;
+ return;
+ }
+
+ double endValue = Math.Clamp(ActualProgress, ActualMinimum, ActualMaximum);
+ LinearGradientBrush gradient = new LinearGradientBrush();
+ gradient.StartPoint = new Point(0, 0.5);
+ gradient.EndPoint = new Point(1, 0.5);
+ if (GradientStops.Count == 1)
+ {
+#if ANDROID
+ gradient.GradientStops.Add(new GradientStop()
+ {
+ Color = GradientStops[0].Color,
+ Offset = 0
+ });
+#endif
+ gradient.GradientStops.Add(new GradientStop()
+ {
+ Color = GradientStops[0].Color,
+ Offset = (float)ValueToFactor(ActualMaximum, ActualMinimum, endValue)
+ });
+ }
+ else
+ {
+ List gradientStopsList = GradientStops.OrderBy(x => x.ActualValue).ToList();
+ if (gradientStopsList[0].Value != ActualMinimum)
+ {
+ gradientStopsList.Insert(0, new ProgressGradientStop
+ {
+ Color = gradientStopsList[0].Color,
+ Value = ActualMinimum
+ });
+ }
+
+ if (gradientStopsList[^1].Value != endValue) //Equivalent code: if (gradientStopsList[gradientStopsList.Count -1].Value != endValue)
+ {
+ gradientStopsList.Add(new ProgressGradientStop
+ {
+ Color = gradientStopsList[^1].Color,
+ Value = ActualMaximum
+ });
+ }
+
+ for (int i = 0; i < gradientStopsList.Count; i++)
+ {
+ if (gradientStopsList[i].Value >= ActualMinimum && gradientStopsList[i].ActualValue <= endValue)
+ {
+ gradient.GradientStops.Add(new GradientStop()
+ {
+ Color = gradientStopsList[i].Color,
+ Offset = (float)ValueToFactor(gradientStopsList[i].ActualValue, ActualMinimum, endValue)
+ });
+ }
+ }
+
+ if (gradient.GradientStops.Count == 0)
+ {
+ for (int i = 0; i < gradientStopsList.Count; i++)
+ {
+ if (gradientStopsList[i].ActualValue >= ActualMinimum)
+ {
+ gradient.GradientStops.Add(new GradientStop()
+ {
+ Color = gradientStopsList[i].Color,
+ Offset = (float)ValueToFactor(ActualMaximum, ActualMinimum, endValue)
+ });
+ break;
+ }
+ }
+ }
+
+#if ANDROID
+ if (gradient.GradientStops.Count == 1)
+ {
+ gradient.GradientStops.Add(new GradientStop()
+ {
+ Color = gradientStopsList[0].Color,
+ Offset = 1
+ });
+ }
+#endif
+ }
+
+ _linearGradientBrush = gradient;
+ }
+
+ #endregion
+
+ #region Property Changed
+
+ ///
+ /// Invoked whenever or is set for the progress bar.
+ ///
+ /// The bindable object.
+ /// The old value.
+ /// The new value.
+ static void OnHeightPropertyChanged(BindableObject bindable, object oldValue, object newValue)
+ {
+ if (bindable is SfLinearProgressBar linearProgressBar && !linearProgressBar.AvailableSize.IsZero)
+ {
+ linearProgressBar.InvalidateMeasureOverride();
+ linearProgressBar.InvalidateDrawable();
+ }
+ }
+
+ ///
+ /// Invoked whenever is set for the progress bar.
+ ///
+ /// The bindable object.
+ /// The old value.
+ /// The new value.
+ static void OnTrackCornerRadiusChanged(BindableObject bindable, object oldValue, object newValue)
+ {
+ if (bindable is SfLinearProgressBar linearProgressBar && !linearProgressBar.AvailableSize.IsZero)
+ {
+ linearProgressBar.CreateTrackPath();
+ linearProgressBar.InvalidateDrawable();
+ }
+ }
+
+ ///
+ /// Invoked whenever is set for the progress bar.
+ ///
+ /// The bindable object.
+ /// The old value.
+ /// The new value.
+ static void OnProgressCornerRadiusChanged(BindableObject bindable, object oldValue, object newValue)
+ {
+ if (bindable is SfLinearProgressBar linearProgressBar && !linearProgressBar.AvailableSize.IsZero)
+ {
+ linearProgressBar.CreateProgressPath();
+ linearProgressBar.InvalidateDrawable();
+ }
+ }
+
+ ///
+ /// Called when property changed.
+ ///
+ /// The bindable object.
+ /// The old value.
+ /// The new value.
+ static void OnSecondaryProgressPropertyChanged(BindableObject bindable, object oldValue, object newValue)
+ {
+ if (bindable is SfLinearProgressBar linearProgressBar)
+ {
+ linearProgressBar._canAnimateSecondaryProgress = linearProgressBar.SecondaryAnimationDuration > 0 ? true : false;
+ if (!linearProgressBar.AvailableSize.IsZero)
+ {
+ linearProgressBar._actualSecondaryProgress = (double)oldValue;
+ linearProgressBar.CreateSecondaryProgressPath();
+ linearProgressBar.InvalidateDrawable();
+ }
+ }
+ }
+
+ ///
+ /// Invoked whenever is set for the progress bar.
+ ///
+ /// The bindable object.
+ /// The old value.
+ /// The new value.
+ static void OnSecondaryProgressFillChanged(BindableObject bindable, object oldValue, object newValue)
+ {
+ if (bindable is SfLinearProgressBar linearProgressBar && !linearProgressBar.AvailableSize.IsZero)
+ {
+ linearProgressBar.InvalidateDrawable();
+ }
+ }
+
+ ///
+ /// Invoked whenever is set for the progress bar.
+ ///
+ /// The bindable object.
+ /// The old value.
+ /// The new value.
+ static void OnSecondaryProgressCornerRadiusChanged(BindableObject bindable, object oldValue, object newValue)
+ {
+ if (bindable is SfLinearProgressBar linearProgressBar && !linearProgressBar.AvailableSize.IsZero)
+ {
+ linearProgressBar.CreateSecondaryProgressPath();
+ linearProgressBar.InvalidateDrawable();
+ }
+ }
+
+ ///
+ /// Invoked whenever property of progress is changed.
+ ///
+ /// The bindable object.
+ /// The old value.
+ /// The new value.
+ static void OnProgressPaddingPropertyChanged(BindableObject bindable, object oldValue, object newValue)
+ {
+ if (bindable is SfLinearProgressBar linearProgressBar && !linearProgressBar.AvailableSize.IsZero)
+ {
+ linearProgressBar.CreateProgressPath();
+ linearProgressBar.CreateSecondaryProgressPath();
+ linearProgressBar.InvalidateDrawable();
+ }
+ }
+
+ ///
+ /// called when property changed.
+ ///
+ /// The bindable object.
+ /// The old value.
+ /// The new value.
+ static void OnLinearProgressBarBackgroundChanged(BindableObject bindable, object oldValue, object newValue)
+ {
+ if (bindable is SfLinearProgressBar linearProgressBar)
+ {
+ linearProgressBar.BackgroundColor = linearProgressBar.LinearProgressBarBackground;
+ }
+ }
+
+ #endregion
+ }
+}
\ No newline at end of file
diff --git a/maui/src/ProgressBar/Utility/Utility.cs b/maui/src/ProgressBar/Utility/Utility.cs
new file mode 100644
index 00000000..3fc88494
--- /dev/null
+++ b/maui/src/ProgressBar/Utility/Utility.cs
@@ -0,0 +1,151 @@
+namespace Syncfusion.Maui.Toolkit.ProgressBar
+{
+ ///
+ /// Contains utility methods.
+ ///
+ internal static class Utility
+ {
+ ///
+ /// Validate the minimum and maximum values, if maximum is less than minimum then swap the values.
+ ///
+ /// The minimum
+ /// The maximum
+ /// Validated minimum and maximum tuple.
+ internal static (double, double) ValidateMinimumMaximumValue(double minimum, double maximum)
+ {
+ double min = double.IsNaN(minimum) ? 0 : minimum;
+ double max = double.IsNaN(maximum) ? 0 : maximum;
+ minimum = min < max ? min : max;
+ maximum = min > max ? min : max;
+ return (minimum, maximum);
+ }
+
+ ///
+ /// To convert the given degree to radian.
+ ///
+ /// The degree.
+ /// Radian equivalent of given degree.
+ internal static double DegreeToRadian(double degree)
+ {
+ return degree * Math.PI / 180;
+ }
+
+ ///
+ /// To calculate SweepAngle.
+ ///
+ /// The start angle.
+ /// The end angle.
+ /// Sweep angle between start and end angle
+ internal static double CalculateSweepAngle(double startAngle, double endAngle)
+ {
+ double actualEndAngle = endAngle > 360 ? endAngle % 360 : endAngle;
+ double sweepAngle = actualEndAngle - startAngle;
+ return sweepAngle <= 0 ? (sweepAngle + 360) : sweepAngle;
+ }
+
+ ///
+ /// To convert angle to vector.
+ ///
+ /// The angle
+ /// Vector of given angle
+ internal static Point AngleToVector(double angle)
+ {
+ double angleRadian = Utility.DegreeToRadian(angle);
+ return new Point(Math.Cos(angleRadian), Math.Sin(angleRadian));
+ }
+
+ ///
+ /// To find the minimum and maximum value
+ ///
+ /// Sets the point1
+ /// Sets the point2
+ /// Sets the degree
+ /// Min and Max value.
+ internal static double GetMinMaxValue(Point point1, Point point2, int degree)
+ {
+ return degree switch
+ {
+ 270 => Math.Max(point1.Y, point2.Y),
+ 0 or 360 => Math.Min(point1.X, point2.X),
+ 90 => Math.Min(point1.Y, point2.Y),
+ 180 => Math.Max(point1.X, point2.X),
+ _ => 0d
+ };
+ }
+
+ ///
+ /// To calculate angle for corner radius
+ ///
+ /// axis radius.
+ /// Corner radius ellipse radius
+ /// Angle for corner radius
+ internal static double CornerRadiusAngle(double radius, double circleRadius)
+ {
+ var perimeter = ((2 * radius) + circleRadius) / 2;
+ var area = Math.Sqrt(perimeter * (perimeter - radius) * (perimeter - radius) * (perimeter - circleRadius));
+ return Math.Asin(2 * area / (radius * radius)) * (180 / Math.PI);
+ }
+
+ ///
+ /// To update the gradient stops based actual range.
+ ///
+ /// The gradient stops collection.
+ /// The range start.
+ /// The range end.
+ /// Updated gradient stops collection.
+ internal static List UpdateGradientStopCollection(List gradientStops, double rangeStart, double rangeEnd)
+ {
+ gradientStops = gradientStops.OrderBy(x => x.ActualValue).ToList();
+
+ if (gradientStops.First().ActualValue > rangeStart)
+ {
+ gradientStops.Insert(0, new ProgressGradientStop
+ {
+ Color = gradientStops.First().Color,
+ ActualValue = rangeStart
+ });
+ }
+
+ if (gradientStops.Last().ActualValue < rangeEnd)
+ {
+ gradientStops.Add(new ProgressGradientStop
+ {
+ Color = gradientStops.Last().Color,
+ ActualValue = rangeEnd
+ });
+ }
+
+ return gradientStops;
+ }
+
+ ///
+ /// To get the gradient range based on gradient stops collection, range start and end values.
+ ///
+ /// The gradient stops.
+ /// The range start.
+ /// The range end.
+ /// The gradient range based on gradient stops collection, range start and end values.
+ internal static List GetGradientRange(List gradientStops, double rangeStart, double rangeEnd)
+ {
+ List gradientRange = new List()
+ {
+ rangeStart,
+ rangeEnd
+ };
+
+ foreach (ProgressGradientStop gradient in gradientStops)
+ {
+ if (gradient.ActualValue > rangeStart && gradient.ActualValue < rangeEnd)
+ {
+ if (!gradientRange.Contains(gradient.ActualValue))
+ {
+ gradientRange.Add(gradient.ActualValue);
+ }
+ }
+ }
+
+ gradientRange.Sort();
+ return gradientRange;
+ }
+ }
+}
\ No newline at end of file
diff --git a/maui/src/PullToRefresh/SfProgressCircleView.cs b/maui/src/PullToRefresh/SfProgressCircleView.cs
index 43c0d0c4..c0c8118b 100644
--- a/maui/src/PullToRefresh/SfProgressCircleView.cs
+++ b/maui/src/PullToRefresh/SfProgressCircleView.cs
@@ -283,7 +283,7 @@ void DrawBackgroundCircle(ICanvas canvas)
canvas.SetShadow(new SizeF(0, 1), androidShadowSize, shadowColor);
#else
const int defaultShadowSize = 3;
- canvas.SetShadow(new SizeF(0, 1), defaultShadowSize, Color.FromArgb(shadowColor));
+ canvas.SetShadow(new SizeF(0, 1), defaultShadowSize, shadowColor);
#endif
canvas.FillEllipse((float)_fillRect.X, (float)_fillRect.Y, (float)_fillRect.Width, (float)_fillRect.Height);
}
diff --git a/maui/src/SegmentedControl/Helper/SegmentViewHelper.cs b/maui/src/SegmentedControl/Helper/SegmentViewHelper.cs
index 3167007f..a56693c1 100644
--- a/maui/src/SegmentedControl/Helper/SegmentViewHelper.cs
+++ b/maui/src/SegmentedControl/Helper/SegmentViewHelper.cs
@@ -280,10 +280,20 @@ internal static Color GetSelectedSegmentForeground(ISegmentItemInfo? itemInfo, S
{
return segmentItem.SelectedSegmentTextColor ?? itemInfo?.SelectionIndicatorSettings?.TextColor ?? Colors.White;
}
+ else
+ {
+ Color textColor;
+ if (itemInfo != null && itemInfo.SelectionIndicatorSettings != null && !itemInfo.SelectedSegmentTextColor.Equals(itemInfo.SelectionIndicatorSettings.TextColor))
+ {
+ textColor = itemInfo.SelectionIndicatorSettings.TextColor;
+ }
+ else
+ {
+ textColor = BrushToColorConverter(GetSelectedSegmentStroke(itemInfo, segmentItem));
+ }
- // If the selection indicator is not filled, get the text color from the selected segment background.
- Brush textColor = GetSelectedSegmentStroke(itemInfo, segmentItem);
- return BrushToColorConverter(textColor);
+ return segmentItem.SelectedSegmentTextColor ?? textColor;
+ }
}
///
diff --git a/maui/src/SegmentedControl/Interface/ISegmentInfo.cs b/maui/src/SegmentedControl/Interface/ISegmentInfo.cs
index f65a57f9..663a8604 100644
--- a/maui/src/SegmentedControl/Interface/ISegmentInfo.cs
+++ b/maui/src/SegmentedControl/Interface/ISegmentInfo.cs
@@ -22,6 +22,11 @@ internal interface ISegmentInfo
///
bool ShowSeparator { get; }
+ ///
+ /// Gets or sets a value indicating whether the ripple effect animation should be applied to a segment item when it is selected for default and segment template added.
+ ///
+ bool EnableRippleEffect { get; }
+
///
/// Gets a value indicating whether the layout is in Right-to-Left (RTL) direction.
///
@@ -87,6 +92,11 @@ internal interface ISegmentInfo
///
Brush KeyboardFocusStroke { get; }
+ ///
+ /// Gets the selected text color for the segment.
+ ///
+ Color SelectedSegmentTextColor { get; }
+
///
/// Gets the background brush for the segment when it is disabled.
///
diff --git a/maui/src/SegmentedControl/Model/SfSegmentedControl.cs b/maui/src/SegmentedControl/Model/SfSegmentedControl.cs
index af956509..d740962c 100644
--- a/maui/src/SegmentedControl/Model/SfSegmentedControl.cs
+++ b/maui/src/SegmentedControl/Model/SfSegmentedControl.cs
@@ -277,6 +277,19 @@ public partial class SfSegmentedControl
typeof(SfSegmentedControl),
defaultValueCreator: bindable => true);
+ ///
+ /// Identifies the dependency property.
+ ///
+ ///
+ /// The identifier for dependency property.
+ ///
+ public static readonly BindableProperty EnableRippleEffectProperty =
+ BindableProperty.Create(
+ nameof(EnableRippleEffect),
+ typeof(bool),
+ typeof(SfSegmentedControl),
+ true);
+
///
/// Identifies the dependency property.
///
@@ -290,6 +303,19 @@ public partial class SfSegmentedControl
typeof(SfSegmentedControl),
defaultValueCreator: bindable => null);
+ ///
+ /// Identifies the dependency property.
+ ///
+ ///
+ /// Identifies the bindable property.
+ ///
+ internal static readonly BindableProperty SelectedSegmentTextColorProperty =
+ BindableProperty.Create(
+ nameof(SelectedSegmentTextColor),
+ typeof(Color), typeof(SfSegmentedControl),
+ defaultValueCreator: bindable => Color.FromArgb("#FFFFFF"),
+ propertyChanged: OnSelectedSegmentTextColorChanged);
+
///
/// Identifies the dependency property.
///
@@ -984,6 +1010,39 @@ public bool ShowSeparator
set { SetValue(ShowSeparatorProperty, value); }
}
+ ///
+ /// Gets or sets a value indicating whether the ripple effect animation should be applied to a segment item when it is selected for default and segment template added.
+ ///
+ ///
+ /// The default value is true.
+ ///
+ ///
+ /// The below examples shows, how to use the property in the .
+ /// # [XAML](#tab/tabid-37)
+ ///
+ ///
+ ///
+ /// Day
+ /// Week
+ /// Month
+ /// Year
+ ///
+ ///
+ ///
+ /// ]]>
+ /// # [C#](#tab/tabid-38)
+ ///
+ ///
+ public bool EnableRippleEffect
+ {
+ get { return (bool)this.GetValue(EnableRippleEffectProperty); }
+ set { this.SetValue(EnableRippleEffectProperty, value); }
+ }
+
///
/// Gets or sets the hovered selected background brush for the segment.
///
@@ -993,6 +1052,15 @@ internal Brush HoveredBackground
set { SetValue(HoveredBackgroundProperty, value); }
}
+ ///
+ /// Gets or sets the selected text color brush for the segment.
+ ///
+ internal Color SelectedSegmentTextColor
+ {
+ get { return (Color)this.GetValue(SelectedSegmentTextColorProperty); }
+ set { this.SetValue(SelectedSegmentTextColorProperty, value); }
+ }
+
///
/// Gets or sets the focused keyboard stroke for the segment.
///
@@ -1280,6 +1348,23 @@ static void OnKeyboardFocusStrokeChanged(BindableObject bindable, object oldValu
segmentedControl._keyNavigationView?.InvalidateDrawable();
}
+ ///
+ /// Occurs when property changed.
+ ///
+ /// The bindable object.
+ /// The old value.
+ /// The new value.
+ static void OnSelectedSegmentTextColorChanged(BindableObject bindable, object oldValue, object newValue)
+ {
+ SfSegmentedControl segmentedControl = (SfSegmentedControl)bindable;
+ if (segmentedControl == null || segmentedControl._segmentLayout == null || !segmentedControl.IsLoaded)
+ {
+ return;
+ }
+
+ segmentedControl._segmentLayout?.UpdateSelectedSegmentItemStyle();
+ }
+
///
/// Occurs when property changed.
///
diff --git a/maui/src/SegmentedControl/SfSegmentedControl.cs b/maui/src/SegmentedControl/SfSegmentedControl.cs
index bc25ec23..d8b35fb9 100644
--- a/maui/src/SegmentedControl/SfSegmentedControl.cs
+++ b/maui/src/SegmentedControl/SfSegmentedControl.cs
@@ -88,6 +88,7 @@ public SfSegmentedControl()
ThemeElement.InitializeThemeResources(this, "SfSegmentedControlTheme");
SetDynamicResource(HoveredBackgroundProperty, "SfSegmentedControlHoveredBackground");
SetDynamicResource(KeyboardFocusStrokeProperty, "SfSegmentedControlKeyboardFocusStroke");
+ SetDynamicResource(SelectedSegmentTextColorProperty, "SfSegmentedControlSelectionTextColor");
MinimumWidthRequest = 180;
MinimumHeightRequest = 40;
SelectionIndicatorSettings.Parent = this;
@@ -95,6 +96,10 @@ public SfSegmentedControl()
#if !WINDOWS
this.AddKeyboardListener(this);
#endif
+
+#if __IOS__
+ this.IgnoreSafeArea = true;
+#endif
}
#endregion
@@ -126,6 +131,11 @@ public SfSegmentedControl()
///
Brush ISegmentInfo.KeyboardFocusStroke => KeyboardFocusStroke;
+ ///
+ /// Gets the selected text color for the segment.
+ ///
+ Color ISegmentInfo.SelectedSegmentTextColor => this.SelectedSegmentTextColor;
+
#endregion
#region Public methods
@@ -316,6 +326,9 @@ void InitializeSegment()
RemoveOutlinedBorderView();
_grid = [];
+#if __IOS__
+ _grid.IgnoreSafeArea = true;
+#endif
_grid.SizeChanged += GridSizeChanged;
// The FlowDirection of the grid is set to LeftToRight to handle RTL rendering logic manually, as RTL behavior may not be consistent across Windows and macOS platforms.
diff --git a/maui/src/SegmentedControl/Views/SegmentItemView.cs b/maui/src/SegmentedControl/Views/SegmentItemView.cs
index 12709943..4b99b571 100644
--- a/maui/src/SegmentedControl/Views/SegmentItemView.cs
+++ b/maui/src/SegmentedControl/Views/SegmentItemView.cs
@@ -61,13 +61,15 @@ internal partial class SegmentItemView : SfContentView, ITouchListener, ITapGest
/// The segment item.
internal SegmentItemView(ISegmentItemInfo itemInfo, SfSegmentItem item)
{
+#if __IOS__
+ this.IgnoreSafeArea = true;
+#endif
this.itemInfo = itemInfo;
_segmentItem = item;
DrawingOrder = DrawingOrder.BelowContent;
UpdateItemViewEnabledState();
InitializeSelectionView();
InitializeImageView();
- InitializeEffectsView();
this.AddTouchListener(this);
this.AddGestureListener(this);
#if WINDOWS
@@ -78,6 +80,7 @@ internal SegmentItemView(ISegmentItemInfo itemInfo, SfSegmentItem item)
CreateSegmentItemTemplateView();
}
+ InitializeEffectsView();
UpdateSemantics();
}
@@ -875,10 +878,10 @@ void ITouchListener.OnTouch(PointerEventArgs e)
{
if (e.Action == PointerActions.Pressed)
{
- if (itemInfo?.SegmentTemplate is null)
+ if (itemInfo != null && itemInfo.EnableRippleEffect)
{
- _effectsView?.ApplyEffects();
- }
+ _effectsView?.ApplyEffects();
+ }
}
#if __MACCATALYST__ || WINDOWS
else if (e.Action == PointerActions.Entered && e.PointerDeviceType == PointerDeviceType.Mouse)
diff --git a/maui/src/SegmentedControl/Views/SegmentLayout.cs b/maui/src/SegmentedControl/Views/SegmentLayout.cs
index 0a576992..06608db4 100644
--- a/maui/src/SegmentedControl/Views/SegmentLayout.cs
+++ b/maui/src/SegmentedControl/Views/SegmentLayout.cs
@@ -42,6 +42,9 @@ internal class SegmentLayout : SfView
/// The providing information about the segment items.
internal SegmentLayout(ISegmentItemInfo itemInfo)
{
+#if __IOS__
+ this.IgnoreSafeArea = true;
+#endif
FlowDirection = Microsoft.Maui.FlowDirection.LeftToRight;
_itemInfo = itemInfo;
DrawingOrder = DrawingOrder.AboveContent;
diff --git a/maui/src/Shimmer/SfShimmer.cs b/maui/src/Shimmer/SfShimmer.cs
index d73fc09f..59f6a3d6 100644
--- a/maui/src/Shimmer/SfShimmer.cs
+++ b/maui/src/Shimmer/SfShimmer.cs
@@ -609,6 +609,24 @@ static void OnIsActivePropertyChanged(BindableObject bindable, object oldValue,
return;
}
+ //// Issue Scenario: In Android platform when we use the shimmer along with the PullToRefresh, collection view and change the IsActive property on refresh
+ //// "Cannot access disposed object" exception occurs after certain number of refresh and page navigations.
+ //// Fix: So we are check if the Android native view is still valid before proceeding
+ //// This prevents "Cannot access disposed object" exceptions that occur when
+ //// trying to interact with platform views that have been disposed during
+ //// page navigation or when the control is removed from the visual tree.
+#if ANDROID
+ var layoutHandler = shimmer.Handler;
+ if (layoutHandler != null)
+ {
+ var nativeView = layoutHandler.PlatformView as Android.Views.View;
+ if (nativeView == null || nativeView.Handle == IntPtr.Zero || nativeView.Context == null)
+ {
+ return;
+ }
+ }
+#endif
+
bool isActive = (bool)newValue;
if (isActive)
{
@@ -643,18 +661,22 @@ static void OnIsActivePropertyChanged(BindableObject bindable, object oldValue,
shimmer.ShimmerDrawable.AbortAnimation("ShimmerAnimation");
}
- shimmer.Remove(shimmer.ShimmerDrawable);
- if (shimmer.ShimmerDrawable.Handler != null && shimmer.ShimmerDrawable.Handler.PlatformView != null)
+ if (shimmer.ShimmerDrawable != null)
{
- shimmer.ShimmerDrawable.Handler.DisconnectHandler();
- }
+ shimmer.Remove(shimmer.ShimmerDrawable);
- if (shimmer.CustomView != null)
- {
- shimmer.Remove(shimmer.CustomView);
- }
+ if (shimmer.ShimmerDrawable.Handler != null && shimmer.ShimmerDrawable.Handler.PlatformView != null)
+ {
+ shimmer.ShimmerDrawable.Handler.DisconnectHandler();
+ }
+
+ if (shimmer.CustomView != null)
+ {
+ shimmer.Remove(shimmer.CustomView);
+ }
- shimmer.ShimmerDrawable = null;
+ shimmer.ShimmerDrawable = null;
+ }
}
}
diff --git a/maui/src/Shimmer/ShimmerDrawable.cs b/maui/src/Shimmer/ShimmerDrawable.cs
index 959dadbb..6a7582ad 100644
--- a/maui/src/Shimmer/ShimmerDrawable.cs
+++ b/maui/src/Shimmer/ShimmerDrawable.cs
@@ -800,7 +800,8 @@ void CreateAnimator()
/// The current value of the animation.
void OnAnimationUpdate(double animationValue)
{
- if (Shimmer == null)
+ IShimmer? shimmer = Shimmer;
+ if (shimmer == null)
{
return;
}
@@ -808,22 +809,21 @@ void OnAnimationUpdate(double animationValue)
float offset = (float)animationValue;
// Calculating the wave width in factor value.
- float waveWidthFactor = (float)(Shimmer.WaveWidth / _availableSize.Width);
+ float waveWidthFactor = (float)(shimmer.WaveWidth / _availableSize.Width);
// Validating the factor, because the factor should lie between 0 and 1.
waveWidthFactor = Math.Clamp(waveWidthFactor, 0, 1);
-
float gradientOffset3 = Math.Clamp(offset, 0, 1);
float gradientOffset2 = Math.Clamp(offset - (waveWidthFactor / 2), 0, 1);
float gradientOffset1 = Math.Clamp(offset - waveWidthFactor, 0, 1);
if (_gradient != null)
{
// If gradient brush was set to fill property. only the first gradient stop color will be used for shimmer fill.
- Color color = ((Paint)Shimmer.Fill).ToColor() ?? Colors.Transparent;
+ Color color = BrushToColorConverter(shimmer.Fill);
_gradient.GradientStops =
[
new GradientStop(){Color = color,Offset = gradientOffset1},
- new GradientStop(){Color = Shimmer.WaveColor,Offset = gradientOffset2},
+ new GradientStop(){Color = shimmer.WaveColor,Offset = gradientOffset2},
new GradientStop(){Color = color,Offset = gradientOffset3},
];
}
@@ -832,6 +832,20 @@ void OnAnimationUpdate(double animationValue)
InvalidateDrawable();
}
+ ///
+ /// Converts a to a .
+ ///
+ /// The to convert.
+ ///
+ /// Returns the representation of the .
+ /// If the conversion fails, returns .
+ ///
+ static Color BrushToColorConverter(Brush color)
+ {
+ Paint paint = color;
+ return paint.ToColor() ?? Colors.Transparent;
+ }
+
#endregion
#region Override Methods
@@ -875,4 +889,4 @@ protected override void OnDraw(ICanvas canvas, RectF dirtyRect)
#endregion
}
-}
\ No newline at end of file
+}
diff --git a/maui/src/TabView/Control/HorizontalContent/SfHorizontalContent.Windows.cs b/maui/src/TabView/Control/HorizontalContent/SfHorizontalContent.Windows.cs
index 8a5db230..d8f291f5 100644
--- a/maui/src/TabView/Control/HorizontalContent/SfHorizontalContent.Windows.cs
+++ b/maui/src/TabView/Control/HorizontalContent/SfHorizontalContent.Windows.cs
@@ -54,6 +54,7 @@ void WireEvents()
_horizontalContentNativeView.ManipulationCompleted += OnManipulationCompleted;
_horizontalContentNativeView.PointerReleased += OnPointerReleased;
_horizontalContentNativeView.PointerMoved += OnPointerMoved;
+ _horizontalContentNativeView.PointerCaptureLost += OnPointerCaptureLost;
}
}
}
@@ -72,6 +73,7 @@ void UnWireEvents()
_horizontalContentNativeView.ManipulationCompleted -= OnManipulationCompleted;
_horizontalContentNativeView.PointerReleased -= OnPointerReleased;
_horizontalContentNativeView.PointerMoved -= OnPointerMoved;
+ _horizontalContentNativeView.PointerCaptureLost -= OnPointerCaptureLost;
}
}
}
@@ -105,6 +107,18 @@ void OnPointerPressed(object sender, PointerRoutedEventArgs e)
{
_isTouchHandled = false;
}
+ _horizontalContentNativeView?.ReleasePointerCapture(e.Pointer);
+ }
+
+ void OnPointerCaptureLost(object sender, PointerRoutedEventArgs e)
+ {
+ if (_horizontalContentNativeView != null)
+ {
+ var point = e.GetCurrentPoint(_horizontalContentNativeView).Position;
+ OnHandleTouchInteraction(PointerActions.Released, new Point(point.X, point.Y));
+ }
+ _isManipulationStarted = false;
+ _isTouchHandled = false;
}
void OnPointerMoved(object sender, PointerRoutedEventArgs e)
diff --git a/maui/src/TabView/Control/HorizontalContent/SfHorizontalContent.iOS.cs b/maui/src/TabView/Control/HorizontalContent/SfHorizontalContent.iOS.cs
index 1eb780c3..4a6cde43 100644
--- a/maui/src/TabView/Control/HorizontalContent/SfHorizontalContent.iOS.cs
+++ b/maui/src/TabView/Control/HorizontalContent/SfHorizontalContent.iOS.cs
@@ -221,9 +221,7 @@ internal class SfHorizontalContentProxy(SfHorizontalContent horizontalContent)
{
readonly WeakReference _view = new(horizontalContent);
-#pragma warning disable IDE0060 // Remove unused parameter
internal bool GestureShouldBegin(UIGestureRecognizer uIGestureRecognizer)
-#pragma warning restore IDE0060 // Remove unused parameter
{
_view.TryGetTarget(out var view);
if (view != null)
diff --git a/maui/src/TabView/Control/SfTabBar.cs b/maui/src/TabView/Control/SfTabBar.cs
index 2282a1a5..c29acd7c 100644
--- a/maui/src/TabView/Control/SfTabBar.cs
+++ b/maui/src/TabView/Control/SfTabBar.cs
@@ -1446,7 +1446,20 @@ void OnTabItemPropertyChanged(object? sender, System.ComponentModel.PropertyChan
{
if (!string.IsNullOrEmpty(e.PropertyName))
{
- if (e.PropertyName.Equals(nameof(SfTabItem.IsVisible), StringComparison.Ordinal))
+ if (e.PropertyName.Equals(nameof(SfTabItem.Header), StringComparison.Ordinal))
+ {
+ if (sender is SfTabItem)
+ {
+ var item = sender as SfTabItem;
+ if (item is not null && item.Header is null)
+ {
+ item.Header = string.Empty;
+ return;
+ }
+ }
+ }
+
+ if (e.PropertyName.Equals(nameof(SfTabItem.IsVisible), StringComparison.Ordinal))
{
if (sender is SfTabItem item && item != null)
{
@@ -1865,7 +1878,8 @@ void ConfigureTabItemProperties(SfTabItem tabItem)
tabItem.HeaderHorizontalTextAlignment = HeaderHorizontalTextAlignment;
tabItem.IndicatorPlacement = IndicatorPlacement;
tabItem.HeaderDisplayMode = HeaderDisplayMode;
- tabItem.Touched += OnTabItemTouched;
+ tabItem.Header = tabItem.Header ?? string.Empty;
+ tabItem.Touched += OnTabItemTouched;
tabItem.PropertyChanged += OnTabItemPropertyChanged;
tabItem.IsDescriptionNotSetByUser = String.IsNullOrEmpty(SemanticProperties.GetDescription(tabItem));
tabItem.FontAutoScalingEnabled = FontAutoScalingEnabled;
@@ -3199,12 +3213,19 @@ protected override Size MeasureOverride(double widthConstraint, double heightCon
///
protected override void OnHandlerChanged()
{
- base.OnHandlerChanged();
- if (Handler == null)
- {
- Children.Clear();
- }
- }
+ if (Handler == null)
+ {
+ if (Items != null)
+ {
+ foreach (var item in Items)
+ {
+ item.Touched -= OnTabItemTouched;
+ }
+ }
+ }
+
+ base.OnHandlerChanged();
+ }
#endregion
diff --git a/maui/src/TabView/Control/SfTabItem.cs b/maui/src/TabView/Control/SfTabItem.cs
index f3fc9673..2f538030 100644
--- a/maui/src/TabView/Control/SfTabItem.cs
+++ b/maui/src/TabView/Control/SfTabItem.cs
@@ -99,7 +99,8 @@ public partial class SfTabItem : ContentView, ITouchListener, ITextElement
nameof(Header),
typeof(string),
typeof(SfTabItem),
- string.Empty);
+ string.Empty,
+ BindingMode.TwoWay);
///
/// Identifies the bindable property.
diff --git a/maui/src/TextInputLayout/SfTextInputLayout.Methods.cs b/maui/src/TextInputLayout/SfTextInputLayout.Methods.cs
index 4f4043ad..99db7722 100644
--- a/maui/src/TextInputLayout/SfTextInputLayout.Methods.cs
+++ b/maui/src/TextInputLayout/SfTextInputLayout.Methods.cs
@@ -27,6 +27,14 @@ public partial class SfTextInputLayout
{
inputView.Focus();
}
+ else if(Content is SfNumericEntry numericEntry)
+ {
+ numericEntry.Focus();
+ }
+ else
+ {
+ Content?.Focus();
+ }
}
///
@@ -618,7 +626,7 @@ void SetupWindowsView(object? sender)
{
ConfigureWindowsEntry(windowEntry);
}
- else if (sender is Picker picker && picker.Handler != null && picker.Handler.PlatformView is Microsoft.UI.Xaml.Controls.ComboBox windowPicker)
+ else if (sender is Microsoft.Maui.Controls.Picker picker && picker.Handler != null && picker.Handler.PlatformView is Microsoft.UI.Xaml.Controls.ComboBox windowPicker)
{
ConfigureWindowsPicker(windowPicker);
}
@@ -808,7 +816,7 @@ void UnwireEvents()
inputView.TextChanged -= OnTextInputViewTextChanged;
inputView.HandlerChanged -= OnTextInputViewHandlerChanged;
break;
- case Picker picker:
+ case Microsoft.Maui.Controls.Picker picker:
picker.Focused -= OnTextInputViewFocused;
picker.Unfocused -= OnTextInputViewUnfocused;
picker.HandlerChanged -= OnTextInputViewHandlerChanged;
@@ -947,7 +955,7 @@ void WireEvents()
inputView.TextChanged += OnTextInputViewTextChanged;
inputView.HandlerChanged += OnTextInputViewHandlerChanged;
break;
- case Picker picker:
+ case Microsoft.Maui.Controls.Picker picker:
picker.Focused += OnTextInputViewFocused;
picker.Unfocused += OnTextInputViewUnfocused;
picker.HandlerChanged += OnTextInputViewHandlerChanged;
@@ -1765,36 +1773,39 @@ void UpdatePasswordToggleIconRectF()
///
void UpdateHintColor()
{
- if (!HintLabelStyle.TextColor.Equals(Color.FromRgba(0, 0, 0, 0.87)))
+ if(HintLabelStyle != null)
{
- _internalHintLabelStyle.TextColor = HintLabelStyle.TextColor;
- }
- else
- {
- if (Application.Current != null && !Application.Current.Resources.TryGetValue("SfTextInputLayoutTheme", out _))
+ if (!HintLabelStyle.TextColor.Equals(Color.FromRgba(0, 0, 0, 0.87)))
{
- _internalHintLabelStyle.TextColor = IsEnabled ? ((IsLayoutFocused || HasError) && Stroke != null) ? Stroke : HintLabelStyle.TextColor : DisabledColor;
+ _internalHintLabelStyle.TextColor = HintLabelStyle.TextColor;
}
else
{
- if (IsEnabled)
+ if (Application.Current != null && !Application.Current.Resources.TryGetValue("SfTextInputLayoutTheme", out _))
+ {
+ _internalHintLabelStyle.TextColor = IsEnabled ? ((IsLayoutFocused || HasError) && Stroke != null) ? Stroke : HintLabelStyle.TextColor : DisabledColor;
+ }
+ else
{
- if (IsLayoutFocused)
+ if (IsEnabled)
{
- SetDynamicResource(HintTextColorProperty, "SfTextInputLayoutFocusedHintTextColor");
- _internalHintLabelStyle.TextColor = HintTextColor;
+ if (IsLayoutFocused)
+ {
+ SetDynamicResource(HintTextColorProperty, "SfTextInputLayoutFocusedHintTextColor");
+ _internalHintLabelStyle.TextColor = HintTextColor;
+ }
+ else
+ {
+ SetDynamicResource(HintTextColorProperty, "SfTextInputLayoutHintTextColor");
+ _internalHintLabelStyle.TextColor = HintTextColor;
+ }
+
}
else
{
- SetDynamicResource(HintTextColorProperty, "SfTextInputLayoutHintTextColor");
+ SetDynamicResource(HintTextColorProperty, "SfTextInputLayoutDisabledHintTextColor");
_internalHintLabelStyle.TextColor = HintTextColor;
}
-
- }
- else
- {
- SetDynamicResource(HintTextColorProperty, "SfTextInputLayoutDisabledHintTextColor");
- _internalHintLabelStyle.TextColor = HintTextColor;
}
}
}
@@ -1805,27 +1816,30 @@ void UpdateHintColor()
///
void UpdateHelperTextColor()
{
- if (!HelperLabelStyle.TextColor.Equals(Color.FromRgba(0, 0, 0, 0.87)))
- {
- _internalHelperLabelStyle.TextColor = HelperLabelStyle.TextColor;
- }
- else
+ if(HelperLabelStyle != null)
{
- if (Application.Current != null && !Application.Current.Resources.TryGetValue("SfTextInputLayoutTheme", out _))
+ if (!HelperLabelStyle.TextColor.Equals(Color.FromRgba(0, 0, 0, 0.87)))
{
- _internalHelperLabelStyle.TextColor = IsEnabled ? ((HasError) && Stroke != null) ? Stroke : HelperLabelStyle.TextColor : DisabledColor;
+ _internalHelperLabelStyle.TextColor = HelperLabelStyle.TextColor;
}
else
{
- if (IsEnabled)
+ if (Application.Current != null && !Application.Current.Resources.TryGetValue("SfTextInputLayoutTheme", out _))
{
- SetDynamicResource(HelperTextColorProperty, "SfTextInputLayoutHelperTextColor");
- _internalHelperLabelStyle.TextColor = HelperTextColor;
+ _internalHelperLabelStyle.TextColor = IsEnabled ? ((HasError) && Stroke != null) ? Stroke : HelperLabelStyle.TextColor : DisabledColor;
}
else
{
- SetDynamicResource(HelperTextColorProperty, "SfTextInputLayoutDisabledHelperTextColor");
- _internalHelperLabelStyle.TextColor = HelperTextColor;
+ if (IsEnabled)
+ {
+ SetDynamicResource(HelperTextColorProperty, "SfTextInputLayoutHelperTextColor");
+ _internalHelperLabelStyle.TextColor = HelperTextColor;
+ }
+ else
+ {
+ SetDynamicResource(HelperTextColorProperty, "SfTextInputLayoutDisabledHelperTextColor");
+ _internalHelperLabelStyle.TextColor = HelperTextColor;
+ }
}
}
}
@@ -1836,7 +1850,10 @@ void UpdateHelperTextColor()
///
void UpdateErrorTextColor()
{
- _internalErrorLabelStyle.TextColor = IsEnabled ? ErrorLabelStyle.TextColor.Equals(Color.FromRgba(0, 0, 0, 0.87)) ? Stroke : ErrorLabelStyle.TextColor : DisabledColor;
+ if(ErrorLabelStyle != null)
+ {
+ _internalErrorLabelStyle.TextColor = IsEnabled ? ErrorLabelStyle.TextColor.Equals(Color.FromRgba(0, 0, 0, 0.87)) ? Stroke : ErrorLabelStyle.TextColor : DisabledColor;
+ }
}
///
@@ -1844,7 +1861,10 @@ void UpdateErrorTextColor()
///
void UpdateCounterTextColor()
{
- _internalCounterLabelStyle.TextColor = IsEnabled ? HasError ? ErrorLabelStyle.TextColor.Equals(Color.FromRgba(0, 0, 0, 0.87)) ? Stroke : ErrorLabelStyle.TextColor : CounterLabelStyle.TextColor : DisabledColor;
+ if(ErrorLabelStyle != null)
+ {
+ _internalCounterLabelStyle.TextColor = IsEnabled ? HasError ? ErrorLabelStyle.TextColor.Equals(Color.FromRgba(0, 0, 0, 0.87)) ? Stroke : ErrorLabelStyle.TextColor : CounterLabelStyle.TextColor : DisabledColor;
+ }
}
///
@@ -1972,7 +1992,7 @@ void DrawOutlinedBorder(ICanvas canvas)
if (((IsLayoutFocused && !string.IsNullOrEmpty(Hint)) || IsHintFloated) && ShowHint)
{
CalculateClipRect();
- if(_clipRect.Width >= 0 && _clipRect.Height >= 0)
+ if (_clipRect.Width >= 0 && _clipRect.Height >= 0)
{
canvas.SubtractFromClip(_clipRect);
}
@@ -1996,20 +2016,9 @@ void DrawOutlinedBorder(ICanvas canvas)
void SetOutlinedContainerBackground(ICanvas canvas)
{
- if (Application.Current != null && Application.Current.Resources.TryGetValue("SfTextInputLayoutTheme", out _))
+ if (_outlinedContainerBackground is SolidColorBrush backgroundColor)
{
- SetDynamicResource(OutlinedContainerBackgroundProperty, "SfTextInputLayoutOutlinedContainerBackground");
- if (OutlinedContainerBackground is SolidColorBrush backgroundColor)
- {
- canvas.FillColor = backgroundColor.Color;
- }
- }
- else
- {
- if (ContainerBackground is SolidColorBrush backgroundColor)
- {
- canvas.FillColor = backgroundColor.Color;
- }
+ canvas.FillColor = backgroundColor.Color;
}
}
diff --git a/maui/src/TextInputLayout/SfTextInputLayout.Properties.cs b/maui/src/TextInputLayout/SfTextInputLayout.Properties.cs
index e79ffd2d..308b8115 100644
--- a/maui/src/TextInputLayout/SfTextInputLayout.Properties.cs
+++ b/maui/src/TextInputLayout/SfTextInputLayout.Properties.cs
@@ -211,7 +211,7 @@ public partial class SfTextInputLayout
_defaultContainerBackground,
BindingMode.Default,
null,
- OnPropertyChanged);
+ OnContainerBackgroundPropertyChanged);
///
/// Identifies the bindable property.
@@ -634,19 +634,6 @@ public partial class SfTextInputLayout
typeof(LabelStyle),
Color.FromArgb("#49454F"));
- ///
- /// Identifies the bindable property.
- ///
- ///
- /// The property determines the background color of the outline container in .
- ///
- internal static readonly BindableProperty OutlinedContainerBackgroundProperty =
- BindableProperty.Create(
- nameof(OutlinedContainerBackground),
- typeof(Brush),
- typeof(SfTextInputLayout),
- new SolidColorBrush(Colors.Transparent));
-
///
/// Identifies the bindable property.
///
@@ -1612,7 +1599,7 @@ internal Color ClearButtonColor
/// inputLayout.Content = new Entry();
/// ]]>
///
- public LabelStyle HintLabelStyle
+ public LabelStyle? HintLabelStyle
{
get { return (LabelStyle)GetValue(HintLabelStyleProperty); }
set { SetValue(HintLabelStyleProperty, value); }
@@ -1643,7 +1630,7 @@ public LabelStyle HintLabelStyle
/// inputLayout.Content = new Entry();
/// ]]>
///
- public LabelStyle HelperLabelStyle
+ public LabelStyle? HelperLabelStyle
{
get { return (LabelStyle)GetValue(HelperLabelStyleProperty); }
set { SetValue(HelperLabelStyleProperty, value); }
@@ -1675,7 +1662,7 @@ public LabelStyle HelperLabelStyle
/// inputLayout.Content = new Entry();
/// ]]>
///
- public LabelStyle ErrorLabelStyle
+ public LabelStyle? ErrorLabelStyle
{
get { return (LabelStyle)GetValue(ErrorLabelStyleProperty); }
set { SetValue(ErrorLabelStyleProperty, value); }
@@ -1707,15 +1694,6 @@ internal Color HelperTextColor
set { SetValue(HelperTextColorProperty, value); }
}
- ///
- /// Gets or sets the color value of Outlined ContainerBackground
- ///
- internal Brush OutlinedContainerBackground
- {
- get { return (Brush)GetValue(OutlinedContainerBackgroundProperty); }
- set { SetValue(OutlinedContainerBackgroundProperty, value); }
- }
-
///
/// Gets or sets a value indicating whether to show the UpDown buttons.
///
@@ -2219,7 +2197,7 @@ static void OnIsHintFloatedPropertyChanged(BindableObject bindable, object oldVa
{
if (bindable is SfTextInputLayout inputLayout && newValue is bool value)
{
- if (inputLayout.Content is InputView || inputLayout.Content is Picker)
+ if (inputLayout.Content is InputView || inputLayout.Content is Microsoft.Maui.Controls.Picker)
{
inputLayout.Content.Opacity = value ? 1 : (DeviceInfo.Platform == DevicePlatform.iOS ? 0.00001 : 0);
}
@@ -2394,14 +2372,11 @@ static void OnReserveSpacePropertyChanged(BindableObject bindable, object oldVal
static void OnContainerTypePropertyChanged(BindableObject bindable, object oldValue, object newValue)
{
- if (bindable is SfTextInputLayout inputLayout && inputLayout.Content != null)
+ if (bindable is SfTextInputLayout inputLayout)
{
inputLayout.UpdateContentMargin(inputLayout.Content);
- if (inputLayout._initialLoaded)
- {
+ if (inputLayout._initialLoaded && inputLayout.Content != null)
inputLayout.UpdateViewBounds();
- }
-
inputLayout.ChangeVisualState();
}
}
@@ -2415,6 +2390,15 @@ static void OnPropertyChanged(BindableObject bindable, object oldValue, object n
}
}
+ static void OnContainerBackgroundPropertyChanged(BindableObject bindable, object oldValue, object newValue)
+ {
+ if (bindable is SfTextInputLayout inputLayout)
+ {
+ inputLayout._outlinedContainerBackground = (Brush)newValue;
+ inputLayout.UpdateViewBounds();
+ }
+ }
+
///
/// This method triggers when the up and down button colors change.
///
diff --git a/maui/src/TextInputLayout/SfTextInputLayout.cs b/maui/src/TextInputLayout/SfTextInputLayout.cs
index 92803cc1..38f2d852 100644
--- a/maui/src/TextInputLayout/SfTextInputLayout.cs
+++ b/maui/src/TextInputLayout/SfTextInputLayout.cs
@@ -235,11 +235,13 @@ public partial class SfTextInputLayout : SfContentView, ITouchListener, IParentT
///
bool _isPasswordTextVisible = false;
+ Brush _outlinedContainerBackground = Colors.Transparent;
+
#if !ANDROID
- ///
- /// Gets or sets a value indicating the clear icon or toggle icon is pressing.
- ///
- internal bool IsIconPressed { get; private set; } = false;
+ ///
+ /// Gets or sets a value indicating the clear icon or toggle icon is pressing.
+ ///
+ internal bool IsIconPressed { get; private set; } = false;
#endif
///
@@ -247,10 +249,10 @@ public partial class SfTextInputLayout : SfContentView, ITouchListener, IParentT
///
internal bool IsLayoutTapped { get; set; }
- ///
- /// Gets or sets a value indicating the hint was animating from down to up.
- ///
- internal bool IsHintDownToUp = true;
+ ///
+ /// Gets or sets a value indicating the hint was animating from down to up.
+ ///
+ internal bool IsHintDownToUp = true;
///
/// Gets or sets a value indicating whether the alignment of the up-down button alignment is left.
@@ -291,7 +293,7 @@ public partial class SfTextInputLayout : SfContentView, ITouchListener, IParentT
/// The field sets and checks the new size of the drawn elements.
///
Size _controlSize = Size.Zero;
-
+
///
/// Indicates whether semantics need to be reset.
///
@@ -387,7 +389,10 @@ public SfTextInputLayout()
{
ThemeElement.InitializeThemeResources(this, "SfTextInputLayoutTheme");
DrawingOrder = DrawingOrder.BelowContent;
- HintFontSize = (float)HintLabelStyle.FontSize;
+ if(HintLabelStyle != null)
+ {
+ HintFontSize = (float)HintLabelStyle.FontSize;
+ }
this.AddTouchListener(this);
_effectsRenderer = new EffectsRenderer(this);
SetRendererBasedOnPlatform();
@@ -626,7 +631,7 @@ void HandleInputView(object newValue)
void HandlePickerView(object newValue)
{
- if (newValue is Picker picker)
+ if (newValue is Microsoft.Maui.Controls.Picker picker)
{
picker.Focused += OnTextInputViewFocused;
picker.Unfocused += OnTextInputViewUnfocused;
@@ -670,7 +675,7 @@ void HandleTimePickerView(object newValue)
/// Represents the event data
void OnPickerSelectedIndexChanged(object? sender, EventArgs e)
{
- var picker = sender as Picker;
+ var picker = sender as Microsoft.Maui.Controls.Picker;
if (picker == null)
{
return;
@@ -859,7 +864,7 @@ protected override void OnContentChanged(object oldValue, object newValue)
#endif
}
}
- else if (newValue is Picker picker)
+ else if (newValue is Microsoft.Maui.Controls.Picker picker)
{
if (DeviceInfo.Platform != DevicePlatform.WinUI)
{
@@ -1374,7 +1379,16 @@ protected override void OnHandlerChanged()
WireEvents();
OnTextInputViewHandlerChanged(this.Content, new EventArgs());
}
- }
+ else
+ {
+ if (HintLabelStyle != null && HelperLabelStyle != null && ErrorLabelStyle != null)
+ {
+ HintLabelStyle = null;
+ ErrorLabelStyle = null;
+ HelperLabelStyle = null;
+ }
+ }
+ }
#endregion
diff --git a/maui/src/Themes/SfButtonStyles.xaml b/maui/src/Themes/SfButtonStyles.xaml
index 4e15f972..349f608b 100644
--- a/maui/src/Themes/SfButtonStyles.xaml
+++ b/maui/src/Themes/SfButtonStyles.xaml
@@ -4,7 +4,7 @@
x:Class="Syncfusion.Maui.Toolkit.Buttons.SfButtonStyles"
xmlns:buttons="clr-namespace:Syncfusion.Maui.Toolkit.Buttons">
-
+
+
+
+
\ No newline at end of file
diff --git a/maui/src/Themes/SfCircularProgressBarStyles.xaml.cs b/maui/src/Themes/SfCircularProgressBarStyles.xaml.cs
new file mode 100644
index 00000000..af299323
--- /dev/null
+++ b/maui/src/Themes/SfCircularProgressBarStyles.xaml.cs
@@ -0,0 +1,17 @@
+using Microsoft.Maui.Controls;
+
+namespace Syncfusion.Maui.Toolkit.ProgressBar;
+
+///
+/// Represents class.
+///
+public partial class SfCircularProgressBarStyles : ResourceDictionary
+{
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ public SfCircularProgressBarStyles()
+ {
+ InitializeComponent();
+ }
+}
\ No newline at end of file
diff --git a/maui/src/Themes/SfDatePickerStyles.xaml b/maui/src/Themes/SfDatePickerStyles.xaml
new file mode 100644
index 00000000..7e519adf
--- /dev/null
+++ b/maui/src/Themes/SfDatePickerStyles.xaml
@@ -0,0 +1,22 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/maui/src/Themes/SfDatePickerStyles.xaml.cs b/maui/src/Themes/SfDatePickerStyles.xaml.cs
new file mode 100644
index 00000000..a8dd6d66
--- /dev/null
+++ b/maui/src/Themes/SfDatePickerStyles.xaml.cs
@@ -0,0 +1,16 @@
+namespace Syncfusion.Maui.Toolkit.Picker
+{
+ ///
+ /// Represents class.
+ ///
+ public partial class SfDatePickerStyles : ResourceDictionary
+ {
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ public SfDatePickerStyles()
+ {
+ InitializeComponent();
+ }
+ }
+}
\ No newline at end of file
diff --git a/maui/src/Themes/SfDateTimePickerStyles.xaml b/maui/src/Themes/SfDateTimePickerStyles.xaml
new file mode 100644
index 00000000..38e7e71b
--- /dev/null
+++ b/maui/src/Themes/SfDateTimePickerStyles.xaml
@@ -0,0 +1,41 @@
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/maui/src/Themes/SfDateTimePickerStyles.xaml.cs b/maui/src/Themes/SfDateTimePickerStyles.xaml.cs
new file mode 100644
index 00000000..cf6209b9
--- /dev/null
+++ b/maui/src/Themes/SfDateTimePickerStyles.xaml.cs
@@ -0,0 +1,16 @@
+namespace Syncfusion.Maui.Toolkit.Picker
+{
+ ///
+ /// Represents class.
+ ///
+ public partial class SfDateTimePickerStyles : ResourceDictionary
+ {
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ public SfDateTimePickerStyles()
+ {
+ InitializeComponent();
+ }
+ }
+}
\ No newline at end of file
diff --git a/maui/src/Themes/SfLinearProgressBarStyles.xaml b/maui/src/Themes/SfLinearProgressBarStyles.xaml
new file mode 100644
index 00000000..e2c7d6e1
--- /dev/null
+++ b/maui/src/Themes/SfLinearProgressBarStyles.xaml
@@ -0,0 +1,20 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/maui/src/Themes/SfLinearProgressBarStyles.xaml.cs b/maui/src/Themes/SfLinearProgressBarStyles.xaml.cs
new file mode 100644
index 00000000..829c5f39
--- /dev/null
+++ b/maui/src/Themes/SfLinearProgressBarStyles.xaml.cs
@@ -0,0 +1,17 @@
+using Microsoft.Maui.Controls;
+
+namespace Syncfusion.Maui.Toolkit.ProgressBar;
+
+///
+/// Represents class.
+///
+public partial class SfLinearProgressBarStyles : ResourceDictionary
+{
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ public SfLinearProgressBarStyles()
+ {
+ InitializeComponent();
+ }
+}
\ No newline at end of file
diff --git a/maui/src/Themes/SfPickerStyles.xaml b/maui/src/Themes/SfPickerStyles.xaml
new file mode 100644
index 00000000..cd0b603e
--- /dev/null
+++ b/maui/src/Themes/SfPickerStyles.xaml
@@ -0,0 +1,22 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/maui/src/Themes/SfPickerStyles.xaml.cs b/maui/src/Themes/SfPickerStyles.xaml.cs
new file mode 100644
index 00000000..4c04a0ff
--- /dev/null
+++ b/maui/src/Themes/SfPickerStyles.xaml.cs
@@ -0,0 +1,16 @@
+namespace Syncfusion.Maui.Toolkit.Picker
+{
+ ///
+ /// Represents class.
+ ///
+ public partial class SfPickerStyles : ResourceDictionary
+ {
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ public SfPickerStyles()
+ {
+ InitializeComponent();
+ }
+ }
+}
\ No newline at end of file
diff --git a/maui/src/Themes/SfTextInputLayoutStyles.xaml b/maui/src/Themes/SfTextInputLayoutStyles.xaml
index bd869325..80df236c 100644
--- a/maui/src/Themes/SfTextInputLayoutStyles.xaml
+++ b/maui/src/Themes/SfTextInputLayoutStyles.xaml
@@ -17,7 +17,7 @@
-
+
@@ -34,7 +34,7 @@
-
+
@@ -51,7 +51,7 @@
-
+
@@ -68,7 +68,7 @@
-
+
@@ -90,7 +90,7 @@
-
+
diff --git a/maui/src/Themes/SfTimePickerStyles.xaml b/maui/src/Themes/SfTimePickerStyles.xaml
new file mode 100644
index 00000000..1de4f909
--- /dev/null
+++ b/maui/src/Themes/SfTimePickerStyles.xaml
@@ -0,0 +1,22 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/maui/src/Themes/SfTimePickerStyles.xaml.cs b/maui/src/Themes/SfTimePickerStyles.xaml.cs
new file mode 100644
index 00000000..03a3bbe2
--- /dev/null
+++ b/maui/src/Themes/SfTimePickerStyles.xaml.cs
@@ -0,0 +1,16 @@
+namespace Syncfusion.Maui.Toolkit.Picker
+{
+ ///
+ /// Represents class.
+ ///
+ public partial class SfTimePickerStyles : ResourceDictionary
+ {
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ public SfTimePickerStyles()
+ {
+ InitializeComponent();
+ }
+ }
+}
\ No newline at end of file
diff --git a/maui/tests/Syncfusion.Maui.Toolkit.UnitTest/Buttons/SfButtonUnitTests.cs b/maui/tests/Syncfusion.Maui.Toolkit.UnitTest/Buttons/SfButtonUnitTests.cs
index a1f0b929..ef4a8e03 100644
--- a/maui/tests/Syncfusion.Maui.Toolkit.UnitTest/Buttons/SfButtonUnitTests.cs
+++ b/maui/tests/Syncfusion.Maui.Toolkit.UnitTest/Buttons/SfButtonUnitTests.cs
@@ -89,8 +89,8 @@ public void Stroke_ShouldSetAndGetCorrectly(string colorHex)
var button = new SfButton();
var expectedBrush = GetSolidColorBrush(colorHex);
button.Stroke = expectedBrush;
- var actualBrush = button.Stroke;
- Assert.Equal(expectedBrush.Color, actualBrush);
+ var actualStroke = (Color?)GetNonPublicProperty(button, "BaseStrokeColor");
+ Assert.Equal(expectedBrush.Color, actualStroke);
}
[Theory]
@@ -115,7 +115,9 @@ public void Text_ShouldSetAndGetCorrectly(string expectedText)
var button = new SfButton();
button.Text = expectedText;
var actualText = button.Text;
+ var isTextChanged = (bool?)GetPrivateMember(button, "_isSemanticTextChanged");
Assert.Equal(expectedText, actualText);
+ Assert.True(isTextChanged);
}
[Theory]
@@ -185,9 +187,12 @@ public void ImageSource_ShouldSetAndGetCorrectly(string imagePath)
{
var button = new SfButton();
var expectedImageSource = ImageSource.FromFile(imagePath);
+ button.ShowIcon = true;
button.ImageSource = expectedImageSource;
var actualImageSource = button.ImageSource;
+ var isImageUpdated = (bool?)GetPrivateMember(button, "_isImageIconUpdated");
Assert.Equal(expectedImageSource, actualImageSource);
+ Assert.True(isImageUpdated);
}
[Theory]
@@ -197,20 +202,28 @@ public void ShowIcon_ShouldSetAndGetCorrectly(bool expectedValue)
{
var button = new SfButton();
button.ShowIcon = expectedValue;
+ button.ImageSource = "SampleImage1.png";
var actualValue = button.ShowIcon;
+ var isImageUpdated = (bool?)GetPrivateMember(button, "_isImageIconUpdated");
Assert.Equal(expectedValue, actualValue);
+ Assert.Equal(expectedValue, isImageUpdated);
}
[Theory]
- [InlineData(10.0)]
- [InlineData(20.5)]
- [InlineData(0.0)]
- public void ImageSize_ShouldSetAndGetCorrectly(double expectedValue)
+ [InlineData(10.0, 24)]
+ [InlineData(20.5, 34.5)]
+ [InlineData(0.0, 14)]
+ public void ImageSize_ShouldSetAndGetCorrectly(double expectedValue, double expectedHeight)
{
var button = new SfButton();
+ button.IsCreatedInternally = true;
+ button.ShowIcon = true;
+ button.ImageSource = "SampleImage1.png";
button.ImageSize = expectedValue;
var actualValue = button.ImageSize;
+ var actualHeight = button.HeightRequest;
Assert.Equal(expectedValue, actualValue);
+ Assert.Equal(expectedHeight, actualHeight);
}
[Theory]
@@ -235,7 +248,9 @@ public void BackgroundImageSource_ShouldSetAndGetCorrectly(string imagePath)
var expectedValue = ImageSource.FromFile(imagePath);
button.BackgroundImageSource = expectedValue;
var actualValue = button.BackgroundImageSource;
+ var backgroundImageView = (Image?)GetPrivateMember(button, "_backgroundImageView");
Assert.Equal(expectedValue, actualValue);
+ Assert.Equal(expectedValue, backgroundImageView?.Source);
}
[Theory]
@@ -388,14 +403,40 @@ public void BackgroundImageAspect_ShouldSetAndGetCorrectly(Aspect expectedAspect
var button = new SfButton();
button.BackgroundImageAspect = expectedAspect;
+ var backgroundImageView = (Image?)GetPrivateMember(button, "_backgroundImageView");
+ button.BackgroundImageSource = "SampleImage1.png";
Assert.Equal(expectedAspect, button.BackgroundImageAspect);
- }
+ Assert.Equal(expectedAspect, backgroundImageView?.Aspect);
+ }
#endregion
#region Private Method
+ protected object? GetPrivateMember(T obj, string memberName)
+ {
+ var type = obj?.GetType();
+ while (type != null)
+ {
+ var field = type.GetField(memberName, BindingFlags.NonPublic | BindingFlags.Instance);
+ if (field != null)
+ {
+ return field.GetValue(obj);
+ }
+
+ var property = type.GetProperty(memberName, BindingFlags.NonPublic | BindingFlags.Instance);
+ if (property != null)
+ {
+ return property.GetValue(obj);
+ }
+
+ type = type.BaseType;
+ }
+
+ throw new InvalidOperationException($"Field or property '{memberName}' not found.");
+ }
+
private SolidColorBrush GetSolidColorBrush(string colorString)
{
var brush = _converter.ConvertFromString(colorString) as SolidColorBrush;
@@ -734,5 +775,27 @@ private void AttachVisualStates(SfButton button)
}
#endregion
+
+ #region AutomationScenario
+
+ [Theory]
+ [InlineData("SampleImage1.png", "#FFFFFF")]
+ [InlineData("SampleImage2.png", "#FF5733")]
+ public void BackgroundImageSource_Background(string imagePath, string colorHex)
+ {
+ var button = new SfButton();
+ var expectedValue = ImageSource.FromFile(imagePath);
+ var expectedBrush = GetSolidColorBrush(colorHex);
+ button.BackgroundImageSource = expectedValue;
+ button.Background = expectedBrush;
+ var actualValue = button.BackgroundImageSource;
+ var actualBrush = button.Background;
+ var backgroundImageView = (Image?)GetPrivateMember(button, "_backgroundImageView");
+ Assert.Equal(expectedValue, actualValue);
+ Assert.Equal(expectedValue, backgroundImageView?.Source);
+ Assert.Equal(expectedBrush.Color, actualBrush);
+ }
+
+ #endregion
}
}
diff --git a/maui/tests/Syncfusion.Maui.Toolkit.UnitTest/Buttons/SfChipsUnitTests.cs b/maui/tests/Syncfusion.Maui.Toolkit.UnitTest/Buttons/SfChipsUnitTests.cs
index 9604da73..ccfa61d3 100644
--- a/maui/tests/Syncfusion.Maui.Toolkit.UnitTest/Buttons/SfChipsUnitTests.cs
+++ b/maui/tests/Syncfusion.Maui.Toolkit.UnitTest/Buttons/SfChipsUnitTests.cs
@@ -53,7 +53,7 @@ public void CloseButtonColor_SetValue_ReturnsExpectedValue(string colorName)
var chips = new SfChip();
var expectedColor = Color.FromArgb(colorName);
chips.CloseButtonColor = expectedColor;
- var actualColor = chips.CloseButtonColor;
+ var actualColor = chips.ChipCloseButtonColor;
Assert.Equal(expectedColor, actualColor);
}
@@ -91,8 +91,9 @@ public void SelectionIndicator_SetValue_ReturnsExpectedValue(string colorName)
{
var chip = new SfChip();
var expectedValue = Color.FromArgb(colorName);
+ chip.ShowSelectionIndicator = true;
chip.SelectionIndicatorColor = expectedValue;
- var actualValue = chip.SelectionIndicatorColor;
+ var actualValue = chip.SelectionIndicatorColorValue;
Assert.Equal(expectedValue, actualValue);
}
@@ -163,8 +164,8 @@ public void Stroke_SetValue_ReturnsExpectedValue(string colorHex)
var chip = new SfChip();
var expectedBrush = GetSolidColorBrush(colorHex);
chip.Stroke = expectedBrush;
- var actualBrush = chip.Stroke;
- Assert.Equal(expectedBrush.Color, actualBrush);
+ var actualStroke = (Color?)GetNonPublicProperty(chip, "BaseStrokeColor");
+ Assert.Equal(expectedBrush.Color, actualStroke);
}
[Theory]
@@ -191,7 +192,9 @@ public void Text_SetValue_ReturnsExpectedValue(string expectedText)
Text = expectedText
};
var actualText = chip.Text;
+ var isTextChanged = (bool?)GetPrivateMember(chip, "_isSemanticTextChanged");
Assert.Equal(expectedText, actualText);
+ Assert.True(isTextChanged);
}
[Theory]
@@ -269,9 +272,12 @@ public void ImageSource_SetValue_ReturnsExpectedValue(string imagePath)
{
var chip = new SfChip();
var expectedImageSource = ImageSource.FromFile(imagePath);
+ chip.ShowIcon = true;
chip.ImageSource = expectedImageSource;
+ var isImageUpdated = (bool?)GetPrivateMember(chip, "_isImageIconUpdated");
var actualImageSource = chip.ImageSource;
Assert.Equal(expectedImageSource, actualImageSource);
+ Assert.True(isImageUpdated);
}
[Theory]
@@ -281,24 +287,33 @@ public void ShowIcon_SetValue_ReturnsExpectedValue(bool expectedValue)
{
var chip = new SfChip
{
- ShowIcon = expectedValue
+ ShowIcon = expectedValue,
+ ImageSource = "SampleImage1.png"
};
var actualValue = chip.ShowIcon;
+ var isImageUpdated = (bool?)GetPrivateMember(chip, "_isImageIconUpdated");
Assert.Equal(expectedValue, actualValue);
+ Assert.Equal(expectedValue, isImageUpdated);
}
[Theory]
- [InlineData(10.0)]
- [InlineData(20.5)]
- [InlineData(0.0)]
- public void ImageSize_SetValue_ReturnsExpectedValue(double expectedValue)
+ [InlineData(10.0, 24)]
+ [InlineData(20.5, 34.5)]
+ [InlineData(0.0, 14)]
+ public void ImageSize_SetValue_ReturnsExpectedValue(double expectedValue, double expectedHeight)
{
var chip = new SfChip
{
+ IsCreatedInternally = true,
+ ShowIcon = true,
+ ImageSource = "SampleImage1.png",
ImageSize = expectedValue
+
};
var actualValue = chip.ImageSize;
+ var actualHeight = chip.HeightRequest;
Assert.Equal(expectedValue, actualValue);
+ Assert.Equal(expectedHeight, actualHeight);
}
[Theory]
@@ -325,7 +340,9 @@ public void BackgroundImageSource_SetValue_ReturnsExpectedValue(string imagePath
var expectedValue = ImageSource.FromFile(imagePath);
chip.BackgroundImageSource = expectedValue;
var actualValue = chip.BackgroundImageSource;
+ var backgroundImageView = (Image?)GetPrivateMember(chip, "_backgroundImageView");
Assert.Equal(expectedValue, actualValue);
+ Assert.Equal(expectedValue, backgroundImageView?.Source);
}
[Theory]
@@ -461,6 +478,29 @@ public void IsCloseButtonClicked_Should_SetAndGetCorrectly()
#endregion
#region Private Method
+
+ protected object? GetPrivateMember(T obj, string memberName)
+ {
+ var type = obj?.GetType();
+ while (type != null)
+ {
+ var field = type.GetField(memberName, BindingFlags.NonPublic | BindingFlags.Instance);
+ if (field != null)
+ {
+ return field.GetValue(obj);
+ }
+
+ var property = type.GetProperty(memberName, BindingFlags.NonPublic | BindingFlags.Instance);
+ if (property != null)
+ {
+ return property.GetValue(obj);
+ }
+
+ type = type.BaseType;
+ }
+
+ throw new InvalidOperationException($"Field or property '{memberName}' not found.");
+ }
private SolidColorBrush GetSolidColorBrush(string colorString)
{
if (_converter.ConvertFromString(colorString) is not SolidColorBrush brush)
diff --git a/maui/tests/Syncfusion.Maui.Toolkit.UnitTest/Buttons/SfSegmentedControlUnitTests.cs b/maui/tests/Syncfusion.Maui.Toolkit.UnitTest/Buttons/SfSegmentedControlUnitTests.cs
index 89ed0ace..e750aa20 100644
--- a/maui/tests/Syncfusion.Maui.Toolkit.UnitTest/Buttons/SfSegmentedControlUnitTests.cs
+++ b/maui/tests/Syncfusion.Maui.Toolkit.UnitTest/Buttons/SfSegmentedControlUnitTests.cs
@@ -1222,7 +1222,7 @@ public void GetSelectedSegmentForeground_ReturnsColorFromSelectedSegmentbackgrou
var itemInfo = new SfSegmentedControl { SelectionIndicatorSettings = new SelectionIndicatorSettings { SelectionIndicatorPlacement = SelectionIndicatorPlacement.Border } };
var segmentItem = new SfSegmentItem { SelectedSegmentTextColor = Colors.Yellow };
var resultColor = SegmentViewHelper.GetSelectedSegmentForeground(itemInfo, segmentItem);
- var expected = Color.FromRgba(0.40392157, 0.3137255, 0.6431373, 1);
+ var expected = Color.FromRgba(255, 255, 0, 255);
Assert.Equal(expected, resultColor);
}
diff --git a/maui/tests/Syncfusion.Maui.Toolkit.UnitTest/Chart/ChartAnnotationUnitTests.cs b/maui/tests/Syncfusion.Maui.Toolkit.UnitTest/Chart/ChartAnnotationUnitTests.cs
index 21f39d00..05fb1fe2 100644
--- a/maui/tests/Syncfusion.Maui.Toolkit.UnitTest/Chart/ChartAnnotationUnitTests.cs
+++ b/maui/tests/Syncfusion.Maui.Toolkit.UnitTest/Chart/ChartAnnotationUnitTests.cs
@@ -1,6 +1,6 @@
using Syncfusion.Maui.Toolkit.Charts;
-namespace Syncfusion.Maui.Toolkit.UnitTest
+namespace Syncfusion.Maui.Toolkit.UnitTest.Charts
{
public class ChartAnnotationUnitTests
{
diff --git a/maui/tests/Syncfusion.Maui.Toolkit.UnitTest/Chart/ChartAxisUnitTests.cs b/maui/tests/Syncfusion.Maui.Toolkit.UnitTest/Chart/ChartAxisUnitTests.cs
index 4b7bab97..69e35bd5 100644
--- a/maui/tests/Syncfusion.Maui.Toolkit.UnitTest/Chart/ChartAxisUnitTests.cs
+++ b/maui/tests/Syncfusion.Maui.Toolkit.UnitTest/Chart/ChartAxisUnitTests.cs
@@ -1,6 +1,6 @@
using Syncfusion.Maui.Toolkit.Charts;
-namespace Syncfusion.Maui.Toolkit.UnitTest
+namespace Syncfusion.Maui.Toolkit.UnitTest.Charts
{
public class ChartAxisUnitTests
{
diff --git a/maui/tests/Syncfusion.Maui.Toolkit.UnitTest/Chart/ChartBehaviorUnitTests.cs b/maui/tests/Syncfusion.Maui.Toolkit.UnitTest/Chart/ChartBehaviorUnitTests.cs
index 2a404729..88ef32a4 100644
--- a/maui/tests/Syncfusion.Maui.Toolkit.UnitTest/Chart/ChartBehaviorUnitTests.cs
+++ b/maui/tests/Syncfusion.Maui.Toolkit.UnitTest/Chart/ChartBehaviorUnitTests.cs
@@ -1,6 +1,6 @@
using Syncfusion.Maui.Toolkit.Charts;
-namespace Syncfusion.Maui.Toolkit.UnitTest
+namespace Syncfusion.Maui.Toolkit.UnitTest.Charts
{
public class ChartBehaviorUnitTests
{
diff --git a/maui/tests/Syncfusion.Maui.Toolkit.UnitTest/Chart/ChartDataLabelUnitTests.cs b/maui/tests/Syncfusion.Maui.Toolkit.UnitTest/Chart/ChartDataLabelUnitTests.cs
index 314ac53b..005c326d 100644
--- a/maui/tests/Syncfusion.Maui.Toolkit.UnitTest/Chart/ChartDataLabelUnitTests.cs
+++ b/maui/tests/Syncfusion.Maui.Toolkit.UnitTest/Chart/ChartDataLabelUnitTests.cs
@@ -1,6 +1,6 @@
using Syncfusion.Maui.Toolkit.Charts;
-namespace Syncfusion.Maui.Toolkit.UnitTest
+namespace Syncfusion.Maui.Toolkit.UnitTest.Charts
{
public class ChartDataLabelTests
{
diff --git a/maui/tests/Syncfusion.Maui.Toolkit.UnitTest/Chart/ChartLegendUnitTests.cs b/maui/tests/Syncfusion.Maui.Toolkit.UnitTest/Chart/ChartLegendUnitTests.cs
index 5ec3ae6e..410e4007 100644
--- a/maui/tests/Syncfusion.Maui.Toolkit.UnitTest/Chart/ChartLegendUnitTests.cs
+++ b/maui/tests/Syncfusion.Maui.Toolkit.UnitTest/Chart/ChartLegendUnitTests.cs
@@ -1,6 +1,6 @@
using Syncfusion.Maui.Toolkit.Charts;
-namespace Syncfusion.Maui.Toolkit.UnitTest
+namespace Syncfusion.Maui.Toolkit.UnitTest.Charts
{
public class ChartLegendUnitTests
{
diff --git a/maui/tests/Syncfusion.Maui.Toolkit.UnitTest/Chart/ChartModelUnitTests.cs b/maui/tests/Syncfusion.Maui.Toolkit.UnitTest/Chart/ChartModelUnitTests.cs
index 7ec549d1..ecb84208 100644
--- a/maui/tests/Syncfusion.Maui.Toolkit.UnitTest/Chart/ChartModelUnitTests.cs
+++ b/maui/tests/Syncfusion.Maui.Toolkit.UnitTest/Chart/ChartModelUnitTests.cs
@@ -1,6 +1,6 @@
using Syncfusion.Maui.Toolkit.Charts;
-namespace Syncfusion.Maui.Toolkit.UnitTest
+namespace Syncfusion.Maui.Toolkit.UnitTest.Charts
{
public class ChartModelUnitTests
{
diff --git a/maui/tests/Syncfusion.Maui.Toolkit.UnitTest/Chart/ChartPlotBandUnitTests.cs b/maui/tests/Syncfusion.Maui.Toolkit.UnitTest/Chart/ChartPlotBandUnitTests.cs
index 35606d8c..f7a4e4ca 100644
--- a/maui/tests/Syncfusion.Maui.Toolkit.UnitTest/Chart/ChartPlotBandUnitTests.cs
+++ b/maui/tests/Syncfusion.Maui.Toolkit.UnitTest/Chart/ChartPlotBandUnitTests.cs
@@ -1,6 +1,6 @@
using Syncfusion.Maui.Toolkit.Charts;
-namespace Syncfusion.Maui.Toolkit.UnitTest
+namespace Syncfusion.Maui.Toolkit.UnitTest.Charts
{
public class ChartPlotBandTests
{
diff --git a/maui/tests/Syncfusion.Maui.Toolkit.UnitTest/Chart/ChartSegmentUnitTests.cs b/maui/tests/Syncfusion.Maui.Toolkit.UnitTest/Chart/ChartSegmentUnitTests.cs
index bec319d9..4a98ac39 100644
--- a/maui/tests/Syncfusion.Maui.Toolkit.UnitTest/Chart/ChartSegmentUnitTests.cs
+++ b/maui/tests/Syncfusion.Maui.Toolkit.UnitTest/Chart/ChartSegmentUnitTests.cs
@@ -1,6 +1,6 @@
using Syncfusion.Maui.Toolkit.Charts;
-namespace Syncfusion.Maui.Toolkit.UnitTest
+namespace Syncfusion.Maui.Toolkit.UnitTest.Charts
{
public class ChartSegmentUnitTests
{
diff --git a/maui/tests/Syncfusion.Maui.Toolkit.UnitTest/Chart/ChartSeriesUnitTests.cs b/maui/tests/Syncfusion.Maui.Toolkit.UnitTest/Chart/ChartSeriesUnitTests.cs
index 8085b3a3..a91259fa 100644
--- a/maui/tests/Syncfusion.Maui.Toolkit.UnitTest/Chart/ChartSeriesUnitTests.cs
+++ b/maui/tests/Syncfusion.Maui.Toolkit.UnitTest/Chart/ChartSeriesUnitTests.cs
@@ -1,6 +1,6 @@
using Syncfusion.Maui.Toolkit.Charts;
-namespace Syncfusion.Maui.Toolkit.UnitTest
+namespace Syncfusion.Maui.Toolkit.UnitTest.Charts
{
public class ChartSeriesUnitTests
{
diff --git a/maui/tests/Syncfusion.Maui.Toolkit.UnitTest/Chart/DefaultTests/BehaviorDefaultTests.cs b/maui/tests/Syncfusion.Maui.Toolkit.UnitTest/Chart/DefaultTests/BehaviorDefaultTests.cs
index c9ed521f..a10ebb25 100644
--- a/maui/tests/Syncfusion.Maui.Toolkit.UnitTest/Chart/DefaultTests/BehaviorDefaultTests.cs
+++ b/maui/tests/Syncfusion.Maui.Toolkit.UnitTest/Chart/DefaultTests/BehaviorDefaultTests.cs
@@ -1,7 +1,7 @@
using Syncfusion.Maui.Toolkit.Charts;
using Syncfusion.Maui.Toolkit.Internals;
-namespace Syncfusion.Maui.Toolkit.UnitTest
+namespace Syncfusion.Maui.Toolkit.UnitTest.Charts
{
public class BehaviorDefaultTests
{
diff --git a/maui/tests/Syncfusion.Maui.Toolkit.UnitTest/Chart/DefaultTests/ChartAxisUnitTest.cs b/maui/tests/Syncfusion.Maui.Toolkit.UnitTest/Chart/DefaultTests/ChartAxisUnitTest.cs
index 28a86bf3..d544aba6 100644
--- a/maui/tests/Syncfusion.Maui.Toolkit.UnitTest/Chart/DefaultTests/ChartAxisUnitTest.cs
+++ b/maui/tests/Syncfusion.Maui.Toolkit.UnitTest/Chart/DefaultTests/ChartAxisUnitTest.cs
@@ -1,6 +1,6 @@
using Syncfusion.Maui.Toolkit.Charts;
-namespace Syncfusion.Maui.Toolkit.UnitTest
+namespace Syncfusion.Maui.Toolkit.UnitTest.Charts
{
public class ChartAxisUnitTest
{
diff --git a/maui/tests/Syncfusion.Maui.Toolkit.UnitTest/Chart/DefaultTests/ChartDefaultTests.cs b/maui/tests/Syncfusion.Maui.Toolkit.UnitTest/Chart/DefaultTests/ChartDefaultTests.cs
index 0b49fc45..f119919a 100644
--- a/maui/tests/Syncfusion.Maui.Toolkit.UnitTest/Chart/DefaultTests/ChartDefaultTests.cs
+++ b/maui/tests/Syncfusion.Maui.Toolkit.UnitTest/Chart/DefaultTests/ChartDefaultTests.cs
@@ -1,7 +1,7 @@
using System.Collections.ObjectModel;
using Syncfusion.Maui.Toolkit.Charts;
-namespace Syncfusion.Maui.Toolkit.UnitTest
+namespace Syncfusion.Maui.Toolkit.UnitTest.Charts
{
public class ChartDefaultTests
{
diff --git a/maui/tests/Syncfusion.Maui.Toolkit.UnitTest/Chart/DefaultTests/DefaultAnnotationUnitTests.cs b/maui/tests/Syncfusion.Maui.Toolkit.UnitTest/Chart/DefaultTests/DefaultAnnotationUnitTests.cs
index bcaec197..19f6035c 100644
--- a/maui/tests/Syncfusion.Maui.Toolkit.UnitTest/Chart/DefaultTests/DefaultAnnotationUnitTests.cs
+++ b/maui/tests/Syncfusion.Maui.Toolkit.UnitTest/Chart/DefaultTests/DefaultAnnotationUnitTests.cs
@@ -1,6 +1,6 @@
using Syncfusion.Maui.Toolkit.Charts;
-namespace Syncfusion.Maui.Toolkit.UnitTest
+namespace Syncfusion.Maui.Toolkit.UnitTest.Charts
{
public class DefaultAnnotationUnitTests
{
diff --git a/maui/tests/Syncfusion.Maui.Toolkit.UnitTest/Chart/DefaultTests/DefaultSegmentUnitTest.cs b/maui/tests/Syncfusion.Maui.Toolkit.UnitTest/Chart/DefaultTests/DefaultSegmentUnitTest.cs
index 2992988b..1da76b50 100644
--- a/maui/tests/Syncfusion.Maui.Toolkit.UnitTest/Chart/DefaultTests/DefaultSegmentUnitTest.cs
+++ b/maui/tests/Syncfusion.Maui.Toolkit.UnitTest/Chart/DefaultTests/DefaultSegmentUnitTest.cs
@@ -1,7 +1,7 @@
using System.Collections.ObjectModel;
using Syncfusion.Maui.Toolkit.Charts;
-namespace Syncfusion.Maui.Toolkit.UnitTest
+namespace Syncfusion.Maui.Toolkit.UnitTest.Charts
{
public class DefaultSegmentUnitTest : BaseUnitTest
{
@@ -1126,7 +1126,7 @@ public void ScatterSegment_InitializesScatterSpecificPropertiesCorrectly()
{
var segment = new ScatterSegment();
Assert.Equal(1f, segment.Opacity);
- Assert.Equal(Charts.ShapeType.Custom, segment.Type);
+ Assert.Equal(Syncfusion.Maui.Toolkit.Charts.ShapeType.Custom, segment.Type);
Assert.Equal(0f, segment.PointWidth);
Assert.Equal(0f, segment.PointHeight);
Assert.Equal(0f, segment.CenterX);
diff --git a/maui/tests/Syncfusion.Maui.Toolkit.UnitTest/Chart/DefaultTests/LabelStyleUnitTest.cs b/maui/tests/Syncfusion.Maui.Toolkit.UnitTest/Chart/DefaultTests/LabelStyleUnitTest.cs
index e2e8aed7..d0015e2d 100644
--- a/maui/tests/Syncfusion.Maui.Toolkit.UnitTest/Chart/DefaultTests/LabelStyleUnitTest.cs
+++ b/maui/tests/Syncfusion.Maui.Toolkit.UnitTest/Chart/DefaultTests/LabelStyleUnitTest.cs
@@ -1,6 +1,6 @@
using Syncfusion.Maui.Toolkit.Charts;
-namespace Syncfusion.Maui.Toolkit.UnitTest
+namespace Syncfusion.Maui.Toolkit.UnitTest.Charts
{
public class LabelStyleUnitTest
{
@@ -299,7 +299,7 @@ public void ErrorBarCapLineStyle_DefaultConstructor_SetsDefaultValues()
public void ChartMarkerSettings_DefaultConstructor_SetsDefaultValues()
{
var markerSettings = new ChartMarkerSettings();
- Assert.Equal(Charts.ShapeType.Circle, markerSettings.Type);
+ Assert.Equal(Syncfusion.Maui.Toolkit.Charts.ShapeType.Circle, markerSettings.Type);
Assert.Null(markerSettings.Fill);
Assert.Null(markerSettings.Stroke);
Assert.Equal(0d, markerSettings.StrokeWidth);
diff --git a/maui/tests/Syncfusion.Maui.Toolkit.UnitTest/Chart/DefaultTests/SeriesDefaultTests.cs b/maui/tests/Syncfusion.Maui.Toolkit.UnitTest/Chart/DefaultTests/SeriesDefaultTests.cs
index 1440276a..b4741a87 100644
--- a/maui/tests/Syncfusion.Maui.Toolkit.UnitTest/Chart/DefaultTests/SeriesDefaultTests.cs
+++ b/maui/tests/Syncfusion.Maui.Toolkit.UnitTest/Chart/DefaultTests/SeriesDefaultTests.cs
@@ -1,5 +1,6 @@
using System.Collections.ObjectModel;
using Syncfusion.Maui.Toolkit.Charts;
+using Chart = Syncfusion.Maui.Toolkit.Charts;
namespace Syncfusion.Maui.Toolkit.UnitTest;
@@ -32,7 +33,7 @@ public void AreaSeries_InitializesMarkerSettingsCorrectly()
Assert.Null(series.TooltipTemplate);
Assert.IsType(series.MarkerSettings);
var markerSettings = series.MarkerSettings;
- Assert.Equal(Charts.ShapeType.Circle, markerSettings.Type);
+ Assert.Equal(Chart.ShapeType.Circle, markerSettings.Type);
Assert.Null(markerSettings.Fill);
Assert.Null(markerSettings.Stroke);
Assert.Equal(0d, markerSettings.StrokeWidth);
@@ -176,7 +177,7 @@ public void BoxAndWhiskerSeries_InitializesBasicPropertiesCorrectly()
{
var series = new BoxAndWhiskerSeries();
Assert.Equal(BoxPlotMode.Exclusive, series.BoxPlotMode);
- Assert.Equal(Charts.ShapeType.Circle, series.OutlierShapeType);
+ Assert.Equal(Chart.ShapeType.Circle, series.OutlierShapeType);
Assert.False(series.ShowMedian);
Assert.True(series.ShowOutlier);
Assert.Equal(0d, series.Spacing);
@@ -865,7 +866,7 @@ public void StepLineSeriesDefaultTests_Part3()
Assert.Null(series.ItemsSource);
Assert.Equal(LabelContext.YValue, series.LabelContext);
Assert.Null(series.LabelTemplate);
- Assert.Equal(Charts.ChartLegendIconType.Circle, series.LegendIcon);
+ Assert.Equal(Chart.ChartLegendIconType.Circle, series.LegendIcon);
Assert.Null(series.PaletteBrushes);
Assert.False(series.ShowDataLabels);
Assert.Null(series.TooltipTemplate);
@@ -1033,7 +1034,7 @@ public void StepAreaSeriesDefaultTests_Part3()
Assert.Null(series.ItemsSource);
Assert.Equal(LabelContext.YValue, series.LabelContext);
Assert.Null(series.LabelTemplate);
- Assert.Equal(Charts.ChartLegendIconType.Circle, series.LegendIcon);
+ Assert.Equal(Chart.ChartLegendIconType.Circle, series.LegendIcon);
Assert.Null(series.PaletteBrushes);
Assert.False(series.ShowDataLabels);
Assert.Null(series.TooltipTemplate);
@@ -1211,7 +1212,7 @@ public void StackingLineSeriesDefaultTests_Part3()
public void StackingLineSeriesDefaultTests_Part4()
{
var series = new StackingLineSeries();
- Assert.Equal(Charts.ChartLegendIconType.Circle, series.LegendIcon);
+ Assert.Equal(Chart.ChartLegendIconType.Circle, series.LegendIcon);
Assert.Null(series.PaletteBrushes);
Assert.False(series.ShowDataLabels);
Assert.Null(series.TooltipTemplate);
@@ -1390,7 +1391,7 @@ public void StackingLine100SeriesDefaultTests_Part3()
public void StackingLine100SeriesDefaultTests_Part4()
{
var series = new StackingLine100Series();
- Assert.Equal(Charts.ChartLegendIconType.Circle, series.LegendIcon);
+ Assert.Equal(Chart.ChartLegendIconType.Circle, series.LegendIcon);
Assert.Null(series.PaletteBrushes);
Assert.False(series.ShowDataLabels);
Assert.Null(series.TooltipTemplate);
@@ -1569,7 +1570,7 @@ public void StackingColumnSeriesDefaultTests_Part3()
public void StackingColumnSeriesDefaultTests_Part4()
{
var series = new StackingColumnSeries();
- Assert.Equal(Charts.ChartLegendIconType.Circle, series.LegendIcon);
+ Assert.Equal(Chart.ChartLegendIconType.Circle, series.LegendIcon);
Assert.Null(series.PaletteBrushes);
Assert.False(series.ShowDataLabels);
Assert.Null(series.TooltipTemplate);
@@ -1748,7 +1749,7 @@ public void StackingColumn100SeriesDefaultTests_Part3()
public void StackingColumn100SeriesDefaultTests_Part4()
{
var series = new StackingColumn100Series();
- Assert.Equal(Charts.ChartLegendIconType.Circle, series.LegendIcon);
+ Assert.Equal(Chart.ChartLegendIconType.Circle, series.LegendIcon);
Assert.Null(series.PaletteBrushes);
Assert.False(series.ShowDataLabels);
Assert.Null(series.TooltipTemplate);
@@ -1927,7 +1928,7 @@ public void StackingAreaSeriesDefaultTests_Part3()
public void StackingAreaSeriesDefaultTests_Part4()
{
var series = new StackingAreaSeries();
- Assert.Equal(Charts.ChartLegendIconType.Circle, series.LegendIcon);
+ Assert.Equal(Chart.ChartLegendIconType.Circle, series.LegendIcon);
Assert.Null(series.PaletteBrushes);
Assert.False(series.ShowDataLabels);
Assert.Null(series.TooltipTemplate);
@@ -2106,7 +2107,7 @@ public void StackingArea100SeriesDefaultTests_Part3()
public void StackingArea100SeriesDefaultTests_Part4()
{
var series = new StackingArea100Series();
- Assert.Equal(Charts.ChartLegendIconType.Circle, series.LegendIcon);
+ Assert.Equal(Chart.ChartLegendIconType.Circle, series.LegendIcon);
Assert.Null(series.PaletteBrushes);
Assert.False(series.ShowDataLabels);
Assert.Null(series.TooltipTemplate);
@@ -2279,11 +2280,11 @@ public void SplineSeriesDefaultTests_Part3()
// Test inherited properties from ChartSeries
Assert.True(series.IsVisible);
- Assert.True(series.IsVisibleOnLegend);
+ Assert.True(series.IsVisibleOnLegend);
Assert.Null(series.ItemsSource);
Assert.Equal(LabelContext.YValue, series.LabelContext);
Assert.Null(series.LabelTemplate);
- Assert.Equal(Charts.ChartLegendIconType.Circle, series.LegendIcon);
+ Assert.Equal(Chart.ChartLegendIconType.Circle, series.LegendIcon);
Assert.Null(series.PaletteBrushes);
Assert.False(series.ShowDataLabels);
Assert.Null(series.TooltipTemplate);
@@ -2467,7 +2468,7 @@ public void SplineRangeAreaSeriesDefaultTests_Part3()
Assert.Null(series.ItemsSource);
Assert.Equal(LabelContext.YValue, series.LabelContext);
Assert.Null(series.LabelTemplate);
- Assert.Equal(Charts.ChartLegendIconType.Circle, series.LegendIcon);
+ Assert.Equal(Chart.ChartLegendIconType.Circle, series.LegendIcon);
Assert.Null(series.PaletteBrushes);
Assert.False(series.ShowDataLabels);
Assert.Null(series.TooltipTemplate);
@@ -2656,7 +2657,7 @@ public void SplineAreaSeriesDefaultTests_Part3()
Assert.Null(series.ItemsSource);
Assert.Equal(LabelContext.YValue, series.LabelContext);
Assert.Null(series.LabelTemplate);
- Assert.Equal(Charts.ChartLegendIconType.Circle, series.LegendIcon);
+ Assert.Equal(Chart.ChartLegendIconType.Circle, series.LegendIcon);
Assert.Null(series.PaletteBrushes);
Assert.False(series.ShowDataLabels);
Assert.Null(series.TooltipTemplate);
@@ -2805,7 +2806,7 @@ public void ScatterSeriesDefaultTests_Part1()
Assert.Equal(5d, series.PointHeight);
Assert.Equal(5d, series.PointWidth);
Assert.Null(series.Stroke);
- Assert.Equal(Charts.ShapeType.Circle, series.Type);
+ Assert.Equal(Chart.ShapeType.Circle, series.Type);
// Test inherited properties from XYDataSeries
Assert.Equal(1d, series.StrokeWidth);
Assert.Null(series.YBindingPath);
diff --git a/maui/tests/Syncfusion.Maui.Toolkit.UnitTest/Chart/DefaultTests/SeriesDefaultTestsPartial.cs b/maui/tests/Syncfusion.Maui.Toolkit.UnitTest/Chart/DefaultTests/SeriesDefaultTestsPartial.cs
index dabf50be..45f05750 100644
--- a/maui/tests/Syncfusion.Maui.Toolkit.UnitTest/Chart/DefaultTests/SeriesDefaultTestsPartial.cs
+++ b/maui/tests/Syncfusion.Maui.Toolkit.UnitTest/Chart/DefaultTests/SeriesDefaultTestsPartial.cs
@@ -1,7 +1,7 @@
using System.Collections.ObjectModel;
using Syncfusion.Maui.Toolkit.Charts;
-namespace Syncfusion.Maui.Toolkit.UnitTest
+namespace Syncfusion.Maui.Toolkit.UnitTest.Charts
{
public partial class SeriesDefaultTests
{
diff --git a/maui/tests/Syncfusion.Maui.Toolkit.UnitTest/Chart/Features/AnnotationUnitTest.cs b/maui/tests/Syncfusion.Maui.Toolkit.UnitTest/Chart/Features/AnnotationUnitTest.cs
index 0bf543e9..e1d5c016 100644
--- a/maui/tests/Syncfusion.Maui.Toolkit.UnitTest/Chart/Features/AnnotationUnitTest.cs
+++ b/maui/tests/Syncfusion.Maui.Toolkit.UnitTest/Chart/Features/AnnotationUnitTest.cs
@@ -1,6 +1,6 @@
using Syncfusion.Maui.Toolkit.Charts;
-namespace Syncfusion.Maui.Toolkit.UnitTest
+namespace Syncfusion.Maui.Toolkit.UnitTest.Charts
{
public class AnnotationUnitTest : BaseUnitTest
{
diff --git a/maui/tests/Syncfusion.Maui.Toolkit.UnitTest/Chart/Features/ChartAreaUnitTest.cs b/maui/tests/Syncfusion.Maui.Toolkit.UnitTest/Chart/Features/ChartAreaUnitTest.cs
index c99a493a..3b7acce9 100644
--- a/maui/tests/Syncfusion.Maui.Toolkit.UnitTest/Chart/Features/ChartAreaUnitTest.cs
+++ b/maui/tests/Syncfusion.Maui.Toolkit.UnitTest/Chart/Features/ChartAreaUnitTest.cs
@@ -1,7 +1,7 @@
using System.Collections.ObjectModel;
using Syncfusion.Maui.Toolkit.Charts;
-namespace Syncfusion.Maui.Toolkit.UnitTest
+namespace Syncfusion.Maui.Toolkit.UnitTest.Charts
{
public class ChartAreaUnitTest : BaseUnitTest
{
diff --git a/maui/tests/Syncfusion.Maui.Toolkit.UnitTest/Chart/Features/ChartEventUnitTest.cs b/maui/tests/Syncfusion.Maui.Toolkit.UnitTest/Chart/Features/ChartEventUnitTest.cs
index d51b9975..7612e83a 100644
--- a/maui/tests/Syncfusion.Maui.Toolkit.UnitTest/Chart/Features/ChartEventUnitTest.cs
+++ b/maui/tests/Syncfusion.Maui.Toolkit.UnitTest/Chart/Features/ChartEventUnitTest.cs
@@ -1,6 +1,6 @@
using Syncfusion.Maui.Toolkit.Charts;
-namespace Syncfusion.Maui.Toolkit.UnitTest
+namespace Syncfusion.Maui.Toolkit.UnitTest.Charts
{
public class ChartEventUnitTest : BaseUnitTest
{
diff --git a/maui/tests/Syncfusion.Maui.Toolkit.UnitTest/Chart/Features/ChartFeatureAxisUnitTest.cs b/maui/tests/Syncfusion.Maui.Toolkit.UnitTest/Chart/Features/ChartFeatureAxisUnitTest.cs
index bbc452b4..527f8632 100644
--- a/maui/tests/Syncfusion.Maui.Toolkit.UnitTest/Chart/Features/ChartFeatureAxisUnitTest.cs
+++ b/maui/tests/Syncfusion.Maui.Toolkit.UnitTest/Chart/Features/ChartFeatureAxisUnitTest.cs
@@ -1,7 +1,7 @@
using System.Collections.ObjectModel;
using Syncfusion.Maui.Toolkit.Charts;
-namespace Syncfusion.Maui.Toolkit.UnitTest
+namespace Syncfusion.Maui.Toolkit.UnitTest.Charts
{
public class ChartFeatureAxisUnitTest : BaseUnitTest
{
diff --git a/maui/tests/Syncfusion.Maui.Toolkit.UnitTest/Chart/Features/ChartFeatureBehaviourUnitTest.cs b/maui/tests/Syncfusion.Maui.Toolkit.UnitTest/Chart/Features/ChartFeatureBehaviourUnitTest.cs
index e167aef3..f7e3e8c6 100644
--- a/maui/tests/Syncfusion.Maui.Toolkit.UnitTest/Chart/Features/ChartFeatureBehaviourUnitTest.cs
+++ b/maui/tests/Syncfusion.Maui.Toolkit.UnitTest/Chart/Features/ChartFeatureBehaviourUnitTest.cs
@@ -1,6 +1,6 @@
using Syncfusion.Maui.Toolkit.Charts;
-namespace Syncfusion.Maui.Toolkit.UnitTest
+namespace Syncfusion.Maui.Toolkit.UnitTest.Charts
{
public class ChartFeatureBehaviourUnitTest : BaseUnitTest
{
diff --git a/maui/tests/Syncfusion.Maui.Toolkit.UnitTest/Chart/Features/ChartSelectionBehaviorUnitTest.cs b/maui/tests/Syncfusion.Maui.Toolkit.UnitTest/Chart/Features/ChartSelectionBehaviorUnitTest.cs
index 76d97618..fc2327be 100644
--- a/maui/tests/Syncfusion.Maui.Toolkit.UnitTest/Chart/Features/ChartSelectionBehaviorUnitTest.cs
+++ b/maui/tests/Syncfusion.Maui.Toolkit.UnitTest/Chart/Features/ChartSelectionBehaviorUnitTest.cs
@@ -1,6 +1,6 @@
using Syncfusion.Maui.Toolkit.Charts;
-namespace Syncfusion.Maui.Toolkit.UnitTest
+namespace Syncfusion.Maui.Toolkit.UnitTest.Charts
{
public class ChartSelectionBehaviorUnitTest : BaseUnitTest
{
diff --git a/maui/tests/Syncfusion.Maui.Toolkit.UnitTest/Chart/Features/ChartUnitTests.cs b/maui/tests/Syncfusion.Maui.Toolkit.UnitTest/Chart/Features/ChartUnitTests.cs
index 7c927a36..e0c501c1 100644
--- a/maui/tests/Syncfusion.Maui.Toolkit.UnitTest/Chart/Features/ChartUnitTests.cs
+++ b/maui/tests/Syncfusion.Maui.Toolkit.UnitTest/Chart/Features/ChartUnitTests.cs
@@ -1,7 +1,7 @@
using System.Collections.ObjectModel;
using Syncfusion.Maui.Toolkit.Charts;
-namespace Syncfusion.Maui.Toolkit.UnitTest
+namespace Syncfusion.Maui.Toolkit.UnitTest.Charts
{
public class ChartUnitTests : BaseUnitTest
{
diff --git a/maui/tests/Syncfusion.Maui.Toolkit.UnitTest/Chart/Features/DataLabelUnitTests.cs b/maui/tests/Syncfusion.Maui.Toolkit.UnitTest/Chart/Features/DataLabelUnitTests.cs
index a197e1a6..63c220ff 100644
--- a/maui/tests/Syncfusion.Maui.Toolkit.UnitTest/Chart/Features/DataLabelUnitTests.cs
+++ b/maui/tests/Syncfusion.Maui.Toolkit.UnitTest/Chart/Features/DataLabelUnitTests.cs
@@ -3,7 +3,7 @@
using PointF = Microsoft.Maui.Graphics.PointF;
using SizeF = Microsoft.Maui.Graphics.SizeF;
-namespace Syncfusion.Maui.Toolkit.UnitTest
+namespace Syncfusion.Maui.Toolkit.UnitTest.Charts
{
public class DataLabelUnitTests : BaseUnitTest
{
diff --git a/maui/tests/Syncfusion.Maui.Toolkit.UnitTest/Chart/Features/EmptyPointSettingUnitTest.cs b/maui/tests/Syncfusion.Maui.Toolkit.UnitTest/Chart/Features/EmptyPointSettingUnitTest.cs
index 99ffec2a..2e84062f 100644
--- a/maui/tests/Syncfusion.Maui.Toolkit.UnitTest/Chart/Features/EmptyPointSettingUnitTest.cs
+++ b/maui/tests/Syncfusion.Maui.Toolkit.UnitTest/Chart/Features/EmptyPointSettingUnitTest.cs
@@ -1,6 +1,6 @@
using Syncfusion.Maui.Toolkit.Charts;
-namespace Syncfusion.Maui.Toolkit.UnitTest
+namespace Syncfusion.Maui.Toolkit.UnitTest.Charts
{
public class EmptyPointSettingUnitTest
{
diff --git a/maui/tests/Syncfusion.Maui.Toolkit.UnitTest/Chart/Features/HelperClass.cs b/maui/tests/Syncfusion.Maui.Toolkit.UnitTest/Chart/Features/HelperClass.cs
index 08124464..c3196e38 100644
--- a/maui/tests/Syncfusion.Maui.Toolkit.UnitTest/Chart/Features/HelperClass.cs
+++ b/maui/tests/Syncfusion.Maui.Toolkit.UnitTest/Chart/Features/HelperClass.cs
@@ -1,4 +1,4 @@
-namespace Syncfusion.Maui.Toolkit.UnitTest
+namespace Syncfusion.Maui.Toolkit.UnitTest.Charts
{
public class Person
{
diff --git a/maui/tests/Syncfusion.Maui.Toolkit.UnitTest/Chart/Features/InterfaceUnitTests.cs b/maui/tests/Syncfusion.Maui.Toolkit.UnitTest/Chart/Features/InterfaceUnitTests.cs
index 7bd90316..21ba1d04 100644
--- a/maui/tests/Syncfusion.Maui.Toolkit.UnitTest/Chart/Features/InterfaceUnitTests.cs
+++ b/maui/tests/Syncfusion.Maui.Toolkit.UnitTest/Chart/Features/InterfaceUnitTests.cs
@@ -2,7 +2,7 @@
using Syncfusion.Maui.Toolkit.Charts;
using Syncfusion.Maui.Toolkit.Themes;
-namespace Syncfusion.Maui.Toolkit.UnitTest
+namespace Syncfusion.Maui.Toolkit.UnitTest.Charts
{
public class InterfaceUnitTests : BaseUnitTest
{
diff --git a/maui/tests/Syncfusion.Maui.Toolkit.UnitTest/Chart/Features/LegendConverterUnitTest.cs b/maui/tests/Syncfusion.Maui.Toolkit.UnitTest/Chart/Features/LegendConverterUnitTest.cs
index aabd7477..6d2dfb35 100644
--- a/maui/tests/Syncfusion.Maui.Toolkit.UnitTest/Chart/Features/LegendConverterUnitTest.cs
+++ b/maui/tests/Syncfusion.Maui.Toolkit.UnitTest/Chart/Features/LegendConverterUnitTest.cs
@@ -7,7 +7,7 @@
using System.Threading.Tasks;
using Color = Microsoft.Maui.Graphics.Color;
-namespace Syncfusion.Maui.Toolkit.UnitTest
+namespace Syncfusion.Maui.Toolkit.UnitTest.Charts
{
public class LegendConverterUnitTest : BaseUnitTest
{
diff --git a/maui/tests/Syncfusion.Maui.Toolkit.UnitTest/Chart/Features/PlotBandUnitTests.cs b/maui/tests/Syncfusion.Maui.Toolkit.UnitTest/Chart/Features/PlotBandUnitTests.cs
index 9e113fa9..fc82edd4 100644
--- a/maui/tests/Syncfusion.Maui.Toolkit.UnitTest/Chart/Features/PlotBandUnitTests.cs
+++ b/maui/tests/Syncfusion.Maui.Toolkit.UnitTest/Chart/Features/PlotBandUnitTests.cs
@@ -1,6 +1,6 @@
using Syncfusion.Maui.Toolkit.Charts;
-namespace Syncfusion.Maui.Toolkit.UnitTest
+namespace Syncfusion.Maui.Toolkit.UnitTest.Charts
{
public class PlotBandUnitTests : BaseUnitTest
{
diff --git a/maui/tests/Syncfusion.Maui.Toolkit.UnitTest/Chart/Features/StyleUnitTests.cs b/maui/tests/Syncfusion.Maui.Toolkit.UnitTest/Chart/Features/StyleUnitTests.cs
index d481a834..57e01971 100644
--- a/maui/tests/Syncfusion.Maui.Toolkit.UnitTest/Chart/Features/StyleUnitTests.cs
+++ b/maui/tests/Syncfusion.Maui.Toolkit.UnitTest/Chart/Features/StyleUnitTests.cs
@@ -1,6 +1,6 @@
using Syncfusion.Maui.Toolkit.Charts;
-namespace Syncfusion.Maui.Toolkit.UnitTest
+namespace Syncfusion.Maui.Toolkit.UnitTest.Charts
{
public class StyleUnitTests : BaseUnitTest
{
diff --git a/maui/tests/Syncfusion.Maui.Toolkit.UnitTest/Chart/Features/UtilsUnitTests.cs b/maui/tests/Syncfusion.Maui.Toolkit.UnitTest/Chart/Features/UtilsUnitTests.cs
index 99cede8d..3f422cda 100644
--- a/maui/tests/Syncfusion.Maui.Toolkit.UnitTest/Chart/Features/UtilsUnitTests.cs
+++ b/maui/tests/Syncfusion.Maui.Toolkit.UnitTest/Chart/Features/UtilsUnitTests.cs
@@ -1,7 +1,7 @@
using Syncfusion.Maui.Toolkit.Charts;
using Core = Syncfusion.Maui.Toolkit;
-namespace Syncfusion.Maui.Toolkit.UnitTest
+namespace Syncfusion.Maui.Toolkit.UnitTest.Charts
{
public class UtilsUnitTests : BaseUnitTest
{
@@ -302,7 +302,7 @@ public void GetShapeType_ShapeType_ShouldReturnExpectedShapeType()
{
_ = new ChartMarkerSettings();
var expectedShape = Core.ShapeType.Circle;
- var result = ChartUtils.GetShapeType(Charts.ShapeType.Circle);
+ var result = ChartUtils.GetShapeType(Syncfusion.Maui.Toolkit.Charts.ShapeType.Circle);
Assert.Equal(expectedShape, result);
}
diff --git a/maui/tests/Syncfusion.Maui.Toolkit.UnitTest/Chart/SfCartesianChartUnitTest.cs b/maui/tests/Syncfusion.Maui.Toolkit.UnitTest/Chart/SfCartesianChartUnitTest.cs
index 5e0327e8..3806cf44 100644
--- a/maui/tests/Syncfusion.Maui.Toolkit.UnitTest/Chart/SfCartesianChartUnitTest.cs
+++ b/maui/tests/Syncfusion.Maui.Toolkit.UnitTest/Chart/SfCartesianChartUnitTest.cs
@@ -1,6 +1,6 @@
using Syncfusion.Maui.Toolkit.Charts;
-namespace Syncfusion.Maui.Toolkit.UnitTest
+namespace Syncfusion.Maui.Toolkit.UnitTest.Charts
{
public class SfCartesianChartUnitTest
{
diff --git a/maui/tests/Syncfusion.Maui.Toolkit.UnitTest/Chart/SfCircularChartUnitTest.cs b/maui/tests/Syncfusion.Maui.Toolkit.UnitTest/Chart/SfCircularChartUnitTest.cs
index 18cbdada..f440936d 100644
--- a/maui/tests/Syncfusion.Maui.Toolkit.UnitTest/Chart/SfCircularChartUnitTest.cs
+++ b/maui/tests/Syncfusion.Maui.Toolkit.UnitTest/Chart/SfCircularChartUnitTest.cs
@@ -1,6 +1,6 @@
using Syncfusion.Maui.Toolkit.Charts;
-namespace Syncfusion.Maui.Toolkit.UnitTest
+namespace Syncfusion.Maui.Toolkit.UnitTest.Charts
{
public class SfCircularChartUnitTest
{
diff --git a/maui/tests/Syncfusion.Maui.Toolkit.UnitTest/Chart/SfFunnelChartUnitTests.cs b/maui/tests/Syncfusion.Maui.Toolkit.UnitTest/Chart/SfFunnelChartUnitTests.cs
index 1138d33b..bdf9c311 100644
--- a/maui/tests/Syncfusion.Maui.Toolkit.UnitTest/Chart/SfFunnelChartUnitTests.cs
+++ b/maui/tests/Syncfusion.Maui.Toolkit.UnitTest/Chart/SfFunnelChartUnitTests.cs
@@ -1,6 +1,6 @@
using Syncfusion.Maui.Toolkit.Charts;
-namespace Syncfusion.Maui.Toolkit.UnitTest
+namespace Syncfusion.Maui.Toolkit.UnitTest.Charts
{
public class SfFunnelChartUnitTests
{
diff --git a/maui/tests/Syncfusion.Maui.Toolkit.UnitTest/Chart/SfPolarChartUnitTests.cs b/maui/tests/Syncfusion.Maui.Toolkit.UnitTest/Chart/SfPolarChartUnitTests.cs
index 8e060b56..86eb279a 100644
--- a/maui/tests/Syncfusion.Maui.Toolkit.UnitTest/Chart/SfPolarChartUnitTests.cs
+++ b/maui/tests/Syncfusion.Maui.Toolkit.UnitTest/Chart/SfPolarChartUnitTests.cs
@@ -1,6 +1,6 @@
using Syncfusion.Maui.Toolkit.Charts;
-namespace Syncfusion.Maui.Toolkit.UnitTest
+namespace Syncfusion.Maui.Toolkit.UnitTest.Charts
{
public class SfPolarChartUnitTests
{
diff --git a/maui/tests/Syncfusion.Maui.Toolkit.UnitTest/Chart/SfPyramidChartUnitTests.cs b/maui/tests/Syncfusion.Maui.Toolkit.UnitTest/Chart/SfPyramidChartUnitTests.cs
index de6c7b0a..4f8a17d6 100644
--- a/maui/tests/Syncfusion.Maui.Toolkit.UnitTest/Chart/SfPyramidChartUnitTests.cs
+++ b/maui/tests/Syncfusion.Maui.Toolkit.UnitTest/Chart/SfPyramidChartUnitTests.cs
@@ -1,6 +1,6 @@
using Syncfusion.Maui.Toolkit.Charts;
-namespace Syncfusion.Maui.Toolkit.UnitTest
+namespace Syncfusion.Maui.Toolkit.UnitTest.Charts
{
public class SfPyramidChartUnitTests
{
diff --git a/maui/tests/Syncfusion.Maui.Toolkit.UnitTest/Editors/SfNumericEntryUnitTests.cs b/maui/tests/Syncfusion.Maui.Toolkit.UnitTest/Editors/SfNumericEntryUnitTests.cs
index f34bf3be..ad81ab4a 100644
--- a/maui/tests/Syncfusion.Maui.Toolkit.UnitTest/Editors/SfNumericEntryUnitTests.cs
+++ b/maui/tests/Syncfusion.Maui.Toolkit.UnitTest/Editors/SfNumericEntryUnitTests.cs
@@ -519,27 +519,6 @@ public void UpdateEffectsRendererBounds_ShouldReturn_EffectsRenderer()
Assert.NotNull(GetPrivateField(numericEntry, "_effectsRenderer"));
}
- [Fact]
- public void RaiseValueChangedEvent_ShouldTrigger_ValueChangedEvent()
- {
- var numericEntry = new SfNumericEntry();
- double? oldValue = 50;
- double? newValue = 60;
- bool eventTriggered = false;
-
- numericEntry.ValueChanged += (sender, e) =>
- {
- eventTriggered = true;
- Assert.Equal(oldValue, e.OldValue);
- Assert.Equal(newValue, e.NewValue);
- };
-
- InvokeStaticPrivateMethod(numericEntry, "RaiseValueChangedEvent", new object[] { numericEntry, oldValue, newValue });
-
- Assert.True(eventTriggered);
- }
-
-
[Theory]
[InlineData("42.5", 0)]
[InlineData("invalid", 42.5)]
diff --git a/maui/tests/Syncfusion.Maui.Toolkit.UnitTest/Editors/SfOtpInputUnitTests.cs b/maui/tests/Syncfusion.Maui.Toolkit.UnitTest/Editors/SfOtpInputUnitTests.cs
index 2ae1ce13..b81ef8aa 100644
--- a/maui/tests/Syncfusion.Maui.Toolkit.UnitTest/Editors/SfOtpInputUnitTests.cs
+++ b/maui/tests/Syncfusion.Maui.Toolkit.UnitTest/Editors/SfOtpInputUnitTests.cs
@@ -112,22 +112,24 @@ public void GetVisualState_Filled(bool enabled, bool hovered, string stroke, str
#region Internal Properties
[Theory]
- [InlineData(OtpInputStyle.Underlined, 10, true, OtpInputState.Default, "#FF0000", "#008000")]
- [InlineData(OtpInputStyle.Underlined, 12, false, OtpInputState.Success, "#0000FF", "#008000")]
- [InlineData(OtpInputStyle.Filled, 9, true, OtpInputState.Default, "#0000FF", "#FFA500")]
- [InlineData(OtpInputStyle.Filled, 5, false, OtpInputState.Warning, "#FFA500", "#FF0000")]
- [InlineData(OtpInputStyle.Outlined, 15, true, OtpInputState.Default, "#FF0000", "#008000")]
- [InlineData(OtpInputStyle.Outlined, 13, false, OtpInputState.Error, "#008000", "#FF0000")]
- public void UpdateParameters(OtpInputStyle otpInputStyle, double cornerRadius, bool isEnabled, OtpInputState otpInputState, string stroke, string background)
+ [InlineData(OtpInputStyle.Underlined, 10, true, OtpInputState.Default, "#FF0000", "#008000", "#1C1B1F", "#611c1b1f")]
+ [InlineData(OtpInputStyle.Underlined, 12, false, OtpInputState.Success, "#0000FF", "#008000", "#1C1B1F", "#611c1b1f")]
+ [InlineData(OtpInputStyle.Filled, 9, true, OtpInputState.Default, "#0000FF", "#FFA500", "#1C1B1F", "#611c1b1f")]
+ [InlineData(OtpInputStyle.Filled, 5, false, OtpInputState.Warning, "#FFA500", "#FF0000", "#1C1B1F", "#611c1b1f")]
+ [InlineData(OtpInputStyle.Outlined, 15, true, OtpInputState.Default, "#FF0000", "#008000", "#1C1B1F", "#611c1b1f")]
+ [InlineData(OtpInputStyle.Outlined, 13, false, OtpInputState.Error, "#008000", "#FF0000", "#1C1B1F", "#611c1b1f")]
+ public void UpdateParameters(OtpInputStyle otpInputStyle, double cornerRadius, bool isEnabled, OtpInputState otpInputState, string stroke, string background, string textColor, string disabledTextColor)
{
OTPEntry otpEntry = new OTPEntry();
- otpEntry.UpdateParameters(otpInputStyle, cornerRadius, new PointF(10, 10), new PointF(30, 30), new SfOtpInput(),isEnabled, otpInputState, Color.FromArgb(stroke), Color.FromArgb(background));
+ otpEntry.UpdateParameters(otpInputStyle, cornerRadius, new PointF(10, 10), new PointF(30, 30), new SfOtpInput(),isEnabled, otpInputState, Color.FromArgb(stroke), Color.FromArgb(background), Color.FromArgb(textColor), Color.FromArgb(disabledTextColor));
OtpInputStyle? resultInputStyle = (OtpInputStyle?)GetPrivateField(otpEntry, "_styleMode");
double? resultCornerRadius = (double?)GetPrivateField(otpEntry, "_cornerRadius");
bool? resultIsEnabled = (bool?)GetPrivateField(otpEntry, "_isEnabled");
OtpInputState? resultInputState = (OtpInputState?)GetPrivateField(otpEntry, "_inputState");
Color? resultStroke = (Color?)GetPrivateField(otpEntry, "_stroke");
Color? resultBackground = (Color?)GetPrivateField(otpEntry, "_background");
+ Color? resultTextColor = (Color?)GetPrivateField(otpEntry, "_textColor");
+ Color? resultBackgrounDisabledTextColor = (Color?)GetPrivateField(otpEntry, "_disabledTextColor");
Assert.Equal(otpInputStyle, resultInputStyle);
Assert.Equal(cornerRadius, resultCornerRadius);
Assert.Equal(isEnabled, resultIsEnabled);
diff --git a/maui/tests/Syncfusion.Maui.Toolkit.UnitTest/Layout/SfTextInputLayoutUnitTests.cs b/maui/tests/Syncfusion.Maui.Toolkit.UnitTest/Layout/SfTextInputLayoutUnitTests.cs
index 5d8c9ae8..501fd802 100644
--- a/maui/tests/Syncfusion.Maui.Toolkit.UnitTest/Layout/SfTextInputLayoutUnitTests.cs
+++ b/maui/tests/Syncfusion.Maui.Toolkit.UnitTest/Layout/SfTextInputLayoutUnitTests.cs
@@ -1,4 +1,7 @@
-using Syncfusion.Maui.Toolkit.TextInputLayout;
+using Microsoft.Maui;
+using Microsoft.Maui.Controls;
+using Syncfusion.Maui.Toolkit.TextInputLayout;
+using Syncfusion.Maui.Toolkit.NumericEntry;
using System.Reflection;
namespace Syncfusion.Maui.Toolkit.UnitTest
{
@@ -46,7 +49,6 @@ public void Constructor_InitializesDefaultsCorrectly()
Assert.Equal(12, inputLayout.ErrorLabelStyle.FontSize);
Assert.Equal(16, inputLayout.HintLabelStyle.FontSize);
Assert.Null(inputLayout.ClearButtonPath);
- Assert.Equal(Brush.Transparent, inputLayout.OutlinedContainerBackground);
Assert.Equal(Color.FromArgb("#49454F"), inputLayout.HelperTextColor);
Assert.Equal(Color.FromArgb("#49454F"), inputLayout.HintTextColor);
Assert.False(inputLayout.IsHintFloated);
@@ -188,8 +190,10 @@ public void ShowLeadingView_SetValue_ReturnsExpectedValue(bool showLeadingView)
{
ShowLeadingView = showLeadingView
};
+ var leadingView = new Label();
+ inputLayout.LeadingView = leadingView;
- Assert.Equal(showLeadingView, inputLayout.ShowLeadingView);
+ Assert.Equal(showLeadingView, inputLayout.LeadingView.IsVisible);
}
[Theory]
@@ -201,8 +205,10 @@ public void ShowTrailingView_SetValue_ReturnsExpectedValue(bool showTrailingView
{
ShowTrailingView = showTrailingView
};
+ var trailingView = new Label();
+ inputLayout.TrailingView = trailingView;
- Assert.Equal(showTrailingView, inputLayout.ShowTrailingView);
+ Assert.Equal(showTrailingView, inputLayout.TrailingView.IsVisible);
}
[Theory]
@@ -349,7 +355,7 @@ public void ControlText_ShouldUpdate_BasedOnEditorText()
public void IsHintFloated_RetrunsTrue_WhenPickerSelectedItemIsNotNull()
{
var inputLayout = new SfTextInputLayout();
- var picker = new Picker { SelectedItem = "SelectedItem" };
+ var picker = new Microsoft.Maui.Controls.Picker { SelectedItem = "SelectedItem" };
inputLayout.Content = picker;
Assert.True(inputLayout.IsHintFloated);
@@ -419,7 +425,7 @@ public void Control_IsDisabled_WhenIsEnabledIsFalse(bool isEnabled)
public void OnPickerSelectedIndexChanged_SelectedIndexGreaterThanOrEqualToZero_ShouldSetIsHintFloatedToTrue()
{
var inputLayout = new SfTextInputLayout();
- var picker = new Picker
+ var picker = new Microsoft.Maui.Controls.Picker
{
ItemsSource = new List { "Item1", "Item2", "Items3" }
};
@@ -438,7 +444,7 @@ public void OnPickerSelectedIndexChanged_SelectedIndexGreaterThanOrEqualToZero_S
public void OnPickerSelectedIndexChanged_TextProperty_ShouldUpdateBasedOnIndex()
{
var inputLayout = new SfTextInputLayout();
- var picker = new Picker
+ var picker = new Microsoft.Maui.Controls.Picker
{
ItemsSource = new List { "Item1", "Item2", "Items3" }
};
@@ -507,7 +513,7 @@ public void Content_SettingEditor_ShouldUpdateProperty()
public void Content_SettingPicker_ShouldUpdateProperty()
{
var inputLayout = new SfTextInputLayout();
- var picker = new Picker();
+ var picker = new Microsoft.Maui.Controls.Picker();
inputLayout.Content = picker;
Assert.Equal(picker, inputLayout.Content);
@@ -589,11 +595,15 @@ public void TestClearButtonPath()
[Fact]
public void TestOutlinedContainerBackground()
{
- var inputLayout = new SfTextInputLayout
- {
- OutlinedContainerBackground = new SolidColorBrush(Colors.Red)
- };
- Assert.Equal(Colors.Red, inputLayout.OutlinedContainerBackground);
+ var inputLayout = new SfTextInputLayout();
+ var redBrush = new SolidColorBrush(Colors.Red);
+ SetPrivateField(inputLayout, "_outlinedContainerBackground", redBrush);
+ var expectedBrushObj = GetPrivateField(inputLayout, "_outlinedContainerBackground");
+ Assert.NotNull(expectedBrushObj);
+
+ var expectedBrush = expectedBrushObj as SolidColorBrush;
+ Assert.NotNull(expectedBrush);
+ Assert.Equal(Colors.Red, expectedBrush.Color);
}
[Fact]
@@ -667,7 +677,7 @@ public void TestIsLayoutFocused()
public void TestOnPickerSelectedIndexChanged_SelectedIndex()
{
var inputLayout = new SfTextInputLayout();
- var picker = new Picker();
+ var picker = new Microsoft.Maui.Controls.Picker();
picker.Items.Add("Apple");
picker.Items.Add("Orange");
picker.Items.Add("Strawberry");
@@ -874,7 +884,10 @@ public void TestUpdateErrorTextColorMethod()
var inputLayout = new SfTextInputLayout();
InvokePrivateMethod(inputLayout, "UpdateErrorTextColor");
var expected = Color.FromRgba(0, 0, 0, 0.87);
- Assert.Equal(expected, inputLayout.ErrorLabelStyle.TextColor);
+ if (inputLayout.ErrorLabelStyle != null)
+ {
+ Assert.Equal(expected, inputLayout.ErrorLabelStyle.TextColor);
+ }
}
[Fact]
@@ -902,6 +915,284 @@ public void TestUpdateBaseLinePointsMethod()
Assert.Equal(endResult, endPoint);
}
#endregion
+
+ #region Automation Cases
+
+
+ [Theory]
+ [InlineData(ContainerType.Filled)]
+ [InlineData(ContainerType.Outlined)]
+ [InlineData(ContainerType.None)]
+ public void Content_SettingEditor(ContainerType container)
+ {
+ var inputLayout = new SfTextInputLayout();
+ var editor = new Editor();
+ editor.Text = "Enter your name";
+ editor.FontSize = 13;
+ inputLayout.ContainerType = container;
+ inputLayout.Content = editor;
+
+ Assert.Equal(editor, inputLayout.Content);
+ Assert.Equal("Enter your name", editor.Text);
+ Assert.Equal(13, editor.FontSize);
+ Assert.Equal(container, inputLayout.ContainerType);
+ }
+
+ [Fact]
+ public void Content_SettingEntry()
+ {
+ var inputLayout = new SfTextInputLayout();
+ var entry = new Entry();
+ entry.Text = "Enter your name";
+ entry.FontSize = 13;
+ inputLayout.Content = entry;
+
+ Assert.Equal(entry, inputLayout.Content);
+ Assert.Equal("Enter your name", entry.Text);
+ Assert.Equal(13, entry.FontSize);
+ }
+
+ [Fact]
+ public void ErrorText_HasError()
+ {
+ var inputLayout = new SfTextInputLayout
+ {
+ ErrorText = "Test Error"
+ };
+ inputLayout.HasError = true;
+
+ Assert.Equal("Test Error", inputLayout.ErrorText);
+ Assert.True(inputLayout.HasError);
+ }
+
+ [Theory]
+ [InlineData(ViewPosition.Inside)]
+ [InlineData(ViewPosition.Outside)]
+ public void LeadingViewPosition_ShowLeadingView(ViewPosition position)
+ {
+ var inputLayout = new SfTextInputLayout();
+ inputLayout.ShowLeadingView = true;
+ inputLayout.LeadingViewPosition = position;
+ var leadingView = new Label();
+ inputLayout.LeadingView = leadingView;
+
+
+ Assert.Equal(position, inputLayout.LeadingViewPosition);
+ Assert.True(inputLayout.LeadingView.IsVisible);
+ }
+
+ [Theory]
+ [InlineData(ViewPosition.Inside)]
+ [InlineData(ViewPosition.Outside)]
+ public void TrailingViewPosition_ShowTrailingView(ViewPosition position)
+ {
+ var inputLayout = new SfTextInputLayout();
+ inputLayout.TrailingViewPosition = position;
+ inputLayout.ShowTrailingView = true;
+ var trailingView = new Label();
+ inputLayout.TrailingView = trailingView;
+
+ Assert.Equal(position, inputLayout.TrailingViewPosition);
+ Assert.True(inputLayout.TrailingView.IsVisible);
+ }
+
+ [Theory]
+ [InlineData(true)]
+ [InlineData(false)]
+ public void EnablePasswordVisibilityToggle(bool enableToggle)
+ {
+ var inputLayout = new SfTextInputLayout
+ {
+ EnablePasswordVisibilityToggle = enableToggle
+ };
+ bool? isPasswordVisible = (bool?)GetPrivateField(inputLayout, "_isPasswordTextVisible");
+
+ Assert.Equal(enableToggle, inputLayout.EnablePasswordVisibilityToggle);
+ Assert.False(isPasswordVisible);
+ }
+
+ [Theory]
+ [InlineData(true)]
+ [InlineData(false)]
+ public void ShowHelperText_SetValue_ReturnsExpectedValue(bool showHelperText)
+ {
+ var inputLayout = new SfTextInputLayout
+ {
+ ShowHelperText = showHelperText
+ };
+
+ Assert.Equal(showHelperText, inputLayout.ShowHelperText);
+ }
+
+ [Theory]
+ [InlineData(true)]
+ [InlineData(false)]
+ public void ReserveSpaceForAssistiveLabels_SetValue(bool expectedValue)
+ {
+ var inputLayout = new SfTextInputLayout
+ {
+ ReserveSpaceForAssistiveLabels = expectedValue
+ };
+
+ Assert.Equal(expectedValue, inputLayout.ReserveSpaceForAssistiveLabels);
+ }
+
+ [Fact]
+ public void TestStackLayout()
+ {
+ var entry = new Entry();
+ var textInputLayout1 = new SfTextInputLayout { Content = entry };
+ var textInputLayout2 = new SfTextInputLayout { Content = entry };
+ var stackLayout = new StackLayout { Orientation = StackOrientation.Vertical };
+ stackLayout.Children.Add(textInputLayout1);
+ stackLayout.Children.Add(textInputLayout2);
+
+ Assert.Contains(textInputLayout1, stackLayout.Children);
+ Assert.Contains(textInputLayout2, stackLayout.Children);
+ }
+
+ [Fact]
+ public void TestHorizontalLayout()
+ {
+ var entry = new Entry();
+ var textInputLayout = new SfTextInputLayout { Content = entry };
+ var stackLayout = new StackLayout { Orientation = StackOrientation.Horizontal };
+ stackLayout.Children.Add(textInputLayout);
+
+ Assert.Equal(StackOrientation.Horizontal, stackLayout.Orientation);
+ Assert.Contains(textInputLayout, stackLayout.Children);
+ }
+
+ [Fact]
+ public void TestVerticalLayout()
+ {
+ var entry = new Entry();
+ var textInputLayout = new SfTextInputLayout { Content = entry };
+ var stackLayout = new StackLayout { Orientation = StackOrientation.Vertical };
+ stackLayout.Children.Add(textInputLayout);
+
+ Assert.Equal(StackOrientation.Vertical, stackLayout.Orientation);
+ Assert.Contains(textInputLayout, stackLayout.Children);
+ }
+
+ [Fact]
+ public void TestGrid()
+ {
+ var entry = new Entry();
+ var textInputLayout = new SfTextInputLayout { Content = entry };
+
+ var grid = new Grid
+ {
+ RowDefinitions = new RowDefinitionCollection
+ {
+ new RowDefinition { Height = GridLength.Auto }
+ },
+ ColumnDefinitions = new ColumnDefinitionCollection
+ {
+ new ColumnDefinition { Width = GridLength.Auto }
+ }
+ };
+
+ grid.Children.Add(textInputLayout);
+ Grid.SetRow(textInputLayout, 0);
+ Grid.SetColumn(textInputLayout, 0);
+ Assert.Contains(textInputLayout, grid.Children);
+ Assert.Single(grid.RowDefinitions);
+ Assert.Single(grid.ColumnDefinitions);
+ }
+
+ [Fact]
+ public void TestBorder()
+ {
+ var entry = new Entry();
+ var textInputLayout = new SfTextInputLayout { Content = entry };
+ var border = new Border
+ {
+ Content = textInputLayout,
+ Stroke = Colors.Green,
+ StrokeThickness = 2,
+ BackgroundColor = Colors.White
+ };
+
+ Assert.Equal(textInputLayout, border.Content);
+
+ Assert.Equal(Colors.Green, border.Stroke);
+ Assert.Equal(2, border.StrokeThickness);
+ Assert.Equal(Colors.White, border.BackgroundColor);
+ }
+
+ [Theory]
+ [InlineData(ContainerType.Filled)]
+ [InlineData(ContainerType.Outlined)]
+ [InlineData(ContainerType.None)]
+ public void Content_SfNumericEntry(ContainerType container)
+ {
+ var inputLayout = new SfTextInputLayout();
+ var numericEntry = new SfNumericEntry();
+ numericEntry.Value = 123;
+ numericEntry.FontSize = 13;
+ inputLayout.ContainerType = container;
+ inputLayout.Content = numericEntry;
+
+ Assert.Equal(numericEntry, inputLayout.Content);
+ Assert.Equal(13, numericEntry.FontSize);
+ Assert.Equal(container, inputLayout.ContainerType);
+ }
+
+ [Fact]
+ public void Content_SfNumericEntry_AllowNull()
+ {
+ var inputLayout = new SfTextInputLayout();
+ var numericEntry = new SfNumericEntry();
+ numericEntry.Value = 123;
+ numericEntry.AllowNull = true;
+ inputLayout.Content = numericEntry;
+
+ Assert.Equal(numericEntry, inputLayout.Content);
+ Assert.True(numericEntry.AllowNull);
+ }
+
+ [Theory]
+ [InlineData(ContainerType.Filled)]
+ [InlineData(ContainerType.Outlined)]
+ [InlineData(ContainerType.None)]
+ public void Content_Picker(ContainerType container)
+ {
+ var inputLayout = new SfTextInputLayout();
+ var picker = new Microsoft.Maui.Controls.Picker();
+ picker.Items.Add("Item 1");
+ picker.Items.Add("Item 2");
+ picker.Items.Add("Item 3");
+ picker.SelectedIndex = 1;
+ inputLayout.ContainerType = container;
+ inputLayout.Content = picker;
+
+ Assert.Equal(picker, inputLayout.Content);
+ Assert.Equal(container, inputLayout.ContainerType);
+ }
+
+ [Theory]
+ [InlineData(ContainerType.Filled)]
+ [InlineData(ContainerType.Outlined)]
+ [InlineData(ContainerType.None)]
+ public void Content_TimePicker(ContainerType container)
+ {
+ var inputLayout = new SfTextInputLayout();
+ var timePicker = new TimePicker
+ {
+ Time = new TimeSpan(10, 30, 0)
+ };
+
+ inputLayout.ContainerType = container;
+ inputLayout.Content = timePicker;
+
+ Assert.Equal(timePicker, inputLayout.Content);
+ Assert.Equal(container, inputLayout.ContainerType);
+ Assert.Equal(new TimeSpan(10, 30, 0), timePicker.Time);
+ }
+
+
+ #endregion
}
}
diff --git a/maui/tests/Syncfusion.Maui.Toolkit.UnitTest/Navigation/SfBottomSheetUnitTests.cs b/maui/tests/Syncfusion.Maui.Toolkit.UnitTest/Navigation/SfBottomSheetUnitTests.cs
index f78d0009..28c58c3c 100644
--- a/maui/tests/Syncfusion.Maui.Toolkit.UnitTest/Navigation/SfBottomSheetUnitTests.cs
+++ b/maui/tests/Syncfusion.Maui.Toolkit.UnitTest/Navigation/SfBottomSheetUnitTests.cs
@@ -732,7 +732,8 @@ public void UpdatePadding(Thickness value, Thickness expected)
SetPrivateField(_bottomSheet, "_bottomSheet", border);
InvokePrivateMethod(_bottomSheet, "UpdatePadding", [value]);
BottomSheetBorder? resultBorder = (BottomSheetBorder?)GetPrivateField(_bottomSheet, "_bottomSheet");
- Assert.Equal(expected, resultBorder?.Padding);
+ var expectedPadding = new Thickness(Math.Max(0, expected.Left), Math.Max(0, expected.Top), Math.Max(0, expected.Right), Math.Max(0, expected.Bottom));
+ Assert.Equal(expectedPadding, resultBorder?.Padding);
}
[Theory]
diff --git a/maui/tests/Syncfusion.Maui.Toolkit.UnitTest/Navigation/SfNavigationDrawerUnitTests.cs b/maui/tests/Syncfusion.Maui.Toolkit.UnitTest/Navigation/SfNavigationDrawerUnitTests.cs
index 9888f811..4bf7ee37 100644
--- a/maui/tests/Syncfusion.Maui.Toolkit.UnitTest/Navigation/SfNavigationDrawerUnitTests.cs
+++ b/maui/tests/Syncfusion.Maui.Toolkit.UnitTest/Navigation/SfNavigationDrawerUnitTests.cs
@@ -1,3 +1,4 @@
+using Syncfusion.Maui.Toolkit.Helper;
using Syncfusion.Maui.Toolkit.NavigationDrawer;
using System.Reflection;
@@ -920,7 +921,7 @@ public void TestCompleteSwipeOnRightvelocitiyNegative()
SfNavigationDrawer navigationDrawer = [];
navigationDrawer.DrawerSettings.Position = Position.Right;
navigationDrawer.DrawerSettings.Transition = Transition.Push;
- SetPrivateField(navigationDrawer, "_velocityX", -550);
+ SetPrivateField(navigationDrawer, "_velocityX", 550);
InvokePrivateMethod(navigationDrawer, "CompletedDrawerSwipe");
bool expectedValue = Convert.ToBoolean(GetPrivateField(navigationDrawer, "_isTransitionDifference"));
Assert.False(expectedValue);
@@ -943,7 +944,7 @@ public void TestCompleteSwipeOnBottomvelocitiyNegative()
SfNavigationDrawer navigationDrawer = [];
navigationDrawer.DrawerSettings.Position = Position.Bottom;
navigationDrawer.DrawerSettings.Transition = Transition.Push;
- SetPrivateField(navigationDrawer, "_velocityY", -550);
+ SetPrivateField(navigationDrawer, "_velocityY", 550);
InvokePrivateMethod(navigationDrawer, "CompletedDrawerSwipe");
bool expectedValue = Convert.ToBoolean(GetPrivateField(navigationDrawer, "_isTransitionDifference"));
Assert.False(expectedValue);
@@ -1348,7 +1349,8 @@ public void TestUpdateDrawerFlowDirection()
SfNavigationDrawer navigationDrawer = new SfNavigationDrawer() { DrawerSettings = new DrawerSettings(), ContentView = new Grid() };
SetPrivateField(navigationDrawer, "_isRTL", true);
InvokePrivateMethod(navigationDrawer, "UpdateDrawerFlowDirection");
- Assert.Equal(FlowDirection.RightToLeft, navigationDrawer.ContentView.FlowDirection);
+ var _mainContentGrid = GetPrivateField(navigationDrawer, "_mainContentGrid") as SfGrid;
+ Assert.Equal(FlowDirection.RightToLeft, _mainContentGrid?.FlowDirection);
}
[Fact]
@@ -1371,7 +1373,8 @@ public void TestPositionUpdateOnDrawerCloseRightTransition(Transition transition
navigationDrawer.DrawerSettings.Position = Position.Right;
navigationDrawer.DrawerSettings.Transition = transition;
InvokePrivateMethod(navigationDrawer, "PositionUpdate");
- Assert.Equal(0, navigationDrawer.ContentView.TranslationX);
+ var _mainContentGrid = GetPrivateField(navigationDrawer, "_mainContentGrid") as SfGrid;
+ Assert.Equal(0, _mainContentGrid?.TranslationX);
}
@@ -1405,7 +1408,8 @@ public void TestPositionUpdateOnDrawerOpenLeft()
navigationDrawer.DrawerSettings.Position = Position.Left;
SetPrivateField(navigationDrawer, "_isDrawerOpen", true);
InvokePrivateMethod(navigationDrawer, "PositionUpdate");
- Assert.Equal(0, navigationDrawer.ContentView.TranslationX);
+ var _mainContentGrid = GetPrivateField(navigationDrawer, "_mainContentGrid") as SfGrid;
+ Assert.Equal(0, _mainContentGrid?.TranslationX);
}
[Theory]
@@ -1418,7 +1422,8 @@ public void TestPositionUpdateOnDrawerOpenLeftWithTransition(Transition transiti
navigationDrawer.DrawerSettings.Transition = transition;
SetPrivateField(navigationDrawer, "_isDrawerOpen", true);
InvokePrivateMethod(navigationDrawer, "PositionUpdate");
- Assert.Equal(navigationDrawer.DrawerSettings.DrawerWidth, navigationDrawer.ContentView.TranslationX);
+ var _mainContentGrid = GetPrivateField(navigationDrawer, "_mainContentGrid") as SfGrid;
+ Assert.Equal(navigationDrawer.DrawerSettings.DrawerWidth, _mainContentGrid?.TranslationX);
}
[Fact]
@@ -1428,7 +1433,8 @@ public void TestPositionUpdateOnDrawerCloseTopReveal()
navigationDrawer.DrawerSettings.Position = Position.Top;
navigationDrawer.DrawerSettings.Transition = Transition.Reveal;
InvokePrivateMethod(navigationDrawer, "PositionUpdate");
- Assert.Equal(0, navigationDrawer.ContentView.TranslationY);
+ var _mainContentGrid = GetPrivateField(navigationDrawer, "_mainContentGrid") as SfGrid;
+ Assert.Equal(0, _mainContentGrid?.TranslationY);
}
[Fact]
@@ -1438,7 +1444,8 @@ public void TestPositionUpdateOnDrawerCloseTopPush()
navigationDrawer.DrawerSettings.Position = Position.Top;
navigationDrawer.DrawerSettings.Transition = Transition.Push;
InvokePrivateMethod(navigationDrawer, "PositionUpdate");
- Assert.Equal(0, navigationDrawer.ContentView.TranslationY);
+ var _mainContentGrid = GetPrivateField(navigationDrawer, "_mainContentGrid") as SfGrid;
+ Assert.Equal(0, _mainContentGrid?.TranslationY);
}
[Fact]
@@ -1448,7 +1455,8 @@ public void TestPositionUpdateOnDrawerOpenTop()
navigationDrawer.DrawerSettings.Position = Position.Top;
SetPrivateField(navigationDrawer, "_isDrawerOpen", true);
InvokePrivateMethod(navigationDrawer, "PositionUpdate");
- Assert.Equal(0, navigationDrawer.ContentView.TranslationY);
+ var _mainContentGrid = GetPrivateField(navigationDrawer, "_mainContentGrid") as SfGrid;
+ Assert.Equal(0, _mainContentGrid?.TranslationY);
}
[Theory]
@@ -1461,7 +1469,8 @@ public void TestPositionUpdateOnDrawerOpenTopWithTransition(Transition transitio
navigationDrawer.DrawerSettings.Transition = transition;
SetPrivateField(navigationDrawer, "_isDrawerOpen", true);
InvokePrivateMethod(navigationDrawer, "PositionUpdate");
- Assert.Equal(navigationDrawer.DrawerSettings.DrawerHeight, navigationDrawer.ContentView.TranslationY);
+ var _mainContentGrid = GetPrivateField(navigationDrawer, "_mainContentGrid") as SfGrid;
+ Assert.Equal(navigationDrawer.DrawerSettings.DrawerHeight, _mainContentGrid?.TranslationY);
}
[Fact]
@@ -1471,7 +1480,8 @@ public void TestPositionUpdateOnDrawerOpenBottom()
navigationDrawer.DrawerSettings.Position = Position.Bottom;
SetPrivateField(navigationDrawer, "_isDrawerOpen", true);
InvokePrivateMethod(navigationDrawer, "PositionUpdate");
- Assert.Equal(0, navigationDrawer.ContentView.TranslationY);
+ var _mainContentGrid = GetPrivateField(navigationDrawer, "_mainContentGrid") as SfGrid;
+ Assert.Equal(0, _mainContentGrid?.TranslationY);
}
[Theory]
@@ -1484,7 +1494,8 @@ public void TestPositionUpdateOnDrawerOpenBottomWithTransition(Transition transi
navigationDrawer.DrawerSettings.Transition = transition;
SetPrivateField(navigationDrawer, "_isDrawerOpen", true);
InvokePrivateMethod(navigationDrawer, "PositionUpdate");
- Assert.Equal(-navigationDrawer.DrawerSettings.DrawerHeight, navigationDrawer.ContentView.TranslationY);
+ var _mainContentGrid = GetPrivateField(navigationDrawer, "_mainContentGrid") as SfGrid;
+ Assert.Equal(-navigationDrawer.DrawerSettings.DrawerHeight, _mainContentGrid?.TranslationY);
}
[Theory]
@@ -1496,7 +1507,8 @@ public void TestPositionUpdateOnDrawerCloseBottom(Transition transition)
navigationDrawer.DrawerSettings.Position = Position.Bottom;
navigationDrawer.DrawerSettings.Transition = transition;
InvokePrivateMethod(navigationDrawer, "PositionUpdate");
- Assert.Equal(0, navigationDrawer.ContentView.TranslationY);
+ var _mainContentGrid = GetPrivateField(navigationDrawer, "_mainContentGrid") as SfGrid;
+ Assert.Equal(0, _mainContentGrid?.TranslationY);
}
[Fact]
diff --git a/maui/tests/Syncfusion.Maui.Toolkit.UnitTest/Navigation/SfTabViewUnitTests.cs b/maui/tests/Syncfusion.Maui.Toolkit.UnitTest/Navigation/SfTabViewUnitTests.cs
index 85fa7710..0ad99647 100644
--- a/maui/tests/Syncfusion.Maui.Toolkit.UnitTest/Navigation/SfTabViewUnitTests.cs
+++ b/maui/tests/Syncfusion.Maui.Toolkit.UnitTest/Navigation/SfTabViewUnitTests.cs
@@ -63,7 +63,7 @@ private List