CommunityToolkit / CommunityToolkit/Windows

TokenizingTextBox should always create a new AutoSuggestTextBox

Aperta
#260 0 commenti 0 reazioni 0 assegnatari Vedi su GitHub
bug components::controls::tokenizingtextbox
Lingua principale
C#
Stelle
1.1k
Fork
166
Metriche di merge delle PR
Nessuna PR unita negli ultimi 30g

Descrizione

### Describe the bug

I'm using the proposed [`IsSuggestionListOpen`](https://github.com/CommunityToolkit/Windows/issues/256) .

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 `AutoSuggestTextBox `item of `TokenizingTextBox_CharacterReceived` into its own function (to avoid duplicate code)
- call that code from `TokenizingTextBox_CharacterReceived`
- in `AddTokenAsync `the currentTextBox is always removed and a new `AutoSuggestTextBox `gets 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

```text
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.

Guida per i contributori

Apri la guida per i contributori

Direzione di ricerca

Inizia in TokenizingTextBox.cs tracciando AddTokenAsync, TokenizingTextBox_CharacterReceived e UpdateCurrentTextEditAndContainerize. Riproduci la sequenza di token, elenco dei suggerimenti, ESC e Delete-key con il codice sorgente attuale; è completato quando AutoSuggestBox non emette l’evento TextChanged imprevisto e l’eliminazione dei token continua a funzionare direttamente.

Scritto dal modello di indicizzazione a partire dal testo della issue.

Valutazione

Stack tecnologico
csharp
Ambito
desktop, frontend
Tipo di issue
Bug
Difficoltà
4/5
Tempo stimato
3-5 giorni
Stato di attività
Ferma
Chiarezza
Da chiarire
Idoneità per principianti
25/100

Ricevi le nuove issue nella tua casella

Un breve riepilogo di issue GitHub adatte ai principianti.