Make TokenizingTextBox more flexible
Describe the problem
The current model only allows a single use case: adding text as AddTokenAsync() data has to be a string. Otherwise the TokenItemAdding event isn't raised and there is no chance to change the Item to be added.
By always raising the TokenItemAdding event the TokenizingTextBox could be used in ways not possible without.
Also needed: having the possibility to access the AutoSuggestBox IsSuggestionListOpen property...
Describe the solution
TokenizingTextBox.cs
if data is not a string, call the event with string.Empty and the item set to data... should be compatible this way with existing code.
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);
}
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);
}
// Focus back to our end box as Outlook does.
var last = ContainerFromItem(_lastTextEdit) as TokenizingTextBoxItem;
last?._autoSuggestTextBox.Focus(FocusState.Keyboard);
TokenItemAdded?.Invoke(this, data);
GuardAgainstPlaceholderTextLayoutIssue();
}
public bool IsSuggestionListOpen
{
get => (ContainerFromItem(_lastTextEdit) is TokenizingTextBoxItem lastContainer) ? lastContainer.IsSuggestionListOpen : false;
set
{
if (ContainerFromItem(_lastTextEdit) is TokenizingTextBoxItem lastContainer) lastContainer.IsSuggestionListOpen = value;
}
}
TokenizingTextBoxItem.AutoSuggestBox.cs
internal bool IsSuggestionListOpen
{
get => (_autoSuggestBox != null) ? _autoSuggestBox.IsSuggestionListOpen : false;
set { if (_autoSuggestBox != null) _autoSuggestBox.IsSuggestionListOpen = value; }
}
Alternatives
None
Additional info
Sorry for not providing a pull request.
Spent hours integrating the needed source files into my project in Visual Studio... . "total mess" for someone new to Windows development just wanting to code and having no clue about the tool chain.
Help us help you
None
Hello, 'minesworld! Thanks for submitting a new feature request. I've automatically added a vote 👍 reaction to help get things started. Other community members can vote to help us prioritize this feature in the future!
About the compability with existing code:
It would be nice to notice the users of TokenizingTextBox that they should check in their event handler for the case that the TokenItemAddingEventArgs TokenText is string.Empty and the Item is set instead...
The solution shouldn't break existing code - but better "no surprises" for sure.
moved this to the new repository
@minesworld I'm confused, AddTokenItem allows you to directly pass in whatever data object you want already:
https://github.com/CommunityToolkit/Windows/blob/82eb9a9e066c877ff2eb31768cb2ea995f1af0b7/components/TokenizingTextBox/src/TokenizingTextBox.cs#L466
And in that case, you don't need to transform it. The call to TokenItemAdding for string is for when the user types in something directly into the box which needs to be mapped to your programmatic model within an application.
If you want to programmatically add a specific item, you can do whatever transform you need to first, and then call AddTokenItem with the resultant object of whatever type you have.
Can you elaborate on your scenario and why the above isn't sufficient? Thanks!
Per default I'm presenting the user a List of Templates to choose from. So the choosen data should NOT be inserted. The TokenItemAdding event handler could Cancel the event, but then nothing gets inserted. Of course it can call the AddTokenItem directly or maybe using the DispatcherQueue.
Might have to check that, don't remember if I've tried AddTokenItem within the TokenItemAdding event handler - but its easier to use it this way...
You're right. its even easier - by replacing the Token in an TokenItemAdded handler with another one using directly theTokenizingTextBox ItemsSource .
Using AddTokenItem in the TokenItemAdded event handler would raise that event again...
But : to replace an added Token with Text - setting the TokenizingTextBox Text has no effect within the TokenItemAdded handler.
To be able to do this this must be done by handling the LostFocus / GotFocus handlers and doing it there. To be sure that the new AutoSuggestBox has the focus the GetIsAutoSuggestTextBoxFocused() ( #259 ) method is needed... (i think)
The SuggestionListOpen is really needed - have made a pull request.
I have to revise my comment. It is a lot more code not having the AddTokenAsync always fire an TokenItemAdding event. Its near impossible to archive the same desired behaviour.
I will provide within the next days (hopefully) a working example what I am doing with the TokenizingTextBox - then you might see why this call is such usefull at least to me. But I think when others see what is possible they might to want to use it too...