Skip to content

Commit 8a2f830

Browse files
[.NET 10] Add "Open/Close" API to picker controls (#29323)
* Initial changes * More changes * More changes * More changes * Implement DatePìcker IsOpen * More changes * Implemented on Picker * Added DatePicker sample * Added TimePicker sample * Added Picker sample * More changes * More fixes * More public API changes * Updated sample * Added UITest * More changes * More changes * More tests * More changes * Pending comments * Fixes in Stubs * More changes * Updated tests * Implement IsOpen property on Windows TimePicker * More changes to tests * Adding Windows snapshots * Fix the build * Fix Android Picker issue not opening * Added Android DatePicker dismiss listener * Fix mistake * Added dismiss listener to Android TimePicker * Changes in the test * Android snapshots * More changes * Changes in tests * Updated sample adding specific date etc * More snapshots * Changes in the tests * Updated test * More changes in the test * More Windows snapshots * iOS snapshots * Pending snapshots * Added test orders * More changes * More changes * Updated tests * More changes * More changes, fix test on Android * Add check opening the Android TimePicker * Changes based on PR feedback * More PublicAPIs changes * Fix issue dimissing the DatePicker * More changes based on feedback * More publicAPI fixes * More publicAPI changes * More fixes in publicAPIs * Fixed test * Complete merge * Added default implementations * More fixes * More tests * Fixed tests * Updated tests * Fixed tests on Windows * Added more EventArgs for DatePicker and TimePicker opened and closed events * More publicAPI changes * More publicAPI changes * - make internal * - txt file updates * Make MapIsOpen internal * More changes in publicAPIs --------- Co-authored-by: Shane Neuville <shneuvil@microsoft.com>
1 parent dedcc40 commit 8a2f830

File tree

73 files changed

+1558
-36
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

73 files changed

+1558
-36
lines changed

src/Controls/samples/Controls.Sample/Pages/Controls/DatePickerPage.xaml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,13 @@
9797
Date="{x:Null}"/>
9898
<Button Text="Set to null" Clicked="SetDatePickerToNull" />
9999
<Button Text="Set to today" Clicked="SetDatePickerToToday" />
100+
<Label
101+
Text="IsOpen"
102+
Style="{StaticResource Headline}"/>
103+
<DatePicker
104+
x:Name="IsOpenDatePicker"/>
105+
<Button Text="Open DatePicker" Clicked="OnOpenClicked" />
106+
<Button Text="Close DatePicker" Clicked="OnCloseClicked" />
100107
</VerticalStackLayout>
101108
</views:BasePage.Content>
102109
</views:BasePage>

src/Controls/samples/Controls.Sample/Pages/Controls/DatePickerPage.xaml.cs

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,22 @@ public DatePickerPage()
1414
UpdateDatePickerBackground();
1515
}
1616

17+
protected override void OnAppearing()
18+
{
19+
base.OnAppearing();
20+
21+
IsOpenDatePicker.Opened += IsOpenDatePickerOpened;
22+
IsOpenDatePicker.Closed += IsOpenDatePickerClosed;
23+
}
24+
25+
protected override void OnDisappearing()
26+
{
27+
base.OnDisappearing();
28+
29+
IsOpenDatePicker.Opened -= IsOpenDatePickerOpened;
30+
IsOpenDatePicker.Closed -= IsOpenDatePickerClosed;
31+
}
32+
1733
void OnUpdateBackgroundButtonClicked(object sender, EventArgs e)
1834
{
1935
UpdateDatePickerBackground();
@@ -60,5 +76,25 @@ void SetDatePickerToToday(object sender, EventArgs e)
6076
{
6177
NullDatePicker.Date = DateTime.Now;
6278
}
79+
80+
void OnOpenClicked(object sender, EventArgs e)
81+
{
82+
IsOpenDatePicker.IsOpen = true;
83+
}
84+
85+
void OnCloseClicked(object sender, EventArgs e)
86+
{
87+
IsOpenDatePicker.IsOpen = false;
88+
}
89+
90+
void IsOpenDatePickerOpened(object? sender, DatePickerOpenedEventArgs e)
91+
{
92+
Console.WriteLine("IsOpenDatePicker Opened");
93+
}
94+
95+
void IsOpenDatePickerClosed(object? sender, DatePickerClosedEventArgs e)
96+
{
97+
Console.WriteLine("IsOpenDatePicker Closed");
98+
}
6399
}
64100
}

src/Controls/samples/Controls.Sample/Pages/Controls/PickerPage.xaml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,14 @@
114114
</Picker.Items>
115115
</Picker>
116116
</Grid>
117+
<Label
118+
Text="IsOpen"
119+
Style="{StaticResource Headline}"/>
120+
<Picker
121+
x:Name="IsOpenPicker"
122+
Title="Select an item" ItemsSource="{Binding PickerItems}" />
123+
<Button Text="Open Picker" Clicked="OnOpenClicked" />
124+
<Button Text="Close Picker" Clicked="OnCloseClicked" />
117125
</VerticalStackLayout>
118126
</ScrollView>
119127
</views:BasePage.Content>

src/Controls/samples/Controls.Sample/Pages/Controls/PickerPage.xaml.cs

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,22 @@ public PickerPage()
3030
};
3131
}
3232

33+
protected override void OnAppearing()
34+
{
35+
base.OnAppearing();
36+
37+
IsOpenPicker.Opened += IsOpenPickerOpened;
38+
IsOpenPicker.Closed += IsOpenPickerClosed;
39+
}
40+
41+
protected override void OnDisappearing()
42+
{
43+
base.OnDisappearing();
44+
45+
IsOpenPicker.Opened -= IsOpenPickerOpened;
46+
IsOpenPicker.Closed -= IsOpenPickerClosed;
47+
}
48+
3349
void OnSelectedIndexChanged(object sender, EventArgs e)
3450
{
3551
string selectedCountry = (string)((Picker)sender).SelectedItem;
@@ -152,6 +168,26 @@ LayoutOptions GetLayoutOptions(int index)
152168
return LayoutOptions.Fill;
153169
}
154170
}
171+
172+
void OnOpenClicked(object sender, EventArgs e)
173+
{
174+
IsOpenPicker.IsOpen = true;
175+
}
176+
177+
void OnCloseClicked(object sender, EventArgs e)
178+
{
179+
IsOpenPicker.IsOpen = false;
180+
}
181+
182+
void IsOpenPickerOpened(object? sender, PickerOpenedEventArgs e)
183+
{
184+
Console.WriteLine("IsOpenPicker Opened");
185+
}
186+
187+
void IsOpenPickerClosed(object? sender, PickerClosedEventArgs e)
188+
{
189+
Console.WriteLine("IsOpenPicker Closed");
190+
}
155191
}
156192

157193
public class PickerData

src/Controls/samples/Controls.Sample/Pages/Controls/TimePickerPage.xaml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,13 @@
9595
Time="{x:Null}"/>
9696
<Button Text="Set to null" Clicked="SetTimePickerToNull" />
9797
<Button Text="Set to now" Clicked="SetTimePickerToNow" />
98+
<Label
99+
Text="IsOpen"
100+
Style="{StaticResource Headline}"/>
101+
<TimePicker
102+
x:Name="IsOpenTimePicker"/>
103+
<Button Text="Open TimePicker" Clicked="OnOpenClicked" />
104+
<Button Text="Close TimePicker" Clicked="OnCloseClicked" />
98105
</VerticalStackLayout>
99106
</views:BasePage.Content>
100107
</views:BasePage>

src/Controls/samples/Controls.Sample/Pages/Controls/TimePickerPage.xaml.cs

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,22 @@ public TimePickerPage()
1313
UpdateTimePickerBackground();
1414
}
1515

16+
protected override void OnAppearing()
17+
{
18+
base.OnAppearing();
19+
20+
IsOpenTimePicker.Opened += IsOpenTimePickerOpened;
21+
IsOpenTimePicker.Closed += IsOpenTimePickerClosed;
22+
}
23+
24+
protected override void OnDisappearing()
25+
{
26+
base.OnDisappearing();
27+
28+
IsOpenTimePicker.Opened -= IsOpenTimePickerOpened;
29+
IsOpenTimePicker.Closed -= IsOpenTimePickerClosed;
30+
}
31+
1632
void OnUpdateBackgroundButtonClicked(object sender, EventArgs e)
1733
{
1834
UpdateTimePickerBackground();
@@ -49,5 +65,25 @@ void SetTimePickerToNow(object sender, EventArgs e)
4965
{
5066
NullTimePicker.Time = DateTime.Now.TimeOfDay;
5167
}
68+
69+
void OnOpenClicked(object sender, EventArgs e)
70+
{
71+
IsOpenTimePicker.IsOpen = true;
72+
}
73+
74+
void OnCloseClicked(object sender, EventArgs e)
75+
{
76+
IsOpenTimePicker.IsOpen = false;
77+
}
78+
79+
void IsOpenTimePickerOpened(object? sender, TimePickerOpenedEventArgs e)
80+
{
81+
Console.WriteLine("IsOpenTimePicker Opened");
82+
}
83+
84+
void IsOpenTimePickerClosed(object? sender, TimePickerClosedEventArgs e)
85+
{
86+
Console.WriteLine("IsOpenTimePicker Closed");
87+
}
5288
}
5389
}

src/Controls/src/Core/DatePicker/DatePicker.cs

Lines changed: 57 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
#nullable disable
22
using System;
3+
using System.Collections.Generic;
34
using System.Diagnostics;
45
using Microsoft.Maui.Controls.Internals;
56
using Microsoft.Maui.Graphics;
@@ -45,6 +46,11 @@ public partial class DatePicker : View, IFontElement, ITextElement, IElementConf
4546
/// <summary>Bindable property for <see cref="FontAutoScalingEnabled"/>.</summary>
4647
public static readonly BindableProperty FontAutoScalingEnabledProperty = FontElement.FontAutoScalingEnabledProperty;
4748

49+
/// <summary>Bindable property for <see cref="IsOpen"/>.</summary>
50+
public static readonly BindableProperty IsOpenProperty =
51+
BindableProperty.Create(nameof(IDatePicker.IsOpen), typeof(bool), typeof(DatePicker), default, BindingMode.TwoWay,
52+
propertyChanged: OnIsOpenPropertyChanged);
53+
4854
readonly Lazy<PlatformConfigurationRegistry<DatePicker>> _platformConfigurationRegistry;
4955

5056
/// <include file="../../docs/Microsoft.Maui.Controls/DatePicker.xml" path="//Member[@MemberName='.ctor']/Docs/*" />
@@ -132,7 +138,18 @@ public bool FontAutoScalingEnabled
132138
get => (bool)GetValue(FontAutoScalingEnabledProperty);
133139
set => SetValue(FontAutoScalingEnabledProperty, value);
134140
}
135-
141+
142+
public bool IsOpen
143+
{
144+
get => (bool)GetValue(IsOpenProperty);
145+
set => SetValue(IsOpenProperty, value);
146+
}
147+
148+
static void OnIsOpenPropertyChanged(BindableObject bindable, object oldValue, object newValue)
149+
{
150+
((DatePicker)bindable).OnIsOpenPropertyChanged((bool)oldValue, (bool)newValue);
151+
}
152+
136153
void IFontElement.OnFontFamilyChanged(string oldValue, string newValue) =>
137154
HandleFontChanged();
138155

@@ -158,11 +175,50 @@ void ITextElement.OnTextTransformChanged(TextTransform oldValue, TextTransform n
158175
{
159176
}
160177

178+
readonly Queue<Action> _pendingIsOpenActions = new Queue<Action>();
179+
180+
void OnIsOpenPropertyChanged(bool oldValue, bool newValue)
181+
{
182+
if (Handler?.VirtualView is DatePicker)
183+
{
184+
HandleIsOpenChanged();
185+
}
186+
else
187+
{
188+
_pendingIsOpenActions.Enqueue(HandleIsOpenChanged);
189+
}
190+
}
191+
192+
protected override void OnHandlerChanged()
193+
{
194+
base.OnHandlerChanged();
195+
196+
// Process any pending actions when handler becomes available
197+
while (_pendingIsOpenActions.Count > 0 && Handler != null)
198+
{
199+
var action = _pendingIsOpenActions.Dequeue();
200+
action.Invoke();
201+
}
202+
}
203+
204+
void HandleIsOpenChanged()
205+
{
206+
if (Handler?.VirtualView is not DatePicker datePicker)
207+
return;
208+
209+
if (datePicker.IsOpen)
210+
datePicker.Opened?.Invoke(datePicker, DatePickerOpenedEventArgs.Empty);
211+
else
212+
datePicker.Closed?.Invoke(datePicker, DatePickerClosedEventArgs.Empty);
213+
}
214+
161215
/// <include file="../../docs/Microsoft.Maui.Controls/DatePicker.xml" path="//Member[@MemberName='UpdateFormsText']/Docs/*" />
162216
public virtual string UpdateFormsText(string source, TextTransform textTransform)
163217
=> TextTransformUtilities.GetTransformedText(source, textTransform);
164218

165219
public event EventHandler<DateChangedEventArgs> DateSelected;
220+
public event EventHandler<DatePickerOpenedEventArgs> Opened;
221+
public event EventHandler<DatePickerClosedEventArgs> Closed;
166222

167223
static object CoerceDate(BindableObject bindable, object value)
168224
{
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
using System;
2+
3+
namespace Microsoft.Maui.Controls;
4+
5+
/// <summary>
6+
/// Provides event data for the event that is raised when a DatePicker control is closed.
7+
/// </summary>
8+
public class DatePickerClosedEventArgs : EventArgs
9+
{
10+
/// <summary>
11+
/// Gets a reusable empty instance of DatePickerClosedEventArgs.
12+
/// </summary>
13+
/// <value>
14+
/// A static readonly instance of DatePickerClosedEventArgs that can be reused to avoid unnecessary object allocation.
15+
/// </value>
16+
internal new static readonly DatePickerClosedEventArgs Empty = new();
17+
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
using System;
2+
3+
namespace Microsoft.Maui.Controls;
4+
5+
/// <summary>
6+
/// Provides event data for the event that is raised when a DatePicker control is opened.
7+
/// </summary>
8+
public class DatePickerOpenedEventArgs : EventArgs
9+
{
10+
/// <summary>
11+
/// Gets a reusable empty instance of DatePickerOpenedEventArgs.
12+
/// </summary>
13+
/// <value>
14+
/// A static readonly instance of DatePickerOpenedEventArgs that can be reused to avoid unnecessary object allocation.
15+
/// </value>
16+
internal new static readonly DatePickerOpenedEventArgs Empty = new();
17+
}

src/Controls/src/Core/Picker/Picker.cs

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,11 @@ public partial class Picker : View, IFontElement, ITextElement, ITextAlignmentEl
6565
/// <summary>Bindable property for <see cref="VerticalTextAlignment"/>.</summary>
6666
public static readonly BindableProperty VerticalTextAlignmentProperty = TextAlignmentElement.VerticalTextAlignmentProperty;
6767

68+
/// <summary>Bindable property for <see cref="IsOpen"/>.</summary>
69+
public static readonly BindableProperty IsOpenProperty =
70+
BindableProperty.Create(nameof(IPicker.IsOpen), typeof(bool), typeof(Picker), default, BindingMode.TwoWay,
71+
propertyChanged: OnIsOpenPropertyChanged);
72+
6873
readonly Lazy<PlatformConfigurationRegistry<Picker>> _platformConfigurationRegistry;
6974

7075
/// <include file="../../docs/Microsoft.Maui.Controls/Picker.xml" path="//Member[@MemberName='.ctor']/Docs/*" />
@@ -220,7 +225,20 @@ public BindingBase ItemDisplayBinding
220225
}
221226
}
222227

228+
public bool IsOpen
229+
{
230+
get => (bool)GetValue(IsOpenProperty);
231+
set => SetValue(IsOpenProperty, value);
232+
}
233+
234+
static void OnIsOpenPropertyChanged(BindableObject bindable, object oldValue, object newValue)
235+
{
236+
((Picker)bindable).OnIsOpenPropertyChanged((bool)oldValue, (bool)newValue);
237+
}
238+
223239
public event EventHandler SelectedIndexChanged;
240+
public event EventHandler<PickerOpenedEventArgs> Opened;
241+
public event EventHandler<PickerClosedEventArgs> Closed;
224242

225243
static readonly BindableProperty s_displayProperty =
226244
BindableProperty.Create("Display", typeof(string), typeof(Picker), default(string));
@@ -287,6 +305,43 @@ void OnItemsSourceChanged(IList oldValue, IList newValue)
287305
}
288306
}
289307

308+
readonly Queue<Action> _pendingIsOpenActions = new Queue<Action>();
309+
310+
void OnIsOpenPropertyChanged(bool oldValue, bool newValue)
311+
{
312+
if (Handler?.VirtualView is Picker)
313+
{
314+
HandleIsOpenChanged();
315+
}
316+
else
317+
{
318+
_pendingIsOpenActions.Enqueue(HandleIsOpenChanged);
319+
}
320+
}
321+
322+
protected override void OnHandlerChanged()
323+
{
324+
base.OnHandlerChanged();
325+
326+
// Process any pending actions when handler becomes available
327+
while (_pendingIsOpenActions.Count > 0 && Handler != null)
328+
{
329+
var action = _pendingIsOpenActions.Dequeue();
330+
action.Invoke();
331+
}
332+
}
333+
334+
void HandleIsOpenChanged()
335+
{
336+
if (Handler?.VirtualView is not Picker picker)
337+
return;
338+
339+
if (picker.IsOpen)
340+
picker.Opened?.Invoke(picker, PickerOpenedEventArgs.Empty);
341+
else
342+
picker.Closed?.Invoke(picker, PickerClosedEventArgs.Empty);
343+
}
344+
290345
void CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
291346
{
292347
switch (e.Action)

0 commit comments

Comments
 (0)