feat: fix separator visibility. add dropdown button.

This commit is contained in:
rabbitism
2024-04-27 00:39:50 +08:00
parent 63b9ccfe5e
commit 9cce1cc180
4 changed files with 159 additions and 45 deletions

View File

@@ -1,13 +1,28 @@
<UserControl xmlns="https://github.com/avaloniaui" <UserControl
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" x:Class="Ursa.Demo.Pages.TimePickerDemo"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns="https://github.com/avaloniaui"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:u="https://irihi.tech/ursa" xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
x:Class="Ursa.Demo.Pages.TimePickerDemo"> xmlns:u="https://irihi.tech/ursa"
d:DesignHeight="450"
d:DesignWidth="800"
mc:Ignorable="d">
<StackPanel HorizontalAlignment="Left"> <StackPanel HorizontalAlignment="Left">
<ToggleSwitch Content="Need Confirm" Name="needConfirm"></ToggleSwitch> <ToggleSwitch Name="needConfirm" Content="Need Confirm" />
<TextBlock Text="{Binding #picker.SelectedTime}" ></TextBlock> <TextBlock Text="{Binding #picker.SelectedTime}" />
<u:TimePicker Name="picker" Width="200" NeedConfirmation="{Binding #needConfirm.IsChecked}" PanelFormat="hh mm tt"></u:TimePicker> <u:TimePicker
Name="picker"
Width="200"
HorizontalAlignment="Left"
NeedConfirmation="{Binding #needConfirm.IsChecked}"
PanelFormat="hh mm tt" />
<u:TimePicker
Width="300"
DisplayFormat="HH 时 mm 分 ss 秒"
PanelFormat="HH mm ss tt"
HorizontalAlignment="Left"
InnerLeftContent="时刻"
InnerRightContent="截止" />
</StackPanel> </StackPanel>
</UserControl> </UserControl>

View File

@@ -2,6 +2,7 @@
xmlns="https://github.com/avaloniaui" xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:converters="clr-namespace:Ursa.Converters;assembly=Ursa" xmlns:converters="clr-namespace:Ursa.Converters;assembly=Ursa"
xmlns:iri="https://irihi.tech/shared"
xmlns:u="https://irihi.tech/ursa"> xmlns:u="https://irihi.tech/ursa">
<!-- Add Resources Here --> <!-- Add Resources Here -->
<Design.PreviewWith> <Design.PreviewWith>
@@ -102,6 +103,11 @@
</ControlTheme> </ControlTheme>
<ControlTheme x:Key="{x:Type u:TimePicker}" TargetType="u:TimePicker"> <ControlTheme x:Key="{x:Type u:TimePicker}" TargetType="u:TimePicker">
<Setter Property="Background" Value="{DynamicResource TextBoxDefaultBackground}" />
<Setter Property="Foreground" Value="{DynamicResource TextBoxForeground}" />
<Setter Property="BorderBrush" Value="{DynamicResource TextBoxDefaultBorderBrush}" />
<Setter Property="BorderThickness" Value="{DynamicResource TextBoxBorderThickness}" />
<Setter Property="CornerRadius" Value="{DynamicResource TextBoxDefaultCornerRadius}" />
<Setter Property="Template"> <Setter Property="Template">
<ControlTemplate TargetType="u:TimePicker"> <ControlTemplate TargetType="u:TimePicker">
<DataValidationErrors> <DataValidationErrors>
@@ -115,34 +121,61 @@
BorderBrush="{TemplateBinding BorderBrush}" BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}" BorderThickness="{TemplateBinding BorderThickness}"
CornerRadius="{TemplateBinding CornerRadius}" /> CornerRadius="{TemplateBinding CornerRadius}" />
<Grid ColumnDefinitions="Auto, *, Auto, Auto, Auto"> <Grid ColumnDefinitions="*, Auto, Auto">
<!-- InnerLeftContent, Text and Watermark, ClearButton, InnerRightContent, Icon --> <!-- InnerLeftContent, Text and Watermark, ClearButton, InnerRightContent, Icon -->
<ContentPresenter
Grid.Column="0"
Padding="{TemplateBinding Padding,
Converter={x:Static converters:ThicknessIncludeConverter.Right}}"
VerticalAlignment="Stretch"
VerticalContentAlignment="Center"
Content="{TemplateBinding InnerLeftContent}"
DockPanel.Dock="Left"
Foreground="{DynamicResource TextBoxInnerForeground}"
IsVisible="{Binding Path=InnerLeftContent, RelativeSource={RelativeSource TemplatedParent}, Converter={x:Static ObjectConverters.IsNotNull}}" />
<ContentPresenter
Grid.Column="3"
Padding="{TemplateBinding Padding,
Converter={x:Static converters:ThicknessIncludeConverter.Left}}"
VerticalAlignment="Stretch"
VerticalContentAlignment="Center"
Content="{TemplateBinding InnerRightContent}"
DockPanel.Dock="Right"
Foreground="{DynamicResource TextBoxInnerForeground}"
IsVisible="{Binding Path=InnerRightContent, RelativeSource={RelativeSource TemplatedParent}, Converter={x:Static ObjectConverters.IsNotNull}}" />
<TextBlock Grid.Column="1" Text="{TemplateBinding Watermark}" />
<TextBox <TextBox
Name="{x:Static u:TimePicker.PART_TextBox}" Name="{x:Static u:TimePicker.PART_TextBox}"
Grid.Column="1" Grid.Column="0"
Grid.ColumnSpan="2"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch" VerticalAlignment="Stretch"
BorderThickness="0" /> Background="Transparent"
BorderBrush="Transparent"
BorderThickness="0"
Foreground="{TemplateBinding Foreground}"
InnerLeftContent="{TemplateBinding InnerLeftContent}"
InnerRightContent="{TemplateBinding InnerRightContent}"
IsReadOnly="{TemplateBinding IsReadonly}"
Theme="{DynamicResource NoErrorTextBox}"
Watermark="{TemplateBinding Watermark}">
<TextBox.Styles>
<Style Selector="TextBox#PART_TextBox:pointerover /template/ Border#PART_ContentPresenterBorder">
<!-- By default the TextBox has its own focused state, override this to disable it here -->
<Setter Property="Background" Value="Transparent" />
<Setter Property="BorderBrush" Value="Transparent" />
<Setter Property="BorderThickness" Value="0" />
</Style>
<Style Selector="TextBox#PART_TextBox:focus /template/ Border#PART_ContentPresenterBorder">
<!-- By default the TextBox has its own focused state, override this to disable it here -->
<Setter Property="Background" Value="Transparent" />
<Setter Property="BorderBrush" Value="Transparent" />
<Setter Property="BorderThickness" Value="0" />
</Style>
<Style Selector="TextBox#PART_TextBox:disabled">
<Style Selector="^ /template/ Border#PART_ContentPresenterBorder">
<!-- By default the TextBox has its own disabled state, override this to make the border background show through -->
<Setter Property="Background" Value="Transparent" />
<Setter Property="BorderBrush" Value="Transparent" />
</Style>
</Style>
</TextBox.Styles>
</TextBox>
<Button
Name="ClearButton"
Grid.Column="1"
Padding="0,0,8,0"
Command="{Binding $parent[iri:IClearControl].Clear}"
Content="{DynamicResource IconButtonClearData}"
Focusable="False"
IsVisible="False"
Theme="{DynamicResource InnerIconButton}" />
<Button
Name="{x:Static u:TimePicker.PART_Button}"
Grid.Column="2"
Padding="0,0,8,0"
Content="{DynamicResource TimePickerIconGlyph}"
Focusable="False"
Theme="{DynamicResource InnerIconButton}" />
<Popup <Popup
Name="{x:Static u:TimePicker.PART_Popup}" Name="{x:Static u:TimePicker.PART_Popup}"
Grid.Column="0" Grid.Column="0"
@@ -151,10 +184,21 @@
Mode=TwoWay}" Mode=TwoWay}"
Placement="BottomEdgeAlignedLeft" Placement="BottomEdgeAlignedLeft"
PlacementTarget="Background"> PlacementTarget="Background">
<Border Theme="{DynamicResource CardBorder}"> <Border
Margin="0,4"
HorizontalAlignment="Stretch"
Background="{DynamicResource ComboBoxPopupBackground}"
BorderBrush="{DynamicResource ComboBoxPopupBorderBrush}"
BorderThickness="{DynamicResource ComboBoxPopupBorderThickness}"
BoxShadow="{DynamicResource ComboBoxPopupBoxShadow}"
ClipToBounds="True"
CornerRadius="6">
<DockPanel> <DockPanel>
<StackPanel DockPanel.Dock="Bottom" IsVisible="{TemplateBinding NeedConfirmation}"> <StackPanel DockPanel.Dock="Bottom" IsVisible="{TemplateBinding NeedConfirmation}">
<Button Content="Confirm" Command="{Binding $parent[u:TimePicker].Confirm}"></Button> <Button
HorizontalAlignment="Right"
Command="{Binding $parent[u:TimePicker].Confirm}"
Content="Confirm" />
</StackPanel> </StackPanel>
<u:TimePickerPresenter <u:TimePickerPresenter
Name="{x:Static u:TimePicker.PART_Presenter}" Name="{x:Static u:TimePicker.PART_Presenter}"
@@ -169,5 +213,37 @@
</DataValidationErrors> </DataValidationErrors>
</ControlTemplate> </ControlTemplate>
</Setter> </Setter>
<Style Selector="^.clearButton, ^.ClearButton">
<Style Selector="^:pointerover /template/ Button#ClearButton">
<Setter Property="IsVisible" Value="{Binding $parent[u:TimePicker].SelectedTime, Converter={x:Static ObjectConverters.IsNotNull}}" />
</Style>
</Style>
<Style Selector="^:pointerover">
<Style Selector="^ /template/ Border#Background">
<Setter Property="Background" Value="{DynamicResource CalendarDatePickerPointeroverBackground}" />
</Style>
</Style>
<!-- Disabled State -->
<Style Selector="^:disabled">
<Style Selector="^ /template/ Border#Background">
<Setter Property="Background" Value="{DynamicResource CalendarDatePickerDisabledBackground}" />
</Style>
<Style Selector="^ /template/ Button#PART_Button">
<Setter Property="Foreground" Value="{DynamicResource CalendarDatePickerDisabledIconForeground}" />
</Style>
<Style Selector="^ /template/ TextBox#PART_TextBox">
<Setter Property="Background" Value="Transparent" />
<Setter Property="BorderBrush" Value="Transparent" />
</Style>
</Style>
<!-- Focused State -->
<Style Selector="^:focus-within /template/ Border#Background">
<Setter Property="BorderBrush" Value="{DynamicResource CalendarDatePickerFocusBorderBrush}" />
</Style>
</ControlTheme> </ControlTheme>
</ResourceDictionary> </ResourceDictionary>

View File

@@ -14,11 +14,13 @@ namespace Ursa.Controls;
[TemplatePart(PART_TextBox, typeof(TextBox))] [TemplatePart(PART_TextBox, typeof(TextBox))]
[TemplatePart(PART_Popup, typeof(Popup))] [TemplatePart(PART_Popup, typeof(Popup))]
[TemplatePart(PART_Presenter, typeof(TimePickerPresenter))] [TemplatePart(PART_Presenter, typeof(TimePickerPresenter))]
[TemplatePart(PART_Button, typeof(Button))]
public class TimePicker : TemplatedControl, IClearControl, IInnerContentControl, IPopupInnerContent public class TimePicker : TemplatedControl, IClearControl, IInnerContentControl, IPopupInnerContent
{ {
public const string PART_TextBox = "PART_TextBox"; public const string PART_TextBox = "PART_TextBox";
public const string PART_Popup = "PART_Popup"; public const string PART_Popup = "PART_Popup";
public const string PART_Presenter = "PART_Presenter"; public const string PART_Presenter = "PART_Presenter";
public const string PART_Button = "PART_Button";
public static readonly StyledProperty<string?> DisplayFormatProperty = public static readonly StyledProperty<string?> DisplayFormatProperty =
AvaloniaProperty.Register<TimePicker, string?>( AvaloniaProperty.Register<TimePicker, string?>(
@@ -57,12 +59,21 @@ public class TimePicker : TemplatedControl, IClearControl, IInnerContentControl,
public static readonly StyledProperty<bool> IsDropdownOpenProperty = AvaloniaProperty.Register<TimePicker, bool>( public static readonly StyledProperty<bool> IsDropdownOpenProperty = AvaloniaProperty.Register<TimePicker, bool>(
nameof(IsDropdownOpen), defaultBindingMode: BindingMode.TwoWay); nameof(IsDropdownOpen), defaultBindingMode: BindingMode.TwoWay);
public static readonly StyledProperty<bool> IsReadonlyProperty = AvaloniaProperty.Register<TimePicker, bool>(
nameof(IsReadonly));
public bool IsReadonly
{
get => GetValue(IsReadonlyProperty);
set => SetValue(IsReadonlyProperty, value);
}
private Popup? _popup; private Popup? _popup;
private TimePickerPresenter? _presenter; private TimePickerPresenter? _presenter;
private TextBox? _textBox; private TextBox? _textBox;
private Button? _button;
private bool _updateFromText;
static TimePicker() static TimePicker()
{ {
@@ -139,30 +150,43 @@ public class TimePicker : TemplatedControl, IClearControl, IInnerContentControl,
protected override void OnApplyTemplate(TemplateAppliedEventArgs e) protected override void OnApplyTemplate(TemplateAppliedEventArgs e)
{ {
base.OnApplyTemplate(e); base.OnApplyTemplate(e);
GotFocusEvent.RemoveHandler(OnTextBoxGetFocus, _textBox);
TextBox.TextChangedEvent.RemoveHandler(OnTextChanged, _textBox);
PointerPressedEvent.RemoveHandler(OnTextBoxPointerPressed, _textBox);
Button.ClickEvent.RemoveHandler(OnButtonClick, _button);
_textBox = e.NameScope.Find<TextBox>(PART_TextBox); _textBox = e.NameScope.Find<TextBox>(PART_TextBox);
_popup = e.NameScope.Find<Popup>(PART_Popup); _popup = e.NameScope.Find<Popup>(PART_Popup);
_presenter = e.NameScope.Find<TimePickerPresenter>(PART_Presenter); _presenter = e.NameScope.Find<TimePickerPresenter>(PART_Presenter);
_button = e.NameScope.Find<Button>(PART_Button);
GotFocusEvent.AddHandler(OnTextBoxGetFocus, _textBox); GotFocusEvent.AddHandler(OnTextBoxGetFocus, _textBox);
TextBox.TextChangedEvent.AddDisposableHandler(OnTextChanged, _textBox); TextBox.TextChangedEvent.AddHandler(OnTextChanged, _textBox);
PointerPressedEvent.AddHandler(OnTextBoxPointerPressed, RoutingStrategies.Tunnel, false, _textBox); PointerPressedEvent.AddHandler(OnTextBoxPointerPressed, RoutingStrategies.Tunnel, false, _textBox);
Button.ClickEvent.AddHandler(OnButtonClick, _button);
SetCurrentValue(SelectedTimeProperty, DateTime.Now.TimeOfDay); SetCurrentValue(SelectedTimeProperty, DateTime.Now.TimeOfDay);
} }
private void OnTextBoxPointerPressed(object sender, PointerPressedEventArgs e) private void OnButtonClick(object? sender, RoutedEventArgs e)
{
SetCurrentValue(IsDropdownOpenProperty, !IsDropdownOpen);
}
private void OnTextBoxPointerPressed(object? sender, PointerPressedEventArgs e)
{ {
SetCurrentValue(IsDropdownOpenProperty, true); SetCurrentValue(IsDropdownOpenProperty, true);
} }
private void OnTextBoxGetFocus(object sender, GotFocusEventArgs e) private void OnTextBoxGetFocus(object? sender, GotFocusEventArgs e)
{ {
IsDropdownOpen = true; IsDropdownOpen = true;
} }
private void OnTextChanged(object sender, TextChangedEventArgs e) private void OnTextChanged(object? sender, TextChangedEventArgs e)
{ {
_updateFromText = true;
if (DisplayFormat is null || DisplayFormat.Length == 0) if (DisplayFormat is null || DisplayFormat.Length == 0)
{ {
if (TimeSpan.TryParse(_textBox?.Text, out var defaultTime)) if (TimeSpan.TryParse(_textBox?.Text, out var defaultTime))
@@ -173,7 +197,6 @@ public class TimePicker : TemplatedControl, IClearControl, IInnerContentControl,
if (DateTime.TryParseExact(_textBox?.Text, DisplayFormat, CultureInfo.CurrentUICulture, DateTimeStyles.None, if (DateTime.TryParseExact(_textBox?.Text, DisplayFormat, CultureInfo.CurrentUICulture, DateTimeStyles.None,
out var time)) TimePickerPresenter.TimeProperty.SetValue(time.TimeOfDay, _presenter); out var time)) TimePickerPresenter.TimeProperty.SetValue(time.TimeOfDay, _presenter);
} }
_updateFromText = false;
} }
private void OnSelectionChanged(AvaloniaPropertyChangedEventArgs<TimeSpan?> args) private void OnSelectionChanged(AvaloniaPropertyChangedEventArgs<TimeSpan?> args)

View File

@@ -171,7 +171,7 @@ public class TimePickerPresenter: TemplatedControl
2 => _thirdSeparator, 2 => _thirdSeparator,
_ => null, _ => null,
}; };
IsVisibleProperty.SetValue(true, separator); if (i != panels.Count - 1) IsVisibleProperty.SetValue(true, separator);
} }
} }