Skip to content

Commit 051856c

Browse files
Add 'simple' workaround to TokenizingTextBox AutoSuggestBox Layout Issue
This was an issue with the PlaceholderText ContentControl in the AutoSuggestBox which would not update it's layout properly when the ASB had focus when we would add/remove a token.
1 parent b0cf735 commit 051856c

File tree

2 files changed

+81
-0
lines changed

2 files changed

+81
-0
lines changed

Microsoft.Toolkit.Uwp.UI.Controls/TokenizingTextBox/TokenizingTextBox.cs

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@
88
using System.Threading.Tasks;
99
using Microsoft.Toolkit.Uwp.Deferred;
1010
using Microsoft.Toolkit.Uwp.Extensions;
11+
using Microsoft.Toolkit.Uwp.UI.Extensions;
12+
using Microsoft.Toolkit.Uwp.UI.Helpers;
1113
using Windows.System;
1214
using Windows.UI.Core;
1315
using Windows.UI.Xaml;
@@ -435,6 +437,8 @@ internal async Task AddTokenAsync(object data, bool? atEnd = null)
435437
last?._autoSuggestTextBox.Focus(FocusState.Keyboard);
436438

437439
TokenItemAdded?.Invoke(this, data);
440+
441+
GuardAgainstPlaceholderTextLayoutIssue();
438442
}
439443

440444
private void UpdateCurrentTextEdit(PretokenStringContainer edit)
@@ -475,7 +479,34 @@ private async Task<bool> RemoveTokenAsync(TokenizingTextBoxItem item, object dat
475479

476480
TokenItemRemoved?.Invoke(this, data);
477481

482+
GuardAgainstPlaceholderTextLayoutIssue();
483+
478484
return true;
479485
}
486+
487+
private void GuardAgainstPlaceholderTextLayoutIssue()
488+
{
489+
// If the *PlaceholderText is visible* on the last AutoSuggestBox, it can incorrectly layout itself
490+
// when the *ASB has focus*. We think this is an optimization in the platform, but haven't been able to
491+
// isolate a straight-reproduction of this issue outside of this control (though we have eliminated
492+
// most Toolkit influences like ASB/TextBox Style, the InterspersedObservableCollection, etc...).
493+
// The only Toolkit component involved here should be WrapPanel (which is a straight-forward Panel).
494+
// We also know the ASB itself is adjusting it's size correctly, it's the inner component.
495+
//
496+
// To combat this issue:
497+
// We toggle the visibility of the Placeholder ContentControl in order to force it's layout to update properly
498+
var placeholder = ContainerFromItem(_lastTextEdit).FindDescendantByName("PlaceholderTextContentPresenter");
499+
500+
if (placeholder.Visibility == Visibility.Visible)
501+
{
502+
placeholder.Visibility = Visibility.Collapsed;
503+
504+
// After we ensure we've hid the control, make it visible again (this is inperceptable to the user).
505+
_ = CompositionTargetHelper.ExecuteAfterCompositionRenderingAsync(() =>
506+
{
507+
placeholder.Visibility = Visibility.Visible;
508+
});
509+
}
510+
}
480511
}
481512
}
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
// See the LICENSE file in the project root for more information.
4+
5+
using System;
6+
using System.Threading.Tasks;
7+
using Microsoft.Toolkit.Diagnostics;
8+
using Windows.UI.Xaml.Media;
9+
10+
namespace Microsoft.Toolkit.Uwp.UI.Helpers
11+
{
12+
/// <summary>
13+
/// Provides helpers for the <see cref="CompositionTarget"/> class.
14+
/// </summary>
15+
public static class CompositionTargetHelper
16+
{
17+
/// <summary>
18+
/// Provides a method to execute code after the rendering pass is completed.
19+
/// <seealso href="https://github.com/microsoft/microsoft-ui-xaml/blob/c045cde57c5c754683d674634a0baccda34d58c4/dev/dll/SharedHelpers.cpp#L399"/>
20+
/// </summary>
21+
/// <param name="action">Action to be executed after render pass</param>
22+
/// <returns>Awaitable Task</returns>
23+
public static Task<bool> ExecuteAfterCompositionRenderingAsync(Action action)
24+
{
25+
Guard.IsNotNull(action, nameof(action));
26+
27+
var taskCompletionSource = new TaskCompletionSource<bool>();
28+
29+
try
30+
{
31+
void Callback(object sender, object args)
32+
{
33+
CompositionTarget.Rendering -= Callback;
34+
35+
action();
36+
37+
taskCompletionSource.SetResult(true);
38+
}
39+
40+
CompositionTarget.Rendering += Callback;
41+
}
42+
catch (Exception e)
43+
{
44+
taskCompletionSource.SetException(e); // Note this can just sometimes be a wrong thread exception, see WinUI function notes.
45+
}
46+
47+
return taskCompletionSource.Task;
48+
}
49+
}
50+
}

0 commit comments

Comments
 (0)