private void ItemsSource_PropertyChanged(DependencyObject sender, DependencyProperty dp) { // If we're given a different ItemsSource, we need to wrap that collection in our helper class. if (ItemsSource != null && ItemsSource.GetType() != typeof(InterspersedObservableCollection)) { _innerItemsSource = new InterspersedObservableCollection(ItemsSource); _currentTextEdit = _lastTextEdit = new PretokenStringContainer(true); _innerItemsSource.Insert(_innerItemsSource.Count, _currentTextEdit); ItemsSource = _innerItemsSource; } }
/// <summary> /// Initializes a new instance of the <see cref="TokenizingTextBox"/> class. /// </summary> public TokenizingTextBox() { // Setup our base state of our collection _innerItemsSource = new InterspersedObservableCollection(new ObservableCollection <object>()); // TODO: Test this still will let us bind to ItemsSource in XAML? _currentTextEdit = _lastTextEdit = new PretokenStringContainer(true); _innerItemsSource.Insert(_innerItemsSource.Count, _currentTextEdit); ItemsSource = _innerItemsSource; //// TODO: Consolidate with callback below for ItemsSourceProperty changed? DefaultStyleKey = typeof(TokenizingTextBox); // TODO: Do we want to support ItemsSource better? Need to investigate how that works with adding... RegisterPropertyChangedCallback(ItemsSourceProperty, ItemsSource_PropertyChanged); PreviewKeyDown += TokenizingTextBox_PreviewKeyDown; PreviewKeyUp += TokenizingTextBox_PreviewKeyUp; CharacterReceived += TokenizingTextBox_CharacterReceived; ItemClick += TokenizingTextBox_ItemClick; }
private void ItemsSource_PropertyChanged(DependencyObject sender, DependencyProperty dp) { // If we're given a different ItemsSource, we need to wrap that collection in our helper class. 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; } }
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 _ = Dispatcher.RunAsync(CoreDispatcherPriority.Normal, () => { // 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)) { var lastContainer = ContainerFromItem(_lastTextEdit) as TokenizingTextBoxItem; 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. UpdateCurrentTextEdit(new PretokenStringContainer((string.Empty + args.Character).Trim())); // Trim so that 'space' isn't inserted and can be used to insert a new box. _innerItemsSource.Insert(index, _currentTextEdit); // Need to wait for containerization _ = Dispatcher.RunAsync(CoreDispatcherPriority.Normal, () => { var newContainer = ContainerFromIndex(index) as TokenizingTextBoxItem; // 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; }); } }); } 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 (_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); last._autoSuggestTextBox.SelectionStart = position + 1; // Set position to after our new character inserted last._autoSuggestTextBox.Focus(FocusState.Keyboard); } } } }
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 var dispatcherQueue = DispatcherQueue.GetForCurrentThread(); _ = dispatcherQueue.EnqueueAsync( () => { // 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)) { var lastContainer = ContainerFromItem(_lastTextEdit) as TokenizingTextBoxItem; 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. UpdateCurrentTextEdit(new PretokenStringContainer((string.Empty + args.Character).Trim())); // Trim so that 'space' isn't inserted and can be used to insert a new box. _innerItemsSource.Insert(index, _currentTextEdit); // Need to wait for containerization _ = dispatcherQueue.EnqueueAsync( () => { var newContainer = ContainerFromIndex(index) as TokenizingTextBoxItem; // 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); } }, 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) { var last = ContainerFromIndex(Items.Count - 1) as TokenizingTextBoxItem; // 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); } } } }