Skip to content

Commit 86cd4d6

Browse files
committed
Updated Max to Maximum and added token counter
1 parent c3903ab commit 86cd4d6

File tree

7 files changed

+83
-62
lines changed

7 files changed

+83
-62
lines changed

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@
3232
<StackPanel>
3333
<TextBlock FontSize="32" Margin="0,0,0,4">
3434
<Run Text="Select up to" />
35-
<Run Text="{Binding MaxTokens, ElementName=TokenBox, Mode=OneWay}" />
35+
<Run Text="{Binding MaximumTokens, ElementName=TokenBox, Mode=OneWay}" />
3636
<Run Text="actions" />
3737
</TextBlock>
3838
<controls:TokenizingTextBox
@@ -43,7 +43,7 @@
4343
HorizontalAlignment="Stretch"
4444
TextMemberPath="Text"
4545
TokenDelimiter=","
46-
MaxTokens="3">
46+
MaximumTokens="3">
4747
<controls:TokenizingTextBox.SuggestedItemTemplate>
4848
<DataTemplate>
4949
<StackPanel Orientation="Horizontal">

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

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -158,17 +158,17 @@ private static void TextPropertyChanged(DependencyObject d, DependencyPropertyCh
158158
new PropertyMetadata(false));
159159

160160
/// <summary>
161-
/// Identifies the <see cref="MaxTokens"/> property.
161+
/// Identifies the <see cref="MaximumTokens"/> property.
162162
/// </summary>
163-
public static readonly DependencyProperty MaxTokensProperty = DependencyProperty.Register(
164-
nameof(MaxTokens),
163+
public static readonly DependencyProperty MaximumTokensProperty = DependencyProperty.Register(
164+
nameof(MaximumTokens),
165165
typeof(int),
166166
typeof(TokenizingTextBox),
167-
new PropertyMetadata(null, OnMaxTokensChanged));
167+
new PropertyMetadata(null, OnMaximumTokensChanged));
168168

169-
private static void OnMaxTokensChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
169+
private static void OnMaximumTokensChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
170170
{
171-
if (d is TokenizingTextBox ttb && ttb.ReadLocalValue(MaxTokensProperty) != DependencyProperty.UnsetValue && e.NewValue is int newMaxTokens)
171+
if (d is TokenizingTextBox ttb && ttb.ReadLocalValue(MaximumTokensProperty) != DependencyProperty.UnsetValue && e.NewValue is int newMaxTokens)
172172
{
173173
var tokenCount = ttb._innerItemsSource.ItemsSource.Count;
174174
if (tokenCount > 0 && tokenCount > newMaxTokens)
@@ -338,10 +338,10 @@ public string SelectedTokenText
338338
/// <summary>
339339
/// Gets or sets the maximum number of token results allowed at a time.
340340
/// </summary>
341-
public int MaxTokens
341+
public int MaximumTokens
342342
{
343-
get => (int)GetValue(MaxTokensProperty);
344-
set => SetValue(MaxTokensProperty, value);
343+
get => (int)GetValue(MaximumTokensProperty);
344+
set => SetValue(MaximumTokensProperty, value);
345345
}
346346
}
347347
}

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

Lines changed: 3 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -78,10 +78,10 @@ private void ItemsSource_PropertyChanged(DependencyObject sender, DependencyProp
7878
{
7979
_innerItemsSource = new InterspersedObservableCollection(ItemsSource);
8080

81-
if (ReadLocalValue(MaxTokensProperty) != DependencyProperty.UnsetValue && _innerItemsSource.ItemsSource.Count > MaxTokens)
81+
if (ReadLocalValue(MaximumTokensProperty) != DependencyProperty.UnsetValue && _innerItemsSource.ItemsSource.Count > MaximumTokens)
8282
{
8383
// Reduce down to the max as necessary.
84-
for (var i = _innerItemsSource.ItemsSource.Count - 1; i >= Math.Max(MaxTokens, 0); --i)
84+
for (var i = _innerItemsSource.ItemsSource.Count - 1; i >= Math.Max(MaximumTokens, 0); --i)
8585
{
8686
_innerItemsSource.Remove(_innerItemsSource[i]);
8787
}
@@ -440,7 +440,7 @@ public async Task ClearAsync()
440440

441441
internal async Task AddTokenAsync(object data, bool? atEnd = null)
442442
{
443-
if (ReadLocalValue(MaxTokensProperty) != DependencyProperty.UnsetValue && MaxTokens <= 0)
443+
if (ReadLocalValue(MaximumTokensProperty) != DependencyProperty.UnsetValue && (MaximumTokens <= 0 || MaximumTokens <= _innerItemsSource.ItemsSource.Count))
444444
{
445445
// No tokens for you
446446
return;
@@ -465,26 +465,6 @@ internal async Task AddTokenAsync(object data, bool? atEnd = null)
465465
// If we've been typing in the last box, just add this to the end of our collection
466466
if (atEnd == true || _currentTextEdit == _lastTextEdit)
467467
{
468-
if (ReadLocalValue(MaxTokensProperty) != DependencyProperty.UnsetValue && _innerItemsSource.ItemsSource.Count >= MaxTokens)
469-
{
470-
// Remove tokens from the end until below the max number.
471-
for (var i = _innerItemsSource.Count - 2; i >= 0; --i)
472-
{
473-
var item = _innerItemsSource[i];
474-
if (item is not ITokenStringContainer)
475-
{
476-
_innerItemsSource.Remove(item);
477-
TokenItemRemoved?.Invoke(this, item);
478-
479-
// Keep going until we are below the max.
480-
if (_innerItemsSource.ItemsSource.Count < MaxTokens)
481-
{
482-
break;
483-
}
484-
}
485-
}
486-
}
487-
488468
_innerItemsSource.InsertAt(_innerItemsSource.Count - 1, data);
489469
}
490470
else
@@ -493,26 +473,6 @@ internal async Task AddTokenAsync(object data, bool? atEnd = null)
493473
var edit = _currentTextEdit;
494474
var index = _innerItemsSource.IndexOf(edit);
495475

496-
if (ReadLocalValue(MaxTokensProperty) != DependencyProperty.UnsetValue && _innerItemsSource.ItemsSource.Count >= MaxTokens)
497-
{
498-
// Find the next token and remove it, until below the max number of tokens.
499-
for (var i = index; i < _innerItemsSource.Count; i++)
500-
{
501-
var item = _innerItemsSource[i];
502-
if (item is not ITokenStringContainer)
503-
{
504-
_innerItemsSource.Remove(item);
505-
TokenItemRemoved?.Invoke(this, item);
506-
507-
// Keep going until we are below the max.
508-
if (_innerItemsSource.ItemsSource.Count < MaxTokens)
509-
{
510-
break;
511-
}
512-
}
513-
}
514-
}
515-
516476
// Insert our new data item at the location of our textbox
517477
_innerItemsSource.InsertAt(index, data);
518478

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

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,12 @@
44

55
using Windows.Foundation;
66
using Windows.System;
7+
using Windows.UI;
78
using Windows.UI.Xaml;
89
using Windows.UI.Xaml.Controls;
910
using Windows.UI.Xaml.Data;
1011
using Windows.UI.Xaml.Input;
12+
using Windows.UI.Xaml.Media;
1113

1214
namespace Microsoft.Toolkit.Uwp.UI.Controls
1315
{
@@ -16,9 +18,11 @@ namespace Microsoft.Toolkit.Uwp.UI.Controls
1618
/// </summary>
1719
[System.Diagnostics.CodeAnalysis.SuppressMessage("StyleCop.CSharp.ReadabilityRules", "SA1124:Do not use regions", Justification = "Organization")]
1820
[TemplatePart(Name = PART_AutoSuggestBox, Type = typeof(AutoSuggestBox))] //// String case
21+
[TemplatePart(Name = PART_TokensCounter, Type = typeof(TextBlock))]
1922
public partial class TokenizingTextBoxItem
2023
{
2124
private const string PART_AutoSuggestBox = "PART_AutoSuggestBox";
25+
private const string PART_TokensCounter = "PART_TokensCounter";
2226

2327
private AutoSuggestBox _autoSuggestBox;
2428

@@ -231,6 +235,8 @@ private void AutoSuggestBox_GotFocus(object sender, RoutedEventArgs e)
231235
#region Inner TextBox
232236
private void OnASBLoaded(object sender, RoutedEventArgs e)
233237
{
238+
UpdateTokensCounter(this);
239+
234240
// Local function for Selection changed
235241
void AutoSuggestTextBox_SelectionChanged(object box, RoutedEventArgs args)
236242
{
@@ -329,6 +335,47 @@ private void AutoSuggestTextBox_PreviewKeyDown(object sender, KeyRoutedEventArgs
329335
Owner.SelectAllTokensAndText();
330336
}
331337
}
338+
339+
private void UpdateTokensCounter(TokenizingTextBoxItem ttbi)
340+
{
341+
var maxTokensCounter = (TextBlock)_autoSuggestBox?.FindDescendant(PART_TokensCounter);
342+
if (maxTokensCounter == null)
343+
{
344+
return;
345+
}
346+
347+
void OnTokenCountChanged(TokenizingTextBox ttb, object value = null)
348+
{
349+
var itemsSource = ttb.ItemsSource as InterspersedObservableCollection;
350+
var currentTokens = itemsSource.ItemsSource.Count;
351+
var maxTokens = ttb.MaximumTokens;
352+
353+
maxTokensCounter.Text = $"{currentTokens}/{maxTokens}";
354+
maxTokensCounter.Visibility = Visibility.Visible;
355+
356+
maxTokensCounter.Foreground = (currentTokens == maxTokens)
357+
? new SolidColorBrush(Colors.Red)
358+
: _autoSuggestBox.Foreground;
359+
}
360+
361+
ttbi.Owner.TokenItemAdded -= OnTokenCountChanged;
362+
ttbi.Owner.TokenItemRemoved -= OnTokenCountChanged;
363+
364+
// I would have like to compared to DependencyProperty.UnsetValue, but MaximumTokensProperty value is returning 0 even though we didn't set it!
365+
// This means that the token counter will not show up for a specified maximum value of 0. However, it's a pretty uncommon scenario to offer a picker
366+
// with no ability to add items. If the case does arrive where the ttb should be unusable by design, developers should disable the control instead or setting the maximum to 0.
367+
if (Content is ITokenStringContainer str && str.IsLast && ttbi?.Owner != null && (int)ttbi.Owner.GetValue(TokenizingTextBox.MaximumTokensProperty) > 0)
368+
{
369+
ttbi.Owner.TokenItemAdded += OnTokenCountChanged;
370+
ttbi.Owner.TokenItemRemoved += OnTokenCountChanged;
371+
OnTokenCountChanged(ttbi.Owner);
372+
}
373+
else
374+
{
375+
maxTokensCounter.Visibility = Visibility.Collapsed;
376+
maxTokensCounter.Text = string.Empty;
377+
}
378+
}
332379
#endregion
333380
}
334381
}

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

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,7 @@
137137
<ColumnDefinition Width="*" />
138138
<ColumnDefinition Width="Auto" />
139139
<ColumnDefinition Width="Auto" />
140+
<ColumnDefinition Width="Auto" />
140141
</Grid.ColumnDefinitions>
141142
<Grid.RowDefinitions>
142143
<RowDefinition Height="Auto" />
@@ -176,7 +177,7 @@
176177
ZoomMode="Disabled" />
177178
<ContentControl x:Name="PlaceholderTextContentPresenter"
178179
Grid.Row="1"
179-
Grid.ColumnSpan="3"
180+
Grid.ColumnSpan="2"
180181
Margin="{TemplateBinding BorderThickness}"
181182
Padding="{TemplateBinding Padding}"
182183
Content="{TemplateBinding PlaceholderText}"
@@ -194,9 +195,15 @@
194195
IsTabStop="False"
195196
Style="{StaticResource TokenizingTextBoxDeleteButtonStyle}"
196197
Visibility="Collapsed" />
198+
199+
<TextBlock Name="PART_TokensCounter"
200+
Grid.Row="1"
201+
Grid.Column="2"
202+
Margin="0,4,0,0" />
203+
197204
<Button x:Name="QueryButton"
198205
Grid.Row="1"
199-
Grid.Column="2"
206+
Grid.Column="3"
200207
Width="{TemplateBinding Height}"
201208
MinWidth="30"
202209
VerticalAlignment="Stretch"

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

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@
44

55
using Windows.Foundation;
66
using Windows.System;
7-
using Windows.UI.Core;
87
using Windows.UI.Xaml;
98
using Windows.UI.Xaml.Controls;
109
using Windows.UI.Xaml.Controls.Primitives;

UnitTests/UnitTests.UWP/UI/Controls/Test_TokenizingTextBox_General.cs

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@ public void Test_Clear()
6666

6767
[TestCategory("Test_TokenizingTextBox_General")]
6868
[UITestMethod]
69-
public void Test_MaxTokens()
69+
public void Test_MaximumTokens()
7070
{
7171
var maxTokens = 2;
7272

@@ -76,7 +76,7 @@ public void Test_MaxTokens()
7676
xmlns:x=""http://schemas.microsoft.com/winfx/2006/xaml""
7777
xmlns:controls=""using:Microsoft.Toolkit.Uwp.UI.Controls"">
7878
79-
<controls:TokenizingTextBox x:Name=""tokenboxname"" MaxTokens=""{maxTokens}"">
79+
<controls:TokenizingTextBox x:Name=""tokenboxname"" MaximumTokens=""{maxTokens}"">
8080
</controls:TokenizingTextBox>
8181
8282
</Page>") as FrameworkElement;
@@ -87,23 +87,31 @@ public void Test_MaxTokens()
8787

8888
Assert.IsNotNull(tokenBox, "Could not find TokenizingTextBox in tree.");
8989

90+
// Items includes the text fields as well, so we can expect at least one item to exist initially, the input box.
91+
// Use the starting count as an offset.
9092
var startingItemsCount = tokenBox.Items.Count;
9193

94+
// Add two items.
9295
tokenBox.AddTokenItem("TokenItem1");
9396
tokenBox.AddTokenItem("TokenItem2");
9497

98+
// Make sure we have the appropriate amount of items and that they are in the appropriate order.
9599
Assert.AreEqual(startingItemsCount + maxTokens, tokenBox.Items.Count, "Token Add failed");
96100
Assert.AreEqual("TokenItem1", tokenBox.Items[0]);
97101
Assert.AreEqual("TokenItem2", tokenBox.Items[1]);
98102

103+
// Attempt to add an additional item, beyond the maximum.
99104
tokenBox.AddTokenItem("TokenItem3");
100105

101-
Assert.AreEqual(startingItemsCount + maxTokens, tokenBox.Items.Count, "Token Replace failed");
106+
// Check that the number of items did not change, because the maximum number of items are already present.
107+
Assert.AreEqual(startingItemsCount + maxTokens, tokenBox.Items.Count, "Token Add succeeded, where it should have failed.");
102108
Assert.AreEqual("TokenItem1", tokenBox.Items[0]);
103-
Assert.AreEqual("TokenItem3", tokenBox.Items[1]);
109+
Assert.AreEqual("TokenItem2", tokenBox.Items[1]);
104110

105-
tokenBox.MaxTokens = 1;
111+
// Reduce the maximum number of tokens.
112+
tokenBox.MaximumTokens = 1;
106113

114+
// The last token should be removed to account for the reduced maximum.
107115
Assert.AreEqual(startingItemsCount + 1, tokenBox.Items.Count);
108116
Assert.AreEqual("TokenItem1", tokenBox.Items[0]);
109117
}

0 commit comments

Comments
 (0)