TokenizingTextBox should always create a new AutoSuggestTextBox
Describe the bug
I'm using the proposed IsSuggestionListOpen .
But there is something strange happening: after creating a Token, doing a keystroke so that a new SuggestedItems will be shown and then pressing ESC to close that list the AutoSuggestBox will invoke a TextChanged Event out of nowhere - having the text of the last created Token.
My workaround is as follow
- move code to containerize a new
AutoSuggestTextBoxitem ofTokenizingTextBox_CharacterReceivedinto its own function (to avoid duplicate code) - call that code from
TokenizingTextBox_CharacterReceived - in
AddTokenAsyncthe currentTextBox is always removed and a newAutoSuggestTextBoxgets created
TokenizingTextBox.cs
private void UpdateCurrentTextEditAndContainerize(string text, int index)
{
UpdateCurrentTextEdit(new PretokenStringContainer(text)); // Trim so that 'space' isn't inserted and can be used to insert a new box.
_innerItemsSource.Insert(index, _currentTextEdit);
_lastTextEdit = _currentTextEdit;
// Need to wait for containerization
#if WINAPPSDK
_ = DispatcherQueue.EnqueueAsync(
#else
_ = dispatcherQueue.EnqueueAsync(
#endif
() =>
{
if (ContainerFromIndex(index) is TokenizingTextBoxItem newContainer) // Should be our last text box
{
newContainer.UseCharacterAsUser = true; // Make sure we trigger a refresh of suggested items.
void WaitForLoad(object s, RoutedEventArgs eargs)
{
if (newContainer._autoSuggestTextBox != null)
{
newContainer._autoSuggestTextBox.SelectionStart = 1; // Set position to after our new character inserted
newContainer._autoSuggestTextBox.Focus(FocusState.Keyboard);
}
newContainer.Loaded -= WaitForLoad;
}
newContainer.AutoSuggestTextBoxLoaded += WaitForLoad;
}
}, DispatcherQueuePriority.Normal);
}
private async void TokenizingTextBox_CharacterReceived(UIElement sender, CharacterReceivedRoutedEventArgs args)
{
var container = ContainerFromItem(_currentTextEdit) as TokenizingTextBoxItem;
if (container != null && !(GetFocusedElement().Equals(container._autoSuggestTextBox) || char.IsControl(args.Character)))
{
if (SelectedItems.Count > 0)
{
var index = _innerItemsSource.IndexOf(SelectedItems.First());
await RemoveAllSelectedTokens();
// Wait for removal of old items
#if WINAPPSDK
_ = DispatcherQueue.EnqueueAsync(
#else
var dispatcherQueue = DispatcherQueue.GetForCurrentThread();
_ = dispatcherQueue.EnqueueAsync(
#endif
() =>
{
// If we're before the last textbox and it's empty, redirect focus to that one instead
if (index == _innerItemsSource.Count - 1 && string.IsNullOrWhiteSpace(_lastTextEdit.Text))
{
if (ContainerFromItem(_lastTextEdit) is TokenizingTextBoxItem lastContainer)
{
lastContainer.UseCharacterAsUser = true; // Make sure we trigger a refresh of suggested items.
_lastTextEdit.Text = string.Empty + args.Character;
UpdateCurrentTextEdit(_lastTextEdit);
lastContainer._autoSuggestTextBox.SelectionStart = 1; // Set position to after our new character inserted
lastContainer._autoSuggestTextBox.Focus(FocusState.Keyboard);
}
}
else
{
//// Otherwise, create a new textbox for this text.
///
UpdateCurrentTextEditAndContainerize((string.Empty + args.Character).Trim(), index);
}
}, DispatcherQueuePriority.Normal);
}
else
{
// 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)
{
if (ContainerFromIndex(Items.Count - 1) is TokenizingTextBoxItem last) // Should be our last text box
{
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
last._autoSuggestTextBox.Focus(FocusState.Keyboard);
}
}
}
}
}
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 (TokenItemAdding != null)
{
TokenItemAddingEventArgs tiaea;
if (data is string str)
{
tiaea = new TokenItemAddingEventArgs(str);
}
else
{
tiaea = new TokenItemAddingEventArgs(string.Empty);
tiaea.Item = data;
}
await TokenItemAdding.InvokeAsync(this, tiaea);
if (tiaea.Cancel)
{
return;
}
if (tiaea.Item != null)
{
data = tiaea.Item; // Transformed by event implementor
}
}
// If we've been typing in the last box, just add this to the end of our collection
if (atEnd == true || _currentTextEdit == _lastTextEdit)
{
_innerItemsSource.InsertAt(_innerItemsSource.Count - 1, data);
_innerItemsSource.Remove(_currentTextEdit);
}
else
{
// Otherwise, we'll insert before our current box
var edit = _currentTextEdit;
var index = _innerItemsSource.IndexOf(edit);
// Insert our new data item at the location of our textbox
_innerItemsSource.InsertAt(index, data);
// Remove our textbox
_innerItemsSource.Remove(edit);
}
UpdateCurrentTextEditAndContainerize(String.Empty, _innerItemsSource.Count);
TokenItemAdded?.Invoke(this, data);
}
But there is an unwanted side effect: the user can not delete the Token before the new AutoSuggestTextBox by pressing the Delete key directly - putting a character first and then pressing Delete twice works ....
I have no clue why this is the case as having to less understanding of the inner workings of the TokenizingTextBox ....
Steps to reproduce
Does not happen with the current source code.
Expected behavior
The AutoSuggestBox shouldn't fire a TextChanged Event.
Screenshots
No response
Code Platform
- [ ] UWP
- [ ] WinAppSDK / WinUI 3
- [ ] Web Assembly (WASM)
- [ ] Android
- [ ] iOS
- [ ] MacOS
- [ ] Linux / GTK
Windows Build Number
- [ ] Windows 10 1809 (Build 17763)
- [ ] Windows 10 1903 (Build 18362)
- [ ] Windows 10 1909 (Build 18363)
- [ ] Windows 10 2004 (Build 19041)
- [ ] Windows 10 20H2 (Build 19042)
- [ ] Windows 10 21H1 (Build 19043)
- [ ] Windows 10 21H2 (Build 19044)
- [ ] Windows 10 22H2 (Build 19045)
- [X] Windows 11 21H2 (Build 22000)
- [ ] Other (specify)
Other Windows Build number
No response
App minimum and target SDK version
- [ ] Windows 10, version 1809 (Build 17763)
- [ ] Windows 10, version 1903 (Build 18362)
- [ ] Windows 10, version 1909 (Build 18363)
- [ ] Windows 10, version 2004 (Build 19041)
- [ ] Windows 10, version 2104 (Build 20348)
- [ ] Windows 11, version 22H2 (Build 22000)
- [ ] Other (specify)
Other SDK version
No response
Visual Studio Version
No response
Visual Studio Build Number
No response
Device form factor
No response
Additional context
No response
Help us help you
Yes, but only if others can assist.