Skip to content

Commit 8e099de

Browse files
Merge pull request #3319 from windows-toolkit/mhawker/ttb-updates
Mhawker/ttb updates
2 parents aec2ade + f7737e4 commit 8e099de

File tree

8 files changed

+131
-12
lines changed

8 files changed

+131
-12
lines changed

Microsoft.Toolkit.Uwp.SampleApp/SamplePages/TabView/TabViewPage.xaml.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,5 +69,5 @@ private void Tabs_TabDraggedOutside(object sender, TabDraggedOutsideEventArgs e)
6969
TabViewNotification.Show("Tore Tab '" + str + "' Outside of TabView.", 2000);
7070
}
7171
#pragma warning restore CS0618 // Type or member is obsolete
72-
}
72+
}
7373
}

Microsoft.Toolkit.Uwp.SampleApp/SamplePages/TokenizingTextBox/TokenizingTextBoxXaml.bind

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616
<controls:TokenizingTextBox
1717
x:Name="TokenBox"
1818
PlaceholderText="Add Actions"
19-
QueryIcon="{ex:SymbolIconSource Glyph=Setting, FontSize=16}"
19+
QueryIcon="{ex:SymbolIconSource Glyph=Setting}"
2020
MaxHeight="104"
2121
HorizontalAlignment="Stretch"
2222
TextMemberPath="Text"
@@ -52,7 +52,7 @@
5252
PlaceholderText="Select Names"
5353
MaxHeight="104"
5454
HorizontalAlignment="Stretch"
55-
QueryIcon="{ex:SymbolIconSource Glyph=Find, FontSize=16}"
55+
QueryIcon="{ex:SymbolIconSource Glyph=Find}"
5656
TextMemberPath="Text"
5757
TokenDelimiter=","
5858
IsItemClickEnabled="True">

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
}

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
<Thickness x:Key="TokenizingTextBoxPresenterMargin">0,0,6,0</Thickness>
1313
<x:Double x:Key="TokenizingTextBoxTokenSpacing">2</x:Double>
1414
<Thickness x:Key="TextControlBorderThemeThicknessFocused">2</Thickness>
15-
15+
1616
<controls:TokenizingTextBoxStyleSelector x:Key="TokenizingTextBoxStyleSelector"
1717
TextStyle="{StaticResource TokenizingTextBoxItemTextStyle}"
1818
TokenStyle="{StaticResource TokenizingTextBoxItemTokenStyle}"/>

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

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,15 @@ private void OnApplyTemplateAutoSuggestBox(AutoSuggestBox auto)
101101
// Setup a binding to the QueryIcon of the Parent if we're the last box.
102102
if (Content is PretokenStringContainer str && str.IsLast)
103103
{
104+
// Workaround for https://github.com/microsoft/microsoft-ui-xaml/issues/2568
105+
if (Owner.QueryIcon is FontIconSource fis &&
106+
fis.ReadLocalValue(FontIconSource.FontSizeProperty) == DependencyProperty.UnsetValue)
107+
{
108+
// This can be expensive, could we optimize?
109+
// Also, this is changing the FontSize on the IconSource (which could be shared?)
110+
fis.FontSize = Owner.TryFindResource("TokenizingTextBoxIconFontSize") as double? ?? 16;
111+
}
112+
104113
var iconBinding = new Binding()
105114
{
106115
Source = Owner,

Microsoft.Toolkit.Uwp.UI.Controls/TokenizingTextBox/TokenizingTextBoxItem.AutoSuggestBox.xaml

Lines changed: 5 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,8 @@
44

55
<!--<Thickness x:Key="ButtonPadding">8,4,8,5</Thickness> Not sure if we'll also need this later-->
66
<Thickness x:Key="TextControlThemePadding">10,3,6,6</Thickness><!-- Need local copy of this, as including WinUI overrides this to something that adds too much padding for our inner box -->
7-
7+
<x:Double x:Key="TokenizingTextBoxIconFontSize">16</x:Double><!-- 'Example' hard-coded currently in link:TokenizingTextBoxItem.AutoSuggestBox.cs#L110, but can be overwritten in developer's resources as 'normal' -->
8+
89
<!--#region Button Styles -->
910
<Style x:Key="TokenizingTextBoxDeleteButtonStyle"
1011
TargetType="Button">
@@ -15,12 +16,14 @@
1516
Background="{ThemeResource TextControlButtonBackground}"
1617
BorderBrush="{ThemeResource TextControlButtonBorderBrush}"
1718
BorderThickness="{TemplateBinding BorderThickness}">
19+
<!-- FontSize is ignored here, see https://github.com/microsoft/microsoft-ui-xaml/issues/2568 -->
20+
<!-- Set in code-behind link:TokenizingTextBoxItem.AutoSuggestBox.cs#L104 -->
1821
<TextBlock x:Name="GlyphElement"
1922
HorizontalAlignment="Center"
2023
VerticalAlignment="Center"
2124
AutomationProperties.AccessibilityView="Raw"
2225
FontFamily="{ThemeResource SymbolThemeFontFamily}"
23-
FontSize="{ThemeResource AutoSuggestBoxIconFontSize}"
26+
FontSize="{ThemeResource TokenizingTextBoxIconFontSize}"
2427
FontStyle="Normal"
2528
Foreground="{ThemeResource TextControlButtonForeground}"
2629
Text="&#xE10A;" />
@@ -290,17 +293,12 @@
290293
<Style x:Key="TokenizingTextBoxItemTextStyle" TargetType="controls:TokenizingTextBoxItem">
291294
<Setter Property="Background" Value="{ThemeResource SystemControlBackgroundChromeMediumLowBrush}" />
292295
<Setter Property="BorderBrush" Value="{ThemeResource SystemControlTransparentBrush}" />
293-
<!-- <Setter Property="BorderThickness" Value="{StaticResource TokenizingTextBoxItemBorderThickness}" />
294-
<Setter Property="HorizontalContentAlignment" Value="{StaticResource TokenizingTextBoxItemHorizontalContentAlignment}" />
295-
<Setter Property="Padding" Value="{StaticResource TokenizingTextBoxItemPadding}" />
296-
<Setter Property="VerticalContentAlignment" Value="{StaticResource TokenizingTextBoxItemVerticalContentAlignment}" />-->
297296
<Setter Property="IsTabStop" Value="False" />
298297
<Setter Property="UseSystemFocusVisuals" Value="False" />
299298
<Setter Property="MinWidth" Value="128" />
300299
<Setter Property="Template">
301300
<Setter.Value>
302301
<ControlTemplate TargetType="controls:TokenizingTextBoxItem">
303-
<!-- TODO: Thinking we should just give a reference to the parent for each item when we construct the container? -->
304302
<AutoSuggestBox Name="PART_AutoSuggestBox"
305303
UpdateTextOnSelect="False"
306304
DisplayMemberPath="{Binding Path=Owner.DisplayMemberPath, RelativeSource={RelativeSource Mode=TemplatedParent}}"
@@ -311,7 +309,6 @@
311309
Style="{StaticResource SystemAutoSuggestBoxStyle}"
312310
Text="{Binding Text, Mode=TwoWay}"
313311
TextBoxStyle="{StaticResource TokenizingTextBoxTextBoxStyle}"/>
314-
<!-- TODO: Visual State to style autosuggestbox based on focus? -->
315312
</ControlTemplate>
316313
</Setter.Value>
317314
</Setter>

Microsoft.Toolkit.Uwp.UI/Extensions/Tree/LogicalTree.cs

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -286,5 +286,37 @@ private static string ContentPropertySearch(Type type)
286286

287287
return ContentPropertySearch(type.GetTypeInfo().BaseType);
288288
}
289+
290+
/// <summary>
291+
/// Provides a WPF compatible version of TryFindResource to provide a static resource lookup.
292+
/// If the key is not found in the current element's resources, the logical tree is then searched element-by-element to look for the resource in each element's resources.
293+
/// If none of the elements contain the resource, the Application's resources are then searched.
294+
/// <seealso href="https://docs.microsoft.com/en-us/dotnet/api/system.windows.frameworkelement.tryfindresource"/>
295+
/// <seealso href="https://docs.microsoft.com/en-us/dotnet/desktop-wpf/fundamentals/xaml-resources-define#static-resource-lookup-behavior"/>
296+
/// </summary>
297+
/// <param name="start"><see cref="FrameworkElement"/> to start searching for Resource.</param>
298+
/// <param name="resourceKey">Key to search for.</param>
299+
/// <returns>Requested resource or null.</returns>
300+
public static object TryFindResource(this FrameworkElement start, object resourceKey)
301+
{
302+
object value = null;
303+
var current = start;
304+
305+
// Look in our dictionary and then walk-up parents
306+
while (current != null)
307+
{
308+
if (current.Resources?.TryGetValue(resourceKey, out value) == true)
309+
{
310+
return value;
311+
}
312+
313+
current = current.Parent as FrameworkElement;
314+
}
315+
316+
// Finally try application resources.
317+
Application.Current?.Resources?.TryGetValue(resourceKey, out value);
318+
319+
return value;
320+
}
289321
}
290322
}
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)