Browse Source

Adds support for Seconds to TimePicker (#16079)

* Adds seconds support to TimePicker.

* Updates TimePicker to support UseSeconds. Seconds are not displayed unless UseSeconds == true.

* Fixes & updates Unit Tests related to adding Seconds & UseSeconds to TimePicker.

* Adds a simple TimePicker with seconds enabled to  DateTimePickerPage.xaml.
pull/16258/head
Sean Begley 2 years ago
committed by GitHub
parent
commit
ae017763fc
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
  1. 74
      samples/ControlCatalog/Pages/DateTimePickerPage.xaml
  2. 2
      samples/ControlCatalog/Pages/DateTimePickerPage.xaml.cs
  3. 3
      src/Avalonia.Controls/DateTimePickers/DateTimePickerPanel.cs
  4. 98
      src/Avalonia.Controls/DateTimePickers/TimePicker.cs
  5. 92
      src/Avalonia.Controls/DateTimePickers/TimePickerPresenter.cs
  6. 39
      src/Avalonia.Themes.Fluent/Controls/TimePicker.xaml
  7. 1
      src/Avalonia.Themes.Fluent/Strings/InvariantResources.xaml
  8. 49
      src/Avalonia.Themes.Simple/Controls/TimePicker.xaml
  9. 6
      tests/Avalonia.Controls.UnitTests/DatePickerTests.cs
  10. 59
      tests/Avalonia.Controls.UnitTests/TimePickerTests.cs

74
samples/ControlCatalog/Pages/DateTimePickerPage.xaml

@ -77,6 +77,23 @@
</Panel>
</StackPanel>
<TextBlock FontSize="18">A TimePicker with seconds enabled.</TextBlock>
<StackPanel Orientation="Vertical">
<Border BorderBrush="{DynamicResource CatalogBaseLowColor}"
BorderThickness="1" Padding="15">
<TimePicker UseSeconds="True" />
</Border>
<Panel Background="{DynamicResource CatalogBaseLowColor}">
<TextBlock Padding="15">
<TextBlock.Text>
<x:String>
&lt;TimePicker UseSeconds="True" /&gt;
</x:String>
</TextBlock.Text>
</TextBlock>
</Panel>
</StackPanel>
<Border BorderBrush="{DynamicResource CatalogBaseLowColor}"
BorderThickness="1" Padding="15">
<TimePicker>
@ -85,8 +102,8 @@
</DataValidationErrors.Error>
</TimePicker>
</Border>
<TextBlock FontSize="18">A TimePicker with minute increments specified.</TextBlock>
<TextBlock FontSize="18">A TimePicker with minute increment specified.</TextBlock>
<StackPanel Orientation="Vertical">
<Border BorderBrush="{DynamicResource CatalogBaseLowColor}"
BorderThickness="1" Padding="15">
@ -96,7 +113,24 @@
<TextBlock Padding="15">
<TextBlock.Text>
<x:String>
&lt;TimePicker MinuteIncrement="15" /&gt;
&lt;TimePicker MinuteIncrement="15" SecondIncrement="30" /&gt;
</x:String>
</TextBlock.Text>
</TextBlock>
</Panel>
</StackPanel>
<TextBlock FontSize="18">A TimePicker with seconds enabled and minute &amp; second increments specified.</TextBlock>
<StackPanel Orientation="Vertical">
<Border BorderBrush="{DynamicResource CatalogBaseLowColor}"
BorderThickness="1" Padding="15">
<TimePicker UseSeconds="True" MinuteIncrement="15" SecondIncrement="30" />
</Border>
<Panel Background="{DynamicResource CatalogBaseLowColor}">
<TextBlock Padding="15">
<TextBlock.Text>
<x:String>
&lt;TimePicker UseSeconds="True" MinuteIncrement="15" SecondIncrement="30" /&gt;
</x:String>
</TextBlock.Text>
</TextBlock>
@ -137,6 +171,40 @@
</Panel>
</StackPanel>
<TextBlock FontSize="18">A TimePicker using a 12-hour clock and seconds.</TextBlock>
<StackPanel Orientation="Vertical">
<Border BorderBrush="{DynamicResource CatalogBaseLowColor}"
BorderThickness="1" Padding="15">
<TimePicker ClockIdentifier="12HourClock" UseSeconds="True" />
</Border>
<Panel Background="{DynamicResource CatalogBaseLowColor}">
<TextBlock Padding="15">
<TextBlock.Text>
<x:String>
&lt;TimePicker ClockIdentifier="12HourClock" UseSeconds="True" /&gt;
</x:String>
</TextBlock.Text>
</TextBlock>
</Panel>
</StackPanel>
<TextBlock FontSize="18">A TimePicker using a 24-hour clock and seconds.</TextBlock>
<StackPanel Orientation="Vertical">
<Border BorderBrush="{DynamicResource CatalogBaseLowColor}"
BorderThickness="1" Padding="15">
<TimePicker ClockIdentifier="24HourClock" UseSeconds="True" />
</Border>
<Panel Background="{DynamicResource CatalogBaseLowColor}">
<TextBlock Padding="15">
<TextBlock.Text>
<x:String>
&lt;TimePicker ClockIdentifier="24HourClock" UseSeconds="True" /&gt;
</x:String>
</TextBlock.Text>
</TextBlock>
</Panel>
</StackPanel>
</StackPanel>
</StackPanel>
</UserControl>

2
samples/ControlCatalog/Pages/DateTimePickerPage.xaml.cs

@ -15,7 +15,7 @@ namespace ControlCatalog.Pages
"Order of month, day, and year is dynamically set based on user date settings";
this.Get<TextBlock>("TimePickerDesc").Text = "Use a TimePicker to let users set a time in your app, for example " +
"to set a reminder. The TimePicker displays three controls for hour, minute, and AM / PM(if necessary).These controls " +
"to set a reminder. The TimePicker displays four controls for hour, minute, seconds(optional), and AM / PM(if necessary).These controls " +
"are easy to use with touch or mouse, and they can be styled and configured in several different ways. " +
"12 - hour or 24 - hour clock and visibility of AM / PM is dynamically set based on user time settings, or can be overridden.";

3
src/Avalonia.Controls/DateTimePickers/DateTimePickerPanel.cs

@ -18,6 +18,7 @@ namespace Avalonia.Controls.Primitives
Day,
Hour,
Minute,
Second,
TimePeriod //AM or PM
}
@ -516,6 +517,8 @@ namespace Avalonia.Controls.Primitives
return new TimeSpan(value, 0, 0).ToString(ItemFormat);
case DateTimePickerPanelType.Minute:
return new TimeSpan(0, value, 0).ToString(ItemFormat);
case DateTimePickerPanelType.Second:
return new TimeSpan(0, 0, value).ToString(ItemFormat);
case DateTimePickerPanelType.TimePeriod:
return value == MinimumValue ? TimeUtils.GetAMDesignator() : TimeUtils.GetPMDesignator();
default:

98
src/Avalonia.Controls/DateTimePickers/TimePicker.cs

@ -18,12 +18,15 @@ namespace Avalonia.Controls
[TemplatePart("PART_FlyoutButtonContentGrid", typeof(Grid))]
[TemplatePart("PART_HourTextBlock", typeof(TextBlock))]
[TemplatePart("PART_MinuteTextBlock", typeof(TextBlock))]
[TemplatePart("PART_SecondTextBlock", typeof(TextBlock))]
[TemplatePart("PART_PeriodTextBlock", typeof(TextBlock))]
[TemplatePart("PART_PickerPresenter", typeof(TimePickerPresenter))]
[TemplatePart("PART_Popup", typeof(Popup))]
[TemplatePart("PART_SecondColumnDivider", typeof(Rectangle))]
[TemplatePart("PART_SecondPickerHost", typeof(Border))]
[TemplatePart("PART_ThirdPickerHost", typeof(Border))]
[TemplatePart("PART_ThirdColumnDivider", typeof(Rectangle))]
[TemplatePart("PART_ThirdPickerHost", typeof(Border))]
[TemplatePart("PART_FourthPickerHost", typeof(Border))]
[PseudoClasses(":hasnotime")]
public class TimePicker : TemplatedControl
{
@ -32,12 +35,24 @@ namespace Avalonia.Controls
/// </summary>
public static readonly StyledProperty<int> MinuteIncrementProperty =
AvaloniaProperty.Register<TimePicker, int>(nameof(MinuteIncrement), 1, coerce: CoerceMinuteIncrement);
/// <summary>
/// Defines the <see cref="SecondIncrement"/> property
/// </summary>
public static readonly StyledProperty<int> SecondIncrementProperty =
AvaloniaProperty.Register<TimePicker, int>(nameof(SecondIncrement), 1, coerce: CoerceSecondIncrement);
/// <summary>
/// Defines the <see cref="ClockIdentifier"/> property
/// </summary>
public static readonly StyledProperty<string> ClockIdentifierProperty =
AvaloniaProperty.Register<TimePicker, string>(nameof(ClockIdentifier), "12HourClock", coerce: CoerceClockIdentifier);
/// <summary>
/// Defines the <see cref="UseSeconds"/> property
/// </summary>
public static readonly StyledProperty<bool> UseSecondsProperty =
AvaloniaProperty.Register<TimePicker, bool>(nameof(UseSeconds), false, coerce: CoerceUseSeconds);
/// <summary>
/// Defines the <see cref="SelectedTime"/> property
@ -52,11 +67,14 @@ namespace Avalonia.Controls
private Border? _firstPickerHost;
private Border? _secondPickerHost;
private Border? _thirdPickerHost;
private Border? _fourthPickerHost;
private TextBlock? _hourText;
private TextBlock? _minuteText;
private TextBlock? _secondText;
private TextBlock? _periodText;
private Rectangle? _firstSplitter;
private Rectangle? _secondSplitter;
private Rectangle? _thirdSplitter;
private Grid? _contentGrid;
private Popup? _popup;
@ -85,6 +103,23 @@ namespace Avalonia.Controls
return value;
}
/// <summary>
/// Gets or sets the second increment in the picker
/// </summary>
public int SecondIncrement
{
get => GetValue(SecondIncrementProperty);
set => SetValue(SecondIncrementProperty, value);
}
private static int CoerceSecondIncrement(AvaloniaObject sender, int value)
{
if (value < 1 || value > 59)
throw new ArgumentOutOfRangeException(null, "1 >= SecondIncrement <= 59");
return value;
}
/// <summary>
/// Gets or sets the clock identifier, either 12HourClock or 24HourClock
@ -103,6 +138,24 @@ namespace Avalonia.Controls
return value;
}
/// <summary>
/// Gets or sets the use seconds switch, either true or false
/// </summary>
public bool UseSeconds
{
get => GetValue(UseSecondsProperty);
set => SetValue(UseSecondsProperty, value);
}
private static bool CoerceUseSeconds(AvaloniaObject sender, bool value)
{
if (!(value == true || value == false))
throw new ArgumentException("Invalid UseSeconds", default(bool).ToString());
return value;
}
/// <summary>
/// Gets or sets the selected time. Can be null.
@ -135,13 +188,16 @@ namespace Avalonia.Controls
_firstPickerHost = e.NameScope.Find<Border>("PART_FirstPickerHost");
_secondPickerHost = e.NameScope.Find<Border>("PART_SecondPickerHost");
_thirdPickerHost = e.NameScope.Find<Border>("PART_ThirdPickerHost");
_fourthPickerHost = e.NameScope.Find<Border>("PART_FourthPickerHost");
_hourText = e.NameScope.Find<TextBlock>("PART_HourTextBlock");
_minuteText = e.NameScope.Find<TextBlock>("PART_MinuteTextBlock");
_secondText = e.NameScope.Find<TextBlock>("PART_SecondTextBlock");
_periodText = e.NameScope.Find<TextBlock>("PART_PeriodTextBlock");
_firstSplitter = e.NameScope.Find<Rectangle>("PART_FirstColumnDivider");
_secondSplitter = e.NameScope.Find<Rectangle>("PART_SecondColumnDivider");
_thirdSplitter = e.NameScope.Find<Rectangle>("PART_ThirdColumnDivider");
_contentGrid = e.NameScope.Find<Grid>("PART_FlyoutButtonContentGrid");
@ -160,7 +216,9 @@ namespace Avalonia.Controls
_presenter.Dismissed += OnDismissPicker;
_presenter[!TimePickerPresenter.MinuteIncrementProperty] = this[!MinuteIncrementProperty];
_presenter[!TimePickerPresenter.SecondIncrementProperty] = this[!SecondIncrementProperty];
_presenter[!TimePickerPresenter.ClockIdentifierProperty] = this[!ClockIdentifierProperty];
_presenter[!TimePickerPresenter.UseSecondsProperty] = this[!UseSecondsProperty];
}
}
@ -172,11 +230,20 @@ namespace Avalonia.Controls
{
SetSelectedTimeText();
}
else if (change.Property == SecondIncrementProperty)
{
SetSelectedTimeText();
}
else if (change.Property == ClockIdentifierProperty)
{
SetGrid();
SetSelectedTimeText();
}
else if (change.Property == UseSecondsProperty)
{
SetGrid();
SetSelectedTimeText();
}
else if (change.Property == SelectedTimeProperty)
{
var (oldValue, newValue) = change.GetOldAndNewValue<TimeSpan?>();
@ -196,29 +263,40 @@ namespace Avalonia.Controls
columnsD.Add(new ColumnDefinition(GridLength.Star));
columnsD.Add(new ColumnDefinition(GridLength.Auto));
columnsD.Add(new ColumnDefinition(GridLength.Star));
if (UseSeconds)
{
columnsD.Add(new ColumnDefinition(GridLength.Auto));
columnsD.Add(new ColumnDefinition(GridLength.Star));
}
if (!use24HourClock)
{
columnsD.Add(new ColumnDefinition(GridLength.Auto));
columnsD.Add(new ColumnDefinition(GridLength.Star));
}
_contentGrid.ColumnDefinitions = columnsD;
_thirdPickerHost!.IsVisible = UseSeconds;
_secondSplitter!.IsVisible = UseSeconds;
_thirdPickerHost!.IsVisible = !use24HourClock;
_secondSplitter!.IsVisible = !use24HourClock;
_fourthPickerHost!.IsVisible = !use24HourClock;
_thirdSplitter!.IsVisible = !use24HourClock;
var amPmColumn = (UseSeconds) ? 6 : 4;
Grid.SetColumn(_firstPickerHost!, 0);
Grid.SetColumn(_secondPickerHost!, 2);
Grid.SetColumn(_thirdPickerHost, use24HourClock ? 0 : 4);
Grid.SetColumn(_thirdPickerHost!, UseSeconds ? 4 : 0);
Grid.SetColumn(_fourthPickerHost, use24HourClock ? 0 : amPmColumn);
Grid.SetColumn(_firstSplitter!, 1);
Grid.SetColumn(_secondSplitter, use24HourClock ? 0 : 3);
Grid.SetColumn(_secondSplitter!, UseSeconds ? 3 : 0);
Grid.SetColumn(_thirdSplitter, use24HourClock ? 0 : amPmColumn-1);
}
private void SetSelectedTimeText()
{
if (_hourText == null || _minuteText == null || _periodText == null)
if (_hourText == null || _minuteText == null || _secondText == null || _periodText == null)
return;
var time = SelectedTime;
@ -230,11 +308,12 @@ namespace Avalonia.Controls
{
var hr = newTime.Hours;
hr = hr > 12 ? hr - 12 : hr == 0 ? 12 : hr;
newTime = new TimeSpan(hr, newTime.Minutes, 0);
newTime = new TimeSpan(hr, newTime.Minutes, newTime.Seconds);
}
_hourText.Text = newTime.ToString("%h");
_minuteText.Text = newTime.ToString("mm");
_secondText.Text = newTime.ToString("ss");
PseudoClasses.Set(":hasnotime", false);
_periodText.Text = time.Value.Hours >= 12 ? TimeUtils.GetPMDesignator() : TimeUtils.GetAMDesignator();
@ -244,6 +323,7 @@ namespace Avalonia.Controls
// By clearing local value, we reset text property to the value from the template.
_hourText.ClearValue(TextBlock.TextProperty);
_minuteText.ClearValue(TextBlock.TextProperty);
_secondText.ClearValue(TextBlock.TextProperty);
PseudoClasses.Set(":hasnotime", true);
_periodText.Text = DateTime.Now.Hour >= 12 ? TimeUtils.GetPMDesignator() : TimeUtils.GetAMDesignator();

92
src/Avalonia.Controls/DateTimePickers/TimePickerPresenter.cs

@ -19,12 +19,16 @@ namespace Avalonia.Controls
[TemplatePart("PART_MinuteDownButton", typeof(RepeatButton))]
[TemplatePart("PART_MinuteSelector", typeof(DateTimePickerPanel), IsRequired = true)]
[TemplatePart("PART_MinuteUpButton", typeof(RepeatButton))]
[TemplatePart("PART_SecondDownButton", typeof(RepeatButton))]
[TemplatePart("PART_SecondHost", typeof(Panel), IsRequired = true)]
[TemplatePart("PART_SecondSelector", typeof(DateTimePickerPanel), IsRequired = true)]
[TemplatePart("PART_SecondUpButton", typeof(RepeatButton))]
[TemplatePart("PART_PeriodDownButton", typeof(RepeatButton))]
[TemplatePart("PART_PeriodHost", typeof(Panel), IsRequired = true)]
[TemplatePart("PART_PeriodSelector", typeof(DateTimePickerPanel), IsRequired = true)]
[TemplatePart("PART_PeriodUpButton", typeof(RepeatButton))]
[TemplatePart("PART_PickerContainer", typeof(Grid), IsRequired = true)]
[TemplatePart("PART_SecondSpacer", typeof(Rectangle), IsRequired = true)]
[TemplatePart("PART_ThirdSpacer", typeof(Rectangle), IsRequired = true)]
public class TimePickerPresenter : PickerPresenterBase
{
/// <summary>
@ -32,12 +36,24 @@ namespace Avalonia.Controls
/// </summary>
public static readonly StyledProperty<int> MinuteIncrementProperty =
TimePicker.MinuteIncrementProperty.AddOwner<TimePickerPresenter>();
/// <summary>
/// Defines the <see cref="SecondIncrement"/> property
/// </summary>
public static readonly StyledProperty<int> SecondIncrementProperty =
TimePicker.SecondIncrementProperty.AddOwner<TimePickerPresenter>();
/// <summary>
/// Defines the <see cref="ClockIdentifier"/> property
/// </summary>
public static readonly StyledProperty<string> ClockIdentifierProperty =
TimePicker.ClockIdentifierProperty.AddOwner<TimePickerPresenter>();
/// <summary>
/// Defines the <see cref="UseSeconds"/> property
/// </summary>
public static readonly StyledProperty<bool> UseSecondsProperty =
TimePicker.UseSecondsProperty.AddOwner<TimePickerPresenter>();
/// <summary>
/// Defines the <see cref="Time"/> property
@ -60,15 +76,20 @@ namespace Avalonia.Controls
private Button? _acceptButton;
private Button? _dismissButton;
private Rectangle? _spacer2;
private Rectangle? _spacer3;
private Panel? _secondHost;
private Panel? _periodHost;
private DateTimePickerPanel? _hourSelector;
private DateTimePickerPanel? _minuteSelector;
private DateTimePickerPanel? _secondSelector;
private DateTimePickerPanel? _periodSelector;
private Button? _hourUpButton;
private Button? _minuteUpButton;
private Button? _secondUpButton;
private Button? _periodUpButton;
private Button? _hourDownButton;
private Button? _minuteDownButton;
private Button? _secondDownButton;
private Button? _periodDownButton;
/// <summary>
@ -79,6 +100,15 @@ namespace Avalonia.Controls
get => GetValue(MinuteIncrementProperty);
set => SetValue(MinuteIncrementProperty, value);
}
/// <summary>
/// Gets or sets the second increment in the selector
/// </summary>
public int SecondIncrement
{
get => GetValue(SecondIncrementProperty);
set => SetValue(SecondIncrementProperty, value);
}
/// <summary>
/// Gets or sets the current clock identifier, either 12HourClock or 24HourClock
@ -88,6 +118,15 @@ namespace Avalonia.Controls
get => GetValue(ClockIdentifierProperty);
set => SetValue(ClockIdentifierProperty, value);
}
/// <summary>
/// Gets or sets the current clock identifier, either 12HourClock or 24HourClock
/// </summary>
public bool UseSeconds
{
get => GetValue(UseSecondsProperty);
set => SetValue(UseSecondsProperty, value);
}
/// <summary>
/// Gets or sets the current time
@ -104,12 +143,15 @@ namespace Avalonia.Controls
_pickerContainer = e.NameScope.Get<Grid>("PART_PickerContainer");
_periodHost = e.NameScope.Get<Panel>("PART_PeriodHost");
_secondHost = e.NameScope.Get<Panel>("PART_SecondHost");
_hourSelector = e.NameScope.Get<DateTimePickerPanel>("PART_HourSelector");
_minuteSelector = e.NameScope.Get<DateTimePickerPanel>("PART_MinuteSelector");
_secondSelector = e.NameScope.Get<DateTimePickerPanel>("PART_SecondSelector");
_periodSelector = e.NameScope.Get<DateTimePickerPanel>("PART_PeriodSelector");
_spacer2 = e.NameScope.Get<Rectangle>("PART_SecondSpacer");
_spacer3 = e.NameScope.Get<Rectangle>("PART_ThirdSpacer");
_acceptButton = e.NameScope.Get<Button>("PART_AcceptButton");
_acceptButton.Click += OnAcceptButtonClicked;
@ -127,6 +169,13 @@ namespace Avalonia.Controls
_minuteDownButton = e.NameScope.Find<RepeatButton>("PART_MinuteDownButton");
if (_minuteDownButton != null)
_minuteDownButton.Click += OnSelectorButtonClick;
_secondUpButton = e.NameScope.Find<RepeatButton>("PART_SecondUpButton");
if (_secondUpButton != null)
_secondUpButton.Click += OnSelectorButtonClick;
_secondDownButton = e.NameScope.Find<RepeatButton>("PART_SecondDownButton");
if (_secondDownButton != null)
_secondDownButton.Click += OnSelectorButtonClick;
_periodUpButton = e.NameScope.Find<RepeatButton>("PART_PeriodUpButton");
if (_periodUpButton != null)
@ -146,7 +195,11 @@ namespace Avalonia.Controls
{
base.OnPropertyChanged(change);
if (change.Property == MinuteIncrementProperty || change.Property == ClockIdentifierProperty || change.Property == TimeProperty)
if (change.Property == MinuteIncrementProperty ||
change.Property == SecondIncrementProperty ||
change.Property == ClockIdentifierProperty ||
change.Property == UseSecondsProperty ||
change.Property == TimeProperty)
{
InitPicker();
}
@ -180,6 +233,7 @@ namespace Avalonia.Controls
{
var hr = _hourSelector!.SelectedValue;
var min = _minuteSelector!.SelectedValue;
var sec = _secondSelector!.SelectedValue;
var per = _periodSelector!.SelectedValue;
if (ClockIdentifier == "12HourClock")
@ -187,7 +241,7 @@ namespace Avalonia.Controls
hr = per == 1 ? (hr == 12) ? 12 : hr + 12 : per == 0 && hr == 12 ? 0 : hr;
}
SetCurrentValue(TimeProperty, new TimeSpan(hr, min, 0));
SetCurrentValue(TimeProperty, new TimeSpan(hr, min, sec));
base.OnConfirmed();
}
@ -210,6 +264,12 @@ namespace Avalonia.Controls
_minuteSelector.Increment = MinuteIncrement;
_minuteSelector.SelectedValue = Time.Minutes;
_minuteSelector.ItemFormat = "mm";
_secondSelector!.MaximumValue = 59;
_secondSelector.MinimumValue = 0;
_secondSelector.Increment = SecondIncrement;
_secondSelector.SelectedValue = Time.Seconds;
_secondSelector.ItemFormat = "ss";
_periodSelector!.MaximumValue = 1;
_periodSelector.MinimumValue = 0;
@ -223,14 +283,24 @@ namespace Avalonia.Controls
{
bool use24HourClock = ClockIdentifier == "24HourClock";
var columnsD = use24HourClock ? "*, Auto, *" : "*, Auto, *, Auto, *";
var columnsD = "*, Auto, *";
if (UseSeconds) columnsD += ", Auto *";
if (!use24HourClock) columnsD += ", Auto *";
_pickerContainer!.ColumnDefinitions = new ColumnDefinitions(columnsD);
_spacer2!.IsVisible = !use24HourClock;
_periodHost!.IsVisible = !use24HourClock;
_spacer2!.IsVisible = UseSeconds;
_secondHost!.IsVisible = UseSeconds;
Grid.SetColumn(_spacer2, use24HourClock ? 0 : 3);
Grid.SetColumn(_periodHost, use24HourClock ? 0 : 4);
_spacer3!.IsVisible = !use24HourClock;
_periodHost!.IsVisible = !use24HourClock;
var amPmColumn = (UseSeconds) ? 6 : 4;
Grid.SetColumn(_spacer2, UseSeconds ? 3 : 0);
Grid.SetColumn(_secondHost, UseSeconds ? 4 : 0);
Grid.SetColumn(_spacer3, use24HourClock ? 0 : amPmColumn-1);
Grid.SetColumn(_periodHost, use24HourClock ? 0 : amPmColumn);
}
private void OnDismissButtonClicked(object? sender, RoutedEventArgs e)
@ -253,6 +323,10 @@ namespace Avalonia.Controls
_minuteSelector!.ScrollUp();
else if (sender == _minuteDownButton)
_minuteSelector!.ScrollDown();
else if (sender == _secondUpButton)
_secondSelector!.ScrollUp();
else if (sender == _secondDownButton)
_secondSelector!.ScrollDown();
else if (sender == _periodUpButton)
_periodSelector!.ScrollUp();
else if (sender == _periodDownButton)

39
src/Avalonia.Themes.Fluent/Controls/TimePicker.xaml

@ -128,11 +128,27 @@
HorizontalAlignment="Center"
Width="{DynamicResource TimePickerSpacerThemeWidth}"
Grid.Column="3" />
<Border x:Name="PART_ThirdPickerHost"
Grid.Column="4"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch">
<TextBlock x:Name="PART_SecondTextBlock"
Text="{DynamicResource StringTimePickerSecondText}"
HorizontalAlignment="Center"
Padding="{DynamicResource TimePickerHostPadding}"/>
</Border>
<Rectangle Name="PART_ThirdColumnDivider"
Fill="{DynamicResource TimePickerSpacerFill}"
HorizontalAlignment="Center"
Width="{DynamicResource TimePickerSpacerThemeWidth}"
Grid.Column="5" />
<Border x:Name="PART_FourthPickerHost"
Grid.Column="6"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch">
<TextBlock x:Name="PART_PeriodTextBlock"
HorizontalAlignment="Center"
Padding="{DynamicResource TimePickerHostPadding}" />
@ -209,8 +225,20 @@
<RepeatButton Name="PART_MinuteUpButton" Theme="{StaticResource FluentDateTimePickerUpButton}"/>
<RepeatButton Name="PART_MinuteDownButton" Theme="{StaticResource FluentDateTimePickerDownButton}"/>
</Panel>
<Panel Name="PART_SecondHost" Grid.Column="4">
<ScrollViewer HorizontalScrollBarVisibility="Disabled"
VerticalScrollBarVisibility="Hidden">
<DateTimePickerPanel Name="PART_SecondSelector"
PanelType="Second"
ShouldLoop="True"
ItemHeight="{DynamicResource TimePickerFlyoutPresenterItemHeight}"/>
</ScrollViewer>
<RepeatButton Name="PART_SecondUpButton" Theme="{StaticResource FluentDateTimePickerUpButton}"/>
<RepeatButton Name="PART_SecondDownButton" Theme="{StaticResource FluentDateTimePickerDownButton}"/>
</Panel>
<Panel Name="PART_PeriodHost" Grid.Column="4">
<Panel Name="PART_PeriodHost" Grid.Column="6">
<ScrollViewer HorizontalScrollBarVisibility="Disabled"
VerticalScrollBarVisibility="Hidden">
<DateTimePickerPanel Name="PART_PeriodSelector"
@ -225,7 +253,7 @@
<Rectangle x:Name="HighlightRect" ZIndex="-1"
Fill="{DynamicResource TimePickerFlyoutPresenterHighlightFill}"
Grid.Column="0"
Grid.ColumnSpan="5"
Grid.ColumnSpan="7"
VerticalAlignment="Center"
Height="{DynamicResource TimePickerFlyoutPresenterHighlightHeight}" />
<Rectangle Name="PART_FirstSpacer"
@ -238,6 +266,11 @@
HorizontalAlignment="Center"
Width="{DynamicResource TimePickerSpacerThemeWidth}"
Grid.Column="3" />
<Rectangle Name="PART_ThirdSpacer"
Fill="{DynamicResource TimePickerFlyoutPresenterSpacerFill}"
HorizontalAlignment="Center"
Width="{DynamicResource TimePickerSpacerThemeWidth}"
Grid.Column="5" />
</Grid>
<Grid Name="AcceptDismissGrid"

1
src/Avalonia.Themes.Fluent/Strings/InvariantResources.xaml

@ -8,6 +8,7 @@
<!-- TimePicker -->
<x:String x:Key="StringTimePickerHourText">hour</x:String>
<x:String x:Key="StringTimePickerMinuteText">minute</x:String>
<x:String x:Key="StringTimePickerSecondText">second</x:String>
<!-- TextBox/SelectableTextBox flyout -->
<x:String x:Key="StringTextFlyoutCutText">Cut</x:String>
<x:String x:Key="StringTextFlyoutCopyText">Copy</x:String>

49
src/Avalonia.Themes.Simple/Controls/TimePicker.xaml

@ -142,11 +142,30 @@
Width="{DynamicResource TimePickerSpacerThemeWidth}"
HorizontalAlignment="Center"
Fill="{DynamicResource TimePickerSpacerFill}" />
<Border x:Name="PART_ThirdPickerHost"
Grid.Column="4"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch">
<TextBlock x:Name="PART_SecondTextBlock"
Text="{DynamicResource StringTimePickerSecondText}"
Padding="{DynamicResource TimePickerHostPadding}"
HorizontalAlignment="Center"
FontFamily="{TemplateBinding FontFamily}"
FontSize="{TemplateBinding FontSize}"
FontWeight="{TemplateBinding FontWeight}" />
</Border>
<Rectangle Name="PART_ThirdColumnDivider"
Grid.Column="5"
Width="{DynamicResource TimePickerSpacerThemeWidth}"
HorizontalAlignment="Center"
Fill="{DynamicResource TimePickerSpacerFill}" />
<Border x:Name="PART_FourthPickerHost"
Grid.Column="6"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch">
<TextBlock x:Name="PART_PeriodTextBlock"
Padding="{DynamicResource TimePickerHostPadding}"
HorizontalAlignment="Center"
@ -154,6 +173,12 @@
FontSize="{TemplateBinding FontSize}"
FontWeight="{TemplateBinding FontWeight}" />
</Border>
<Rectangle Name="PART_ThirdColumnDivider"
Grid.Column="5"
Width="{DynamicResource TimePickerSpacerThemeWidth}"
HorizontalAlignment="Center"
Fill="{DynamicResource TimePickerSpacerFill}" />
</Grid>
</Button>
@ -230,9 +255,24 @@
<RepeatButton Name="PART_MinuteDownButton"
Theme="{StaticResource SimpleDateTimePickerDownButton}" />
</Panel>
<Panel Name="PART_SecondHost"
Grid.Column="4">
<ScrollViewer HorizontalScrollBarVisibility="Disabled"
VerticalScrollBarVisibility="Hidden">
<DateTimePickerPanel Name="PART_SecondSelector"
ItemHeight="{DynamicResource TimePickerFlyoutPresenterItemHeight}"
PanelType="Second"
ShouldLoop="True" />
</ScrollViewer>
<RepeatButton Name="PART_SecondUpButton"
Theme="{StaticResource SimpleDateTimePickerUpButton}" />
<RepeatButton Name="PART_SecondDownButton"
Theme="{StaticResource SimpleDateTimePickerDownButton}" />
</Panel>
<Panel Name="PART_PeriodHost"
Grid.Column="4">
Grid.Column="6">
<ScrollViewer HorizontalScrollBarVisibility="Disabled"
VerticalScrollBarVisibility="Hidden">
<DateTimePickerPanel Name="PART_PeriodSelector"
@ -267,6 +307,11 @@
Width="{DynamicResource TimePickerSpacerThemeWidth}"
HorizontalAlignment="Center"
Fill="{DynamicResource ThemeControlMidHighBrush}" />
<Rectangle Name="PART_ThirdSpacer"
Grid.Column="5"
Width="{DynamicResource TimePickerSpacerThemeWidth}"
HorizontalAlignment="Center"
Fill="{DynamicResource ThemeControlMidHighBrush}" />
</Grid>
<Grid Name="AcceptDismissGrid"

6
tests/Avalonia.Controls.UnitTests/DatePickerTests.cs

@ -287,8 +287,12 @@ namespace Avalonia.Controls.UnitTests
{
Name = "PART_SecondSpacer"
}.RegisterInNameScope(scope);
var thirdSpacer = new Rectangle
{
Name = "PART_ThirdSpacer"
}.RegisterInNameScope(scope);
contentGrid.Children.AddRange(new Control[] { dayText, monthText, yearText, firstSpacer, secondSpacer });
contentGrid.Children.AddRange(new Control[] { dayText, monthText, yearText, firstSpacer, secondSpacer, thirdSpacer });
flyoutButton.Content = contentGrid;
layoutRoot.Children.Add(flyoutButton);
return layoutRoot;

59
tests/Avalonia.Controls.UnitTests/TimePickerTests.cs

@ -55,7 +55,7 @@ namespace Avalonia.Controls.UnitTests
container = (desc.ElementAt(1) as Button).Content as Grid;
Assert.True(container != null);
var periodTextHost = container.Children[4] as Border;
var periodTextHost = container.Children[6] as Border;
Assert.True(periodTextHost != null);
Assert.True(periodTextHost.IsVisible);
@ -63,6 +63,36 @@ namespace Avalonia.Controls.UnitTests
Assert.False(periodTextHost.IsVisible);
}
}
[Fact]
public void UseSeconds_Equals_False_Should_Hide_Seconds()
{
using (UnitTestApplication.Start(Services))
{
TimePicker timePicker = new TimePicker()
{
UseSeconds = true,
Template = CreateTemplate()
};
timePicker.ApplyTemplate();
var desc = timePicker.GetVisualDescendants();
Assert.True(desc.Count() > 1);//Should be layoutroot grid & button
Grid container = null;
Assert.True(desc.ElementAt(1) is Button);
container = (desc.ElementAt(1) as Button).Content as Grid;
Assert.True(container != null);
var periodTextHost = container.Children[4] as Border;
Assert.True(periodTextHost != null);
Assert.True(periodTextHost.IsVisible);
timePicker.UseSeconds = false;
Assert.False(periodTextHost.IsVisible);
}
}
[Fact]
public void SelectedTime_null_Should_Use_Placeholders()
@ -90,15 +120,20 @@ namespace Avalonia.Controls.UnitTests
var minuteTextHost = container.Children[2] as Border;
Assert.True(minuteTextHost != null);
var minuteText = minuteTextHost.Child as TextBlock;
var secondTextHost = container.Children[4] as Border;
Assert.True(secondTextHost != null);
var secondText = secondTextHost.Child as TextBlock;
TimeSpan ts = TimeSpan.FromHours(10);
timePicker.SelectedTime = ts;
Assert.NotNull(hourText.Text);
Assert.NotNull(minuteText.Text);
Assert.NotNull(secondText.Text);
timePicker.SelectedTime = null;
Assert.Null(hourText.Text);
Assert.Null(minuteText.Text);
Assert.Null(secondText.Text);
}
}
@ -122,7 +157,7 @@ namespace Avalonia.Controls.UnitTests
var container = (desc.ElementAt(1) as Button).Content as Grid;
Assert.True(container != null);
var periodTextHost = container.Children[4] as Border;
var periodTextHost = container.Children[6] as Border;
Assert.NotNull(periodTextHost);
var periodText = periodTextHost.Child as TextBlock;
Assert.NotNull(periodTextHost);
@ -227,10 +262,20 @@ namespace Avalonia.Controls.UnitTests
Name = "PART_ThirdPickerHost",
Child = new TextBlock
{
Name = "PART_PeriodTextBlock"
Name = "PART_SecondTextBlock"
}.RegisterInNameScope(scope)
}.RegisterInNameScope(scope);
Grid.SetColumn(thirdPickerHost, 4);
var fourthPickerHost = new Border
{
Name = "PART_FourthPickerHost",
Child = new TextBlock
{
Name = "PART_PeriodTextBlock"
}.RegisterInNameScope(scope)
}.RegisterInNameScope(scope);
Grid.SetColumn(fourthPickerHost, 6);
var firstSpacer = new Rectangle
{
@ -243,8 +288,14 @@ namespace Avalonia.Controls.UnitTests
Name = "PART_SecondColumnDivider"
}.RegisterInNameScope(scope);
Grid.SetColumn(secondSpacer, 3);
var thirdSpacer = new Rectangle
{
Name = "PART_ThirdColumnDivider"
}.RegisterInNameScope(scope);
Grid.SetColumn(thirdSpacer, 5);
contentGrid.Children.AddRange(new Control[] { firstPickerHost, firstSpacer, secondPickerHost, secondSpacer, thirdPickerHost });
contentGrid.Children.AddRange(new Control[] { firstPickerHost, firstSpacer, secondPickerHost, secondSpacer, thirdPickerHost, thirdSpacer, fourthPickerHost });
flyoutButton.Content = contentGrid;
layoutRoot.Children.Add(flyoutButton);
return layoutRoot;

Loading…
Cancel
Save