Skip to content

Commit b84c44e

Browse files
KebooCopilot
andcommitted
Position date and time picker popups below fields
Fixes issue MaterialDesignInXAML#4050 by using Bottom popup placement for DatePicker and TimePicker templates, removing the unused custom popup callback helper, and adding UI tests that assert the popup opens below the text box by default. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent 4ccb265 commit b84c44e

5 files changed

Lines changed: 60 additions & 16 deletions

File tree

src/MaterialDesignThemes.Wpf/CustomPopupPlacementCallbackHelper.cs

Lines changed: 0 additions & 12 deletions
This file was deleted.

src/MaterialDesignThemes.Wpf/Themes/MaterialDesignTheme.DatePicker.xaml

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -120,8 +120,7 @@
120120
Template="{StaticResource CalendarButtonTemplate}" />
121121
<Popup x:Name="PART_Popup"
122122
AllowsTransparency="True"
123-
CustomPopupPlacementCallback="{x:Static wpf:CustomPopupPlacementCallbackHelper.LargePopupCallback}"
124-
Placement="Custom"
123+
Placement="Bottom"
125124
PlacementTarget="{Binding ElementName=PART_TextBox}"
126125
PopupAnimation="Fade"
127126
StaysOpen="False" />

src/MaterialDesignThemes.Wpf/Themes/MaterialDesignTheme.TimePicker.xaml

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -132,8 +132,7 @@
132132
Template="{StaticResource ClockButtonTemplate}" />
133133
<Popup x:Name="PART_Popup"
134134
AllowsTransparency="True"
135-
CustomPopupPlacementCallback="{x:Static wpf:CustomPopupPlacementCallbackHelper.LargePopupCallback}"
136-
Placement="Custom"
135+
Placement="Bottom"
137136
PlacementTarget="{Binding ElementName=PART_TextBox}"
138137
PopupAnimation="Fade"
139138
StaysOpen="False" />

tests/MaterialDesignThemes.UITests/WPF/DatePickers/DatePickerTests.cs

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -333,6 +333,35 @@ public async Task DatePicker_ShouldApplyIsMouseOverTriggers_WhenHoveringCalendar
333333

334334
recorder.Success();
335335
}
336+
337+
[Test]
338+
[Description("Issue 4050")]
339+
public async Task DatePicker_PopupOpensBelowTextBoxByDefault()
340+
{
341+
await using var recorder = new TestRecorder(App);
342+
343+
var stackPanel = await LoadXaml<StackPanel>("""
344+
<StackPanel Margin="40">
345+
<DatePicker Width="200" />
346+
</StackPanel>
347+
""");
348+
var datePicker = await stackPanel.GetElement<DatePicker>("/DatePicker");
349+
var datePickerTextBox = await datePicker.GetElement<DatePickerTextBox>("/DatePickerTextBox");
350+
var button = await datePicker.GetElement<Button>("PART_Button");
351+
var popup = await datePicker.GetElement<Popup>("PART_Popup");
352+
353+
await button.LeftClick();
354+
await Wait.For(async () => await popup.GetIsOpen());
355+
356+
Rect? textBoxCoordinates = await datePickerTextBox.GetCoordinates();
357+
Rect? popupCoordinates = await popup.GetCoordinates();
358+
359+
await Assert.That(textBoxCoordinates).IsNotNull();
360+
await Assert.That(popupCoordinates).IsNotNull();
361+
await Assert.That(popupCoordinates.Value.Top).IsGreaterThanOrEqualTo(textBoxCoordinates.Value.Bottom - 1);
362+
363+
recorder.Success();
364+
}
336365
}
337366

338367
public class FutureDateValidationRule : ValidationRule

tests/MaterialDesignThemes.UITests/WPF/TimePickers/TimePickerTests.cs

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -642,6 +642,35 @@ public async Task TimePicker_ShouldApplyIsMouseOverTriggers_WhenHoveringTimeButt
642642
recorder.Success();
643643
}
644644

645+
[Test]
646+
[Description("Issue 4050")]
647+
public async Task TimePicker_PopupOpensBelowTextBoxByDefault()
648+
{
649+
await using var recorder = new TestRecorder(App);
650+
651+
var stackPanel = await LoadXaml<StackPanel>("""
652+
<StackPanel Margin="40">
653+
<materialDesign:TimePicker Width="200" />
654+
</StackPanel>
655+
""");
656+
var timePicker = await stackPanel.GetElement<TimePicker>("/TimePicker");
657+
var timePickerTextBox = await timePicker.GetElement<TimePickerTextBox>("/TimePickerTextBox");
658+
var button = await timePicker.GetElement<Button>("PART_Button");
659+
var popup = await timePicker.GetElement<Popup>("PART_Popup");
660+
661+
await button.LeftClick();
662+
await Wait.For(async () => await popup.GetIsOpen());
663+
664+
Rect? textBoxCoordinates = await timePickerTextBox.GetCoordinates();
665+
Rect? popupCoordinates = await popup.GetCoordinates();
666+
667+
await Assert.That(textBoxCoordinates).IsNotNull();
668+
await Assert.That(popupCoordinates).IsNotNull();
669+
await Assert.That(popupCoordinates.Value.Top).IsGreaterThanOrEqualTo(textBoxCoordinates.Value.Bottom - 1);
670+
671+
recorder.Success();
672+
}
673+
645674
[Test]
646675
[Description("Issue 3650")]
647676
public async Task TimePicker_MovesFocusToPrevious_WhenShiftAndTabIsPressed()

0 commit comments

Comments
 (0)