From 66602f137bf12c15b6dafbe7fffe2826a286b7c6 Mon Sep 17 00:00:00 2001 From: Marcus Perryman Date: Wed, 20 May 2020 11:56:26 +0100 Subject: [PATCH 1/6] fix Release build comment error add code to reset the caret position when using keyboard to navigate between pretoken text items --- .../TokenizingTextBox/SampleEmailDataType.cs | 2 +- .../TokenizingTextBox.Selection.cs | 15 ++++++++++++++- 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/TokenizingTextBox/SampleEmailDataType.cs b/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/TokenizingTextBox/SampleEmailDataType.cs index 0822836189d..3fc2a0eca62 100644 --- a/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/TokenizingTextBox/SampleEmailDataType.cs +++ b/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/TokenizingTextBox/SampleEmailDataType.cs @@ -8,7 +8,7 @@ namespace Microsoft.Toolkit.Uwp.SampleApp.SamplePages { /// - /// Sample of strongly-typed email address simulated data for . + /// Sample of strongly-typed email address simulated data for . /// public class SampleEmailDataType { diff --git a/Microsoft.Toolkit.Uwp.UI.Controls/TokenizingTextBox/TokenizingTextBox.Selection.cs b/Microsoft.Toolkit.Uwp.UI.Controls/TokenizingTextBox/TokenizingTextBox.Selection.cs index 31c98e025ff..8c19fd5a69a 100644 --- a/Microsoft.Toolkit.Uwp.UI.Controls/TokenizingTextBox/TokenizingTextBox.Selection.cs +++ b/Microsoft.Toolkit.Uwp.UI.Controls/TokenizingTextBox/TokenizingTextBox.Selection.cs @@ -69,6 +69,18 @@ private bool MoveFocusAndSelection(MoveDirection direction) if (index != previousIndex) { var newItem = ContainerFromIndex(index) as TokenizingTextBoxItem; + + // Check for the new item being a text control. + // this must happen before focus is set to avoid seeing the caret + // jump in come cases + if (Items[index] is PretokenStringContainer && !IsShiftPressed) + { + newItem._autoSuggestTextBox.SelectionLength = 0; + newItem._autoSuggestTextBox.SelectionStart = direction == MoveDirection.Next + ? 0 + : newItem._autoSuggestTextBox.Text.Length; + } + newItem.Focus(FocusState.Keyboard); // if no control keys are selected then the selection also becomes just this item @@ -89,7 +101,8 @@ private bool MoveFocusAndSelection(MoveDirection direction) SelectedItems.Remove(Items[previousIndex]); } } - else if (!IsControlPressed) + else + if (!IsControlPressed) { SelectedIndex = index; From 828125677cfe4fc5af9c351165f0accc78ebd3fb Mon Sep 17 00:00:00 2001 From: Marcus Perryman Date: Wed, 20 May 2020 11:58:14 +0100 Subject: [PATCH 2/6] tidy format --- .../TokenizingTextBox/TokenizingTextBox.Selection.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Microsoft.Toolkit.Uwp.UI.Controls/TokenizingTextBox/TokenizingTextBox.Selection.cs b/Microsoft.Toolkit.Uwp.UI.Controls/TokenizingTextBox/TokenizingTextBox.Selection.cs index 8c19fd5a69a..37366371724 100644 --- a/Microsoft.Toolkit.Uwp.UI.Controls/TokenizingTextBox/TokenizingTextBox.Selection.cs +++ b/Microsoft.Toolkit.Uwp.UI.Controls/TokenizingTextBox/TokenizingTextBox.Selection.cs @@ -101,8 +101,7 @@ private bool MoveFocusAndSelection(MoveDirection direction) SelectedItems.Remove(Items[previousIndex]); } } - else - if (!IsControlPressed) + else if (!IsControlPressed) { SelectedIndex = index; From 526818cc40c0508b18e132fffb2bb7d01722495a Mon Sep 17 00:00:00 2001 From: michael-hawker <24302614+michael-hawker@users.noreply.github.com> Date: Sat, 23 May 2020 22:15:24 -0700 Subject: [PATCH 3/6] Add back QueryIcon property as IconSource Note: This restricts us to 17763 due to the IconSourceElement I had to use (until WinUI 3). --- .../TokenizingTextBoxXaml.bind | 7 ++++--- .../PretokenStringContainer.cs | 5 ++++- .../TokenizingTextBox.Properties.cs | 6 +++--- .../TokenizingTextBox/TokenizingTextBox.cs | 4 ++-- .../TokenizingTextBoxItem.AutoSuggestBox.cs | 21 +++++++++++++++++++ .../TokenizingTextBoxItem.AutoSuggestBox.xaml | 4 +--- 6 files changed, 35 insertions(+), 12 deletions(-) diff --git a/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/TokenizingTextBox/TokenizingTextBoxXaml.bind b/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/TokenizingTextBox/TokenizingTextBoxXaml.bind index fd6626519d3..f5588f6225f 100644 --- a/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/TokenizingTextBox/TokenizingTextBoxXaml.bind +++ b/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/TokenizingTextBox/TokenizingTextBoxXaml.bind @@ -1,8 +1,9 @@  @@ -15,7 +16,7 @@ diff --git a/Microsoft.Toolkit.Uwp.UI.Controls/TokenizingTextBox/PretokenStringContainer.cs b/Microsoft.Toolkit.Uwp.UI.Controls/TokenizingTextBox/PretokenStringContainer.cs index 7c0ef3bf970..a53e182ad85 100644 --- a/Microsoft.Toolkit.Uwp.UI.Controls/TokenizingTextBox/PretokenStringContainer.cs +++ b/Microsoft.Toolkit.Uwp.UI.Controls/TokenizingTextBox/PretokenStringContainer.cs @@ -18,8 +18,11 @@ public string Text public static readonly DependencyProperty TextProperty = DependencyProperty.Register(nameof(Text), typeof(string), typeof(PretokenStringContainer), new PropertyMetadata(string.Empty)); - public PretokenStringContainer() + public bool IsLast { get; private set; } + + public PretokenStringContainer(bool isLast = false) { + IsLast = isLast; } public PretokenStringContainer(string text) diff --git a/Microsoft.Toolkit.Uwp.UI.Controls/TokenizingTextBox/TokenizingTextBox.Properties.cs b/Microsoft.Toolkit.Uwp.UI.Controls/TokenizingTextBox/TokenizingTextBox.Properties.cs index 48ca960c60b..6a738b4a5a4 100644 --- a/Microsoft.Toolkit.Uwp.UI.Controls/TokenizingTextBox/TokenizingTextBox.Properties.cs +++ b/Microsoft.Toolkit.Uwp.UI.Controls/TokenizingTextBox/TokenizingTextBox.Properties.cs @@ -91,7 +91,7 @@ public partial class TokenizingTextBox : ListViewBase /// public static readonly DependencyProperty QueryIconProperty = DependencyProperty.Register( nameof(QueryIcon), - typeof(IconElement), + typeof(IconSource), typeof(TokenizingTextBox), new PropertyMetadata(null)); @@ -232,9 +232,9 @@ public string PlaceholderText /// /// Gets or sets the icon to display in the AutoSuggestBox template part. /// - public IconElement QueryIcon + public IconSource QueryIcon { - get => (IconElement)GetValue(QueryIconProperty); + get => (IconSource)GetValue(QueryIconProperty); set => SetValue(QueryIconProperty, value); } diff --git a/Microsoft.Toolkit.Uwp.UI.Controls/TokenizingTextBox/TokenizingTextBox.cs b/Microsoft.Toolkit.Uwp.UI.Controls/TokenizingTextBox/TokenizingTextBox.cs index d1846d24e5a..014dc85aa89 100644 --- a/Microsoft.Toolkit.Uwp.UI.Controls/TokenizingTextBox/TokenizingTextBox.cs +++ b/Microsoft.Toolkit.Uwp.UI.Controls/TokenizingTextBox/TokenizingTextBox.cs @@ -57,7 +57,7 @@ public TokenizingTextBox() { // Setup our base state of our collection _innerItemsSource = new InterspersedObservableCollection(new ObservableCollection()); // TODO: Test this still will let us bind to ItemsSource in XAML? - _currentTextEdit = _lastTextEdit = new PretokenStringContainer(); + _currentTextEdit = _lastTextEdit = new PretokenStringContainer(true); _innerItemsSource.Insert(_innerItemsSource.Count, _currentTextEdit); ItemsSource = _innerItemsSource; //// TODO: Consolidate with callback below for ItemsSourceProperty changed? @@ -80,7 +80,7 @@ private void ItemsSource_PropertyChanged(DependencyObject sender, DependencyProp if (ItemsSource != null && ItemsSource.GetType() != typeof(InterspersedObservableCollection)) { _innerItemsSource = new InterspersedObservableCollection(ItemsSource); - _currentTextEdit = _lastTextEdit = new PretokenStringContainer(); + _currentTextEdit = _lastTextEdit = new PretokenStringContainer(true); _innerItemsSource.Insert(_innerItemsSource.Count, _currentTextEdit); ItemsSource = _innerItemsSource; } diff --git a/Microsoft.Toolkit.Uwp.UI.Controls/TokenizingTextBox/TokenizingTextBoxItem.AutoSuggestBox.cs b/Microsoft.Toolkit.Uwp.UI.Controls/TokenizingTextBox/TokenizingTextBoxItem.AutoSuggestBox.cs index dc1bf58c694..827455c37d2 100644 --- a/Microsoft.Toolkit.Uwp.UI.Controls/TokenizingTextBox/TokenizingTextBoxItem.AutoSuggestBox.cs +++ b/Microsoft.Toolkit.Uwp.UI.Controls/TokenizingTextBox/TokenizingTextBoxItem.AutoSuggestBox.cs @@ -9,6 +9,7 @@ using Windows.UI.Xaml; using Windows.UI.Xaml.Controls; using Windows.UI.Xaml.Controls.Primitives; +using Windows.UI.Xaml.Data; using Windows.UI.Xaml.Input; namespace Microsoft.Toolkit.Uwp.UI.Controls @@ -76,6 +77,9 @@ private void OnApplyTemplateAutoSuggestBox(AutoSuggestBox auto) _autoSuggestBox.PointerCaptureLost -= AutoSuggestBox_PointerExited; _autoSuggestBox.GotFocus -= AutoSuggestBox_GotFocus; _autoSuggestBox.LostFocus -= AutoSuggestBox_LostFocus; + + // Remove any previous QueryIcon + _autoSuggestBox.QueryIcon = null; } _autoSuggestBox = auto; @@ -93,6 +97,23 @@ private void OnApplyTemplateAutoSuggestBox(AutoSuggestBox auto) _autoSuggestBox.PointerCaptureLost += AutoSuggestBox_PointerExited; _autoSuggestBox.GotFocus += AutoSuggestBox_GotFocus; _autoSuggestBox.LostFocus += AutoSuggestBox_LostFocus; + + // Setup a binding to the QueryIcon of the Parent if we're the last box. + if (Content is PretokenStringContainer str && str.IsLast) + { + var iconBinding = new Binding() + { + Source = Owner, + Path = new PropertyPath(nameof(Owner.QueryIcon)), + RelativeSource = new RelativeSource() { Mode = RelativeSourceMode.TemplatedParent } + }; + + var iconSourceElement = new IconSourceElement(); + + iconSourceElement.SetBinding(IconSourceElement.IconSourceProperty, iconBinding); + + _autoSuggestBox.QueryIcon = iconSourceElement; + } } } diff --git a/Microsoft.Toolkit.Uwp.UI.Controls/TokenizingTextBox/TokenizingTextBoxItem.AutoSuggestBox.xaml b/Microsoft.Toolkit.Uwp.UI.Controls/TokenizingTextBox/TokenizingTextBoxItem.AutoSuggestBox.xaml index 6941e0472b8..b6ab76f25e3 100644 --- a/Microsoft.Toolkit.Uwp.UI.Controls/TokenizingTextBox/TokenizingTextBoxItem.AutoSuggestBox.xaml +++ b/Microsoft.Toolkit.Uwp.UI.Controls/TokenizingTextBox/TokenizingTextBoxItem.AutoSuggestBox.xaml @@ -307,9 +307,7 @@ PlaceholderText="{Binding Path=Owner.PlaceholderText, RelativeSource={RelativeSource Mode=TemplatedParent}}" Text="{Binding Text, Mode=TwoWay}" TextBoxStyle="{StaticResource TokenizingTextBoxTextBoxStyle}"/> - - - + From 30224808b869a837e1c1486ef151a2fb881b772f Mon Sep 17 00:00:00 2001 From: Marcus Perryman Date: Thu, 28 May 2020 12:36:13 +0100 Subject: [PATCH 4/6] Added a new margin property to awork around layout issues with panel and ListView --- .../TokenizingTextBox.Properties.cs | 19 +++++++++++++++++++ .../TokenizingTextBox/TokenizingTextBox.xaml | 3 +++ 2 files changed, 22 insertions(+) diff --git a/Microsoft.Toolkit.Uwp.UI.Controls/TokenizingTextBox/TokenizingTextBox.Properties.cs b/Microsoft.Toolkit.Uwp.UI.Controls/TokenizingTextBox/TokenizingTextBox.Properties.cs index 6a738b4a5a4..d0ffa8242e1 100644 --- a/Microsoft.Toolkit.Uwp.UI.Controls/TokenizingTextBox/TokenizingTextBox.Properties.cs +++ b/Microsoft.Toolkit.Uwp.UI.Controls/TokenizingTextBox/TokenizingTextBox.Properties.cs @@ -157,6 +157,16 @@ private static void TextPropertyChanged(DependencyObject d, DependencyPropertyCh typeof(TokenizingTextBox), new PropertyMetadata(false)); + /// + /// Identifies the property. + /// Note: this has been added to work around an issue with internal layout conflicts between panel and ListView that manifests as a bad margin. + /// + public static readonly DependencyProperty ItemsPresenterMarginProperty = DependencyProperty.Register( + nameof(ItemsPresenterMargin), + typeof(Thickness), + typeof(TokenizingTextBox), + new PropertyMetadata(default(Thickness))); + /// /// Gets or sets the Style for the contained AutoSuggestBox template part. /// @@ -247,6 +257,15 @@ public string Text set => SetValue(TextProperty, value); } + /// + /// Gets or sets the margin value for the items presenter component. + /// + public Thickness ItemsPresenterMargin + { + get => (Thickness)GetValue(ItemsPresenterMarginProperty); + set => SetValue(ItemsPresenterMarginProperty, value); + } + /// /// Gets or sets the items source for token suggestions. /// diff --git a/Microsoft.Toolkit.Uwp.UI.Controls/TokenizingTextBox/TokenizingTextBox.xaml b/Microsoft.Toolkit.Uwp.UI.Controls/TokenizingTextBox/TokenizingTextBox.xaml index d7e822bdc9e..23c0ebc8d94 100644 --- a/Microsoft.Toolkit.Uwp.UI.Controls/TokenizingTextBox/TokenizingTextBox.xaml +++ b/Microsoft.Toolkit.Uwp.UI.Controls/TokenizingTextBox/TokenizingTextBox.xaml @@ -10,6 +10,7 @@ 2 4 + 0,0,6,0 2 + @@ -89,6 +91,7 @@ ZoomMode="{TemplateBinding ScrollViewer.ZoomMode}"> Date: Thu, 28 May 2020 17:48:15 +0100 Subject: [PATCH 5/6] Add a public way to access to the pretoken string items --- .../TokenizingTextBoxPage.xaml.cs | 41 ++++++++++++++++--- .../TokenizingTextBoxXaml.bind | 5 ++- .../ITokenStringContainer.cs | 17 ++++++++ .../PretokenStringContainer.cs | 11 ++++- 4 files changed, 66 insertions(+), 8 deletions(-) create mode 100644 Microsoft.Toolkit.Uwp.UI.Controls/TokenizingTextBox/ITokenStringContainer.cs diff --git a/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/TokenizingTextBox/TokenizingTextBoxPage.xaml.cs b/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/TokenizingTextBox/TokenizingTextBoxPage.xaml.cs index 110c6518894..86bbd2121c0 100644 --- a/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/TokenizingTextBox/TokenizingTextBoxPage.xaml.cs +++ b/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/TokenizingTextBox/TokenizingTextBoxPage.xaml.cs @@ -83,6 +83,7 @@ public sealed partial class TokenizingTextBoxPage : Page, IXamlRenderListener private TokenizingTextBox _ttbEmail; private ListView _ttbEmailSuggestions; private Button _ttbEmailClear; + private Button _ttbEmailShowItems; private AdvancedCollectionView _acv; private AdvancedCollectionView _acvEmail; @@ -117,9 +118,6 @@ public void OnXamlRendered(FrameworkElement control) if (control.FindChildByName("TokenBox") is TokenizingTextBox ttb) { _ttb = ttb; - - ////_ttb.ItemsSource = new ObservableCollection(); // TODO: This shouldn't be required, we should initialize in control constructor??? - _ttb.TokenItemAdded += TokenItemAdded; _ttb.TokenItemRemoving += TokenItemRemoved; _ttb.TextChanged += TextChanged; @@ -146,8 +144,7 @@ public void OnXamlRendered(FrameworkElement control) _ttbEmail = ttbEmail; _ttbEmail.ItemsSource = _selectedEmails; - - // _ttbEmail.ItemClick += EmailTokenItemClick; + _ttbEmail.ItemClick += EmailTokenItemClick; _ttbEmail.TokenItemAdding += EmailTokenItemAdding; _ttbEmail.TokenItemAdded += EmailTokenItemAdded; _ttbEmail.TokenItemRemoved += EmailTokenItemRemoved; @@ -181,9 +178,19 @@ public void OnXamlRendered(FrameworkElement control) if (control.FindChildByName("ClearButton") is Button btn) { _ttbEmailClear = btn; - _ttbEmailClear.Click += ClearButtonClick; } + + if (_ttbEmailShowItems != null) + { + _ttbEmailShowItems.Click -= ShowButtonClick; + } + + if (control.FindChildByName("ShowSelectedEmails") is Button showBtn) + { + _ttbEmailShowItems = showBtn; + _ttbEmailShowItems.Click += ShowButtonClick; + } } private async void EmailTokenItemClick(object sender, ItemClickEventArgs e) @@ -316,6 +323,28 @@ private void ClearButtonClick(object sender, RoutedEventArgs e) _acvEmail.RefreshFilter(); } + private async void ShowButtonClick(object sender, RoutedEventArgs e) + { + // Grab the list of items and identify which ones are free text, which ones are tokens + string message = string.Empty; + + foreach (var item in _ttbEmail.Items) + { + if (!string.IsNullOrEmpty(message)) + { + message += "\r\n"; + } + + message += item is ITokenStringContainer ? "Unrslvd: " : "Token : "; + var textVal = item.ToString(); + + message += string.IsNullOrEmpty(textVal) ? "" : textVal; + } + + MessageDialog md = new MessageDialog(message, "Item List with type"); + await md.ShowAsync(); + } + // Move to Email Suggest ListView list when we keydown from the TTB private void EmailPreviewKeyDown(object sender, KeyRoutedEventArgs e) { diff --git a/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/TokenizingTextBox/TokenizingTextBoxXaml.bind b/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/TokenizingTextBox/TokenizingTextBoxXaml.bind index f5588f6225f..54ccba3b621 100644 --- a/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/TokenizingTextBox/TokenizingTextBoxXaml.bind +++ b/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/TokenizingTextBox/TokenizingTextBoxXaml.bind @@ -79,7 +79,10 @@ -