diff --git a/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/TokenizingTextBox/TokenizingTextBoxXaml.bind b/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/TokenizingTextBox/TokenizingTextBoxXaml.bind
index b38058a507d..fca7b60e1de 100644
--- a/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/TokenizingTextBox/TokenizingTextBoxXaml.bind
+++ b/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/TokenizingTextBox/TokenizingTextBoxXaml.bind
@@ -30,8 +30,11 @@
-
+
+
+
+
+
+ TokenDelimiter=","
+ MaximumTokens="3">
diff --git a/Microsoft.Toolkit.Uwp.UI.Controls.Input/TokenizingTextBox/TokenizingTextBox.Properties.cs b/Microsoft.Toolkit.Uwp.UI.Controls.Input/TokenizingTextBox/TokenizingTextBox.Properties.cs
index d6099b8596e..1a5a1e36b89 100644
--- a/Microsoft.Toolkit.Uwp.UI.Controls.Input/TokenizingTextBox/TokenizingTextBox.Properties.cs
+++ b/Microsoft.Toolkit.Uwp.UI.Controls.Input/TokenizingTextBox/TokenizingTextBox.Properties.cs
@@ -157,6 +157,37 @@ private static void TextPropertyChanged(DependencyObject d, DependencyPropertyCh
typeof(TokenizingTextBox),
new PropertyMetadata(false));
+ ///
+ /// Identifies the property.
+ ///
+ public static readonly DependencyProperty MaximumTokensProperty = DependencyProperty.Register(
+ nameof(MaximumTokens),
+ typeof(int),
+ typeof(TokenizingTextBox),
+ new PropertyMetadata(null, OnMaximumTokensChanged));
+
+ private static void OnMaximumTokensChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
+ {
+ if (d is TokenizingTextBox ttb && ttb.ReadLocalValue(MaximumTokensProperty) != DependencyProperty.UnsetValue && e.NewValue is int newMaxTokens)
+ {
+ var tokenCount = ttb._innerItemsSource.ItemsSource.Count;
+ if (tokenCount > 0 && tokenCount > newMaxTokens)
+ {
+ int tokensToRemove = tokenCount - Math.Max(newMaxTokens, 0);
+
+ // Start at the end, remove any extra tokens.
+ for (var i = tokenCount; i > tokenCount - tokensToRemove; --i)
+ {
+ var token = ttb._innerItemsSource.ItemsSource[i - 1];
+
+ // Force remove the items. No warning and no option to cancel.
+ ttb._innerItemsSource.Remove(token);
+ ttb.TokenItemRemoved?.Invoke(ttb, token);
+ }
+ }
+ }
+ }
+
///
/// Gets or sets the Style for the contained AutoSuggestBox template part.
///
@@ -303,5 +334,14 @@ public string SelectedTokenText
return PrepareSelectionForClipboard();
}
}
+
+ ///
+ /// Gets or sets the maximum number of token results allowed at a time.
+ ///
+ public int MaximumTokens
+ {
+ get => (int)GetValue(MaximumTokensProperty);
+ set => SetValue(MaximumTokensProperty, value);
+ }
}
-}
\ No newline at end of file
+}
diff --git a/Microsoft.Toolkit.Uwp.UI.Controls.Input/TokenizingTextBox/TokenizingTextBox.cs b/Microsoft.Toolkit.Uwp.UI.Controls.Input/TokenizingTextBox/TokenizingTextBox.cs
index 89474a89fc6..bf4d2567a0f 100644
--- a/Microsoft.Toolkit.Uwp.UI.Controls.Input/TokenizingTextBox/TokenizingTextBox.cs
+++ b/Microsoft.Toolkit.Uwp.UI.Controls.Input/TokenizingTextBox/TokenizingTextBox.cs
@@ -77,6 +77,17 @@ private void ItemsSource_PropertyChanged(DependencyObject sender, DependencyProp
if (ItemsSource != null && ItemsSource.GetType() != typeof(InterspersedObservableCollection))
{
_innerItemsSource = new InterspersedObservableCollection(ItemsSource);
+
+ if (ReadLocalValue(MaximumTokensProperty) != DependencyProperty.UnsetValue && _innerItemsSource.ItemsSource.Count >= MaximumTokens)
+ {
+ // Reduce down to below the max as necessary.
+ var endCount = MaximumTokens > 0 ? MaximumTokens : 0;
+ for (var i = _innerItemsSource.ItemsSource.Count - 1; i >= endCount; --i)
+ {
+ _innerItemsSource.Remove(_innerItemsSource[i]);
+ }
+ }
+
_currentTextEdit = _lastTextEdit = new PretokenStringContainer(true);
_innerItemsSource.Insert(_innerItemsSource.Count, _currentTextEdit);
ItemsSource = _innerItemsSource;
@@ -278,18 +289,16 @@ void WaitForLoad(object s, RoutedEventArgs eargs)
}
else
{
- // TODO: It looks like we're setting selection and focus together on items? Not sure if that's what we want...
- // If that's the case, don't think this code will ever be called?
-
- //// TODO: Behavior question: if no items selected (just focus) does it just go to our last active textbox?
- //// Community voted that typing in the end box made sense
-
+ // If no items are selected, send input to the last active string container.
+ // This code is only fires during an edgecase where an item is in the process of being deleted and the user inputs a character before the focus has been redirected to a string container.
if (_innerItemsSource[_innerItemsSource.Count - 1] is ITokenStringContainer textToken)
{
var last = ContainerFromIndex(Items.Count - 1) as TokenizingTextBoxItem; // Should be our last text box
- var position = last._autoSuggestTextBox.SelectionStart;
- textToken.Text = last._autoSuggestTextBox.Text.Substring(0, position) + args.Character +
- last._autoSuggestTextBox.Text.Substring(position);
+ var text = last._autoSuggestTextBox.Text;
+ var selectionStart = last._autoSuggestTextBox.SelectionStart;
+ var position = selectionStart > text.Length ? text.Length : selectionStart;
+ textToken.Text = text.Substring(0, position) + args.Character +
+ text.Substring(position);
last._autoSuggestTextBox.SelectionStart = position + 1; // Set position to after our new character inserted
@@ -432,6 +441,12 @@ public async Task ClearAsync()
internal async Task AddTokenAsync(object data, bool? atEnd = null)
{
+ if (ReadLocalValue(MaximumTokensProperty) != DependencyProperty.UnsetValue && (MaximumTokens <= 0 || MaximumTokens <= _innerItemsSource.ItemsSource.Count))
+ {
+ // No tokens for you
+ return;
+ }
+
if (data is string str && TokenItemAdding != null)
{
var tiaea = new TokenItemAddingEventArgs(str);
diff --git a/Microsoft.Toolkit.Uwp.UI.Controls.Input/TokenizingTextBox/TokenizingTextBoxItem.AutoSuggestBox.cs b/Microsoft.Toolkit.Uwp.UI.Controls.Input/TokenizingTextBox/TokenizingTextBoxItem.AutoSuggestBox.cs
index ebd64de2539..5a95f5b4b45 100644
--- a/Microsoft.Toolkit.Uwp.UI.Controls.Input/TokenizingTextBox/TokenizingTextBoxItem.AutoSuggestBox.cs
+++ b/Microsoft.Toolkit.Uwp.UI.Controls.Input/TokenizingTextBox/TokenizingTextBoxItem.AutoSuggestBox.cs
@@ -4,10 +4,12 @@
using Windows.Foundation;
using Windows.System;
+using Windows.UI;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Data;
using Windows.UI.Xaml.Input;
+using Windows.UI.Xaml.Media;
namespace Microsoft.Toolkit.Uwp.UI.Controls
{
@@ -16,9 +18,11 @@ namespace Microsoft.Toolkit.Uwp.UI.Controls
///
[System.Diagnostics.CodeAnalysis.SuppressMessage("StyleCop.CSharp.ReadabilityRules", "SA1124:Do not use regions", Justification = "Organization")]
[TemplatePart(Name = PART_AutoSuggestBox, Type = typeof(AutoSuggestBox))] //// String case
+ [TemplatePart(Name = PART_TokensCounter, Type = typeof(TextBlock))]
public partial class TokenizingTextBoxItem
{
private const string PART_AutoSuggestBox = "PART_AutoSuggestBox";
+ private const string PART_TokensCounter = "PART_TokensCounter";
private AutoSuggestBox _autoSuggestBox;
@@ -231,6 +235,8 @@ private void AutoSuggestBox_GotFocus(object sender, RoutedEventArgs e)
#region Inner TextBox
private void OnASBLoaded(object sender, RoutedEventArgs e)
{
+ UpdateTokensCounter(this);
+
// Local function for Selection changed
void AutoSuggestTextBox_SelectionChanged(object box, RoutedEventArgs args)
{
@@ -329,6 +335,44 @@ private void AutoSuggestTextBox_PreviewKeyDown(object sender, KeyRoutedEventArgs
Owner.SelectAllTokensAndText();
}
}
+
+ private void UpdateTokensCounter(TokenizingTextBoxItem ttbi)
+ {
+ var maxTokensCounter = (TextBlock)_autoSuggestBox?.FindDescendant(PART_TokensCounter);
+ if (maxTokensCounter == null)
+ {
+ return;
+ }
+
+ void OnTokenCountChanged(TokenizingTextBox ttb, object value = null)
+ {
+ var itemsSource = ttb.ItemsSource as InterspersedObservableCollection;
+ var currentTokens = itemsSource.ItemsSource.Count;
+ var maxTokens = ttb.MaximumTokens;
+
+ maxTokensCounter.Text = $"{currentTokens}/{maxTokens}";
+ maxTokensCounter.Visibility = Visibility.Visible;
+
+ maxTokensCounter.Foreground = (currentTokens >= maxTokens)
+ ? new SolidColorBrush(Colors.Red)
+ : _autoSuggestBox.Foreground;
+ }
+
+ ttbi.Owner.TokenItemAdded -= OnTokenCountChanged;
+ ttbi.Owner.TokenItemRemoved -= OnTokenCountChanged;
+
+ if (Content is ITokenStringContainer str && str.IsLast && ttbi?.Owner != null && ttbi.Owner.ReadLocalValue(TokenizingTextBox.MaximumTokensProperty) != DependencyProperty.UnsetValue)
+ {
+ ttbi.Owner.TokenItemAdded += OnTokenCountChanged;
+ ttbi.Owner.TokenItemRemoved += OnTokenCountChanged;
+ OnTokenCountChanged(ttbi.Owner);
+ }
+ else
+ {
+ maxTokensCounter.Visibility = Visibility.Collapsed;
+ maxTokensCounter.Text = string.Empty;
+ }
+ }
#endregion
}
}
\ No newline at end of file
diff --git a/Microsoft.Toolkit.Uwp.UI.Controls.Input/TokenizingTextBox/TokenizingTextBoxItem.AutoSuggestBox.xaml b/Microsoft.Toolkit.Uwp.UI.Controls.Input/TokenizingTextBox/TokenizingTextBoxItem.AutoSuggestBox.xaml
index e0b9725492f..26dd6f69844 100644
--- a/Microsoft.Toolkit.Uwp.UI.Controls.Input/TokenizingTextBox/TokenizingTextBoxItem.AutoSuggestBox.xaml
+++ b/Microsoft.Toolkit.Uwp.UI.Controls.Input/TokenizingTextBox/TokenizingTextBoxItem.AutoSuggestBox.xaml
@@ -137,6 +137,7 @@
+
@@ -176,7 +177,7 @@
ZoomMode="Disabled" />
+
+
+