Replaced TokenSelectionMode with MaxTokens property on TokenizingTextBox

This commit is contained in:
Shane Weaver 2021-08-05 16:32:49 -07:00
Родитель fbeb2c4cf6
Коммит 93ca18ad9d
3 изменённых файлов: 96 добавлений и 47 удалений

Просмотреть файл

@ -30,7 +30,7 @@
<RowDefinition/>
</Grid.RowDefinitions>
<StackPanel>
<TextBlock FontSize="32" Text="Select Actions"
<TextBlock FontSize="32" Text="Select up to 3 Actions"
Margin="0,0,0,4"/>
<controls:TokenizingTextBox
x:Name="TokenBox"
@ -40,7 +40,7 @@
HorizontalAlignment="Stretch"
TextMemberPath="Text"
TokenDelimiter=","
TokenSelectionMode="Single">
MaxTokens="3">
<controls:TokenizingTextBox.SuggestedItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
@ -76,7 +76,6 @@
QueryIcon="{ui:SymbolIconSource Symbol=Find}"
TextMemberPath="Text"
TokenDelimiter=","
TokenSelectionMode="Multiple"
IsItemClickEnabled="True"
TokenItemTemplate="{StaticResource EmailTokenTemplate}">
</controls:TokenizingTextBox>

Просмотреть файл

@ -158,27 +158,40 @@ namespace Microsoft.Toolkit.Uwp.UI.Controls
new PropertyMetadata(false));
/// <summary>
/// Identifies the <see cref="TokenSelectionMode"/> property.
/// Identifies the <see cref="MaxTokens"/> property.
/// </summary>
public static readonly DependencyProperty TokenSelectionModeProperty = DependencyProperty.Register(
nameof(TokenSelectionMode),
typeof(TokenSelectionMode),
public static readonly DependencyProperty MaxTokensProperty = DependencyProperty.Register(
nameof(MaxTokens),
typeof(int?),
typeof(TokenizingTextBox),
new PropertyMetadata(TokenSelectionMode.Multiple, OnTokenSelectionModeChanged));
new PropertyMetadata(null, OnMaxTokensChanged));
private static void OnTokenSelectionModeChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
private static void OnMaxTokensChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if (d is TokenizingTextBox ttb && e.NewValue is TokenSelectionMode newTokenSelectionMode && newTokenSelectionMode == TokenSelectionMode.Single)
if (d is TokenizingTextBox ttb && e.NewValue is int newMaxTokens)
{
// Start at the end, remove all but the first token.
for (var i = ttb._innerItemsSource.Count - 1; i >= 1; --i)
var tokenCount = ttb.Items.Count;
if (tokenCount > newMaxTokens)
{
var item = ttb._innerItemsSource[i];
if (item is not ITokenStringContainer)
int tokensToRemove = newMaxTokens - tokenCount;
var tokensRemoved = 0;
// Start at the end, remove any extra tokens.
for (var i = ttb._innerItemsSource.Count - 1; i >= 0; --i)
{
// Force remove the items. No warning and no option to cancel.
ttb._innerItemsSource.Remove(item);
ttb.TokenItemRemoved?.Invoke(ttb, item);
var item = ttb._innerItemsSource[i];
if (item is not ITokenStringContainer)
{
// Force remove the items. No warning and no option to cancel.
ttb._innerItemsSource.Remove(item);
ttb.TokenItemRemoved?.Invoke(ttb, item);
tokensRemoved++;
if (tokensRemoved == tokensToRemove)
{
break;
}
}
}
}
}
@ -332,14 +345,12 @@ namespace Microsoft.Toolkit.Uwp.UI.Controls
}
/// <summary>
/// Gets or sets how the control should display tokens.
/// <see cref="TokenSelectionMode.Multiple"/> is the default. Multiple tokens can be selected at a time.
/// <see cref="TokenSelectionMode.Single"/> indicates that only one token can be present in the control at a time.
/// Gets or sets the maximum number of token results allowed at a time.
/// </summary>
public TokenSelectionMode TokenSelectionMode
public int? MaxTokens
{
get => (TokenSelectionMode)GetValue(TokenSelectionModeProperty);
set => SetValue(TokenSelectionModeProperty, value);
get => (int?)GetValue(MaxTokensProperty);
set => SetValue(MaxTokensProperty, value);
}
}
}

Просмотреть файл

@ -77,6 +77,16 @@ namespace Microsoft.Toolkit.Uwp.UI.Controls
if (ItemsSource != null && ItemsSource.GetType() != typeof(InterspersedObservableCollection))
{
_innerItemsSource = new InterspersedObservableCollection(ItemsSource);
if (MaxTokens.HasValue && _innerItemsSource.ItemsSource.Count > MaxTokens)
{
// Reduce down to the max as necessary.
for (var i = _innerItemsSource.ItemsSource.Count; i > MaxTokens; --i)
{
_innerItemsSource.Remove(_innerItemsSource[i]);
}
}
_currentTextEdit = _lastTextEdit = new PretokenStringContainer(true);
_innerItemsSource.Insert(_innerItemsSource.Count, _currentTextEdit);
ItemsSource = _innerItemsSource;
@ -278,18 +288,16 @@ namespace Microsoft.Toolkit.Uwp.UI.Controls
}
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 +440,12 @@ namespace Microsoft.Toolkit.Uwp.UI.Controls
internal async Task AddTokenAsync(object data, bool? atEnd = null)
{
if (MaxTokens == 0)
{
// No tokens for you
return;
}
if (data is string str && TokenItemAdding != null)
{
var tiaea = new TokenItemAddingEventArgs(str);
@ -448,24 +462,29 @@ namespace Microsoft.Toolkit.Uwp.UI.Controls
}
}
if (TokenSelectionMode == TokenSelectionMode.Single)
{
// Start at the end, remove any existing tokens.
for (var i = _innerItemsSource.Count - 1; i >= 0; --i)
{
var item = _innerItemsSource[i];
if (item is not ITokenStringContainer)
{
// Force remove the items. No warning and no option to cancel.
_innerItemsSource.Remove(item);
TokenItemRemoved?.Invoke(this, item);
}
}
}
// If we've been typing in the last box, just add this to the end of our collection
if (atEnd == true || _currentTextEdit == _lastTextEdit)
{
if (MaxTokens != null && _innerItemsSource.ItemsSource.Count >= MaxTokens)
{
// Remove tokens from the end until below the max number.
for (var i = _innerItemsSource.Count - 2; i >= 0; --i)
{
var item = _innerItemsSource[i];
if (item is not ITokenStringContainer)
{
_innerItemsSource.Remove(item);
TokenItemRemoved?.Invoke(this, item);
// Keep going until we are below the max.
if (_innerItemsSource.ItemsSource.Count < MaxTokens)
{
break;
}
}
}
}
_innerItemsSource.InsertAt(_innerItemsSource.Count - 1, data);
}
else
@ -474,6 +493,26 @@ namespace Microsoft.Toolkit.Uwp.UI.Controls
var edit = _currentTextEdit;
var index = _innerItemsSource.IndexOf(edit);
if (MaxTokens != null && _innerItemsSource.ItemsSource.Count >= MaxTokens)
{
// Find the next token and remove it, until below the max number of tokens.
for (var i = index; i < _innerItemsSource.Count; i++)
{
var item = _innerItemsSource[i];
if (item is not ITokenStringContainer)
{
_innerItemsSource.Remove(item);
TokenItemRemoved?.Invoke(this, item);
// Keep going until we are below the max.
if (_innerItemsSource.ItemsSource.Count < MaxTokens)
{
break;
}
}
}
}
// Insert our new data item at the location of our textbox
_innerItemsSource.InsertAt(index, data);