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 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
private bool _displayDateIsChanging;
@ -375,7 +384,7 @@ public class Calendar : TemplatedControl
HorizontalAlignmentProperty.OverrideDefaultValue<Calendar>(HorizontalAlignment.Left);
VerticalAlignmentProperty.OverrideDefaultValue<Calendar>(VerticalAlignment.Top);
}
public Calendar()
{
SetCurrentValue(DisplayDateProperty, DateTime.Today);

View File

@ -10,6 +10,7 @@ using Avalonia.Controls.Metadata;
using Avalonia.Controls.Primitives;
using Avalonia.Data;
using Avalonia.Input;
using Avalonia.Input.Raw;
using Avalonia.Interactivity;
using Avalonia.Media;
@ -204,6 +205,9 @@ internal class CalendarItem : TemplatedControl
protected IconButton? _previousButton;
protected IconButton? _previousMonthButton;
// 当鼠标移动到日历单元格外面的时候还原 hover 临时的高亮
private IDisposable? _pointerPositionDisposable;
internal Calendar? Owner { get; set; }
/// <summary>
@ -1076,6 +1080,41 @@ internal class CalendarItem : TemplatedControl
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)
{
if (Owner is null)
@ -1099,11 +1138,4 @@ internal class CalendarItem : TemplatedControl
return new Rect(firstDayPos,
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
{
private DatePickerPresenter? _pickerPresenter;
protected override Flyout CreatePickerFlyout()
{
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);
internal DatePickerPresenter? DatePickerPresenter;
protected override void OnApplyTemplate(TemplateAppliedEventArgs e)
{
base.OnApplyTemplate(e);
DatePickerPresenter = e.NameScope.Get<DatePickerPresenter>(DatePickerFlyoutPresenterTheme.DatePickerPresenterPart);
}
}

View File

@ -1,31 +1,177 @@
using AtomUI.Theme.Data;
using AtomUI.Controls.CalendarView;
using AtomUI.Theme.Data;
using AtomUI.Theme.Styling;
using AtomUI.Utils;
using Avalonia;
using Avalonia.Controls;
using Avalonia.Controls.Primitives;
using Avalonia.Data;
using Avalonia.Layout;
using PickerCalendar = AtomUI.Controls.CalendarView.Calendar;
namespace AtomUI.Controls;
public class ChoosingStatusEventArgs : EventArgs
{
public bool IsChoosing { get; }
public ChoosingStatusEventArgs(bool isChoosing)
{
IsChoosing = isChoosing;
}
}
internal class DatePickerPresenter : PickerPresenterBase
{
#region
public static readonly StyledProperty<bool> IsNeedConfirmProperty =
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
{
get => GetValue(IsNeedConfirmProperty);
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
#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)
{
base.OnAttachedToVisualTree(e);
TokenResourceBinder.CreateGlobalTokenBinding(this, BorderThicknessProperty, GlobalTokenResourceKey.BorderThickness, BindingPriority.Template,
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 NowButtonPart = "PART_NowButton";
public const string TodayButtonPart = "PART_TodayButton";
public const string ConfirmButtonPart = "PART_ConfirmButton";
public const string ButtonsContainerPart = "PART_ButtonsContainer";
public const string ButtonsContainerFramePart = "PART_ButtonsContainerFrame";
@ -58,10 +59,12 @@ internal class DatePickerPresenterTheme : BaseControlTheme
protected virtual Control BuildCalendarView(DatePickerPresenter presenter, INameScope scope)
{
return new PickerCalendar()
var calendarView = new PickerCalendar()
{
Name = CalendarViewPart
};
calendarView.RegisterInNameScope(scope);
return calendarView;
}
protected virtual Panel BuildButtons(DatePickerPresenter presenter, INameScope scope)
@ -81,6 +84,17 @@ internal class DatePickerPresenterTheme : BaseControlTheme
LanguageResourceBinder.CreateBinding(nowButton, Button.TextProperty, DatePickerLangResourceKey.Now);
nowButton.RegisterInNameScope(scope);
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
{

View File

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