Skip to content

Commit 010b09f

Browse files
committed
Improve FocusBehavior: support list view lazy render. support control x:load.
1 parent 1973504 commit 010b09f

File tree

3 files changed

+80
-5
lines changed

3 files changed

+80
-5
lines changed

Microsoft.Toolkit.Uwp.SampleApp/SamplePages/FocusBehavior/FocusBehaviorPage.xaml

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
<interactivity:Interaction.Behaviors>
1111
<behaviors:FocusBehavior>
1212
<behaviors:FocusTarget Control="{x:Bind disabledItem}" />
13+
<behaviors:FocusTarget Control="{x:Bind xLoadItem}" />
1314
<behaviors:FocusTarget Control="{x:Bind emptyList}" />
1415
<behaviors:FocusTarget Control="{x:Bind enabledItem}" />
1516
</behaviors:FocusBehavior>
@@ -27,5 +28,13 @@
2728
</ListView>
2829
<Button x:Name="enabledItem"
2930
Content="I can get the focus" />
31+
<StackPanel Orientation="Horizontal">
32+
<ToggleSwitch x:Name="LoadControl"
33+
OffContent="Load the item"
34+
OnContent="Unload the item" />
35+
<Button x:Name="xLoadItem"
36+
x:Load="{x:Bind LoadControl.IsOn, Mode=OneWay}"
37+
Content="I can get the focus when loaded" />
38+
</StackPanel>
3039
</StackPanel>
3140
</Page>

Microsoft.Toolkit.Uwp.SampleApp/SamplePages/FocusBehavior/FocusBehaviorXaml.bind

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
<interactivity:Interaction.Behaviors>
1010
<behaviors:FocusBehavior>
1111
<behaviors:FocusTarget Control="{x:Bind disabledItem}" />
12+
<behaviors:FocusTarget Control="{x:Bind xLoadItem}" />
1213
<behaviors:FocusTarget Control="{x:Bind emptyList}" />
1314
<behaviors:FocusTarget Control="{x:Bind enabledItem}" />
1415
</behaviors:FocusBehavior>
@@ -27,5 +28,13 @@
2728
</ListView>
2829
<Button x:Name="enabledItem"
2930
Content="I can get the focus" />
31+
<StackPanel Orientation="Horizontal">
32+
<ToggleSwitch x:Name="LoadControl"
33+
OffContent="Load the item"
34+
OnContent="Unload the item" />
35+
<Button x:Name="xLoadItem"
36+
x:Load="{x:Bind LoadControl.IsOn, Mode=OneWay}"
37+
Content="I can get the focus when loaded" />
38+
</StackPanel>
3039
</StackPanel>
3140
</Page>

Microsoft.Toolkit.Uwp.UI.Behaviors/Focus/FocusBehavior.cs

Lines changed: 62 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -67,10 +67,27 @@ public TimeSpan FocusEngagementTimeout
6767
}
6868

6969
/// <inheritdoc/>
70-
protected override void OnAssociatedObjectLoaded() => ApplyFocus();
70+
protected override void OnAssociatedObjectLoaded()
71+
{
72+
foreach (var target in Targets)
73+
{
74+
target.ControlChanged += OnTargetControlChanged;
75+
}
76+
77+
ApplyFocus();
78+
}
7179

7280
/// <inheritdoc/>
73-
protected override void OnDetaching() => Stop();
81+
protected override bool Uninitialize()
82+
{
83+
foreach (var target in Targets)
84+
{
85+
target.ControlChanged -= OnTargetControlChanged;
86+
}
87+
88+
Stop();
89+
return true;
90+
}
7491

7592
private static void OnTargetsPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
7693
{
@@ -92,24 +109,39 @@ private void ApplyFocus()
92109
}
93110

94111
var focusedControlIndex = -1;
112+
var listViewBaseControls = 0;
95113
for (var i = 0; i < Targets.Count; i++)
96114
{
97115
var control = Targets[i].Control;
116+
if (control is null)
117+
{
118+
continue;
119+
}
120+
98121
if (control.IsLoaded)
99122
{
100123
if (control.Focus(FocusState.Programmatic))
101124
{
102125
focusedControlIndex = i;
103126
break;
104127
}
128+
129+
if (control is ListViewBase listViewBase)
130+
{
131+
// The list may not have any item yet, we wait until the first item is rendered.
132+
listViewBase.ContainerContentChanging -= OnContainerContentChanging;
133+
listViewBase.ContainerContentChanging += OnContainerContentChanging;
134+
listViewBaseControls++;
135+
}
105136
}
106137
else
107138
{
139+
control.Loaded -= OnControlLoaded;
108140
control.Loaded += OnControlLoaded;
109141
}
110142
}
111143

112-
if (focusedControlIndex == 0 || Targets.All(t => t.Control?.IsLoaded == true))
144+
if (focusedControlIndex == 0 || (listViewBaseControls == 0 && Targets.All(t => t.Control?.IsLoaded == true)))
113145
{
114146
// The first control has received the focus or all the control are loaded and none can take the focus: we stop.
115147
Stop();
@@ -137,6 +169,14 @@ private void OnEngagementTimerTick(object sender, object e)
137169

138170
private void OnControlLoaded(object sender, RoutedEventArgs e) => ApplyFocus();
139171

172+
private void OnTargetControlChanged(object sender, EventArgs e) => ApplyFocus();
173+
174+
private void OnContainerContentChanging(ListViewBase sender, ContainerContentChangingEventArgs args)
175+
{
176+
sender.ContainerContentChanging -= OnContainerContentChanging;
177+
ApplyFocus();
178+
}
179+
140180
private void Stop(FocusTargetList targets = null)
141181
{
142182
if (_timer != null)
@@ -153,6 +193,11 @@ private void Stop(FocusTargetList targets = null)
153193
}
154194

155195
target.Control.Loaded -= OnControlLoaded;
196+
197+
if (target.Control is ListViewBase listViewBase)
198+
{
199+
listViewBase.ContainerContentChanging -= OnContainerContentChanging;
200+
}
156201
}
157202
}
158203
}
@@ -167,7 +212,7 @@ public sealed class FocusTargetList : List<FocusTarget>
167212
/// <summary>
168213
/// A target for the <see cref="FocusBehavior"/>.
169214
/// </summary>
170-
public sealed partial class FocusTarget : DependencyObject
215+
public sealed class FocusTarget : DependencyObject
171216
{
172217
/// <summary>
173218
/// The DP to store the <see cref="Control"/> property value.
@@ -176,7 +221,13 @@ public sealed partial class FocusTarget : DependencyObject
176221
nameof(Control),
177222
typeof(Control),
178223
typeof(FocusTarget),
179-
new PropertyMetadata(null));
224+
new PropertyMetadata(null, OnControlChanged));
225+
226+
/// <summary>
227+
/// Raised when <see cref="Control"/> property changed.
228+
/// It can change if we use x:Load on the control.
229+
/// </summary>
230+
public event EventHandler ControlChanged;
180231

181232
/// <summary>
182233
/// Gets or sets the control that will receive the focus.
@@ -186,6 +237,12 @@ public Control Control
186237
get => (Control)GetValue(ControlProperty);
187238
set => SetValue(ControlProperty, value);
188239
}
240+
241+
private static void OnControlChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
242+
{
243+
var target = (FocusTarget)d;
244+
target.ControlChanged?.Invoke(target, EventArgs.Empty);
245+
}
189246
}
190247
#pragma warning restore SA1402 // File may only contain a single type
191248
}

0 commit comments

Comments
 (0)