Add selected state event to DatePicker

Add selected state event to DatePicker
This commit is contained in:
polarboy 2024-09-13 12:41:24 +08:00
parent c186265537
commit d4fe9e6d91
7 changed files with 257 additions and 17 deletions

View File

@ -358,6 +358,15 @@ public class Calendar : TemplatedControl
internal DateTime DisplayDateRangeEnd => DisplayDateEnd.GetValueOrDefault(DateTime.MaxValue); internal DateTime DisplayDateRangeEnd => DisplayDateEnd.GetValueOrDefault(DateTime.MaxValue);
internal bool HasFocusInternal { get; set; } internal bool HasFocusInternal { get; set; }
internal static readonly StyledProperty<bool> IsPointerInMonthViewProperty =
AvaloniaProperty.Register<Calendar, bool>(nameof(IsPointerInMonthView), false);
internal bool IsPointerInMonthView
{
get => GetValue(IsPointerInMonthViewProperty);
set => SetValue(IsPointerInMonthViewProperty, value);
}
#endregion #endregion
private bool _displayDateIsChanging; private bool _displayDateIsChanging;

View File

@ -10,6 +10,7 @@ using Avalonia.Controls.Metadata;
using Avalonia.Controls.Primitives; using Avalonia.Controls.Primitives;
using Avalonia.Data; using Avalonia.Data;
using Avalonia.Input; using Avalonia.Input;
using Avalonia.Input.Raw;
using Avalonia.Interactivity; using Avalonia.Interactivity;
using Avalonia.Media; using Avalonia.Media;
@ -204,6 +205,9 @@ internal class CalendarItem : TemplatedControl
protected IconButton? _previousButton; protected IconButton? _previousButton;
protected IconButton? _previousMonthButton; protected IconButton? _previousMonthButton;
// 当鼠标移动到日历单元格外面的时候还原 hover 临时的高亮
private IDisposable? _pointerPositionDisposable;
internal Calendar? Owner { get; set; } internal Calendar? Owner { get; set; }
/// <summary> /// <summary>
@ -1076,6 +1080,41 @@ internal class CalendarItem : TemplatedControl
PseudoClasses.Set(CalendarDisabledPC, !isEnabled); PseudoClasses.Set(CalendarDisabledPC, !isEnabled);
} }
protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e)
{
base.OnAttachedToVisualTree(e);
TokenResourceBinder.CreateGlobalTokenBinding(this, BorderThicknessProperty, GlobalTokenResourceKey.BorderThickness, BindingPriority.Template,
new RenderScaleAwareThicknessConfigure(this, thickness => new Thickness(0, 0, 0, thickness.Bottom)));
var inputManager = AvaloniaLocator.Current.GetService<IInputManager>()!;
_pointerPositionDisposable = inputManager.Process.Subscribe(DetectPointerPosition);
}
private void DetectPointerPosition(RawInputEventArgs args)
{
if (Owner is null)
{
return;
}
if (args is RawPointerEventArgs pointerEventArgs)
{
if (!IsPointerInMonthView(pointerEventArgs.Position))
{
Owner.IsPointerInMonthView = false;
}
else
{
Owner.IsPointerInMonthView = true;
}
}
}
protected override void OnDetachedFromVisualTree(VisualTreeAttachmentEventArgs e)
{
base.OnDetachedFromVisualTree(e);
_pointerPositionDisposable?.Dispose();
}
protected virtual bool IsPointerInMonthView(Point position) protected virtual bool IsPointerInMonthView(Point position)
{ {
if (Owner is null) if (Owner is null)
@ -1099,11 +1138,4 @@ internal class CalendarItem : TemplatedControl
return new Rect(firstDayPos, return new Rect(firstDayPos,
new Size(monthView.Bounds.Width, monthViewPos.Y + monthView.Bounds.Height - firstDayPos.Y)); new Size(monthView.Bounds.Width, monthViewPos.Y + monthView.Bounds.Height - firstDayPos.Y));
} }
protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e)
{
base.OnAttachedToVisualTree(e);
TokenResourceBinder.CreateGlobalTokenBinding(this, BorderThicknessProperty, GlobalTokenResourceKey.BorderThickness, BindingPriority.Template,
new RenderScaleAwareThicknessConfigure(this, thickness => new Thickness(0, 0, 0, thickness.Bottom)));
}
} }

View File

@ -5,12 +5,37 @@ namespace AtomUI.Controls;
public class DatePicker : InfoPickerInput public class DatePicker : InfoPickerInput
{ {
private DatePickerPresenter? _pickerPresenter;
protected override Flyout CreatePickerFlyout() protected override Flyout CreatePickerFlyout()
{ {
return new DatePickerFlyout(); return new DatePickerFlyout();
} }
protected override void NotifyPresenterCreated(Control presenter) protected override void NotifyFlyoutPresenterCreated(Control flyoutPresenter)
{ {
if (flyoutPresenter is DatePickerFlyoutPresenter datePickerFlyoutPresenter)
{
datePickerFlyoutPresenter.AttachedToVisualTree += (sender, args) =>
{
_pickerPresenter = datePickerFlyoutPresenter.DatePickerPresenter;
ConfigurePickerPresenter(_pickerPresenter);
};
}
}
private void ConfigurePickerPresenter(DatePickerPresenter? presenter)
{
if (presenter is null)
{
return;
}
presenter.ChoosingStatueChanged += (sender, args) =>
{
_isChoosing = args.IsChoosing;
UpdatePseudoClasses();
};
} }
} }

View File

@ -1,6 +1,17 @@
namespace AtomUI.Controls; using Avalonia.Controls;
using Avalonia.Controls.Primitives;
public class DatePickerFlyoutPresenter : FlyoutPresenter namespace AtomUI.Controls;
internal class DatePickerFlyoutPresenter : FlyoutPresenter
{ {
protected override Type StyleKeyOverride => typeof(DatePickerFlyoutPresenter); protected override Type StyleKeyOverride => typeof(DatePickerFlyoutPresenter);
internal DatePickerPresenter? DatePickerPresenter;
protected override void OnApplyTemplate(TemplateAppliedEventArgs e)
{
base.OnApplyTemplate(e);
DatePickerPresenter = e.NameScope.Get<DatePickerPresenter>(DatePickerFlyoutPresenterTheme.DatePickerPresenterPart);
}
} }

View File

@ -1,12 +1,25 @@
using AtomUI.Theme.Data; using AtomUI.Controls.CalendarView;
using AtomUI.Theme.Data;
using AtomUI.Theme.Styling; using AtomUI.Theme.Styling;
using AtomUI.Utils; using AtomUI.Utils;
using Avalonia; using Avalonia;
using Avalonia.Controls;
using Avalonia.Controls.Primitives; using Avalonia.Controls.Primitives;
using Avalonia.Data; using Avalonia.Data;
using Avalonia.Layout;
using PickerCalendar = AtomUI.Controls.CalendarView.Calendar;
namespace AtomUI.Controls; namespace AtomUI.Controls;
public class ChoosingStatusEventArgs : EventArgs
{
public bool IsChoosing { get; }
public ChoosingStatusEventArgs(bool isChoosing)
{
IsChoosing = isChoosing;
}
}
internal class DatePickerPresenter : PickerPresenterBase internal class DatePickerPresenter : PickerPresenterBase
{ {
#region #region
@ -14,18 +27,151 @@ internal class DatePickerPresenter : PickerPresenterBase
public static readonly StyledProperty<bool> IsNeedConfirmProperty = public static readonly StyledProperty<bool> IsNeedConfirmProperty =
AvaloniaProperty.Register<DatePickerPresenter, bool>(nameof(IsNeedConfirm)); AvaloniaProperty.Register<DatePickerPresenter, bool>(nameof(IsNeedConfirm));
public static readonly StyledProperty<bool> IsShowNowProperty =
AvaloniaProperty.Register<DatePickerPresenter, bool>(nameof(IsShowNow));
public static readonly StyledProperty<bool> IsShowTimeProperty =
AvaloniaProperty.Register<DatePickerPresenter, bool>(nameof(IsShowTime));
public static readonly StyledProperty<DateTime?> SelectedDateTimeProperty =
AvaloniaProperty.Register<DatePickerPresenter, DateTime?>(nameof(SelectedDateTime));
public bool IsNeedConfirm public bool IsNeedConfirm
{ {
get => GetValue(IsNeedConfirmProperty); get => GetValue(IsNeedConfirmProperty);
set => SetValue(IsNeedConfirmProperty, value); set => SetValue(IsNeedConfirmProperty, value);
} }
public bool IsShowNow
{
get => GetValue(IsShowNowProperty);
set => SetValue(IsShowNowProperty, value);
}
public bool IsShowTime
{
get => GetValue(IsShowTimeProperty);
set => SetValue(IsShowTimeProperty, value);
}
public DateTime? SelectedDateTime
{
get => GetValue(SelectedDateTimeProperty);
set => SetValue(SelectedDateTimeProperty, value);
}
#endregion #endregion
#region
/// <summary>
/// 当前 Pointer 选中的日期和时间的变化事件
/// </summary>
public event EventHandler<DateSelectedEventArgs>? HoverDateTimeChanged;
/// <summary>
/// 当前是否处于选择中状态
/// </summary>
public event EventHandler<ChoosingStatusEventArgs>? ChoosingStatueChanged;
#endregion
private Button? _nowButton;
private Button? _todayButton;
private Button? _confirmButton;
private PickerCalendar? _calendarView;
private IDisposable? _choosingStateDisposable;
protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e) protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e)
{ {
base.OnAttachedToVisualTree(e); base.OnAttachedToVisualTree(e);
TokenResourceBinder.CreateGlobalTokenBinding(this, BorderThicknessProperty, GlobalTokenResourceKey.BorderThickness, BindingPriority.Template, TokenResourceBinder.CreateGlobalTokenBinding(this, BorderThicknessProperty, GlobalTokenResourceKey.BorderThickness, BindingPriority.Template,
new RenderScaleAwareThicknessConfigure(this, thickness => new Thickness(0, thickness.Top, 0, 0))); new RenderScaleAwareThicknessConfigure(this, thickness => new Thickness(0, thickness.Top, 0, 0)));
if (_calendarView is not null)
{
_choosingStateDisposable = PickerCalendar.IsPointerInMonthViewProperty.Changed.Subscribe(args =>
{
ChoosingStatueChanged?.Invoke(this, new ChoosingStatusEventArgs(args.GetNewValue<bool>()));
});
}
}
protected override void OnDetachedFromVisualTree(VisualTreeAttachmentEventArgs e)
{
base.OnDetachedFromVisualTree(e);
_choosingStateDisposable = null;
}
protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change)
{
base.OnPropertyChanged(change);
if (change.Property == IsNeedConfirmProperty ||
change.Property == IsShowNowProperty ||
change.Property == IsShowTimeProperty)
{
SetupButtonStatus();
}
}
protected override void OnApplyTemplate(TemplateAppliedEventArgs e)
{
base.OnApplyTemplate(e);
_nowButton = e.NameScope.Get<Button>(DatePickerPresenterTheme.NowButtonPart);
_todayButton = e.NameScope.Get<Button>(DatePickerPresenterTheme.TodayButtonPart);
_confirmButton = e.NameScope.Get<Button>(DatePickerPresenterTheme.ConfirmButtonPart);
_calendarView = e.NameScope.Get<PickerCalendar>(DatePickerPresenterTheme.CalendarViewPart);
SetupButtonStatus();
}
private void SetupButtonStatus()
{
if (_nowButton is null ||
_todayButton is null ||
_confirmButton is null)
{
return;
}
_confirmButton.IsVisible = IsNeedConfirm;
if (IsShowNow)
{
if (IsShowTime)
{
_nowButton.IsVisible = true;
}
else
{
_todayButton.IsVisible = true;
}
if (!IsNeedConfirm)
{
_nowButton.HorizontalAlignment = HorizontalAlignment.Center;
_todayButton.HorizontalAlignment = HorizontalAlignment.Center;
}
else
{
_nowButton.HorizontalAlignment = HorizontalAlignment.Left;
_todayButton.HorizontalAlignment = HorizontalAlignment.Left;
}
}
else
{
_nowButton.IsVisible = false;
_todayButton.IsVisible = false;
_nowButton.HorizontalAlignment = HorizontalAlignment.Left;
_todayButton.HorizontalAlignment = HorizontalAlignment.Left;
}
}
protected override void OnConfirmed()
{
base.OnConfirmed();
}
protected override void OnDismiss()
{
base.OnDismiss();
} }
} }

View File

@ -16,6 +16,7 @@ internal class DatePickerPresenterTheme : BaseControlTheme
{ {
public const string MainLayoutPart = "PART_MainLayout"; public const string MainLayoutPart = "PART_MainLayout";
public const string NowButtonPart = "PART_NowButton"; public const string NowButtonPart = "PART_NowButton";
public const string TodayButtonPart = "PART_TodayButton";
public const string ConfirmButtonPart = "PART_ConfirmButton"; public const string ConfirmButtonPart = "PART_ConfirmButton";
public const string ButtonsContainerPart = "PART_ButtonsContainer"; public const string ButtonsContainerPart = "PART_ButtonsContainer";
public const string ButtonsContainerFramePart = "PART_ButtonsContainerFrame"; public const string ButtonsContainerFramePart = "PART_ButtonsContainerFrame";
@ -58,10 +59,12 @@ internal class DatePickerPresenterTheme : BaseControlTheme
protected virtual Control BuildCalendarView(DatePickerPresenter presenter, INameScope scope) protected virtual Control BuildCalendarView(DatePickerPresenter presenter, INameScope scope)
{ {
return new PickerCalendar() var calendarView = new PickerCalendar()
{ {
Name = CalendarViewPart Name = CalendarViewPart
}; };
calendarView.RegisterInNameScope(scope);
return calendarView;
} }
protected virtual Panel BuildButtons(DatePickerPresenter presenter, INameScope scope) protected virtual Panel BuildButtons(DatePickerPresenter presenter, INameScope scope)
@ -82,6 +85,17 @@ internal class DatePickerPresenterTheme : BaseControlTheme
nowButton.RegisterInNameScope(scope); nowButton.RegisterInNameScope(scope);
buttonsPanel.Children.Add(nowButton); buttonsPanel.Children.Add(nowButton);
var todayButton = new Button
{
ButtonType = ButtonType.Link,
Name = TodayButtonPart,
HorizontalAlignment = HorizontalAlignment.Left,
SizeType = SizeType.Small
};
LanguageResourceBinder.CreateBinding(todayButton, Button.TextProperty, DatePickerLangResourceKey.Today);
todayButton.RegisterInNameScope(scope);
buttonsPanel.Children.Add(todayButton);
var confirmButton = new Button var confirmButton = new Button
{ {
Name = ConfirmButtonPart, Name = ConfirmButtonPart,

View File

@ -17,6 +17,7 @@ namespace AtomUI.Controls.Internal;
public abstract class InfoPickerInput : TemplatedControl public abstract class InfoPickerInput : TemplatedControl
{ {
private const string FlyoutOpenPC = ":flyout-open"; private const string FlyoutOpenPC = ":flyout-open";
private const string ChoosingPC = ":choosing";
#region #region
@ -171,7 +172,8 @@ public abstract class InfoPickerInput : TemplatedControl
protected AddOnDecoratedInnerBox? _pickerInnerBox; protected AddOnDecoratedInnerBox? _pickerInnerBox;
protected TextBox? _infoInputBox; protected TextBox? _infoInputBox;
private bool _isFlyoutOpen; private protected bool _isFlyoutOpen;
private protected bool _isChoosing;
static InfoPickerInput() static InfoPickerInput()
{ {
@ -305,7 +307,7 @@ public abstract class InfoPickerInput : TemplatedControl
}; };
_pickerFlyout.PresenterCreated += (sender, args) => _pickerFlyout.PresenterCreated += (sender, args) =>
{ {
NotifyPresenterCreated(args.Presenter); NotifyFlyoutPresenterCreated(args.Presenter);
}; };
_flyoutStateHelper.Flyout = _pickerFlyout; _flyoutStateHelper.Flyout = _pickerFlyout;
} }
@ -332,7 +334,7 @@ public abstract class InfoPickerInput : TemplatedControl
Clear(); Clear();
} }
protected virtual void NotifyPresenterCreated(Control presenter) protected virtual void NotifyFlyoutPresenterCreated(Control flyoutPresenter)
{ {
} }
@ -352,6 +354,7 @@ public abstract class InfoPickerInput : TemplatedControl
protected virtual void UpdatePseudoClasses() protected virtual void UpdatePseudoClasses()
{ {
PseudoClasses.Set(FlyoutOpenPC, _isFlyoutOpen); PseudoClasses.Set(FlyoutOpenPC, _isFlyoutOpen);
PseudoClasses.Set(ChoosingPC, _isChoosing);
} }
protected override void OnAttachedToLogicalTree(LogicalTreeAttachmentEventArgs e) protected override void OnAttachedToLogicalTree(LogicalTreeAttachmentEventArgs e)