diff --git a/src/Avalonia.Controls/DateTimePickers/DatePicker.cs b/src/Avalonia.Controls/DateTimePickers/DatePicker.cs index 8c6ac17280..6c74eb6b91 100644 --- a/src/Avalonia.Controls/DateTimePickers/DatePicker.cs +++ b/src/Avalonia.Controls/DateTimePickers/DatePicker.cs @@ -4,6 +4,7 @@ using Avalonia.Controls.Shapes; using Avalonia.Controls.Templates; using Avalonia.Data; using Avalonia.Interactivity; +using Avalonia.Layout; using System; using System.Collections.Generic; using System.Globalization; @@ -398,18 +399,27 @@ namespace Avalonia.Controls private void OnFlyoutButtonClicked(object? sender, RoutedEventArgs e) { if (_presenter == null) - throw new InvalidOperationException("No DatePickerPresenter found"); + throw new InvalidOperationException("No DatePickerPresenter found."); + if (_popup == null) + throw new InvalidOperationException("No Popup found."); _presenter.Date = SelectedDate ?? DateTimeOffset.Now; - _popup!.IsOpen = true; + _popup.PlacementMode = PlacementMode.AnchorAndGravity; + _popup.PlacementAnchor = Primitives.PopupPositioning.PopupAnchor.Bottom; + _popup.PlacementGravity = Primitives.PopupPositioning.PopupGravity.Bottom; + _popup.PlacementConstraintAdjustment = Primitives.PopupPositioning.PopupPositionerConstraintAdjustment.SlideY; + _popup.IsOpen = true; + + // Overlay popup hosts won't get measured until the next layout pass, but we need the + // template to be applied to `_presenter` now. Detect this case and force a layout pass. + if (!_presenter.IsMeasureValid) + (VisualRoot as ILayoutRoot)?.LayoutManager?.ExecuteInitialLayoutPass(); var deltaY = _presenter.GetOffsetForPopup(); // The extra 5 px I think is related to default popup placement behavior - _popup!.Host!.ConfigurePosition(_popup.PlacementTarget!, PlacementMode.AnchorAndGravity, new Point(0, deltaY + 5), - Primitives.PopupPositioning.PopupAnchor.Bottom, Primitives.PopupPositioning.PopupGravity.Bottom, - Primitives.PopupPositioning.PopupPositionerConstraintAdjustment.SlideY); + _popup.VerticalOffset = deltaY + 5; } protected virtual void OnSelectedDateChanged(object? sender, DatePickerSelectedValueChangedEventArgs e) diff --git a/src/Avalonia.Controls/DateTimePickers/DatePickerPresenter.cs b/src/Avalonia.Controls/DateTimePickers/DatePickerPresenter.cs index eac6d83074..0caba64758 100644 --- a/src/Avalonia.Controls/DateTimePickers/DatePickerPresenter.cs +++ b/src/Avalonia.Controls/DateTimePickers/DatePickerPresenter.cs @@ -537,8 +537,11 @@ namespace Avalonia.Controls internal double GetOffsetForPopup() { + if (_monthSelector is null) + return 0; + var acceptDismissButtonHeight = _acceptButton != null ? _acceptButton.Bounds.Height : 41; - return -(MaxHeight - acceptDismissButtonHeight) / 2 - (_monthSelector!.ItemHeight / 2); + return -(MaxHeight - acceptDismissButtonHeight) / 2 - (_monthSelector.ItemHeight / 2); } } } diff --git a/src/Avalonia.Controls/DateTimePickers/TimePicker.cs b/src/Avalonia.Controls/DateTimePickers/TimePicker.cs index a0a27bd4ed..3aab49d556 100644 --- a/src/Avalonia.Controls/DateTimePickers/TimePicker.cs +++ b/src/Avalonia.Controls/DateTimePickers/TimePicker.cs @@ -3,6 +3,7 @@ using Avalonia.Controls.Primitives; using Avalonia.Controls.Shapes; using Avalonia.Controls.Templates; using Avalonia.Data; +using Avalonia.Layout; using System; using System.Globalization; @@ -254,16 +255,28 @@ namespace Avalonia.Controls private void OnFlyoutButtonClicked(object? sender, Interactivity.RoutedEventArgs e) { - _presenter!.Time = SelectedTime ?? DateTime.Now.TimeOfDay; + if (_presenter == null) + throw new InvalidOperationException("No DatePickerPresenter found."); + if (_popup == null) + throw new InvalidOperationException("No Popup found."); - _popup!.IsOpen = true; + _presenter.Time = SelectedTime ?? DateTime.Now.TimeOfDay; + + _popup.PlacementMode = PlacementMode.AnchorAndGravity; + _popup.PlacementAnchor = Primitives.PopupPositioning.PopupAnchor.Bottom; + _popup.PlacementGravity = Primitives.PopupPositioning.PopupGravity.Bottom; + _popup.PlacementConstraintAdjustment = Primitives.PopupPositioning.PopupPositionerConstraintAdjustment.SlideY; + _popup.IsOpen = true; + + // Overlay popup hosts won't get measured until the next layout pass, but we need the + // template to be applied to `_presenter` now. Detect this case and force a layout pass. + if (!_presenter.IsMeasureValid) + (VisualRoot as ILayoutRoot)?.LayoutManager?.ExecuteInitialLayoutPass(); var deltaY = _presenter.GetOffsetForPopup(); // The extra 5 px I think is related to default popup placement behavior - _popup.Host!.ConfigurePosition(_popup.PlacementTarget!, PlacementMode.AnchorAndGravity, new Point(0, deltaY + 5), - Primitives.PopupPositioning.PopupAnchor.Bottom, Primitives.PopupPositioning.PopupGravity.Bottom, - Primitives.PopupPositioning.PopupPositionerConstraintAdjustment.SlideY); + _popup.VerticalOffset = deltaY + 5; } private void OnDismissPicker(object? sender, EventArgs e) diff --git a/src/Avalonia.Controls/DateTimePickers/TimePickerPresenter.cs b/src/Avalonia.Controls/DateTimePickers/TimePickerPresenter.cs index 55bc1af3a7..30b6144388 100644 --- a/src/Avalonia.Controls/DateTimePickers/TimePickerPresenter.cs +++ b/src/Avalonia.Controls/DateTimePickers/TimePickerPresenter.cs @@ -256,8 +256,11 @@ namespace Avalonia.Controls internal double GetOffsetForPopup() { + if (_hourSelector is null) + return 0; + var acceptDismissButtonHeight = _acceptButton != null ? _acceptButton.Bounds.Height : 41; - return -(MaxHeight - acceptDismissButtonHeight) / 2 - (_hourSelector!.ItemHeight / 2); + return -(MaxHeight - acceptDismissButtonHeight) / 2 - (_hourSelector.ItemHeight / 2); } } }