From abb3d6b3f7aa4aa92939c1ab3f3286d733b2c63d Mon Sep 17 00:00:00 2001 From: James Croft Date: Wed, 24 Mar 2021 21:07:40 +0000 Subject: [PATCH 01/46] Added automation peer for TokenizingTextBox --- .../TokenizingTextBox/TokenizingTextBox.cs | 12 +- .../TokenizingTextBoxAutomationPeer.cs | 116 ++++++++++++++++++ 2 files changed, 127 insertions(+), 1 deletion(-) create mode 100644 Microsoft.Toolkit.Uwp.UI.Controls.Input/TokenizingTextBox/TokenizingTextBoxAutomationPeer.cs diff --git a/Microsoft.Toolkit.Uwp.UI.Controls.Input/TokenizingTextBox/TokenizingTextBox.cs b/Microsoft.Toolkit.Uwp.UI.Controls.Input/TokenizingTextBox/TokenizingTextBox.cs index 3a388c2b09d..184d022ef9f 100644 --- a/Microsoft.Toolkit.Uwp.UI.Controls.Input/TokenizingTextBox/TokenizingTextBox.cs +++ b/Microsoft.Toolkit.Uwp.UI.Controls.Input/TokenizingTextBox/TokenizingTextBox.cs @@ -2,15 +2,16 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -using System; using System.Collections.ObjectModel; using System.Linq; using System.Threading.Tasks; using Microsoft.Toolkit.Uwp.Deferred; +using Microsoft.Toolkit.Uwp.UI.Automation.Peers; using Microsoft.Toolkit.Uwp.UI.Helpers; using Windows.System; using Windows.UI.Core; using Windows.UI.Xaml; +using Windows.UI.Xaml.Automation.Peers; using Windows.UI.Xaml.Controls; using Windows.UI.Xaml.Input; @@ -486,6 +487,15 @@ protected void UpdateCurrentTextEdit(ITokenStringContainer edit) Text = edit.Text; // Update our text property. } + /// + /// Creates AutomationPeer () + /// + /// An automation peer for this . + protected override AutomationPeer OnCreateAutomationPeer() + { + return new TokenizingTextBoxAutomationPeer(this); + } + /// /// Remove the specified token from the list. /// diff --git a/Microsoft.Toolkit.Uwp.UI.Controls.Input/TokenizingTextBox/TokenizingTextBoxAutomationPeer.cs b/Microsoft.Toolkit.Uwp.UI.Controls.Input/TokenizingTextBox/TokenizingTextBoxAutomationPeer.cs new file mode 100644 index 00000000000..b2b01bbcb86 --- /dev/null +++ b/Microsoft.Toolkit.Uwp.UI.Controls.Input/TokenizingTextBox/TokenizingTextBoxAutomationPeer.cs @@ -0,0 +1,116 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Collections.Generic; +using Microsoft.Toolkit.Uwp.UI.Controls; +using Windows.UI.Xaml.Automation; +using Windows.UI.Xaml.Automation.Peers; +using Windows.UI.Xaml.Controls; + +namespace Microsoft.Toolkit.Uwp.UI.Automation.Peers +{ + /// + /// Defines a framework element automation peer for the control. + /// + public class TokenizingTextBoxAutomationPeer : ListViewBaseAutomationPeer + { + /// + /// Initializes a new instance of the class. + /// + /// + /// The that is associated with this . + /// + public TokenizingTextBoxAutomationPeer(TokenizingTextBox owner) + : base(owner) + { + } + + private TokenizingTextBox OwningTokenizingTextBox + { + get + { + return Owner as TokenizingTextBox; + } + } + + /// + /// Called by GetClassName that gets a human readable name that, in addition to AutomationControlType, + /// differentiates the control represented by this AutomationPeer. + /// + /// The string that contains the name. + protected override string GetClassNameCore() + { + return Owner.GetType().Name; + } + + /// + /// Called by GetName. + /// + /// + /// Returns the first of these that is not null or empty: + /// - Value returned by the base implementation + /// - Name of the owning Carousel + /// - Carousel class name + /// + protected override string GetNameCore() + { + string name = this.OwningTokenizingTextBox.Name; + if (!string.IsNullOrEmpty(name)) + { + return name; + } + + name = AutomationProperties.GetName(this.OwningTokenizingTextBox); + if (!string.IsNullOrEmpty(name)) + { + return name; + } + + return base.GetNameCore(); + } + + /// + /// Gets the control pattern that is associated with the specified Windows.UI.Xaml.Automation.Peers.PatternInterface. + /// + /// A value from the Windows.UI.Xaml.Automation.Peers.PatternInterface enumeration. + /// The object that supports the specified pattern, or null if unsupported. + protected override object GetPatternCore(PatternInterface patternInterface) + { + switch (patternInterface) + { + case PatternInterface.Selection: + return this; + } + + return base.GetPatternCore(patternInterface); + } + + /// + /// Gets the collection of elements that are represented in the UI Automation tree as immediate + /// child elements of the automation peer. + /// + /// The children elements. + protected override IList GetChildrenCore() + { + TokenizingTextBox owner = this.OwningTokenizingTextBox; + + ItemCollection items = owner.Items; + if (items.Count <= 0) + { + return null; + } + + List peers = new List(items.Count); + for (int i = 0; i < items.Count; i++) + { + if (owner.ContainerFromIndex(i) is TokenizingTextBoxItem element) + { + peers.Add(FromElement(element) ?? CreatePeerForElement(element)); + } + } + + return peers; + } + } +} From 5844ec01f5e7ba775119adb800fab081c3aedae9 Mon Sep 17 00:00:00 2001 From: James Croft Date: Wed, 24 Mar 2021 21:24:48 +0000 Subject: [PATCH 02/46] Added tests for TokenizingTextBoxAutomationPeer --- .../TokenizingTextBoxAutomationPeer.cs | 16 ----- .../Test_TokenizingTextBox_AutomationPeer.cs | 59 +++++++++++++++++++ UnitTests/UnitTests.UWP/UnitTests.UWP.csproj | 1 + 3 files changed, 60 insertions(+), 16 deletions(-) create mode 100644 UnitTests/UnitTests.UWP/UI/Controls/Test_TokenizingTextBox_AutomationPeer.cs diff --git a/Microsoft.Toolkit.Uwp.UI.Controls.Input/TokenizingTextBox/TokenizingTextBoxAutomationPeer.cs b/Microsoft.Toolkit.Uwp.UI.Controls.Input/TokenizingTextBox/TokenizingTextBoxAutomationPeer.cs index b2b01bbcb86..49cebb14f9a 100644 --- a/Microsoft.Toolkit.Uwp.UI.Controls.Input/TokenizingTextBox/TokenizingTextBoxAutomationPeer.cs +++ b/Microsoft.Toolkit.Uwp.UI.Controls.Input/TokenizingTextBox/TokenizingTextBoxAutomationPeer.cs @@ -70,22 +70,6 @@ protected override string GetNameCore() return base.GetNameCore(); } - /// - /// Gets the control pattern that is associated with the specified Windows.UI.Xaml.Automation.Peers.PatternInterface. - /// - /// A value from the Windows.UI.Xaml.Automation.Peers.PatternInterface enumeration. - /// The object that supports the specified pattern, or null if unsupported. - protected override object GetPatternCore(PatternInterface patternInterface) - { - switch (patternInterface) - { - case PatternInterface.Selection: - return this; - } - - return base.GetPatternCore(patternInterface); - } - /// /// Gets the collection of elements that are represented in the UI Automation tree as immediate /// child elements of the automation peer. diff --git a/UnitTests/UnitTests.UWP/UI/Controls/Test_TokenizingTextBox_AutomationPeer.cs b/UnitTests/UnitTests.UWP/UI/Controls/Test_TokenizingTextBox_AutomationPeer.cs new file mode 100644 index 00000000000..1f27dbffcb4 --- /dev/null +++ b/UnitTests/UnitTests.UWP/UI/Controls/Test_TokenizingTextBox_AutomationPeer.cs @@ -0,0 +1,59 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Collections.ObjectModel; +using System.Threading.Tasks; +using Windows.UI.Xaml.Automation; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Windows.UI.Xaml.Automation.Peers; +using Microsoft.Toolkit.Uwp; +using Microsoft.Toolkit.Uwp.UI.Automation.Peers; +using Microsoft.Toolkit.Uwp.UI.Controls; + +namespace UnitTests.UWP.UI.Controls +{ + [TestClass] + [TestCategory("Test_TokenizingTextBox")] + public class Test_TokenizingTextBox_AutomationPeer : VisualUITestBase + { + [TestMethod] + public async Task ShouldConfigureTokenizingTextBoxAutomationPeerAsync() + { + await App.DispatcherQueue.EnqueueAsync(async () => + { + const string expectedAutomationName = "MyAutomationName"; + const string expectedName = "MyName"; + + var items = new ObservableCollection { new() { Title = "Hello" }, new() { Title = "World" } }; + + var tokenizingTextBox = new TokenizingTextBox { ItemsSource = items }; + + await SetTestContentAsync(tokenizingTextBox); + + var tokenizingTextBoxAutomationPeer = + FrameworkElementAutomationPeer.CreatePeerForElement(tokenizingTextBox) as TokenizingTextBoxAutomationPeer; + + Assert.IsNotNull(tokenizingTextBoxAutomationPeer, "Verify that the AutomationPeer is TokenizingTextBoxAutomationPeer."); + + // Asserts the automation peer name based on the Automation Property Name value. + tokenizingTextBox.SetValue(AutomationProperties.NameProperty, expectedAutomationName); + Assert.IsTrue(tokenizingTextBoxAutomationPeer.GetName().Contains(expectedAutomationName), "Verify that the UIA name contains the given AutomationProperties.Name of the TokenizingTextBox."); + + // Asserts the automation peer name based on the element Name property. + tokenizingTextBox.Name = expectedName; + Assert.IsTrue(tokenizingTextBoxAutomationPeer.GetName().Contains(expectedName), "Verify that the UIA name contains the given Name of the TokenizingTextBox."); + }); + } + + public class TokenizingTextBoxTestItem + { + public string Title { get; set; } + + public override string ToString() + { + return Title; + } + } + } +} \ No newline at end of file diff --git a/UnitTests/UnitTests.UWP/UnitTests.UWP.csproj b/UnitTests/UnitTests.UWP/UnitTests.UWP.csproj index f5d6d37c4f3..cc421cdfa50 100644 --- a/UnitTests/UnitTests.UWP/UnitTests.UWP.csproj +++ b/UnitTests/UnitTests.UWP/UnitTests.UWP.csproj @@ -200,6 +200,7 @@ + From 4c40b33c6c6ce30fdce5f1d3ceecd285129bde58 Mon Sep 17 00:00:00 2001 From: James Croft Date: Wed, 24 Mar 2021 21:39:43 +0000 Subject: [PATCH 03/46] Added IValueProvider capability to TokenizingTextBoxAutomationPeer --- .../TokenizingTextBoxAutomationPeer.cs | 44 +++++++++++++++---- 1 file changed, 35 insertions(+), 9 deletions(-) diff --git a/Microsoft.Toolkit.Uwp.UI.Controls.Input/TokenizingTextBox/TokenizingTextBoxAutomationPeer.cs b/Microsoft.Toolkit.Uwp.UI.Controls.Input/TokenizingTextBox/TokenizingTextBoxAutomationPeer.cs index 49cebb14f9a..a9c69ddf5ce 100644 --- a/Microsoft.Toolkit.Uwp.UI.Controls.Input/TokenizingTextBox/TokenizingTextBoxAutomationPeer.cs +++ b/Microsoft.Toolkit.Uwp.UI.Controls.Input/TokenizingTextBox/TokenizingTextBoxAutomationPeer.cs @@ -6,6 +6,7 @@ using Microsoft.Toolkit.Uwp.UI.Controls; using Windows.UI.Xaml.Automation; using Windows.UI.Xaml.Automation.Peers; +using Windows.UI.Xaml.Automation.Provider; using Windows.UI.Xaml.Controls; namespace Microsoft.Toolkit.Uwp.UI.Automation.Peers @@ -13,7 +14,7 @@ namespace Microsoft.Toolkit.Uwp.UI.Automation.Peers /// /// Defines a framework element automation peer for the control. /// - public class TokenizingTextBoxAutomationPeer : ListViewBaseAutomationPeer + public class TokenizingTextBoxAutomationPeer : ListViewBaseAutomationPeer, IValueProvider { /// /// Initializes a new instance of the class. @@ -26,6 +27,14 @@ public TokenizingTextBoxAutomationPeer(TokenizingTextBox owner) { } + /// Gets a value that indicates whether the value of a control is read-only. + /// **true** if the value is read-only; **false** if it can be modified. + public bool IsReadOnly => !this.OwningTokenizingTextBox.IsEnabled; + + /// Gets the value of the control. + /// The value of the control. + public string Value => this.OwningTokenizingTextBox.Text; + private TokenizingTextBox OwningTokenizingTextBox { get @@ -34,6 +43,13 @@ private TokenizingTextBox OwningTokenizingTextBox } } + /// Sets the value of a control. + /// The value to set. The provider is responsible for converting the value to the appropriate data type. + public void SetValue(string value) + { + this.OwningTokenizingTextBox.Text = value; + } + /// /// Called by GetClassName that gets a human readable name that, in addition to AutomationControlType, /// differentiates the control represented by this AutomationPeer. @@ -50,8 +66,8 @@ protected override string GetClassNameCore() /// /// Returns the first of these that is not null or empty: /// - Value returned by the base implementation - /// - Name of the owning Carousel - /// - Carousel class name + /// - Name of the owning TokenizingTextBox + /// - TokenizingTextBox class name /// protected override string GetNameCore() { @@ -62,12 +78,22 @@ protected override string GetNameCore() } name = AutomationProperties.GetName(this.OwningTokenizingTextBox); - if (!string.IsNullOrEmpty(name)) - { - return name; - } + return !string.IsNullOrEmpty(name) ? name : base.GetNameCore(); + } - return base.GetNameCore(); + /// + /// Gets the control pattern that is associated with the specified Windows.UI.Xaml.Automation.Peers.PatternInterface. + /// + /// A value from the Windows.UI.Xaml.Automation.Peers.PatternInterface enumeration. + /// The object that supports the specified pattern, or null if unsupported. + protected override object GetPatternCore(PatternInterface patternInterface) + { + return patternInterface switch + { + PatternInterface.Value => this, + PatternInterface.Selection => this, + _ => base.GetPatternCore(patternInterface) + }; } /// @@ -97,4 +123,4 @@ protected override IList GetChildrenCore() return peers; } } -} +} \ No newline at end of file From 9e1c0783ae95019ac6f84413d123b711051f95af Mon Sep 17 00:00:00 2001 From: James Croft Date: Thu, 25 Mar 2021 21:09:22 +0000 Subject: [PATCH 04/46] Updated tests for value provider changes --- .../TokenizingTextBox/TokenizingTextBoxAutomationPeer.cs | 1 - .../UI/Controls/Test_TokenizingTextBox_AutomationPeer.cs | 4 ++++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/Microsoft.Toolkit.Uwp.UI.Controls.Input/TokenizingTextBox/TokenizingTextBoxAutomationPeer.cs b/Microsoft.Toolkit.Uwp.UI.Controls.Input/TokenizingTextBox/TokenizingTextBoxAutomationPeer.cs index a9c69ddf5ce..bd07a348a91 100644 --- a/Microsoft.Toolkit.Uwp.UI.Controls.Input/TokenizingTextBox/TokenizingTextBoxAutomationPeer.cs +++ b/Microsoft.Toolkit.Uwp.UI.Controls.Input/TokenizingTextBox/TokenizingTextBoxAutomationPeer.cs @@ -91,7 +91,6 @@ protected override object GetPatternCore(PatternInterface patternInterface) return patternInterface switch { PatternInterface.Value => this, - PatternInterface.Selection => this, _ => base.GetPatternCore(patternInterface) }; } diff --git a/UnitTests/UnitTests.UWP/UI/Controls/Test_TokenizingTextBox_AutomationPeer.cs b/UnitTests/UnitTests.UWP/UI/Controls/Test_TokenizingTextBox_AutomationPeer.cs index 1f27dbffcb4..0224222e98b 100644 --- a/UnitTests/UnitTests.UWP/UI/Controls/Test_TokenizingTextBox_AutomationPeer.cs +++ b/UnitTests/UnitTests.UWP/UI/Controls/Test_TokenizingTextBox_AutomationPeer.cs @@ -24,6 +24,7 @@ await App.DispatcherQueue.EnqueueAsync(async () => { const string expectedAutomationName = "MyAutomationName"; const string expectedName = "MyName"; + const string expectedValue = "Wor"; var items = new ObservableCollection { new() { Title = "Hello" }, new() { Title = "World" } }; @@ -43,6 +44,9 @@ await App.DispatcherQueue.EnqueueAsync(async () => // Asserts the automation peer name based on the element Name property. tokenizingTextBox.Name = expectedName; Assert.IsTrue(tokenizingTextBoxAutomationPeer.GetName().Contains(expectedName), "Verify that the UIA name contains the given Name of the TokenizingTextBox."); + + tokenizingTextBox.Text = expectedValue; + Assert.IsTrue(tokenizingTextBoxAutomationPeer.Value.Equals(expectedValue), "Verify that the Value contains the given Text of the TokenizingTextBox."); }); } From e41c808af080c78220a26c08b38c1051022fe679 Mon Sep 17 00:00:00 2001 From: James Croft Date: Thu, 25 Mar 2021 21:44:18 +0000 Subject: [PATCH 05/46] Added improvement to automation peer to throw ElementNotEnabledException if attempting to update value when in a readonly state --- .../TokenizingTextBoxAutomationPeer.cs | 6 +++++ .../Test_TokenizingTextBox_AutomationPeer.cs | 23 ++++++++++++++++++- 2 files changed, 28 insertions(+), 1 deletion(-) diff --git a/Microsoft.Toolkit.Uwp.UI.Controls.Input/TokenizingTextBox/TokenizingTextBoxAutomationPeer.cs b/Microsoft.Toolkit.Uwp.UI.Controls.Input/TokenizingTextBox/TokenizingTextBoxAutomationPeer.cs index bd07a348a91..2f8c781ff86 100644 --- a/Microsoft.Toolkit.Uwp.UI.Controls.Input/TokenizingTextBox/TokenizingTextBoxAutomationPeer.cs +++ b/Microsoft.Toolkit.Uwp.UI.Controls.Input/TokenizingTextBox/TokenizingTextBoxAutomationPeer.cs @@ -45,8 +45,14 @@ private TokenizingTextBox OwningTokenizingTextBox /// Sets the value of a control. /// The value to set. The provider is responsible for converting the value to the appropriate data type. + /// Thrown if the control is in a read-only state. public void SetValue(string value) { + if (IsReadOnly) + { + throw new ElementNotEnabledException($"Could not set the value of the {nameof(TokenizingTextBox)} "); + } + this.OwningTokenizingTextBox.Text = value; } diff --git a/UnitTests/UnitTests.UWP/UI/Controls/Test_TokenizingTextBox_AutomationPeer.cs b/UnitTests/UnitTests.UWP/UI/Controls/Test_TokenizingTextBox_AutomationPeer.cs index 0224222e98b..497635d4af3 100644 --- a/UnitTests/UnitTests.UWP/UI/Controls/Test_TokenizingTextBox_AutomationPeer.cs +++ b/UnitTests/UnitTests.UWP/UI/Controls/Test_TokenizingTextBox_AutomationPeer.cs @@ -45,11 +45,32 @@ await App.DispatcherQueue.EnqueueAsync(async () => tokenizingTextBox.Name = expectedName; Assert.IsTrue(tokenizingTextBoxAutomationPeer.GetName().Contains(expectedName), "Verify that the UIA name contains the given Name of the TokenizingTextBox."); - tokenizingTextBox.Text = expectedValue; + tokenizingTextBoxAutomationPeer.SetValue(expectedValue); Assert.IsTrue(tokenizingTextBoxAutomationPeer.Value.Equals(expectedValue), "Verify that the Value contains the given Text of the TokenizingTextBox."); }); } + [TestMethod] + public async Task ShouldThrowElementNotEnabledExceptionIfValueSetWhenDisabled() + { + await App.DispatcherQueue.EnqueueAsync(async () => + { + const string expectedValue = "Wor"; + + var tokenizingTextBox = new TokenizingTextBox { IsEnabled = false }; + + await SetTestContentAsync(tokenizingTextBox); + + var tokenizingTextBoxAutomationPeer = + FrameworkElementAutomationPeer.CreatePeerForElement(tokenizingTextBox) as TokenizingTextBoxAutomationPeer; + + Assert.ThrowsException(() => + { + tokenizingTextBoxAutomationPeer.SetValue(expectedValue); + }); + }); + } + public class TokenizingTextBoxTestItem { public string Title { get; set; } From 299dc32417c006cdd0f84e2780ef86311efe99b0 Mon Sep 17 00:00:00 2001 From: James Croft Date: Fri, 26 Mar 2021 22:23:57 +0000 Subject: [PATCH 06/46] Fixed documentation styling --- .../TokenizingTextBox/TokenizingTextBoxAutomationPeer.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Microsoft.Toolkit.Uwp.UI.Controls.Input/TokenizingTextBox/TokenizingTextBoxAutomationPeer.cs b/Microsoft.Toolkit.Uwp.UI.Controls.Input/TokenizingTextBox/TokenizingTextBoxAutomationPeer.cs index 2f8c781ff86..8ca1bb7508e 100644 --- a/Microsoft.Toolkit.Uwp.UI.Controls.Input/TokenizingTextBox/TokenizingTextBoxAutomationPeer.cs +++ b/Microsoft.Toolkit.Uwp.UI.Controls.Input/TokenizingTextBox/TokenizingTextBoxAutomationPeer.cs @@ -27,7 +27,7 @@ public TokenizingTextBoxAutomationPeer(TokenizingTextBox owner) { } - /// Gets a value that indicates whether the value of a control is read-only. + /// Gets a value indicating whether the value of a control is read-only. /// **true** if the value is read-only; **false** if it can be modified. public bool IsReadOnly => !this.OwningTokenizingTextBox.IsEnabled; From 461d0f32a12424fc15c55ceb6d5159ef1cf3fc44 Mon Sep 17 00:00:00 2001 From: James Croft Date: Fri, 2 Apr 2021 08:55:35 +0100 Subject: [PATCH 07/46] Apply suggestions from code review Co-authored-by: Rosario Pulella --- .../TokenizingTextBox/TokenizingTextBoxAutomationPeer.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Microsoft.Toolkit.Uwp.UI.Controls.Input/TokenizingTextBox/TokenizingTextBoxAutomationPeer.cs b/Microsoft.Toolkit.Uwp.UI.Controls.Input/TokenizingTextBox/TokenizingTextBoxAutomationPeer.cs index 8ca1bb7508e..5f049a80149 100644 --- a/Microsoft.Toolkit.Uwp.UI.Controls.Input/TokenizingTextBox/TokenizingTextBoxAutomationPeer.cs +++ b/Microsoft.Toolkit.Uwp.UI.Controls.Input/TokenizingTextBox/TokenizingTextBoxAutomationPeer.cs @@ -78,13 +78,13 @@ protected override string GetClassNameCore() protected override string GetNameCore() { string name = this.OwningTokenizingTextBox.Name; - if (!string.IsNullOrEmpty(name)) + if (!string.IsNullOrWhiteSpace(name)) { return name; } name = AutomationProperties.GetName(this.OwningTokenizingTextBox); - return !string.IsNullOrEmpty(name) ? name : base.GetNameCore(); + return !string.IsNullOrWhiteSpace(name) ? name : base.GetNameCore(); } /// @@ -128,4 +128,4 @@ protected override IList GetChildrenCore() return peers; } } -} \ No newline at end of file +} From 5b4bd23cc960bac94cf5c7fc6c6cf5ea3bfbb20b Mon Sep 17 00:00:00 2001 From: James Croft Date: Wed, 28 Jul 2021 08:39:25 +0100 Subject: [PATCH 08/46] Added TokenizingTextBox automation peer test for returned child items --- .../Test_TokenizingTextBox_AutomationPeer.cs | 43 ++++++++++++++++++- 1 file changed, 42 insertions(+), 1 deletion(-) diff --git a/UnitTests/UnitTests.UWP/UI/Controls/Test_TokenizingTextBox_AutomationPeer.cs b/UnitTests/UnitTests.UWP/UI/Controls/Test_TokenizingTextBox_AutomationPeer.cs index 497635d4af3..bf6e2ed358c 100644 --- a/UnitTests/UnitTests.UWP/UI/Controls/Test_TokenizingTextBox_AutomationPeer.cs +++ b/UnitTests/UnitTests.UWP/UI/Controls/Test_TokenizingTextBox_AutomationPeer.cs @@ -1,8 +1,10 @@ -// Licensed to the .NET Foundation under one or more agreements. +// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. +using System.Collections.Generic; using System.Collections.ObjectModel; +using System.Linq; using System.Threading.Tasks; using Windows.UI.Xaml.Automation; using Microsoft.VisualStudio.TestTools.UnitTesting; @@ -50,6 +52,45 @@ await App.DispatcherQueue.EnqueueAsync(async () => }); } + [TestMethod] + public async Task ShouldReturnTokensForTokenizingTextBoxAutomationPeerAsync() + { + await App.DispatcherQueue.EnqueueAsync(async () => + { + var items = new ObservableCollection + { + new() { Title = "Hello" }, new() { Title = "World" } + }; + + var tokenizingTextBox = new TokenizingTextBox { ItemsSource = items }; + + await SetTestContentAsync(tokenizingTextBox); + + tokenizingTextBox + .SelectAllTokensAndText(); // Will be 3 items due to the `AndText` that will select an empty text item. + + var tokenizingTextBoxAutomationPeer = + FrameworkElementAutomationPeer.CreatePeerForElement(tokenizingTextBox) as + TokenizingTextBoxAutomationPeer; + + Assert.IsNotNull( + tokenizingTextBoxAutomationPeer, + "Verify that the AutomationPeer is TokenizingTextBoxAutomationPeer."); + + var selectedItems = tokenizingTextBoxAutomationPeer + .GetChildren() + .Cast() + .Select(peer => peer.Owner as TokenizingTextBoxItem) + .Select(item => item?.Content as TokenizingTextBoxTestItem) + .ToList(); + + Assert.AreEqual(3, selectedItems.Count); + Assert.AreEqual(items[0], selectedItems[0]); + Assert.AreEqual(items[1], selectedItems[1]); + Assert.IsNull(selectedItems[2]); // The 3rd item is the empty text item. + }); + } + [TestMethod] public async Task ShouldThrowElementNotEnabledExceptionIfValueSetWhenDisabled() { From f9541bff846bd34b7a0b6b5b40dce2f5a087a578 Mon Sep 17 00:00:00 2001 From: ArchieCoder Date: Wed, 4 Aug 2021 14:49:09 -0400 Subject: [PATCH 09/46] Completed --- .../Microsoft.Toolkit.Uwp.SampleApp.csproj | 9 +++ .../KeyDownTriggerBehaviorPage.xaml | 60 +++++++++++++++++ .../KeyDownTriggerBehaviorPage.xaml.cs | 16 +++++ .../KeyDownTriggerBehaviorXaml.bind | 58 ++++++++++++++++ .../SamplePages/samples.json | 18 +++-- .../Keyboard/KeyDownTriggerBehavior.cs | 66 +++++++++++++++++++ 6 files changed, 223 insertions(+), 4 deletions(-) create mode 100644 Microsoft.Toolkit.Uwp.SampleApp/SamplePages/KeyDownTriggerBehavior/KeyDownTriggerBehaviorPage.xaml create mode 100644 Microsoft.Toolkit.Uwp.SampleApp/SamplePages/KeyDownTriggerBehavior/KeyDownTriggerBehaviorPage.xaml.cs create mode 100644 Microsoft.Toolkit.Uwp.SampleApp/SamplePages/KeyDownTriggerBehavior/KeyDownTriggerBehaviorXaml.bind create mode 100644 Microsoft.Toolkit.Uwp.UI.Behaviors/Keyboard/KeyDownTriggerBehavior.cs diff --git a/Microsoft.Toolkit.Uwp.SampleApp/Microsoft.Toolkit.Uwp.SampleApp.csproj b/Microsoft.Toolkit.Uwp.SampleApp/Microsoft.Toolkit.Uwp.SampleApp.csproj index 07b878addb8..ef365f43ba0 100644 --- a/Microsoft.Toolkit.Uwp.SampleApp/Microsoft.Toolkit.Uwp.SampleApp.csproj +++ b/Microsoft.Toolkit.Uwp.SampleApp/Microsoft.Toolkit.Uwp.SampleApp.csproj @@ -503,6 +503,9 @@ FocusBehaviorPage.xaml + + KeyDownTriggerBehaviorPage.xaml + MetadataControlPage.xaml @@ -626,6 +629,7 @@ Designer + @@ -974,6 +978,10 @@ Designer + + MSBuild:Compile + Designer + Designer MSBuild:Compile @@ -1475,6 +1483,7 @@ Visual C++ 2015 Runtime for Universal Windows Platform Apps + 14.0 diff --git a/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/KeyDownTriggerBehavior/KeyDownTriggerBehaviorPage.xaml b/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/KeyDownTriggerBehavior/KeyDownTriggerBehaviorPage.xaml new file mode 100644 index 00000000000..d5b605fc0f4 --- /dev/null +++ b/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/KeyDownTriggerBehavior/KeyDownTriggerBehaviorPage.xaml @@ -0,0 +1,60 @@ + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/KeyDownTriggerBehavior/KeyDownTriggerBehaviorPage.xaml.cs b/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/KeyDownTriggerBehavior/KeyDownTriggerBehaviorPage.xaml.cs new file mode 100644 index 00000000000..d777b3137cb --- /dev/null +++ b/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/KeyDownTriggerBehavior/KeyDownTriggerBehaviorPage.xaml.cs @@ -0,0 +1,16 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Windows.UI.Xaml.Controls; + +namespace Microsoft.Toolkit.Uwp.SampleApp.SamplePages +{ + /// + /// A page that shows how to use the AutoFocusBehavior + /// + public sealed partial class KeyDownTriggerBehaviorPage : Page + { + public KeyDownTriggerBehaviorPage() => InitializeComponent(); + } +} \ No newline at end of file diff --git a/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/KeyDownTriggerBehavior/KeyDownTriggerBehaviorXaml.bind b/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/KeyDownTriggerBehavior/KeyDownTriggerBehaviorXaml.bind new file mode 100644 index 00000000000..0fc18f48c5e --- /dev/null +++ b/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/KeyDownTriggerBehavior/KeyDownTriggerBehaviorXaml.bind @@ -0,0 +1,58 @@ + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/samples.json b/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/samples.json index b07fedab76b..1a4ccac2cd9 100644 --- a/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/samples.json +++ b/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/samples.json @@ -785,7 +785,7 @@ "Type": "ThemeListenerPage", "Subcategory": "Systems", "About": "The ThemeListener allows you to keep track of changes to the System Theme.", - "CodeUrl" : "https://github.com/CommunityToolkit/WindowsCommunityToolkit/blob/master/Microsoft.Toolkit.Uwp.UI/Helpers/ThemeListener.cs", + "CodeUrl": "https://github.com/CommunityToolkit/WindowsCommunityToolkit/blob/master/Microsoft.Toolkit.Uwp.UI/Helpers/ThemeListener.cs", "Icon": "/Assets/Helpers.png", "DocumentationUrl": "https://raw.githubusercontent.com/MicrosoftDocs/WindowsCommunityToolkitDocs/master/docs/helpers/ThemeListener.md" }, @@ -839,12 +839,22 @@ "Icon": "/Assets/Helpers.png", "DocumentationUrl": "https://raw.githubusercontent.com/MicrosoftDocs/WindowsCommunityToolkitDocs/master/docs/behaviors/AutoSelectBehavior.md" }, + { + "Name": "KeyDownTriggerBehavior", + "Type": "KeyDownTriggerBehaviorPage", + "Subcategory": "Systems", + "About": "Behavior to listen to a key press on a control and executes actions", + "CodeUrl": "https://github.com/CommunityToolkit/WindowsCommunityToolkit/blob/master/Microsoft.Toolkit.Uwp.UI.Behaviors/Keyboard/KeyDownTriggerBehavior.cs", + "XamlCodeFile": "KeyDownTriggerBehaviorXaml.bind", + "Icon": "/Assets/Helpers.png", + "DocumentationUrl": "https://raw.githubusercontent.com/MicrosoftDocs/WindowsCommunityToolkitDocs/master/docs/behaviors/KeyboardBehaviors.md" + }, { "Name": "Win2d Path Mini Language Parser", "Type": "CanvasPathGeometryPage", "Subcategory": "Parser", "About": "CanvasPathGeometry class allows you to convert Win2d Path Mini Language string to CanvasGeometry, Brushes, CanvasStrokes or CanvasStrokeStyles.", - "CodeUrl" : "https://github.com/CommunityToolkit/WindowsCommunityToolkit/tree/main/Microsoft.Toolkit.Uwp.UI.Media/Geometry", + "CodeUrl": "https://github.com/CommunityToolkit/WindowsCommunityToolkit/tree/main/Microsoft.Toolkit.Uwp.UI.Media/Geometry", "Icon": "/SamplePages/CanvasPathGeometry/CanvasPathGeometry.png", "DocumentationUrl": "https://raw.githubusercontent.com/MicrosoftDocs/WindowsCommunityToolkitDocs/master/docs/parsers/CanvasPathGeometry.md" }, @@ -882,7 +892,7 @@ "Name": "Guard APIs", "Subcategory": "Developer", "About": "The Guard APIs can be used to validate method arguments in a streamlined manner, which is also faster, less verbose, more expressive and less error prone than manually writing checks and throwing exceptions.", - "CodeUrl" : "https://github.com/CommunityToolkit/WindowsCommunityToolkit/tree/main/Microsoft.Toolkit.Diagnostics", + "CodeUrl": "https://github.com/CommunityToolkit/WindowsCommunityToolkit/tree/main/Microsoft.Toolkit.Diagnostics", "Icon": "/Assets/Helpers.png", "DocumentationUrl": "https://raw.githubusercontent.com/MicrosoftDocs/WindowsCommunityToolkitDocs/master/docs/developer-tools/Guard.md" }, @@ -890,7 +900,7 @@ "Name": "High Performance APIs", "Subcategory": "Developer", "About": "The High Performance package contains a set of APIs that are heavily focused on optimization. All the new APIs have been carefully crafted to achieve the best possible performance when using them, either through reduced memory allocation, micro-optimizations at the assembly level, or by structuring the APIs in a way that facilitates writing performance oriented code in general.", - "CodeUrl" : "https://github.com/CommunityToolkit/WindowsCommunityToolkit/tree/main/Microsoft.Toolkit.HighPerformance", + "CodeUrl": "https://github.com/CommunityToolkit/WindowsCommunityToolkit/tree/main/Microsoft.Toolkit.HighPerformance", "Icon": "/Assets/Helpers.png", "DocumentationUrl": "https://raw.githubusercontent.com/MicrosoftDocs/WindowsCommunityToolkitDocs/master/docs/high-performance/Introduction.md" }, diff --git a/Microsoft.Toolkit.Uwp.UI.Behaviors/Keyboard/KeyDownTriggerBehavior.cs b/Microsoft.Toolkit.Uwp.UI.Behaviors/Keyboard/KeyDownTriggerBehavior.cs new file mode 100644 index 00000000000..367f1ba8cf8 --- /dev/null +++ b/Microsoft.Toolkit.Uwp.UI.Behaviors/Keyboard/KeyDownTriggerBehavior.cs @@ -0,0 +1,66 @@ +using System.Windows.Input; +using Microsoft.Xaml.Interactivity; +using Windows.System; +using Windows.UI.Xaml; +using Windows.UI.Xaml.Input; + +namespace Microsoft.Toolkit.Uwp.UI.Behaviors +{ + /// + /// This behavior listens to a key down event on the associated when it is loaded and executes an action. + /// + [TypeConstraint(typeof(FrameworkElement))] + public class KeyDownTriggerBehavior : Trigger + { + + /// + /// The DP to store the property value. + /// + public static readonly DependencyProperty KeyProperty = DependencyProperty.Register( + "Key", + typeof(VirtualKey), + typeof(KeyDownTriggerBehavior), + new PropertyMetadata(null)); + + /// + /// Gets or sets the key to listen when the associated object is loaded. + /// + public VirtualKey Key + { + get => (VirtualKey)GetValue(KeyProperty); + set => SetValue(KeyProperty, value); + } + + public static readonly DependencyProperty CommandProperty = DependencyProperty.Register( + "Command", + typeof(ICommand), + typeof(KeyDownTriggerBehavior), + new PropertyMetadata(null)); + + /// + protected override void OnAttached() + { + ((FrameworkElement)AssociatedObject).KeyDown += OnAssociatedObjectKeyDown; + } + + /// + protected override void OnDetaching() + { + ((FrameworkElement)AssociatedObject).KeyDown -= OnAssociatedObjectKeyDown; + } + + /// + /// Invokes the current actions when the is pressed. + /// + /// The source instance. + /// The arguments for the event (unused). + private void OnAssociatedObjectKeyDown(object sender, KeyRoutedEventArgs keyRoutedEventArgs) + { + if (keyRoutedEventArgs.Key == Key) + { + keyRoutedEventArgs.Handled = true; + Interaction.ExecuteActions(sender, Actions, keyRoutedEventArgs); + } + } + } +} \ No newline at end of file From fafbf5b335af26012ef7d21a9c7547c7c3e89dbd Mon Sep 17 00:00:00 2001 From: Shane Weaver Date: Wed, 4 Aug 2021 15:19:31 -0700 Subject: [PATCH 10/46] Added support for TokenSelectionMode in TokenizingTextBox --- .../TokenizingTextBoxXaml.bind | 4 ++- .../TokenizingTextBox/TokenSelectionMode.cs | 28 +++++++++++++++++ .../TokenizingTextBox.Properties.cs | 31 +++++++++++++++++++ .../TokenizingTextBox/TokenizingTextBox.cs | 13 ++++++++ 4 files changed, 75 insertions(+), 1 deletion(-) create mode 100644 Microsoft.Toolkit.Uwp.UI.Controls.Input/TokenizingTextBox/TokenSelectionMode.cs diff --git a/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/TokenizingTextBox/TokenizingTextBoxXaml.bind b/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/TokenizingTextBox/TokenizingTextBoxXaml.bind index b38058a507d..672d2c72d1d 100644 --- a/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/TokenizingTextBox/TokenizingTextBoxXaml.bind +++ b/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/TokenizingTextBox/TokenizingTextBoxXaml.bind @@ -39,7 +39,8 @@ MaxHeight="104" HorizontalAlignment="Stretch" TextMemberPath="Text" - TokenDelimiter=","> + TokenDelimiter="," + TokenSelectionMode="Single"> @@ -75,6 +76,7 @@ QueryIcon="{ui:SymbolIconSource Symbol=Find}" TextMemberPath="Text" TokenDelimiter="," + TokenSelectionMode="Multiple" IsItemClickEnabled="True" TokenItemTemplate="{StaticResource EmailTokenTemplate}"> diff --git a/Microsoft.Toolkit.Uwp.UI.Controls.Input/TokenizingTextBox/TokenSelectionMode.cs b/Microsoft.Toolkit.Uwp.UI.Controls.Input/TokenizingTextBox/TokenSelectionMode.cs new file mode 100644 index 00000000000..77543fb6232 --- /dev/null +++ b/Microsoft.Toolkit.Uwp.UI.Controls.Input/TokenizingTextBox/TokenSelectionMode.cs @@ -0,0 +1,28 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Microsoft.Toolkit.Uwp.UI.Controls +{ + /// + /// Indicates how tokens are selected in the . + /// + public enum TokenSelectionMode + { + /// + /// Only one token can be selected at a time. A new token should replace the active selection. + /// + Single, + + /// + /// Multiple tokens can be selected at a time. + /// + Multiple, + } +} diff --git a/Microsoft.Toolkit.Uwp.UI.Controls.Input/TokenizingTextBox/TokenizingTextBox.Properties.cs b/Microsoft.Toolkit.Uwp.UI.Controls.Input/TokenizingTextBox/TokenizingTextBox.Properties.cs index d6099b8596e..f45c8acc049 100644 --- a/Microsoft.Toolkit.Uwp.UI.Controls.Input/TokenizingTextBox/TokenizingTextBox.Properties.cs +++ b/Microsoft.Toolkit.Uwp.UI.Controls.Input/TokenizingTextBox/TokenizingTextBox.Properties.cs @@ -157,6 +157,26 @@ private static void TextPropertyChanged(DependencyObject d, DependencyPropertyCh typeof(TokenizingTextBox), new PropertyMetadata(false)); + /// + /// Identifies the property. + /// + public static readonly DependencyProperty TokenSelectionModeProperty = DependencyProperty.Register( + nameof(TokenSelectionMode), + typeof(TokenSelectionMode), + typeof(TokenizingTextBox), + new PropertyMetadata(TokenSelectionMode.Multiple, OnTokenSelectionModeChanged)); + + private static void OnTokenSelectionModeChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + if (d is TokenizingTextBox ttb && e.NewValue is TokenSelectionMode newTokenSelectionMode && newTokenSelectionMode == TokenSelectionMode.Single) + { + while (ttb.Items.Count > 1) + { + ttb.Items.RemoveAt(ttb.Items.Count - 1); + } + } + } + /// /// Gets or sets the Style for the contained AutoSuggestBox template part. /// @@ -303,5 +323,16 @@ public string SelectedTokenText return PrepareSelectionForClipboard(); } } + + /// + /// Gets or sets how the control should display tokens. + /// is the default. Multiple tokens can be selected at a time. + /// indicates that only one token can be present in the control at a time. + /// + public TokenSelectionMode TokenSelectionMode + { + get => (TokenSelectionMode)GetValue(TokenSelectionModeProperty); + set => SetValue(TokenSelectionModeProperty, value); + } } } \ No newline at end of file diff --git a/Microsoft.Toolkit.Uwp.UI.Controls.Input/TokenizingTextBox/TokenizingTextBox.cs b/Microsoft.Toolkit.Uwp.UI.Controls.Input/TokenizingTextBox/TokenizingTextBox.cs index 89474a89fc6..849612b9c1d 100644 --- a/Microsoft.Toolkit.Uwp.UI.Controls.Input/TokenizingTextBox/TokenizingTextBox.cs +++ b/Microsoft.Toolkit.Uwp.UI.Controls.Input/TokenizingTextBox/TokenizingTextBox.cs @@ -448,6 +448,19 @@ internal async Task AddTokenAsync(object data, bool? atEnd = null) } } + if (TokenSelectionMode == TokenSelectionMode.Single) + { + // Remove any existing tokens. + for (var i = _innerItemsSource.Count - 1; i >= 0; --i) + { + var item = _innerItemsSource[i]; + if (item is not ITokenStringContainer) + { + _innerItemsSource.Remove(item); + } + } + } + // If we've been typing in the last box, just add this to the end of our collection if (atEnd == true || _currentTextEdit == _lastTextEdit) { From fbeb2c4cf6681a43c756b08b948abc1bfac4a195 Mon Sep 17 00:00:00 2001 From: Shane Weaver Date: Wed, 4 Aug 2021 15:49:50 -0700 Subject: [PATCH 11/46] Added TokenItemRemoved events --- .../TokenizingTextBox/TokenizingTextBox.Properties.cs | 11 +++++++++-- .../TokenizingTextBox/TokenizingTextBox.cs | 4 +++- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/Microsoft.Toolkit.Uwp.UI.Controls.Input/TokenizingTextBox/TokenizingTextBox.Properties.cs b/Microsoft.Toolkit.Uwp.UI.Controls.Input/TokenizingTextBox/TokenizingTextBox.Properties.cs index f45c8acc049..e1409afa3a4 100644 --- a/Microsoft.Toolkit.Uwp.UI.Controls.Input/TokenizingTextBox/TokenizingTextBox.Properties.cs +++ b/Microsoft.Toolkit.Uwp.UI.Controls.Input/TokenizingTextBox/TokenizingTextBox.Properties.cs @@ -170,9 +170,16 @@ private static void OnTokenSelectionModeChanged(DependencyObject d, DependencyPr { if (d is TokenizingTextBox ttb && e.NewValue is TokenSelectionMode newTokenSelectionMode && newTokenSelectionMode == TokenSelectionMode.Single) { - while (ttb.Items.Count > 1) + // Start at the end, remove all but the first token. + for (var i = ttb._innerItemsSource.Count - 1; i >= 1; --i) { - ttb.Items.RemoveAt(ttb.Items.Count - 1); + var item = ttb._innerItemsSource[i]; + if (item is not ITokenStringContainer) + { + // Force remove the items. No warning and no option to cancel. + ttb._innerItemsSource.Remove(item); + ttb.TokenItemRemoved?.Invoke(ttb, item); + } } } } diff --git a/Microsoft.Toolkit.Uwp.UI.Controls.Input/TokenizingTextBox/TokenizingTextBox.cs b/Microsoft.Toolkit.Uwp.UI.Controls.Input/TokenizingTextBox/TokenizingTextBox.cs index 849612b9c1d..36771cc48e4 100644 --- a/Microsoft.Toolkit.Uwp.UI.Controls.Input/TokenizingTextBox/TokenizingTextBox.cs +++ b/Microsoft.Toolkit.Uwp.UI.Controls.Input/TokenizingTextBox/TokenizingTextBox.cs @@ -450,13 +450,15 @@ internal async Task AddTokenAsync(object data, bool? atEnd = null) if (TokenSelectionMode == TokenSelectionMode.Single) { - // Remove any existing tokens. + // Start at the end, remove any existing tokens. for (var i = _innerItemsSource.Count - 1; i >= 0; --i) { var item = _innerItemsSource[i]; if (item is not ITokenStringContainer) { + // Force remove the items. No warning and no option to cancel. _innerItemsSource.Remove(item); + TokenItemRemoved?.Invoke(this, item); } } } From bc098689478642814b14e0b72b76ffeae3d829bb Mon Sep 17 00:00:00 2001 From: Nirmal Guru Date: Sun, 14 Feb 2021 14:02:13 +0530 Subject: [PATCH 12/46] Format Markdown files Clean-up and Format with MarkdownLint --- .github/ISSUE_TEMPLATE/bug_report.md | 25 +++++++++++++++-------- .github/ISSUE_TEMPLATE/documentation.md | 1 - .github/ISSUE_TEMPLATE/feature_request.md | 10 ++++----- .github/ISSUE_TEMPLATE/question.md | 1 - .github/ISSUE_TEMPLATE/win32_controls.md | 1 - .github/PULL_REQUEST_TEMPLATE.md | 18 ++++++++-------- Contributing.md | 15 ++++++++++---- Microsoft.Toolkit.Uwp.SampleApp/ReadMe.md | 18 +++++++++++----- ReadMe.md | 11 ++++++++++ 9 files changed, 66 insertions(+), 34 deletions(-) diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md index 510e2f03e7b..c708280d704 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -4,7 +4,6 @@ about: Create a report to help us fix something that isn't working as expected title: '' labels: "bug :bug:" assignees: '' - --- ## Describe the bug + A clear and concise description of what the bug is. - [ ] Is this bug a regression in the toolkit? If so, what toolkit version did you last see it work: ## Steps to Reproduce -- [ ] Can this be reproduced in the Sample App? (Either in a sample as-is or with new XAML pasted in the editor.) If so, please provide custom XAML or steps to reproduce. If not, let us know why it can't be reproduced (e.g. more complex setup, environment, dependencies, etc...) +- [ ] Can this be reproduced in the Sample App? (Either in a sample as-is or with new XAML pasted in the editor.) If so, please provide custom XAML or steps to reproduce. If not, let us know why it can't be reproduced (e.g. more complex setup, environment, dependencies, etc...) + + Steps to reproduce the behavior: + 1. Given the following environment (Sample App w/ XAML, Project with Isolated setup, etc...) 2. Go to '...' 3. Click on '....' @@ -30,19 +33,23 @@ Steps to reproduce the behavior: ## Expected behavior -A clear and concise description of what you expected to happen. + + ## Screenshots -If applicable, add screenshots to help explain your problem. + + ## Environment + -``` + NuGet Package(s): Package Version(s): Windows 10 Build Number: + - [ ] Fall Creators Update (16299) - [ ] April 2018 Update (17134) - [ ] October 2018 Update (17763) @@ -51,6 +58,7 @@ Windows 10 Build Number: - [ ] Insider Build (build number: ) App min and target version: + - [ ] Fall Creators Update (16299) - [ ] April 2018 Update (17134) - [ ] October 2018 Update (17763) @@ -59,17 +67,18 @@ App min and target version: - [ ] Insider Build (xxxxx) Device form factor: + - [ ] Desktop - [ ] Xbox - [ ] Surface Hub - [ ] IoT Visual Studio + - [ ] 2017 (version: ) - [ ] 2019 (version: ) - [ ] 2019 Preview (version: ) -``` - ## Additional context -Add any other context about the problem here. + + diff --git a/.github/ISSUE_TEMPLATE/documentation.md b/.github/ISSUE_TEMPLATE/documentation.md index ef255cc789e..1fd2f3c19d9 100644 --- a/.github/ISSUE_TEMPLATE/documentation.md +++ b/.github/ISSUE_TEMPLATE/documentation.md @@ -4,7 +4,6 @@ about: I have a documentation suggestion or question title: "[Docs]" labels: documentation assignees: '' - --- +IF NOT CERTAIN ABOUT THE FEATURE AND REQUIRE MORE CLARITY THEN PLEASE POST ON "IDEAS" CATEGORY OF THE DISCUSSIONS PLATFORM [https://github.com/CommunityToolkit/WindowsCommunityToolkit/discussions/categories/ideas] WHERE YOU CAN DISCUSS AND ENGAGE WITH THE COMMUNITY TO GAIN FURTHER CLARITY REGARDING THE FEATURE 🚨 --> ## Describe the problem this feature would solve + - ## Describe the solution - + ## Describe alternatives you've considered - + ## Additional context & Screenshots + diff --git a/.github/ISSUE_TEMPLATE/question.md b/.github/ISSUE_TEMPLATE/question.md index 01f64286db0..b10fd188db8 100644 --- a/.github/ISSUE_TEMPLATE/question.md +++ b/.github/ISSUE_TEMPLATE/question.md @@ -4,7 +4,6 @@ about: I have a question about how to use something in the toolkit. title: "[Question]" labels: "question :grey_question:" assignees: '' - --- - + - ## Fixes # + ## PR Type + What kind of change does this PR introduce? + @@ -23,14 +25,13 @@ What kind of change does this PR introduce? - ## What is the current behavior? - + ## What is the new behavior? - + ## PR Checklist @@ -39,14 +40,13 @@ Please check if your PR fulfills the following requirements: - [ ] Tested code with current [supported SDKs](../readme.md#supported) - [ ] Pull Request has been submitted to the documentation repository [instructions](..\contributing.md#docs). Link: - [ ] Sample in sample app has been added / updated (for bug fixes / features) - - [ ] Icon has been created (if new sample) following the [Thumbnail Style Guide and templates](https://github.com/CommunityToolkit/WindowsCommunityToolkit-design-assets) + - [ ] Icon has been created (if new sample) following the [Thumbnail Style Guide and templates](https://github.com/CommunityToolkit/WindowsCommunityToolkit-design-assets) - [ ] New major technical changes in the toolkit have or will be added to the [Wiki](https://github.com/CommunityToolkit/WindowsCommunityToolkit/wiki) e.g. build changes, source generators, testing infrastructure, sample creation changes, etc... - [ ] Tests for the changes have been added (for bug fixes / features) (if applicable) -- [ ] Header has been added to all new source files (run *build/UpdateHeaders.bat*) +- [ ] Header has been added to all new source files (run _build/UpdateHeaders.bat_) - [ ] Contains **NO** breaking changes - +Please note that breaking changes are likely to be rejected within minor release cycles or held until major versions. --> ## Other information diff --git a/Contributing.md b/Contributing.md index 6e784b3635d..1e2fadfc767 100644 --- a/Contributing.md +++ b/Contributing.md @@ -7,33 +7,40 @@ In the next few steps, you will be able to see a glimpse of ways you can contrib :rotating_light: **It is highly recommended to visit [Windows Community Toolkit Wiki](https://aka.ms/wct/wiki) where you can find complete and detail-oriented content of this page** :rotating_light: ## Questions :grey_question: + Due to the high volume of incoming issues please keep our GitHub issues for bug reports and feature requests. For general questions, there is a higher chance of getting your question answered on [StackOverflow](https://stackoverflow.com/questions/tagged/windows-community-toolkit) where questions should be tagged with the tag windows-community-toolkit. For missing documentation related question, please file an issue at [Microsoft Docs](https://github.com/MicrosoftDocs/WindowsCommunityToolkitDocs/issues/new). ## Fix a Bug :bug: + If you find any bug, you can help the community by [submitting an issue](https://github.com/CommunityToolkit/WindowsCommunityToolkit/issues/new?assignees=&labels=bug+%3Abug%3A&template=bug_report.md&title=). Once the issue is filed, feel free to start working on the PR and submit a PR. ## Good First Issue :ok_hand: + If this is your first time contributing to the Windows Community Toolkit and do not have advanced level programming experience, we have got you covered :boom: WCT has a list of [good first issue](https://github.com/CommunityToolkit/WindowsCommunityToolkit/labels/good%20first%20issue%20%3Aok_hand%3A) that can be a great entryway to find and fix any issues that best fit your expertise or technical background. ## Help Wanted :raising_hand: + WCT has a list of issues that are labeled as [help wanted](https://github.com/CommunityToolkit/WindowsCommunityToolkit/labels/help%20wanted%20%3Araising_hand%3A). The level of complexity in the list can vary but if you have an advanced level of programming experience, feel free to jump in to solve these issues. ## Add New Feature :mailbox_with_mail: -* To contribute a new feature, fill out the [Feature Request Template](https://github.com/CommunityToolkit/WindowsCommunityToolkit/issues/new?assignees=&labels=feature+request+%3Amailbox_with_mail%3A&template=feature_request.md&title=%5BFeature%5D) and provide detailed information to express the proposal. -* Once the Feature Request is submitted, it will be open for discussion. -* If it gets approved by the team, proceed to submit a PR of the proposed Feature. -* If the PR contains an error-free code and the reviewer signs off, the PR will be merged. + +* To contribute a new feature, fill out the [Feature Request Template](https://github.com/CommunityToolkit/WindowsCommunityToolkit/issues/new?assignees=&labels=feature+request+%3Amailbox_with_mail%3A&template=feature_request.md&title=%5BFeature%5D) and provide detailed information to express the proposal. +* Once the Feature Request is submitted, it will be open for discussion. +* If it gets approved by the team, proceed to submit a PR of the proposed Feature. +* If the PR contains an error-free code and the reviewer signs off, the PR will be merged. ## Add or Improve Documentation :page_with_curl: Due to the involvement of multiple steps to add or improve documents; it is required to visit [Windows Community Toolkit Wiki](https://aka.ms/wct/wiki) and follow contribution guidelines. ## Create, Submit or Review Pull Request :rocket: + Anyone with write access can create a Pull Request by forking the Windows Community Toolkit Repository. Here is how you can [Create a Pull Request from fork](https://help.github.com/en/github/collaborating-with-issues-and-pull-requests/creating-a-pull-request-from-a-fork). Once you fork the Windows Community Toolkit repo, it is essential to create all changes in the feature branch of your forked repository. If you have the changes in the forked feature branch, you can then create a Pull Request in the main Windows Community Toolkit. Please visit [Windows Community Toolkit Wiki](https://aka.ms/wct/wiki) for detailed information and steps it requires to Submit or Review Pull Request. # ThankYou :heart::heart: + **Thank you so much for contributing to this amazing project. We hope you will continue to add value and find yourself as a highly reliable source to the Windows Community Toolkit** diff --git a/Microsoft.Toolkit.Uwp.SampleApp/ReadMe.md b/Microsoft.Toolkit.Uwp.SampleApp/ReadMe.md index 6a059b6c637..494a495b72d 100644 --- a/Microsoft.Toolkit.Uwp.SampleApp/ReadMe.md +++ b/Microsoft.Toolkit.Uwp.SampleApp/ReadMe.md @@ -1,3 +1,4 @@ + For the latest info, [visit the wiki article here](https://github.com/CommunityToolkit/WindowsCommunityToolkit/wiki/Sample-Development). # How to add new samples @@ -8,6 +9,7 @@ This document describes how to add a new sample page for a new control you want ## 1. Add Sample page and .bind template + First you need to create a Xaml page in the folder /SamplePages/YourControl. This will be the logical page used to by the app to navigate to the sample and contains code. If providing 'live' XAML, a .bind file is loaded and dynamically fed to the XamlReader.Load method to convert into actual controls. This changes a few things about how samples need to be written (detailed below), but allows developers to actually change the sample and see the results live. @@ -16,6 +18,7 @@ This not only gives us a killer sample app, but it also means that all our sampl ## 2. Binding text + The .bind files are templates which use @[Property Name:Type:DefaultValue:Options] syntax to allow for customized options to be presented to the user in the sample app. The user can play with the values in the property page and see results change instantly. This is accomplished by using {Binding} syntax when on the property page, but switches to the raw value when the developer goes to the XAML page. This makes it easy for a developer to test out values for a control and then copy the XAML needed for that exact result into their app. @@ -30,10 +33,10 @@ Here is an example: - + Text="@[Text:String:Hey!]" Foreground="Black" + FontSize="@[FontSize:Slider:12:10-30]" + VerticalAlignment="@[Vertical Alignment:Enum:VerticalAlignment.Center]"> + ``` @@ -86,12 +89,14 @@ Value="@[Value:Slider:0:0-180]@" ``` ## 3. Have a *'Shallow Copy'* of your example in the sample page + Even though the sample page content is ignored and the dynamic template injected, for the XamlReader to access some classes, a reference to the item is sometimes needed in the hosting app for it to be accessible. (I assume it's an optimization thing.) Therefore, for any new control/extension, you should still have a simplified snippet of it contained in the sample page compiled/loaded by the app. You should remove names, events, and properties (unless extensions) from these so the namespace isn't accidentally polluted. If you re-use the same control, you don't have to include it twice. ## 4. For Events/Resource Templates: Have your sample page implement the **IXamlRendererListener** interface + This gets called whenever the template gets parsed (due to loading or user modification). Here you can use the [LogicalTree](https://github.com/CommunityToolkit/WindowsCommunityToolkit/blob/main/Microsoft.Toolkit.Uwp.UI/Extensions/Tree/LogicalTree.cs) extensions to grab named controls in the template and register their events. **Check for null first** as the developer may have removed the name from the element. ```csharp @@ -106,6 +111,7 @@ You'll have to register all events and grab **control.Resources** for templates ## 5. For Interactive Buttons: Use **SampleController.Current.RegisterNewCommand** + Buttons can be added through this command and are accessible in the main panel so they can be clicked when changing properties or editing XAML. It's important instead of using buttons in your sample (as events can't be directly used, see above) to register these commands. ```csharp @@ -129,10 +135,12 @@ if (resources?.ContainsKey("ThingStyle") == true) ``` ## 6. *Optional:* If you need *extra stuff* around the sample + Now, the sample page content in the app is ignored, but you can override that behavior by adding a `` element to the page. If this element is found, it will serve as the host to the dynamic .bind content instead. In this manner you can have a status/warning message outside of the control of the developer in the XAML sample tab. # Update Samples.json + After creating your page and the binding text, you just need to reference it in the /SamplePages/samples.json file. Select the category where you want your page to be listed and add the following information: @@ -179,7 +187,6 @@ The value is a string which is the fully-qualified typename to check for the pre If the specified type is not found on the system running the sample app the sample will not appear in the sample list. - ### Adding documentation Every API must be accompanied by Markdown documentation in the [documentation repository](..\contributing.md#docs). @@ -193,6 +200,7 @@ Use the DocumentationUrl property to add a link to the raw documentation in *sam > NOTE: The documentation is also packaged with the sample app. If there is no network connection, or the documentation is not yet on GitHub, the sample app will use the packaged version > NOTE: To test your documentation in the sample app while running in debug mode, the docs repository will need to be cloned in the same folder as this repository and named **WindowsCommunityToolkitDocs**. For example, this folder structure works best: + > ``` > repositories > ├── WindowsCommunityToolkit diff --git a/ReadMe.md b/ReadMe.md index 21c7d25a8b2..9ccc3f41555 100644 --- a/ReadMe.md +++ b/ReadMe.md @@ -1,5 +1,6 @@ # Windows Community Toolkit :toolbox: + The Windows Community Toolkit is a collection of helper functions, custom controls, and app services. It simplifies and demonstrates common developer patterns when building experiences for Windows 10. | Target | Branch | Status | Recommended package version | @@ -8,37 +9,47 @@ The Windows Community Toolkit is a collection of helper functions, custom contro | Pre-release beta testing | main | [![Build Status](https://dev.azure.com/dotnet/CommunityToolkit/_apis/build/status/Toolkit-CI?branchName=main)](https://dev.azure.com/dotnet/CommunityToolkit/_build/latest?definitionId=10) | [![DevOps](https://vsrm.dev.azure.com/dotnet/_apis/public/Release/badge/696bc9fd-f160-4e97-a1bd-7cbbb3b58f66/1/1)](https://dev.azure.com/dotnet/CommunityToolkit/_packaging?_a=feed&feed=CommunityToolkit-MainLatest) | ## Getting Started :raised_hands: + Please read the [Getting Started with the Windows Community Toolkit](https://docs.microsoft.com/windows/communitytoolkit/getting-started) page for more detailed information about using the toolkit. ## Documentation :pencil: + All documentation for the toolkit is hosted on [Microsoft Docs](https://docs.microsoft.com/windows/communitytoolkit/). All API documentation can be found at the [.NET API Browser](https://docs.microsoft.com/dotnet/api/?view=win-comm-toolkit-dotnet-stable). ## Windows Community Toolkit Sample App :iphone: + Want to see the toolkit in action before jumping into the code? Download and play with the [Windows Community Toolkit Sample App](https://www.microsoft.com/store/apps/9nblggh4tlcq) from the Store. ## Contribution :rocket: + Do you want to contribute? Check out our [Windows Community Toolkit Wiki](https://aka.ms/wct/wiki) page to learn more about contribution and guidelines. ## NuGet Packages :package: + NuGet is a standard package manager for .NET applications which is built into Visual Studio. When you open solution in Visual Studio, choose the *Tools* menu > *NuGet Package Manager* > *Manage NuGet packages for solution...* Enter one of the package names mentioned in [Windows Community Toolkit Nuget Packages](https://docs.microsoft.com/en-us/windows/communitytoolkit/nuget-packages) table to search for it online. ## Features :mailbox: + The [Features list](https://github.com/MicrosoftDocs/WindowsCommunityToolkitDocs/blob/master/docs/toc.md#controls) refers to all the currently available features that can be found in the Windows Community Toolkit. Most features should work with the October 2018 Update (1809) SDK 17763 and above; however, refer to specific documentation on each feature for more information. ## Principles :ballot_box_with_check: + * Principle **#1**: The toolkit will be kept simple. * Principle **#2**: As soon as a comparable feature is available in the Windows SDK for Windows 10, it will be marked as deprecated. * Principle **#3**: All features will be supported for two Windows SDK for Windows 10 release cycles or until another principle supersedes it. ## Roadmap :earth_americas: + Read what we [plan for next iterations](https://github.com/CommunityToolkit/WindowsCommunityToolkit/milestones), and feel free to ask questions. Check out our [Preview Packages Wiki Page](https://github.com/CommunityToolkit/WindowsCommunityToolkit/wiki/Preview-Packages) to learn more about updating your NuGet sources in Visual Studio, then you can also get pre-release packages of upcoming versions to try. ## Code of Conduct :page_facing_up: + This project has adopted the code of conduct defined by the [Contributor Covenant](http://contributor-covenant.org/) to clarify expected behavior in our community. For more information see the [.NET Foundation Code of Conduct](CODE_OF_CONDUCT.md). ## .NET Foundation + This project is supported by the [.NET Foundation](http://dotnetfoundation.org). From 43cf51f3acd06e0eb35098c33ea6f8bd1a2a9bcf Mon Sep 17 00:00:00 2001 From: Nirmal Guru Date: Sun, 14 Feb 2021 14:02:13 +0530 Subject: [PATCH 13/46] Update Markdown files Fix-up MarkdownLint warnings --- .github/ISSUE_TEMPLATE/bug_report.md | 18 +-- .github/PULL_REQUEST_TEMPLATE.md | 12 +- CODE_OF_CONDUCT.md | 4 +- Contributing.md | 34 ++--- License.md | 8 +- Microsoft.Toolkit.Uwp.SampleApp/ReadMe.md | 169 ++++++++++++---------- Microsoft.Toolkit.Win32/ReadMe.md | 4 +- ReadMe.md | 32 ++-- 8 files changed, 146 insertions(+), 135 deletions(-) diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md index c708280d704..1bdcc56d20e 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -8,7 +8,7 @@ assignees: '' +IF NOT CERTAIN ABOUT THE ISSUE AND REQUIRE MORE CLARITY THEN PLEASE POST ON "QUESTIONS & HELP" CATEGORY OF THE DISCUSSIONS PLATFORM [https://github.com/CommunityToolkit/WindowsCommunityToolkit/discussions/categories/questions-help] WHERE YOU CAN DISCUSS AND ENGAGE WITH THE COMMUNITY TO GAIN FURTHER CLARITY REGARDING THE ISSUE 🚨 --> ## Describe the bug @@ -42,9 +42,9 @@ Steps to reproduce the behavior: ## Environment - + -NuGet Package(s): +NuGet Package(s): Package Version(s): @@ -55,7 +55,7 @@ Windows 10 Build Number: - [ ] October 2018 Update (17763) - [ ] May 2019 Update (18362) - [ ] May 2020 Update (19041) -- [ ] Insider Build (build number: ) +- [ ] Insider Build ({build_number}) App min and target version: @@ -64,7 +64,7 @@ App min and target version: - [ ] October 2018 Update (17763) - [ ] May 2019 Update (18362) - [ ] May 2020 Update (19041) -- [ ] Insider Build (xxxxx) +- [ ] Insider Build ({build_number}) Device form factor: @@ -73,11 +73,11 @@ Device form factor: - [ ] Surface Hub - [ ] IoT -Visual Studio +Visual Studio version: -- [ ] 2017 (version: ) -- [ ] 2019 (version: ) -- [ ] 2019 Preview (version: ) +- [ ] 2017 (15.{minor_version}) +- [ ] 2019 (16.{minor_version}) +- [ ] 2022 (17.{minor_version}) ## Additional context diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index 5e2661fd673..14fe6178110 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -4,9 +4,9 @@ -## Fixes # +## Fixes - + @@ -35,10 +35,10 @@ What kind of change does this PR introduce? ## PR Checklist -Please check if your PR fulfills the following requirements: +Please check if your PR fulfills the following requirements: -- [ ] Tested code with current [supported SDKs](../readme.md#supported) -- [ ] Pull Request has been submitted to the documentation repository [instructions](..\contributing.md#docs). Link: +- [ ] Tested code with current [supported SDKs](../#supported) +- [ ] Pull Request has been submitted to the documentation repository [instructions](../blob/main/Contributing.md#docs). Link: - [ ] Sample in sample app has been added / updated (for bug fixes / features) - [ ] Icon has been created (if new sample) following the [Thumbnail Style Guide and templates](https://github.com/CommunityToolkit/WindowsCommunityToolkit-design-assets) - [ ] New major technical changes in the toolkit have or will be added to the [Wiki](https://github.com/CommunityToolkit/WindowsCommunityToolkit/wiki) e.g. build changes, source generators, testing infrastructure, sample creation changes, etc... @@ -50,3 +50,5 @@ Please check if your PR fulfills the following requirements: Please note that breaking changes are likely to be rejected within minor release cycles or held until major versions. --> ## Other information + + \ No newline at end of file diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md index 71537d29c6e..1f377cc3715 100644 --- a/CODE_OF_CONDUCT.md +++ b/CODE_OF_CONDUCT.md @@ -68,9 +68,9 @@ members of the project's leadership. ## Attribution This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, -available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html +available at [homepage]: https://www.contributor-covenant.org For answers to common questions about this code of conduct, see -https://www.contributor-covenant.org/faq + diff --git a/Contributing.md b/Contributing.md index 1e2fadfc767..f5e8ced4566 100644 --- a/Contributing.md +++ b/Contributing.md @@ -1,46 +1,46 @@ -# Contributing to the Windows Community Toolkit :sparkles::sparkles: +# ✨ Contributing to the Windows Community Toolkit Thank you for exhibiting interest in contributing to the Windows Community Toolkit. The team is delighted to welcome you onboard to our exciting and growing project. Any contribution or value added go a long way to enhance the project! In the next few steps, you will be able to see a glimpse of ways you can contribute to the Windows Community Toolkit. -:rotating_light: **It is highly recommended to visit [Windows Community Toolkit Wiki](https://aka.ms/wct/wiki) where you can find complete and detail-oriented content of this page** :rotating_light: +🚨 **It is highly recommended to visit [Windows Community Toolkit Wiki](https://aka.ms/wct/wiki) where you can find complete and detail-oriented content of this page** 🚨 -## Questions :grey_question: +## ❔ Questions -Due to the high volume of incoming issues please keep our GitHub issues for bug reports and feature requests. For general questions, there is a higher chance of getting your question answered on [StackOverflow](https://stackoverflow.com/questions/tagged/windows-community-toolkit) where questions should be tagged with the tag windows-community-toolkit. +Due to the high volume of incoming issues please keep our GitHub issues for bug reports and feature requests. For general questions, there is a higher chance of getting your question answered on [StackOverflow](https://stackoverflow.com/questions/tagged/windows-community-toolkit) where questions should be tagged with the tag `windows-community-toolkit`. For missing documentation related question, please file an issue at [Microsoft Docs](https://github.com/MicrosoftDocs/WindowsCommunityToolkitDocs/issues/new). -## Fix a Bug :bug: +## 🐛 Fix a Bug -If you find any bug, you can help the community by [submitting an issue](https://github.com/CommunityToolkit/WindowsCommunityToolkit/issues/new?assignees=&labels=bug+%3Abug%3A&template=bug_report.md&title=). Once the issue is filed, feel free to start working on the PR and submit a PR. +If you find any bug, you can help the community by [submitting an issue](https://github.com/CommunityToolkit/WindowsCommunityToolkit/issues/new?template=bug_report.md&labels=bug+:bug:&title=[Bug]). Once the issue is filed, feel free to start working on the PR and submit a PR. -## Good First Issue :ok_hand: +## 👌 Good First Issue -If this is your first time contributing to the Windows Community Toolkit and do not have advanced level programming experience, we have got you covered :boom: WCT has a list of [good first issue](https://github.com/CommunityToolkit/WindowsCommunityToolkit/labels/good%20first%20issue%20%3Aok_hand%3A) that can be a great entryway to find and fix any issues that best fit your expertise or technical background. +If this is your first time contributing to the Windows Community Toolkit (_WCT_) and do not have advanced level programming experience, we have got you covered 💥 WCT has a list of [good first issue](https://github.com/CommunityToolkit/WindowsCommunityToolkit/labels/good%20first%20issue) that can be a great entryway to find and fix any issues that best fit your expertise or technical background. -## Help Wanted :raising_hand: +## 🙋 Help Wanted -WCT has a list of issues that are labeled as [help wanted](https://github.com/CommunityToolkit/WindowsCommunityToolkit/labels/help%20wanted%20%3Araising_hand%3A). The level of complexity in the list can vary but if you have an advanced level of programming experience, feel free to jump in to solve these issues. +WCT also has a list of issues that are labeled as [help wanted](https://github.com/CommunityToolkit/WindowsCommunityToolkit/labels/help%20wanted). The level of complexity in the list can vary but if you have an advanced level of programming experience, feel free to jump in to solve these issues. -## Add New Feature :mailbox_with_mail: +## 📬 Add New Feature -* To contribute a new feature, fill out the [Feature Request Template](https://github.com/CommunityToolkit/WindowsCommunityToolkit/issues/new?assignees=&labels=feature+request+%3Amailbox_with_mail%3A&template=feature_request.md&title=%5BFeature%5D) and provide detailed information to express the proposal. +* To contribute a new feature, fill out the [Feature Request Template](https://github.com/CommunityToolkit/WindowsCommunityToolkit/issues/new?template=feature_request.md&labels=feature+request+:mailbox_with_mail:&title=[Feature]) and provide detailed information to express the proposal. * Once the Feature Request is submitted, it will be open for discussion. * If it gets approved by the team, proceed to submit a PR of the proposed Feature. * If the PR contains an error-free code and the reviewer signs off, the PR will be merged. -## Add or Improve Documentation :page_with_curl: +## 📝 Add or Improve Documentation Due to the involvement of multiple steps to add or improve documents; it is required to visit [Windows Community Toolkit Wiki](https://aka.ms/wct/wiki) and follow contribution guidelines. -## Create, Submit or Review Pull Request :rocket: +## 🚀 Create, Submit or Review Pull Request -Anyone with write access can create a Pull Request by forking the Windows Community Toolkit Repository. Here is how you can [Create a Pull Request from fork](https://help.github.com/en/github/collaborating-with-issues-and-pull-requests/creating-a-pull-request-from-a-fork). Once you fork the Windows Community Toolkit repo, it is essential to create all changes in the feature branch of your forked repository. If you have the changes in the forked feature branch, you can then create a Pull Request in the main Windows Community Toolkit. +Anyone can create a Pull Request by forking the Windows Community Toolkit Repository. Here is how you can [Create a Pull Request from fork](https://help.github.com/en/github/collaborating-with-issues-and-pull-requests/creating-a-pull-request-from-a-fork). Once you fork the Windows Community Toolkit repo, it is essential to create all changes in the feature branch of your forked repository. If you have the changes in the forked feature branch, you can then create a Pull Request in the main Windows Community Toolkit. Please visit [Windows Community Toolkit Wiki](https://aka.ms/wct/wiki) for detailed information and steps it requires to Submit or Review Pull Request. -# ThankYou :heart::heart: +## 💙 Thank You -**Thank you so much for contributing to this amazing project. We hope you will continue to add value and find yourself as a highly reliable source to the Windows Community Toolkit** +**Thank you so much for contributing to this amazing project. We hope you will continue to add value and find yourself as a highly reliable source to the Windows Community Toolkit.** diff --git a/License.md b/License.md index c5ae9403092..5aefa46c69b 100644 --- a/License.md +++ b/License.md @@ -1,13 +1,13 @@ # Windows Community Toolkit -Copyright (c) .NET Foundation and Contributors +Copyright © .NET Foundation and Contributors All rights reserved. -# MIT License (MIT) +## MIT License (MIT) -Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. -THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/Microsoft.Toolkit.Uwp.SampleApp/ReadMe.md b/Microsoft.Toolkit.Uwp.SampleApp/ReadMe.md index 494a495b72d..98554a7f74c 100644 --- a/Microsoft.Toolkit.Uwp.SampleApp/ReadMe.md +++ b/Microsoft.Toolkit.Uwp.SampleApp/ReadMe.md @@ -1,105 +1,110 @@ +# Sample Application For the latest info, [visit the wiki article here](https://github.com/CommunityToolkit/WindowsCommunityToolkit/wiki/Sample-Development). -# How to add new samples +## How to add new samples This document describes how to add a new sample page for a new control you want to add to the toolkit. -*DropShadowPanel*, *ImageEx*, and *ImageCache* are good examples of most of the features mentioned below. +*`DropShadowPanel`*, *`ImageEx`*, and *`ImageCache`* are good examples of most of the features mentioned below. +### 1. Add Sample page and `.bind` template -## 1. Add Sample page and .bind template +First you need to create a XAML page in the folder `/SamplePages/YourControl`. This will be the logical page used to by the app to navigate to the sample and contains code. -First you need to create a Xaml page in the folder /SamplePages/YourControl. This will be the logical page used to by the app to navigate to the sample and contains code. - -If providing 'live' XAML, a .bind file is loaded and dynamically fed to the XamlReader.Load method to convert into actual controls. This changes a few things about how samples need to be written (detailed below), but allows developers to actually change the sample and see the results live. +If providing 'live' XAML, a `.bind` file is loaded and dynamically fed to the `XamlReader.Load` method to convert into actual controls. This changes a few things about how samples need to be written (detailed below), but allows developers to actually change the sample and see the results live. This not only gives us a killer sample app, but it also means that all our samples are also self-validating. There can't be a typo in the sample text given in the sample app anymore, as otherwise the sample won't work and should be caught during testing of said sample. +### 2. Binding text -## 2. Binding text - -The .bind files are templates which use @[Property Name:Type:DefaultValue:Options] syntax to allow for customized options to be presented to the user in the sample app. The user can play with the values in the property page and see results change instantly. This is accomplished by using {Binding} syntax when on the property page, but switches to the raw value when the developer goes to the XAML page. +The `.bind` files are templates which use `@[Property Name:Type:DefaultValue:Options]` syntax to allow for customized options to be presented to the user in the sample app. The user can play with the values in the property page and see results change instantly. This is accomplished by using {Binding} syntax when on the property page, but switches to the raw value when the developer goes to the XAML page. This makes it easy for a developer to test out values for a control and then copy the XAML needed for that exact result into their app. -In order to provide a property UI and associated code, you have to define a the .bind XAML file associated with your page. +In order to provide a property UI and associated code, you have to define a `.bind` XAML file associated with your page. Here is an example: -```xaml +```xml - - + + ``` -You can define "interactive" values in this file. The value types can be: -* String: You want the user to provide a text. The string is built like this @[Name:**String**:Default value] -* Slider: You want the user to provide a double value. The string is built like this @[Name:**Slider**:Default value:min-max] -* DoubleSlider: Same as slider but with double values (0.01 precision) -* TimeSpan: You want the user to provide a duration. The string is built like this (all values in milliseconds) @[Name:**TimeSpan**:DefaultValue:min-max] -* Enum: You want the user to provide a enum value. The string is built like this @[Name:**Enum**:EnumType.DefaultValue] -* Brush: You want the user to select a color from a list. The string is built like this @[Name:**Brush**:Black] -* Bool: You want the user to enable or disable a property. The string is built like this @[Name:**Bool**:True] -* Thickness: You want the user to provide a Thickness. The string is built like this @[Name:**Thickness**:0,20,10,0] +You can define “interactive” values in this file. The value types can be: + +* `String`: You want the user to provide a text. An equivalent syntax is `@[Name:String:Default Value]` +* `Slider`: You want the user to provide a double value. An equivalent syntax is `@[Name:Slider:DefaultValue:Min-Max]` +* `DoubleSlider`: Same as slider but with double values (0.01 precision) +* `TimeSpan`: You want the user to provide a duration. An equivalent syntax is (all values in milliseconds) `@[Name:TimeSpan:DefaultValue:Min-Max]` +* `Enum`: You want the user to provide an enum value. An equivalent syntax is `@[Name:Enum:EnumType.DefaultValue]` +* `Brush`: You want the user to select a color from a list. An equivalent syntax is `@[Name:Brush:Black]` +* `Bool`: You want the user to enable or disable a property. An equivalent syntax is `@[Name:Bool:True]` +* `Thickness`: You want the user to provide a Thickness. An equivalent syntax is `@[Name:Thickness:0,20,10,0]` The `Property Name` can also contain spaces, but these will be removed from the property name used for accessing the value in the property bag for any binding/access, see below. -The name and options will be translated **automatically** to the following syntax when your .bind template is being used on the property page: +The name and options will be translated **automatically** to the following syntax when your `.bind` template is being used on the property page: -```xaml +```xml - - + + - + + ``` When the developer switches to the XAML tab, they'll automatically see the selected values instead: -```xaml +```xml - - + + - + + ``` You can also reuse a `@[Property Name]` reference by itself again later to use the same binding/value again in the same template. This will automatically get mapped to the right place without the need to specify all the types/options again. Just set those options on your first usage. -If you happen to need a two-way binding for the generated XAML, then add an extra '@' after the property definition in the template: +If you happen to need a two-way binding for the generated XAML, then add an extra '**@**' after the property definition in the template: -```xaml -Value="@[Value:Slider:0:0-180]@" +```xml + ``` -## 3. Have a *'Shallow Copy'* of your example in the sample page +### 3. Have a '*Shallow Copy*' of your example in the sample page -Even though the sample page content is ignored and the dynamic template injected, for the XamlReader to access some classes, a reference to the item is sometimes needed in the hosting app for it to be accessible. (I assume it's an optimization thing.) +Even though the sample page content is ignored and the dynamic template injected, for the `XamlReader` to access some classes, a reference to the item is sometimes needed in the hosting app for it to be accessible. (I assume it's an optimization thing.) -Therefore, for any new control/extension, you should still have a simplified snippet of it contained in the sample page compiled/loaded by the app. You should remove names, events, and properties (unless extensions) from these so the namespace isn't accidentally polluted. If you re-use the same control, you don't have to include it twice. +Therefore, for any new control/extension, you should still have a simplified snippet of it contained in the sample page compiled/loaded by the app. You should remove names, events, and properties (unless extensions) from these, so the namespace isn't accidentally polluted. If you re-use the same control, you don't have to include it twice. +### 4. For Events/Resource Templates: Have your sample page implement the **`IXamlRendererListener`** interface -## 4. For Events/Resource Templates: Have your sample page implement the **IXamlRendererListener** interface +This gets called whenever the template gets parsed (due to loading or user modification). Here you can use the [`LogicalTree`](https://github.com/CommunityToolkit/WindowsCommunityToolkit/blob/main/Microsoft.Toolkit.Uwp.UI/Extensions/FrameworkElement/FrameworkElementExtensions.LogicalTree.cs) extensions to grab named controls in the template and register their events. **Check for null first** as the developer may have removed the name from the element. -This gets called whenever the template gets parsed (due to loading or user modification). Here you can use the [LogicalTree](https://github.com/CommunityToolkit/WindowsCommunityToolkit/blob/main/Microsoft.Toolkit.Uwp.UI/Extensions/Tree/LogicalTree.cs) extensions to grab named controls in the template and register their events. **Check for null first** as the developer may have removed the name from the element. - -```csharp +```cs var markdownText = control.FindChild("MarkdownText") as MarkdownTextBlock; if (markdownText != null) { @@ -107,14 +112,13 @@ if (markdownText != null) } ``` -You'll have to register all events and grab **control.Resources** for templates from this method as the regular sample page XAML isn't used and you can't hook in an event from the dynamic XAML, it must be done via code by finding the element here. - +You'll have to register all events and grab **`control.Resources`** for templates from this method as the regular sample page XAML isn't used, and you can't hook in an event from the dynamic XAML, it must be done via code by finding the element here. -## 5. For Interactive Buttons: Use **SampleController.Current.RegisterNewCommand** +### 5. For Interactive Buttons: Use **`SampleController.Current.RegisterNewCommand`** -Buttons can be added through this command and are accessible in the main panel so they can be clicked when changing properties or editing XAML. It's important instead of using buttons in your sample (as events can't be directly used, see above) to register these commands. +Buttons can be added through this command and are accessible in the main panel, so they can be clicked when changing properties or editing XAML. It's important instead of using buttons in your sample (as events can't be directly used, see above) to register these commands. -```csharp +```cs protected override async void OnNavigatedTo(NavigationEventArgs e) { base.OnNavigatedTo(e); @@ -123,28 +127,30 @@ protected override async void OnNavigatedTo(NavigationEventArgs e) { AddImage(false, true); }); + + // ••• +} ``` -If your command adds content dynamically, try and use a style template in the .bind XAML that the user can modify. Then grab `resources = control.Resources;` in the *OnXamlRendered* event and set the element style from it: +If your command adds content dynamically, try and use a style template in the `.bind` XAML that the user can modify. Then grab `resources = control.Resources;` in the *`OnXamlRendered`* event and set the element style from it: -```csharp +```cs if (resources?.ContainsKey("ThingStyle") == true) { newThing.Style = resources["ThingStyle"] as Style; } ``` -## 6. *Optional:* If you need *extra stuff* around the sample - -Now, the sample page content in the app is ignored, but you can override that behavior by adding a `` element to the page. If this element is found, it will serve as the host to the dynamic .bind content instead. In this manner you can have a status/warning message outside of the control of the developer in the XAML sample tab. +### 6. *Optional:* If you need *extra stuff* around the sample +Now, the sample page content in the app is ignored, but you can override that behavior by adding a `` element to the page. If this element is found, it will serve as the host to the dynamic `.bind` content instead. In this manner you can have a status/warning message outside the control of the developer in the XAML sample tab. -# Update Samples.json +## Update `Samples.json` -After creating your page and the binding text, you just need to reference it in the /SamplePages/samples.json file. +After creating your page and the binding text, you just need to reference it in the `/SamplePages/samples.json` file. Select the category where you want your page to be listed and add the following information: -## Basic Structure +### Basic Structure ```json [ @@ -156,7 +162,7 @@ Select the category where you want your page to be listed and add the following "Name": "AdaptiveGridView", "Type": "AdaptiveGridViewPage", "About": "The AdaptiveGridView control allows to present information within a Grid View perfectly adjusting the total display available space. It reacts to changes in the layout as well as the content so it can adapt to different form factors automatically. The number and the width of items are calculated based on the screen resolution in order to fully leverage the available screen space. The property ItemsHeight define the items fixed height and the property DesiredWidth sets the minimum width for the elements to add a new column.", - "CodeUrl": "https://github.com/CommunityToolkit/WindowsCommunityToolkit/tree/main/Microsoft.Toolkit.Uwp.UI.Controls/TextToolbar", + "CodeUrl": "https://github.com/CommunityToolkit/WindowsCommunityToolkit/tree/main/Microsoft.Toolkit.Uwp.UI.Controls.Core/TextToolbar", "XamlCodeFile": "AdaptiveGridViewCode.bind", "DocumentationUrl": "https://raw.githubusercontent.com/CommunityToolkit/WindowsCommunityToolkit/main/docs/controls/AdaptiveGridView.md" } @@ -165,48 +171,51 @@ Select the category where you want your page to be listed and add the following ] ``` -## Thumbnail Images +### Thumbnail Images -> NOTE: If creating a new icon, follow the [Thumbnail Style Guide and templates](https://github.com/CommunityToolkit/WindowsCommunityToolkit-design-assets) +For creating new icons, please follow the [Thumbnail Style Guide and templates](https://github.com/CommunityToolkit/WindowsCommunityToolkit-design-assets) -## Restricting Samples to Specific API Sets +### Restricting Samples to Specific API Sets Some features used by samples aren't available on all the OS versions that the Sample App runs on. In order to make sure a sample is valid for the host OS, add the `ApiCheck` key/value in your JSON definition. -The value is a string which is the fully-qualified typename to check for the presence of. You can also accompany this with the `BadgeUpdateVersionRequred` which uses the string provided to show a short message on the sample information so up level implementors know the minimum version required. +The value is a string which is the fully-qualified type-name to check for the presence of. You can also accompany this with the `BadgeUpdateVersionRequired` which uses the string provided to show a short message on the sample information so up level implementer know the minimum version required. ```json { - //... + // ••• "About": "MySample needs 10.0.18362 or higher to work.", "ApiCheck": "Windows.UI.Xaml.Controls.NavigationView", "BadgeUpdateVersionRequired": "Fall Creators Update required", - //... + // ••• } ``` -If the specified type is not found on the system running the sample app the sample will not appear in the sample list. +If the specified type is not found on the system running the sample app, the sample will not appear in the sample list. -### Adding documentation +#### Adding documentation -Every API must be accompanied by Markdown documentation in the [documentation repository](..\contributing.md#docs). +Every API must be accompanied by Markdown doc in the [documentation](https://github.com/CommunityToolkit/WindowsCommunityToolkit/blob/main/Contributing.md#docs) [repository](https://github.com/MicrosoftDocs/WindowsCommunityToolkitDocs). -Use the DocumentationUrl property to add a link to the raw documentation in *samples.json*. Please follow the following pattern: +Use the `DocumentationUrl` property to add a link to the raw documentation in *`samples.json`*. Please follow the following pattern: `https://raw.githubusercontent.com/MicrosoftDocs/WindowsCommunityToolkitDocs/{branch}/docs/{folder/file.md}` -> NOTE: When building and running the app in release mode, the branch will automatically be changed to **main** before loading. +##### NOTES -> NOTE: The documentation is also packaged with the sample app. If there is no network connection, or the documentation is not yet on GitHub, the sample app will use the packaged version +* When building and running the app in release mode, the branch will automatically be changed to **main** before loading. +* The documentation is also packaged with the sample app. If there is no network connection, or the documentation is not yet on GitHub, the sample app will use the packaged version. +* To test your documentation in the sample app while running in debug mode, the Docs repository will need to be cloned in the same folder as this repository and named **`WindowsCommunityToolkitDocs`**. For -> NOTE: To test your documentation in the sample app while running in debug mode, the docs repository will need to be cloned in the same folder as this repository and named **WindowsCommunityToolkitDocs**. For example, this folder structure works best: +Example, this folder structure works best: -> ``` -> repositories -> ├── WindowsCommunityToolkit -> ├── WindowsCommunityToolkitDocs -> ``` +```txt +Repos +├── WindowsCommunityToolkit +├── WindowsCommunityToolkitDocs +└── Others +``` -### CodeUrl +#### Using `CodeUrl` -The value of CodeUrl is modified when the app is built in release mode. The branch is automatically changed to **main**. This allows you to test the link in debug while pointing to dev. +The value of `CodeUrl` is modified when the app is built in release mode. The branch is automatically changed to **main**. This allows you to test the link in debug while pointing to dev. diff --git a/Microsoft.Toolkit.Win32/ReadMe.md b/Microsoft.Toolkit.Win32/ReadMe.md index 811797c0f70..8e61c138445 100644 --- a/Microsoft.Toolkit.Win32/ReadMe.md +++ b/Microsoft.Toolkit.Win32/ReadMe.md @@ -1,3 +1,3 @@ -# Windows Community Toolkit - WPF and Windows Forms +# Windows Community Toolkit — WPF and Windows Forms -The source has moved to a new repository: https://github.com/CommunityToolkit/Microsoft.Toolkit.Win32 \ No newline at end of file +The source has moved to a new repository: diff --git a/ReadMe.md b/ReadMe.md index 9ccc3f41555..f74d77937d1 100644 --- a/ReadMe.md +++ b/ReadMe.md @@ -1,55 +1,55 @@ -# Windows Community Toolkit :toolbox: +# 🧰 Windows Community Toolkit The Windows Community Toolkit is a collection of helper functions, custom controls, and app services. It simplifies and demonstrates common developer patterns when building experiences for Windows 10. | Target | Branch | Status | Recommended package version | | ------ | ------ | ------ | ------ | | Production | rel/7.0.2 | [![Build Status](https://dev.azure.com/dotnet/CommunityToolkit/_apis/build/status/Toolkit-CI?branchName=rel/7.0.2)](https://dev.azure.com/dotnet/CommunityToolkit/_build/latest?definitionId=10&branchName=rel/7.0.2) | [![NuGet](https://img.shields.io/nuget/v/Microsoft.Toolkit.Uwp.svg)](https://www.nuget.org/profiles/Microsoft.Toolkit) | -| Pre-release beta testing | main | [![Build Status](https://dev.azure.com/dotnet/CommunityToolkit/_apis/build/status/Toolkit-CI?branchName=main)](https://dev.azure.com/dotnet/CommunityToolkit/_build/latest?definitionId=10) | [![DevOps](https://vsrm.dev.azure.com/dotnet/_apis/public/Release/badge/696bc9fd-f160-4e97-a1bd-7cbbb3b58f66/1/1)](https://dev.azure.com/dotnet/CommunityToolkit/_packaging?_a=feed&feed=CommunityToolkit-MainLatest) | +| Previews | main | [![Build Status](https://dev.azure.com/dotnet/CommunityToolkit/_apis/build/status/Toolkit-CI?branchName=main)](https://dev.azure.com/dotnet/CommunityToolkit/_build/latest?definitionId=10) | [![DevOps](https://vsrm.dev.azure.com/dotnet/_apis/public/Release/badge/696bc9fd-f160-4e97-a1bd-7cbbb3b58f66/1/1)](https://dev.azure.com/dotnet/CommunityToolkit/_packaging?_a=feed&feed=CommunityToolkit-MainLatest) | -## Getting Started :raised_hands: +## 🙌 Getting Started Please read the [Getting Started with the Windows Community Toolkit](https://docs.microsoft.com/windows/communitytoolkit/getting-started) page for more detailed information about using the toolkit. -## Documentation :pencil: +## 📃 Documentation All documentation for the toolkit is hosted on [Microsoft Docs](https://docs.microsoft.com/windows/communitytoolkit/). All API documentation can be found at the [.NET API Browser](https://docs.microsoft.com/dotnet/api/?view=win-comm-toolkit-dotnet-stable). -## Windows Community Toolkit Sample App :iphone: +## 📱 Windows Community Toolkit Sample App Want to see the toolkit in action before jumping into the code? Download and play with the [Windows Community Toolkit Sample App](https://www.microsoft.com/store/apps/9nblggh4tlcq) from the Store. -## Contribution :rocket: +## 🚀 Contribution Do you want to contribute? Check out our [Windows Community Toolkit Wiki](https://aka.ms/wct/wiki) page to learn more about contribution and guidelines. -## NuGet Packages :package: +## 📦 NuGet Packages -NuGet is a standard package manager for .NET applications which is built into Visual Studio. When you open solution in Visual Studio, choose the *Tools* menu > *NuGet Package Manager* > *Manage NuGet packages for solution...* Enter one of the package names mentioned in [Windows Community Toolkit Nuget Packages](https://docs.microsoft.com/en-us/windows/communitytoolkit/nuget-packages) table to search for it online. +NuGet is a standard package manager for .NET applications which is built into Visual Studio. When you open solution in Visual Studio, choose the *Tools* menu > *NuGet Package Manager* > *Manage NuGet packages for solution…* Enter one of the package names mentioned in [Windows Community Toolkit NuGet Packages](https://docs.microsoft.com/windows/communitytoolkit/nuget-packages) table to search for it online. -## Features :mailbox: +## 📫 Features The [Features list](https://github.com/MicrosoftDocs/WindowsCommunityToolkitDocs/blob/master/docs/toc.md#controls) refers to all the currently available features that can be found in the Windows Community Toolkit. Most features should work with the October 2018 Update (1809) SDK 17763 and above; however, refer to specific documentation on each feature for more information. -## Principles :ballot_box_with_check: +## 💠 Principles -* Principle **#1**: The toolkit will be kept simple. -* Principle **#2**: As soon as a comparable feature is available in the Windows SDK for Windows 10, it will be marked as deprecated. -* Principle **#3**: All features will be supported for two Windows SDK for Windows 10 release cycles or until another principle supersedes it. +1. The toolkit will be kept simple. +2. As soon as a comparable feature is available in the Windows SDK for Windows 10, it will be marked as deprecated. +3. All features will be supported for two Windows SDK for Windows 10 release cycles or until another principle supersedes it. -## Roadmap :earth_americas: +## 🌍 Roadmap Read what we [plan for next iterations](https://github.com/CommunityToolkit/WindowsCommunityToolkit/milestones), and feel free to ask questions. Check out our [Preview Packages Wiki Page](https://github.com/CommunityToolkit/WindowsCommunityToolkit/wiki/Preview-Packages) to learn more about updating your NuGet sources in Visual Studio, then you can also get pre-release packages of upcoming versions to try. -## Code of Conduct :page_facing_up: +## 📄 Code of Conduct This project has adopted the code of conduct defined by the [Contributor Covenant](http://contributor-covenant.org/) to clarify expected behavior in our community. For more information see the [.NET Foundation Code of Conduct](CODE_OF_CONDUCT.md). -## .NET Foundation +## 🏢 .NET Foundation This project is supported by the [.NET Foundation](http://dotnetfoundation.org). From 118f6ebb81aec1c54ef4784b7a0260dbcf193221 Mon Sep 17 00:00:00 2001 From: Nirmal Guru Date: Sun, 14 Mar 2021 09:09:28 +0530 Subject: [PATCH 14/46] The "MarkdownTextBlock" control is now present in the Toolbox The MarkdownTextBlock's Assembly entry in the VS manifest is wrong, which is why the control was not present in the Toolbox. By simply updating to the correct Assembly Name lets the "MarkdownTextBlock" control to be validated and listed in the Visual Studio Toolbox. --- .../VisualStudioToolsManifest.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Microsoft.Toolkit.Uwp.UI.Controls.Markdown/VisualStudioToolsManifest.xml b/Microsoft.Toolkit.Uwp.UI.Controls.Markdown/VisualStudioToolsManifest.xml index ce34feb76a6..1fadcce11ff 100644 --- a/Microsoft.Toolkit.Uwp.UI.Controls.Markdown/VisualStudioToolsManifest.xml +++ b/Microsoft.Toolkit.Uwp.UI.Controls.Markdown/VisualStudioToolsManifest.xml @@ -1,5 +1,5 @@ - + From 41613a482c337943ec1fe5078e33dea21799e1b0 Mon Sep 17 00:00:00 2001 From: Nirmal Guru Date: Sun, 14 Mar 2021 09:15:49 +0530 Subject: [PATCH 15/46] Add missing controls to the Toolbox There are many controls that were added since the original Design project was authored. Some of them were not added due to lack of docs w.r.t the Design project. So, We'll add them here and now instead. NOTE: If this works properly, then, we'll backport these changes to older versions. --- .../VisualStudioToolsManifest.xml | 1 + .../VisualStudioToolsManifest.xml | 17 +++++++++++++++++ .../VisualStudioToolsManifest.xml | 6 ++++++ .../VisualStudioToolsManifest.xml | 1 + .../VisualStudioToolsManifest.xml | 3 +++ .../VisualStudioToolsManifest.xml | 2 ++ 6 files changed, 30 insertions(+) diff --git a/Microsoft.Toolkit.Uwp.UI.Controls.Core/VisualStudioToolsManifest.xml b/Microsoft.Toolkit.Uwp.UI.Controls.Core/VisualStudioToolsManifest.xml index 161131ea6dd..77c729143a5 100644 --- a/Microsoft.Toolkit.Uwp.UI.Controls.Core/VisualStudioToolsManifest.xml +++ b/Microsoft.Toolkit.Uwp.UI.Controls.Core/VisualStudioToolsManifest.xml @@ -13,6 +13,7 @@ + diff --git a/Microsoft.Toolkit.Uwp.UI.Controls.DataGrid/VisualStudioToolsManifest.xml b/Microsoft.Toolkit.Uwp.UI.Controls.DataGrid/VisualStudioToolsManifest.xml index 761934998a6..00f0341a283 100644 --- a/Microsoft.Toolkit.Uwp.UI.Controls.DataGrid/VisualStudioToolsManifest.xml +++ b/Microsoft.Toolkit.Uwp.UI.Controls.DataGrid/VisualStudioToolsManifest.xml @@ -2,6 +2,23 @@ + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Microsoft.Toolkit.Uwp.UI.Controls.Input/VisualStudioToolsManifest.xml b/Microsoft.Toolkit.Uwp.UI.Controls.Input/VisualStudioToolsManifest.xml index c6f210468c2..d49b8fd2eb3 100644 --- a/Microsoft.Toolkit.Uwp.UI.Controls.Input/VisualStudioToolsManifest.xml +++ b/Microsoft.Toolkit.Uwp.UI.Controls.Input/VisualStudioToolsManifest.xml @@ -1,8 +1,14 @@ + + + + + + \ No newline at end of file diff --git a/Microsoft.Toolkit.Uwp.UI.Controls.Layout/VisualStudioToolsManifest.xml b/Microsoft.Toolkit.Uwp.UI.Controls.Layout/VisualStudioToolsManifest.xml index f8ae329c03d..c8c03cc069c 100644 --- a/Microsoft.Toolkit.Uwp.UI.Controls.Layout/VisualStudioToolsManifest.xml +++ b/Microsoft.Toolkit.Uwp.UI.Controls.Layout/VisualStudioToolsManifest.xml @@ -4,6 +4,7 @@ + diff --git a/Microsoft.Toolkit.Uwp.UI.Controls.Media/VisualStudioToolsManifest.xml b/Microsoft.Toolkit.Uwp.UI.Controls.Media/VisualStudioToolsManifest.xml index 31b1259a180..48cbc81c449 100644 --- a/Microsoft.Toolkit.Uwp.UI.Controls.Media/VisualStudioToolsManifest.xml +++ b/Microsoft.Toolkit.Uwp.UI.Controls.Media/VisualStudioToolsManifest.xml @@ -2,8 +2,11 @@ + + + \ No newline at end of file diff --git a/Microsoft.Toolkit.Uwp.UI.Controls.Primitives/VisualStudioToolsManifest.xml b/Microsoft.Toolkit.Uwp.UI.Controls.Primitives/VisualStudioToolsManifest.xml index da08ea53fd9..05cda5964cf 100644 --- a/Microsoft.Toolkit.Uwp.UI.Controls.Primitives/VisualStudioToolsManifest.xml +++ b/Microsoft.Toolkit.Uwp.UI.Controls.Primitives/VisualStudioToolsManifest.xml @@ -3,9 +3,11 @@ + + From ec5b79efbe20aef051701e21b8a43fc7882fe355 Mon Sep 17 00:00:00 2001 From: Nirmal Guru Date: Sun, 14 Mar 2021 09:34:31 +0530 Subject: [PATCH 16/46] Only display controls where UAP is supported When specified with "UIFramework" tag on the controls, we ensure that the controls doesn't list in places where UWP UI/WinUI is not supported. --- .../VisualStudioToolsManifest.xml | 4 +++- .../VisualStudioToolsManifest.xml | 4 +++- .../VisualStudioToolsManifest.xml | 4 +++- .../VisualStudioToolsManifest.xml | 4 +++- .../VisualStudioToolsManifest.xml | 4 +++- .../VisualStudioToolsManifest.xml | 4 +++- .../VisualStudioToolsManifest.xml | 4 +++- .../VisualStudioToolsManifest.xml | 4 +++- 8 files changed, 24 insertions(+), 8 deletions(-) diff --git a/Microsoft.Toolkit.Uwp.DeveloperTools/VisualStudioToolsManifest.xml b/Microsoft.Toolkit.Uwp.DeveloperTools/VisualStudioToolsManifest.xml index 2c51528df63..5b0b0159144 100644 --- a/Microsoft.Toolkit.Uwp.DeveloperTools/VisualStudioToolsManifest.xml +++ b/Microsoft.Toolkit.Uwp.DeveloperTools/VisualStudioToolsManifest.xml @@ -1,6 +1,8 @@ - + diff --git a/Microsoft.Toolkit.Uwp.UI.Controls.Core/VisualStudioToolsManifest.xml b/Microsoft.Toolkit.Uwp.UI.Controls.Core/VisualStudioToolsManifest.xml index 77c729143a5..031faeee520 100644 --- a/Microsoft.Toolkit.Uwp.UI.Controls.Core/VisualStudioToolsManifest.xml +++ b/Microsoft.Toolkit.Uwp.UI.Controls.Core/VisualStudioToolsManifest.xml @@ -1,6 +1,8 @@ - + diff --git a/Microsoft.Toolkit.Uwp.UI.Controls.DataGrid/VisualStudioToolsManifest.xml b/Microsoft.Toolkit.Uwp.UI.Controls.DataGrid/VisualStudioToolsManifest.xml index 00f0341a283..0eef95ce961 100644 --- a/Microsoft.Toolkit.Uwp.UI.Controls.DataGrid/VisualStudioToolsManifest.xml +++ b/Microsoft.Toolkit.Uwp.UI.Controls.DataGrid/VisualStudioToolsManifest.xml @@ -1,6 +1,8 @@ - + diff --git a/Microsoft.Toolkit.Uwp.UI.Controls.Input/VisualStudioToolsManifest.xml b/Microsoft.Toolkit.Uwp.UI.Controls.Input/VisualStudioToolsManifest.xml index d49b8fd2eb3..4d21f08c983 100644 --- a/Microsoft.Toolkit.Uwp.UI.Controls.Input/VisualStudioToolsManifest.xml +++ b/Microsoft.Toolkit.Uwp.UI.Controls.Input/VisualStudioToolsManifest.xml @@ -1,6 +1,8 @@ - + diff --git a/Microsoft.Toolkit.Uwp.UI.Controls.Layout/VisualStudioToolsManifest.xml b/Microsoft.Toolkit.Uwp.UI.Controls.Layout/VisualStudioToolsManifest.xml index c8c03cc069c..0e6a134c524 100644 --- a/Microsoft.Toolkit.Uwp.UI.Controls.Layout/VisualStudioToolsManifest.xml +++ b/Microsoft.Toolkit.Uwp.UI.Controls.Layout/VisualStudioToolsManifest.xml @@ -1,6 +1,8 @@ - + diff --git a/Microsoft.Toolkit.Uwp.UI.Controls.Markdown/VisualStudioToolsManifest.xml b/Microsoft.Toolkit.Uwp.UI.Controls.Markdown/VisualStudioToolsManifest.xml index 1fadcce11ff..1e886827d3b 100644 --- a/Microsoft.Toolkit.Uwp.UI.Controls.Markdown/VisualStudioToolsManifest.xml +++ b/Microsoft.Toolkit.Uwp.UI.Controls.Markdown/VisualStudioToolsManifest.xml @@ -1,6 +1,8 @@ - + diff --git a/Microsoft.Toolkit.Uwp.UI.Controls.Media/VisualStudioToolsManifest.xml b/Microsoft.Toolkit.Uwp.UI.Controls.Media/VisualStudioToolsManifest.xml index 48cbc81c449..21e6e3e8248 100644 --- a/Microsoft.Toolkit.Uwp.UI.Controls.Media/VisualStudioToolsManifest.xml +++ b/Microsoft.Toolkit.Uwp.UI.Controls.Media/VisualStudioToolsManifest.xml @@ -1,6 +1,8 @@ - + diff --git a/Microsoft.Toolkit.Uwp.UI.Controls.Primitives/VisualStudioToolsManifest.xml b/Microsoft.Toolkit.Uwp.UI.Controls.Primitives/VisualStudioToolsManifest.xml index 05cda5964cf..74a5a2b4a8a 100644 --- a/Microsoft.Toolkit.Uwp.UI.Controls.Primitives/VisualStudioToolsManifest.xml +++ b/Microsoft.Toolkit.Uwp.UI.Controls.Primitives/VisualStudioToolsManifest.xml @@ -1,6 +1,8 @@ - + From 7513e421f70fc9339ec6993b9931abb06dcc855e Mon Sep 17 00:00:00 2001 From: ArchieCoder Date: Thu, 5 Aug 2021 13:26:52 -0400 Subject: [PATCH 17/46] Update Microsoft.Toolkit.Uwp.UI.Behaviors/Keyboard/KeyDownTriggerBehavior.cs Co-authored-by: Michael Hawker MSFT (XAML Llama) <24302614+michael-hawker@users.noreply.github.com> --- .../Keyboard/KeyDownTriggerBehavior.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Microsoft.Toolkit.Uwp.UI.Behaviors/Keyboard/KeyDownTriggerBehavior.cs b/Microsoft.Toolkit.Uwp.UI.Behaviors/Keyboard/KeyDownTriggerBehavior.cs index 367f1ba8cf8..df3a910aa69 100644 --- a/Microsoft.Toolkit.Uwp.UI.Behaviors/Keyboard/KeyDownTriggerBehavior.cs +++ b/Microsoft.Toolkit.Uwp.UI.Behaviors/Keyboard/KeyDownTriggerBehavior.cs @@ -14,7 +14,7 @@ public class KeyDownTriggerBehavior : Trigger { /// - /// The DP to store the property value. + /// Identifies the property. /// public static readonly DependencyProperty KeyProperty = DependencyProperty.Register( "Key", @@ -63,4 +63,4 @@ private void OnAssociatedObjectKeyDown(object sender, KeyRoutedEventArgs keyRout } } } -} \ No newline at end of file +} From fc4e6d713fa2cb1679ce9389f323460d8b612d87 Mon Sep 17 00:00:00 2001 From: ArchieCoder Date: Thu, 5 Aug 2021 13:27:45 -0400 Subject: [PATCH 18/46] Update Microsoft.Toolkit.Uwp.UI.Behaviors/Keyboard/KeyDownTriggerBehavior.cs Co-authored-by: Michael Hawker MSFT (XAML Llama) <24302614+michael-hawker@users.noreply.github.com> --- .../Keyboard/KeyDownTriggerBehavior.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Microsoft.Toolkit.Uwp.UI.Behaviors/Keyboard/KeyDownTriggerBehavior.cs b/Microsoft.Toolkit.Uwp.UI.Behaviors/Keyboard/KeyDownTriggerBehavior.cs index df3a910aa69..18f302ac0d1 100644 --- a/Microsoft.Toolkit.Uwp.UI.Behaviors/Keyboard/KeyDownTriggerBehavior.cs +++ b/Microsoft.Toolkit.Uwp.UI.Behaviors/Keyboard/KeyDownTriggerBehavior.cs @@ -17,7 +17,7 @@ public class KeyDownTriggerBehavior : Trigger /// Identifies the property. /// public static readonly DependencyProperty KeyProperty = DependencyProperty.Register( - "Key", + nameof(Key), typeof(VirtualKey), typeof(KeyDownTriggerBehavior), new PropertyMetadata(null)); From 93ca18ad9dce07d1bb368f9dde210012074e4dbc Mon Sep 17 00:00:00 2001 From: Shane Weaver Date: Thu, 5 Aug 2021 16:32:49 -0700 Subject: [PATCH 19/46] Replaced TokenSelectionMode with MaxTokens property on TokenizingTextBox --- .../TokenizingTextBoxXaml.bind | 5 +- .../TokenizingTextBox.Properties.cs | 51 +++++++----- .../TokenizingTextBox/TokenizingTextBox.cs | 81 ++++++++++++++----- 3 files changed, 93 insertions(+), 44 deletions(-) diff --git a/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/TokenizingTextBox/TokenizingTextBoxXaml.bind b/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/TokenizingTextBox/TokenizingTextBoxXaml.bind index 672d2c72d1d..46025c3b0ce 100644 --- a/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/TokenizingTextBox/TokenizingTextBoxXaml.bind +++ b/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/TokenizingTextBox/TokenizingTextBoxXaml.bind @@ -30,7 +30,7 @@ - + MaxTokens="3"> @@ -76,7 +76,6 @@ QueryIcon="{ui:SymbolIconSource Symbol=Find}" TextMemberPath="Text" TokenDelimiter="," - TokenSelectionMode="Multiple" IsItemClickEnabled="True" TokenItemTemplate="{StaticResource EmailTokenTemplate}"> diff --git a/Microsoft.Toolkit.Uwp.UI.Controls.Input/TokenizingTextBox/TokenizingTextBox.Properties.cs b/Microsoft.Toolkit.Uwp.UI.Controls.Input/TokenizingTextBox/TokenizingTextBox.Properties.cs index e1409afa3a4..b5d36d49517 100644 --- a/Microsoft.Toolkit.Uwp.UI.Controls.Input/TokenizingTextBox/TokenizingTextBox.Properties.cs +++ b/Microsoft.Toolkit.Uwp.UI.Controls.Input/TokenizingTextBox/TokenizingTextBox.Properties.cs @@ -158,27 +158,40 @@ private static void TextPropertyChanged(DependencyObject d, DependencyPropertyCh new PropertyMetadata(false)); /// - /// Identifies the property. + /// Identifies the property. /// - public static readonly DependencyProperty TokenSelectionModeProperty = DependencyProperty.Register( - nameof(TokenSelectionMode), - typeof(TokenSelectionMode), + public static readonly DependencyProperty MaxTokensProperty = DependencyProperty.Register( + nameof(MaxTokens), + typeof(int?), typeof(TokenizingTextBox), - new PropertyMetadata(TokenSelectionMode.Multiple, OnTokenSelectionModeChanged)); + new PropertyMetadata(null, OnMaxTokensChanged)); - private static void OnTokenSelectionModeChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + private static void OnMaxTokensChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { - if (d is TokenizingTextBox ttb && e.NewValue is TokenSelectionMode newTokenSelectionMode && newTokenSelectionMode == TokenSelectionMode.Single) + if (d is TokenizingTextBox ttb && e.NewValue is int newMaxTokens) { - // Start at the end, remove all but the first token. - for (var i = ttb._innerItemsSource.Count - 1; i >= 1; --i) + var tokenCount = ttb.Items.Count; + if (tokenCount > newMaxTokens) { - var item = ttb._innerItemsSource[i]; - if (item is not ITokenStringContainer) + int tokensToRemove = newMaxTokens - tokenCount; + var tokensRemoved = 0; + + // Start at the end, remove any extra tokens. + for (var i = ttb._innerItemsSource.Count - 1; i >= 0; --i) { - // Force remove the items. No warning and no option to cancel. - ttb._innerItemsSource.Remove(item); - ttb.TokenItemRemoved?.Invoke(ttb, item); + var item = ttb._innerItemsSource[i]; + if (item is not ITokenStringContainer) + { + // Force remove the items. No warning and no option to cancel. + ttb._innerItemsSource.Remove(item); + ttb.TokenItemRemoved?.Invoke(ttb, item); + + tokensRemoved++; + if (tokensRemoved == tokensToRemove) + { + break; + } + } } } } @@ -332,14 +345,12 @@ public string SelectedTokenText } /// - /// Gets or sets how the control should display tokens. - /// is the default. Multiple tokens can be selected at a time. - /// indicates that only one token can be present in the control at a time. + /// Gets or sets the maximum number of token results allowed at a time. /// - public TokenSelectionMode TokenSelectionMode + public int? MaxTokens { - get => (TokenSelectionMode)GetValue(TokenSelectionModeProperty); - set => SetValue(TokenSelectionModeProperty, value); + get => (int?)GetValue(MaxTokensProperty); + set => SetValue(MaxTokensProperty, value); } } } \ No newline at end of file diff --git a/Microsoft.Toolkit.Uwp.UI.Controls.Input/TokenizingTextBox/TokenizingTextBox.cs b/Microsoft.Toolkit.Uwp.UI.Controls.Input/TokenizingTextBox/TokenizingTextBox.cs index 36771cc48e4..fe55e37f3f8 100644 --- a/Microsoft.Toolkit.Uwp.UI.Controls.Input/TokenizingTextBox/TokenizingTextBox.cs +++ b/Microsoft.Toolkit.Uwp.UI.Controls.Input/TokenizingTextBox/TokenizingTextBox.cs @@ -77,6 +77,16 @@ private void ItemsSource_PropertyChanged(DependencyObject sender, DependencyProp if (ItemsSource != null && ItemsSource.GetType() != typeof(InterspersedObservableCollection)) { _innerItemsSource = new InterspersedObservableCollection(ItemsSource); + + if (MaxTokens.HasValue && _innerItemsSource.ItemsSource.Count > MaxTokens) + { + // Reduce down to the max as necessary. + for (var i = _innerItemsSource.ItemsSource.Count; i > MaxTokens; --i) + { + _innerItemsSource.Remove(_innerItemsSource[i]); + } + } + _currentTextEdit = _lastTextEdit = new PretokenStringContainer(true); _innerItemsSource.Insert(_innerItemsSource.Count, _currentTextEdit); ItemsSource = _innerItemsSource; @@ -278,18 +288,16 @@ void WaitForLoad(object s, RoutedEventArgs eargs) } 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 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 position = last._autoSuggestTextBox.SelectionStart; - textToken.Text = last._autoSuggestTextBox.Text.Substring(0, position) + args.Character + - last._autoSuggestTextBox.Text.Substring(position); + 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 @@ -432,6 +440,12 @@ public async Task ClearAsync() internal async Task AddTokenAsync(object data, bool? atEnd = null) { + if (MaxTokens == 0) + { + // No tokens for you + return; + } + if (data is string str && TokenItemAdding != null) { var tiaea = new TokenItemAddingEventArgs(str); @@ -448,24 +462,29 @@ internal async Task AddTokenAsync(object data, bool? atEnd = null) } } - if (TokenSelectionMode == TokenSelectionMode.Single) + // If we've been typing in the last box, just add this to the end of our collection + if (atEnd == true || _currentTextEdit == _lastTextEdit) { - // Start at the end, remove any existing tokens. - for (var i = _innerItemsSource.Count - 1; i >= 0; --i) + if (MaxTokens != null && _innerItemsSource.ItemsSource.Count >= MaxTokens) { - var item = _innerItemsSource[i]; - if (item is not ITokenStringContainer) + // Remove tokens from the end until below the max number. + for (var i = _innerItemsSource.Count - 2; i >= 0; --i) { - // Force remove the items. No warning and no option to cancel. - _innerItemsSource.Remove(item); - TokenItemRemoved?.Invoke(this, item); + var item = _innerItemsSource[i]; + if (item is not ITokenStringContainer) + { + _innerItemsSource.Remove(item); + TokenItemRemoved?.Invoke(this, item); + + // Keep going until we are below the max. + if (_innerItemsSource.ItemsSource.Count < MaxTokens) + { + break; + } + } } } - } - // 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 @@ -474,6 +493,26 @@ internal async Task AddTokenAsync(object data, bool? atEnd = null) var edit = _currentTextEdit; var index = _innerItemsSource.IndexOf(edit); + if (MaxTokens != null && _innerItemsSource.ItemsSource.Count >= MaxTokens) + { + // Find the next token and remove it, until below the max number of tokens. + for (var i = index; i < _innerItemsSource.Count; i++) + { + var item = _innerItemsSource[i]; + if (item is not ITokenStringContainer) + { + _innerItemsSource.Remove(item); + TokenItemRemoved?.Invoke(this, item); + + // Keep going until we are below the max. + if (_innerItemsSource.ItemsSource.Count < MaxTokens) + { + break; + } + } + } + } + // Insert our new data item at the location of our textbox _innerItemsSource.InsertAt(index, data); From 9ee8b96502629cddcf61ae19275f908641bad656 Mon Sep 17 00:00:00 2001 From: Shane Weaver Date: Thu, 5 Aug 2021 16:34:17 -0700 Subject: [PATCH 20/46] Removed TokenSelectionMode enum --- .../TokenizingTextBox/TokenSelectionMode.cs | 28 ------------------- 1 file changed, 28 deletions(-) delete mode 100644 Microsoft.Toolkit.Uwp.UI.Controls.Input/TokenizingTextBox/TokenSelectionMode.cs diff --git a/Microsoft.Toolkit.Uwp.UI.Controls.Input/TokenizingTextBox/TokenSelectionMode.cs b/Microsoft.Toolkit.Uwp.UI.Controls.Input/TokenizingTextBox/TokenSelectionMode.cs deleted file mode 100644 index 77543fb6232..00000000000 --- a/Microsoft.Toolkit.Uwp.UI.Controls.Input/TokenizingTextBox/TokenSelectionMode.cs +++ /dev/null @@ -1,28 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace Microsoft.Toolkit.Uwp.UI.Controls -{ - /// - /// Indicates how tokens are selected in the . - /// - public enum TokenSelectionMode - { - /// - /// Only one token can be selected at a time. A new token should replace the active selection. - /// - Single, - - /// - /// Multiple tokens can be selected at a time. - /// - Multiple, - } -} From b4fecf308e651e8ff37016d4b417bfa42c12400c Mon Sep 17 00:00:00 2001 From: Shane Weaver Date: Thu, 5 Aug 2021 16:49:27 -0700 Subject: [PATCH 21/46] Accounting for negative MaxToken value --- .../TokenizingTextBox/TokenizingTextBox.Properties.cs | 2 +- .../TokenizingTextBox/TokenizingTextBox.cs | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Microsoft.Toolkit.Uwp.UI.Controls.Input/TokenizingTextBox/TokenizingTextBox.Properties.cs b/Microsoft.Toolkit.Uwp.UI.Controls.Input/TokenizingTextBox/TokenizingTextBox.Properties.cs index b5d36d49517..1e13a2689dd 100644 --- a/Microsoft.Toolkit.Uwp.UI.Controls.Input/TokenizingTextBox/TokenizingTextBox.Properties.cs +++ b/Microsoft.Toolkit.Uwp.UI.Controls.Input/TokenizingTextBox/TokenizingTextBox.Properties.cs @@ -173,7 +173,7 @@ private static void OnMaxTokensChanged(DependencyObject d, DependencyPropertyCha var tokenCount = ttb.Items.Count; if (tokenCount > newMaxTokens) { - int tokensToRemove = newMaxTokens - tokenCount; + int tokensToRemove = tokenCount - Math.Max(newMaxTokens, 0); var tokensRemoved = 0; // Start at the end, remove any extra tokens. diff --git a/Microsoft.Toolkit.Uwp.UI.Controls.Input/TokenizingTextBox/TokenizingTextBox.cs b/Microsoft.Toolkit.Uwp.UI.Controls.Input/TokenizingTextBox/TokenizingTextBox.cs index fe55e37f3f8..97bbcd4fc49 100644 --- a/Microsoft.Toolkit.Uwp.UI.Controls.Input/TokenizingTextBox/TokenizingTextBox.cs +++ b/Microsoft.Toolkit.Uwp.UI.Controls.Input/TokenizingTextBox/TokenizingTextBox.cs @@ -81,7 +81,7 @@ private void ItemsSource_PropertyChanged(DependencyObject sender, DependencyProp if (MaxTokens.HasValue && _innerItemsSource.ItemsSource.Count > MaxTokens) { // Reduce down to the max as necessary. - for (var i = _innerItemsSource.ItemsSource.Count; i > MaxTokens; --i) + for (var i = _innerItemsSource.ItemsSource.Count - 1; i >= Math.Max(MaxTokens.Value, 0); --i) { _innerItemsSource.Remove(_innerItemsSource[i]); } @@ -440,7 +440,7 @@ public async Task ClearAsync() internal async Task AddTokenAsync(object data, bool? atEnd = null) { - if (MaxTokens == 0) + if (MaxTokens <= 0) { // No tokens for you return; From 358ada42ebea9056dcaed0e2cf2a0fc51d97813c Mon Sep 17 00:00:00 2001 From: Shane Weaver Date: Thu, 5 Aug 2021 17:09:10 -0700 Subject: [PATCH 22/46] Added MaxTokens test --- .../Test_TokenizingTextBox_General.cs | 39 +++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/UnitTests/UnitTests.UWP/UI/Controls/Test_TokenizingTextBox_General.cs b/UnitTests/UnitTests.UWP/UI/Controls/Test_TokenizingTextBox_General.cs index 6ea65407d7e..39a1764bb7b 100644 --- a/UnitTests/UnitTests.UWP/UI/Controls/Test_TokenizingTextBox_General.cs +++ b/UnitTests/UnitTests.UWP/UI/Controls/Test_TokenizingTextBox_General.cs @@ -63,5 +63,44 @@ public void Test_Clear() Assert.AreEqual(tokenBox.Items.Count, 5, "Cancelled Clear Failed "); } + + [TestCategory("Test_TokenizingTextBox_General")] + [UITestMethod] + public void Test_MaxTokens() + { + var maxTokens = 2; + + var treeRoot = XamlReader.Load( +$@" + + + + +") as FrameworkElement; + + Assert.IsNotNull(treeRoot, "Could not load XAML tree."); + + var tokenBox = treeRoot.FindChild("tokenboxname") as TokenizingTextBox; + + Assert.IsNotNull(tokenBox, "Could not find TokenizingTextBox in tree."); + + var startingItemsCount = tokenBox.Items.Count; + + tokenBox.AddTokenItem("TokenItem1"); + tokenBox.AddTokenItem("TokenItem2"); + + Assert.AreEqual(startingItemsCount + maxTokens, tokenBox.Items.Count, "Token Add failed"); + Assert.AreEqual("TokenItem1", tokenBox.Items[0]); + Assert.AreEqual("TokenItem2", tokenBox.Items[1]); + + tokenBox.AddTokenItem("TokenItem3"); + + Assert.AreEqual(startingItemsCount + maxTokens, tokenBox.Items.Count, "Token Replace failed"); + Assert.AreEqual("TokenItem1", tokenBox.Items[0]); + Assert.AreEqual("TokenItem3", tokenBox.Items[1]); + } } } \ No newline at end of file From 6c5f8a231cdf3f50665fbf36ad09a0383305cb71 Mon Sep 17 00:00:00 2001 From: ArchieCoder Date: Fri, 6 Aug 2021 00:21:43 -0400 Subject: [PATCH 23/46] Fixes post code review --- .../Microsoft.Toolkit.Uwp.SampleApp.csproj | 7 -- .../KeyDownTriggerBehaviorPage.xaml | 60 ------------- .../KeyDownTriggerBehaviorPage.xaml.cs | 16 ---- .../KeyDownTriggerBehaviorXaml.bind | 90 +++++++++++++++---- .../SamplePages/XamlOnlyPage.xaml | 1 + .../Keyboard/KeyDownTriggerBehavior.cs | 9 +- 6 files changed, 73 insertions(+), 110 deletions(-) delete mode 100644 Microsoft.Toolkit.Uwp.SampleApp/SamplePages/KeyDownTriggerBehavior/KeyDownTriggerBehaviorPage.xaml delete mode 100644 Microsoft.Toolkit.Uwp.SampleApp/SamplePages/KeyDownTriggerBehavior/KeyDownTriggerBehaviorPage.xaml.cs diff --git a/Microsoft.Toolkit.Uwp.SampleApp/Microsoft.Toolkit.Uwp.SampleApp.csproj b/Microsoft.Toolkit.Uwp.SampleApp/Microsoft.Toolkit.Uwp.SampleApp.csproj index ef365f43ba0..9dddf4fb6b0 100644 --- a/Microsoft.Toolkit.Uwp.SampleApp/Microsoft.Toolkit.Uwp.SampleApp.csproj +++ b/Microsoft.Toolkit.Uwp.SampleApp/Microsoft.Toolkit.Uwp.SampleApp.csproj @@ -503,9 +503,6 @@ FocusBehaviorPage.xaml - - KeyDownTriggerBehaviorPage.xaml - MetadataControlPage.xaml @@ -978,10 +975,6 @@ Designer - - MSBuild:Compile - Designer - Designer MSBuild:Compile diff --git a/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/KeyDownTriggerBehavior/KeyDownTriggerBehaviorPage.xaml b/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/KeyDownTriggerBehavior/KeyDownTriggerBehaviorPage.xaml deleted file mode 100644 index d5b605fc0f4..00000000000 --- a/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/KeyDownTriggerBehavior/KeyDownTriggerBehaviorPage.xaml +++ /dev/null @@ -1,60 +0,0 @@ - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/KeyDownTriggerBehavior/KeyDownTriggerBehaviorPage.xaml.cs b/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/KeyDownTriggerBehavior/KeyDownTriggerBehaviorPage.xaml.cs deleted file mode 100644 index d777b3137cb..00000000000 --- a/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/KeyDownTriggerBehavior/KeyDownTriggerBehaviorPage.xaml.cs +++ /dev/null @@ -1,16 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -using Windows.UI.Xaml.Controls; - -namespace Microsoft.Toolkit.Uwp.SampleApp.SamplePages -{ - /// - /// A page that shows how to use the AutoFocusBehavior - /// - public sealed partial class KeyDownTriggerBehaviorPage : Page - { - public KeyDownTriggerBehaviorPage() => InitializeComponent(); - } -} \ No newline at end of file diff --git a/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/KeyDownTriggerBehavior/KeyDownTriggerBehaviorXaml.bind b/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/KeyDownTriggerBehavior/KeyDownTriggerBehaviorXaml.bind index 0fc18f48c5e..b24f77335be 100644 --- a/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/KeyDownTriggerBehavior/KeyDownTriggerBehaviorXaml.bind +++ b/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/KeyDownTriggerBehavior/KeyDownTriggerBehaviorXaml.bind @@ -1,5 +1,6 @@ - +--> + + + + \ No newline at end of file diff --git a/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/XamlOnlyPage.xaml b/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/XamlOnlyPage.xaml index 631cf5e5c95..780f14479e8 100644 --- a/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/XamlOnlyPage.xaml +++ b/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/XamlOnlyPage.xaml @@ -32,6 +32,7 @@ + diff --git a/Microsoft.Toolkit.Uwp.UI.Behaviors/Keyboard/KeyDownTriggerBehavior.cs b/Microsoft.Toolkit.Uwp.UI.Behaviors/Keyboard/KeyDownTriggerBehavior.cs index 367f1ba8cf8..9eb1afc8766 100644 --- a/Microsoft.Toolkit.Uwp.UI.Behaviors/Keyboard/KeyDownTriggerBehavior.cs +++ b/Microsoft.Toolkit.Uwp.UI.Behaviors/Keyboard/KeyDownTriggerBehavior.cs @@ -1,4 +1,3 @@ -using System.Windows.Input; using Microsoft.Xaml.Interactivity; using Windows.System; using Windows.UI.Xaml; @@ -10,7 +9,7 @@ namespace Microsoft.Toolkit.Uwp.UI.Behaviors /// This behavior listens to a key down event on the associated when it is loaded and executes an action. /// [TypeConstraint(typeof(FrameworkElement))] - public class KeyDownTriggerBehavior : Trigger + public class KeyDownTriggerBehavior : Trigger { /// @@ -31,12 +30,6 @@ public VirtualKey Key set => SetValue(KeyProperty, value); } - public static readonly DependencyProperty CommandProperty = DependencyProperty.Register( - "Command", - typeof(ICommand), - typeof(KeyDownTriggerBehavior), - new PropertyMetadata(null)); - /// protected override void OnAttached() { From b1905cec8fa44fcdfcb6cba811949066d3cec885 Mon Sep 17 00:00:00 2001 From: ArchieCoder Date: Fri, 6 Aug 2021 09:02:17 -0400 Subject: [PATCH 24/46] Update Microsoft.Toolkit.Uwp.SampleApp/SamplePages/samples.json Co-authored-by: Michael Hawker MSFT (XAML Llama) <24302614+michael-hawker@users.noreply.github.com> --- Microsoft.Toolkit.Uwp.SampleApp/SamplePages/samples.json | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/samples.json b/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/samples.json index 1a4ccac2cd9..23dce0a4336 100644 --- a/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/samples.json +++ b/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/samples.json @@ -841,11 +841,10 @@ }, { "Name": "KeyDownTriggerBehavior", - "Type": "KeyDownTriggerBehaviorPage", "Subcategory": "Systems", "About": "Behavior to listen to a key press on a control and executes actions", "CodeUrl": "https://github.com/CommunityToolkit/WindowsCommunityToolkit/blob/master/Microsoft.Toolkit.Uwp.UI.Behaviors/Keyboard/KeyDownTriggerBehavior.cs", - "XamlCodeFile": "KeyDownTriggerBehaviorXaml.bind", + "XamlCodeFile": "/SamplePages/KeyDownTriggerBehavior/KeyDownTriggerBehaviorXaml.bind", "Icon": "/Assets/Helpers.png", "DocumentationUrl": "https://raw.githubusercontent.com/MicrosoftDocs/WindowsCommunityToolkitDocs/master/docs/behaviors/KeyboardBehaviors.md" }, From 3f0df53a80871c1901b5cd8edcd5fcaf0fc760ce Mon Sep 17 00:00:00 2001 From: ArchieCoder Date: Fri, 6 Aug 2021 09:05:54 -0400 Subject: [PATCH 25/46] Fix --- .../KeyDownTriggerBehaviorXaml.bind | 55 +------------------ 1 file changed, 2 insertions(+), 53 deletions(-) diff --git a/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/KeyDownTriggerBehavior/KeyDownTriggerBehaviorXaml.bind b/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/KeyDownTriggerBehavior/KeyDownTriggerBehaviorXaml.bind index b24f77335be..25535c04303 100644 --- a/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/KeyDownTriggerBehavior/KeyDownTriggerBehaviorXaml.bind +++ b/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/KeyDownTriggerBehavior/KeyDownTriggerBehaviorXaml.bind @@ -1,4 +1,4 @@ - - - - - \ No newline at end of file From f0d8b2a6add4febe9cb51ea648bdf2f9094a34e1 Mon Sep 17 00:00:00 2001 From: Shane Weaver Date: Fri, 6 Aug 2021 10:27:36 -0700 Subject: [PATCH 26/46] Made MaxTokens non-nullable and added checks for DP.UnsetValue --- .../TokenizingTextBox/TokenizingTextBox.Properties.cs | 10 +++++----- .../TokenizingTextBox/TokenizingTextBox.cs | 10 +++++----- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/Microsoft.Toolkit.Uwp.UI.Controls.Input/TokenizingTextBox/TokenizingTextBox.Properties.cs b/Microsoft.Toolkit.Uwp.UI.Controls.Input/TokenizingTextBox/TokenizingTextBox.Properties.cs index 1e13a2689dd..21b8eb70a55 100644 --- a/Microsoft.Toolkit.Uwp.UI.Controls.Input/TokenizingTextBox/TokenizingTextBox.Properties.cs +++ b/Microsoft.Toolkit.Uwp.UI.Controls.Input/TokenizingTextBox/TokenizingTextBox.Properties.cs @@ -162,13 +162,13 @@ private static void TextPropertyChanged(DependencyObject d, DependencyPropertyCh /// public static readonly DependencyProperty MaxTokensProperty = DependencyProperty.Register( nameof(MaxTokens), - typeof(int?), + typeof(int), typeof(TokenizingTextBox), - new PropertyMetadata(null, OnMaxTokensChanged)); + new PropertyMetadata(DependencyProperty.UnsetValue, OnMaxTokensChanged)); private static void OnMaxTokensChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { - if (d is TokenizingTextBox ttb && e.NewValue is int newMaxTokens) + if (d is TokenizingTextBox ttb && ttb.ReadLocalValue(MaxTokensProperty) != DependencyProperty.UnsetValue && e.NewValue is int newMaxTokens) { var tokenCount = ttb.Items.Count; if (tokenCount > newMaxTokens) @@ -347,9 +347,9 @@ public string SelectedTokenText /// /// Gets or sets the maximum number of token results allowed at a time. /// - public int? MaxTokens + public int MaxTokens { - get => (int?)GetValue(MaxTokensProperty); + get => (int)GetValue(MaxTokensProperty); set => SetValue(MaxTokensProperty, value); } } diff --git a/Microsoft.Toolkit.Uwp.UI.Controls.Input/TokenizingTextBox/TokenizingTextBox.cs b/Microsoft.Toolkit.Uwp.UI.Controls.Input/TokenizingTextBox/TokenizingTextBox.cs index 97bbcd4fc49..9eaf54bd651 100644 --- a/Microsoft.Toolkit.Uwp.UI.Controls.Input/TokenizingTextBox/TokenizingTextBox.cs +++ b/Microsoft.Toolkit.Uwp.UI.Controls.Input/TokenizingTextBox/TokenizingTextBox.cs @@ -78,10 +78,10 @@ private void ItemsSource_PropertyChanged(DependencyObject sender, DependencyProp { _innerItemsSource = new InterspersedObservableCollection(ItemsSource); - if (MaxTokens.HasValue && _innerItemsSource.ItemsSource.Count > MaxTokens) + if (ReadLocalValue(MaxTokensProperty) != DependencyProperty.UnsetValue && _innerItemsSource.ItemsSource.Count > MaxTokens) { // Reduce down to the max as necessary. - for (var i = _innerItemsSource.ItemsSource.Count - 1; i >= Math.Max(MaxTokens.Value, 0); --i) + for (var i = _innerItemsSource.ItemsSource.Count - 1; i >= Math.Max(MaxTokens, 0); --i) { _innerItemsSource.Remove(_innerItemsSource[i]); } @@ -440,7 +440,7 @@ public async Task ClearAsync() internal async Task AddTokenAsync(object data, bool? atEnd = null) { - if (MaxTokens <= 0) + if (ReadLocalValue(MaxTokensProperty) != DependencyProperty.UnsetValue && MaxTokens <= 0) { // No tokens for you return; @@ -465,7 +465,7 @@ internal async Task AddTokenAsync(object data, bool? atEnd = null) // If we've been typing in the last box, just add this to the end of our collection if (atEnd == true || _currentTextEdit == _lastTextEdit) { - if (MaxTokens != null && _innerItemsSource.ItemsSource.Count >= MaxTokens) + if (ReadLocalValue(MaxTokensProperty) != DependencyProperty.UnsetValue && _innerItemsSource.ItemsSource.Count >= MaxTokens) { // Remove tokens from the end until below the max number. for (var i = _innerItemsSource.Count - 2; i >= 0; --i) @@ -493,7 +493,7 @@ internal async Task AddTokenAsync(object data, bool? atEnd = null) var edit = _currentTextEdit; var index = _innerItemsSource.IndexOf(edit); - if (MaxTokens != null && _innerItemsSource.ItemsSource.Count >= MaxTokens) + if (ReadLocalValue(MaxTokensProperty) != DependencyProperty.UnsetValue && _innerItemsSource.ItemsSource.Count >= MaxTokens) { // Find the next token and remove it, until below the max number of tokens. for (var i = index; i < _innerItemsSource.Count; i++) From 1309ca1c60616789e06e7ba4e028a5666992ed75 Mon Sep 17 00:00:00 2001 From: Shane Weaver Date: Fri, 6 Aug 2021 13:53:42 -0700 Subject: [PATCH 27/46] Update Microsoft.Toolkit.Uwp.UI.Controls.Input/TokenizingTextBox/TokenizingTextBox.Properties.cs Co-authored-by: Michael Hawker MSFT (XAML Llama) <24302614+michael-hawker@users.noreply.github.com> --- .../TokenizingTextBox/TokenizingTextBox.Properties.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Microsoft.Toolkit.Uwp.UI.Controls.Input/TokenizingTextBox/TokenizingTextBox.Properties.cs b/Microsoft.Toolkit.Uwp.UI.Controls.Input/TokenizingTextBox/TokenizingTextBox.Properties.cs index 21b8eb70a55..29521486d4e 100644 --- a/Microsoft.Toolkit.Uwp.UI.Controls.Input/TokenizingTextBox/TokenizingTextBox.Properties.cs +++ b/Microsoft.Toolkit.Uwp.UI.Controls.Input/TokenizingTextBox/TokenizingTextBox.Properties.cs @@ -164,7 +164,7 @@ private static void TextPropertyChanged(DependencyObject d, DependencyPropertyCh nameof(MaxTokens), typeof(int), typeof(TokenizingTextBox), - new PropertyMetadata(DependencyProperty.UnsetValue, OnMaxTokensChanged)); + new PropertyMetadata(null, OnMaxTokensChanged)); private static void OnMaxTokensChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { @@ -353,4 +353,4 @@ public int MaxTokens set => SetValue(MaxTokensProperty, value); } } -} \ No newline at end of file +} From e2e5a658d5b0732276b1ad449a3f867687134f9b Mon Sep 17 00:00:00 2001 From: ArchieCoder Date: Fri, 6 Aug 2021 16:54:00 -0400 Subject: [PATCH 28/46] Added license --- .../Keyboard/KeyDownTriggerBehavior.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Microsoft.Toolkit.Uwp.UI.Behaviors/Keyboard/KeyDownTriggerBehavior.cs b/Microsoft.Toolkit.Uwp.UI.Behaviors/Keyboard/KeyDownTriggerBehavior.cs index 64e0832def9..0480e7895e2 100644 --- a/Microsoft.Toolkit.Uwp.UI.Behaviors/Keyboard/KeyDownTriggerBehavior.cs +++ b/Microsoft.Toolkit.Uwp.UI.Behaviors/Keyboard/KeyDownTriggerBehavior.cs @@ -1,3 +1,7 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + using Microsoft.Xaml.Interactivity; using Windows.System; using Windows.UI.Xaml; From f78319c88d87cb3a0e78a5266971598f38e94358 Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Mon, 9 Aug 2021 13:53:16 +0200 Subject: [PATCH 29/46] Added test for generate properties for [INPC] --- .../Test_INotifyPropertyChangedAttribute.cs | 32 +++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/UnitTests/UnitTests.NetCore/Mvvm/Test_INotifyPropertyChangedAttribute.cs b/UnitTests/UnitTests.NetCore/Mvvm/Test_INotifyPropertyChangedAttribute.cs index 016a75702c2..a59aecce691 100644 --- a/UnitTests/UnitTests.NetCore/Mvvm/Test_INotifyPropertyChangedAttribute.cs +++ b/UnitTests/UnitTests.NetCore/Mvvm/Test_INotifyPropertyChangedAttribute.cs @@ -2,6 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. +using System.Collections.Generic; using System.ComponentModel; using System.Diagnostics.CodeAnalysis; using System.Reflection; @@ -75,5 +76,36 @@ public void Test_INotifyPropertyChanged_WithoutHelpers() public partial class SampleModelWithoutHelpers { } + + [TestCategory("Mvvm")] + [TestMethod] + public void Test_INotifyPropertyChanged_WithGeneratedProperties() + { + Assert.IsTrue(typeof(INotifyPropertyChanged).IsAssignableFrom(typeof(SampleModelWithINPCAndObservableProperties))); + Assert.IsFalse(typeof(INotifyPropertyChanging).IsAssignableFrom(typeof(SampleModelWithINPCAndObservableProperties))); + + SampleModelWithINPCAndObservableProperties model = new(); + List eventArgs = new(); + + model.PropertyChanged += (s, e) => eventArgs.Add(e); + + model.X = 42; + model.Y = 66; + + Assert.AreEqual(eventArgs.Count, 2); + Assert.AreEqual(eventArgs[0].PropertyName, nameof(SampleModelWithINPCAndObservableProperties.X)); + Assert.AreEqual(eventArgs[1].PropertyName, nameof(SampleModelWithINPCAndObservableProperties.Y)); + } + + // See https://github.com/CommunityToolkit/WindowsCommunityToolkit/issues/3665 + [INotifyPropertyChanged] + public partial class SampleModelWithINPCAndObservableProperties + { + [ObservableProperty] + private int x; + + [ObservableProperty] + private int y; + } } } From d6348a3f19d68845ff0da03210a3564de561d773 Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Mon, 9 Aug 2021 20:09:43 +0200 Subject: [PATCH 30/46] Added .NET Standard 2.0 test project --- ...pleModelWithINPCAndObservableProperties.cs | 22 ++++++++++++++++ .../UnitTests.NetStandard.csproj | 13 ++++++++++ Windows Community Toolkit (NET).slnf | 1 + Windows Community Toolkit.sln | 25 ++++++++++++++++++- 4 files changed, 60 insertions(+), 1 deletion(-) create mode 100644 UnitTests/UnitTests.NetStandard/SampleModelWithINPCAndObservableProperties.cs create mode 100644 UnitTests/UnitTests.NetStandard/UnitTests.NetStandard.csproj diff --git a/UnitTests/UnitTests.NetStandard/SampleModelWithINPCAndObservableProperties.cs b/UnitTests/UnitTests.NetStandard/SampleModelWithINPCAndObservableProperties.cs new file mode 100644 index 00000000000..e30b152ec38 --- /dev/null +++ b/UnitTests/UnitTests.NetStandard/SampleModelWithINPCAndObservableProperties.cs @@ -0,0 +1,22 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Microsoft.Toolkit.Mvvm.ComponentModel; + +namespace UnitTests.NetStandard +{ + /// + /// See https://github.com/CommunityToolkit/WindowsCommunityToolkit/issues/3665. + /// This model in particular is loaded from an external .NET Standard 2.0 assembly. + /// + [INotifyPropertyChanged] + public partial class SampleModelWithINPCAndObservableProperties + { + [ObservableProperty] + private int x; + + [ObservableProperty] + private int y; + } +} diff --git a/UnitTests/UnitTests.NetStandard/UnitTests.NetStandard.csproj b/UnitTests/UnitTests.NetStandard/UnitTests.NetStandard.csproj new file mode 100644 index 00000000000..2f33f86d89c --- /dev/null +++ b/UnitTests/UnitTests.NetStandard/UnitTests.NetStandard.csproj @@ -0,0 +1,13 @@ + + + + netstandard2.0 + 9.0 + + + + + + + + diff --git a/Windows Community Toolkit (NET).slnf b/Windows Community Toolkit (NET).slnf index 41a012b46b7..4f6b2af91f6 100644 --- a/Windows Community Toolkit (NET).slnf +++ b/Windows Community Toolkit (NET).slnf @@ -10,6 +10,7 @@ "UnitTests\\UnitTests.HighPerformance.NetCore\\UnitTests.HighPerformance.NetCore.csproj", "UnitTests\\UnitTests.HighPerformance.Shared\\UnitTests.HighPerformance.Shared.shproj", "UnitTests\\UnitTests.NetCore\\UnitTests.NetCore.csproj", + "UnitTests\\UnitTests.NetStandard\\UnitTests.NetStandard.csproj", "UnitTests\\UnitTests.Shared\\UnitTests.Shared.shproj", "UnitTests\\UnitTests.SourceGenerators\\UnitTests.SourceGenerators.csproj", ] diff --git a/Windows Community Toolkit.sln b/Windows Community Toolkit.sln index 9b0c8e72f87..1b9704c9604 100644 --- a/Windows Community Toolkit.sln +++ b/Windows Community Toolkit.sln @@ -159,7 +159,9 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Toolkit.Uwp.UI.Co EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Toolkit.Mvvm.SourceGenerators", "Microsoft.Toolkit.Mvvm.SourceGenerators\Microsoft.Toolkit.Mvvm.SourceGenerators.csproj", "{E24D1146-5AD8-498F-A518-4890D8BF4937}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "UnitTests.SourceGenerators", "UnitTests\UnitTests.SourceGenerators\UnitTests.SourceGenerators.csproj", "{338C3BE4-2E71-4F21-AD30-03FDBB47A272}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "UnitTests.SourceGenerators", "UnitTests\UnitTests.SourceGenerators\UnitTests.SourceGenerators.csproj", "{338C3BE4-2E71-4F21-AD30-03FDBB47A272}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "UnitTests.NetStandard", "UnitTests\UnitTests.NetStandard\UnitTests.NetStandard.csproj", "{D9C82C0D-31D7-4888-B024-3CF3A4F54FE1}" EndProject Global GlobalSection(SharedMSBuildProjectFiles) = preSolution @@ -1154,6 +1156,26 @@ Global {338C3BE4-2E71-4F21-AD30-03FDBB47A272}.Release|x64.Build.0 = Release|Any CPU {338C3BE4-2E71-4F21-AD30-03FDBB47A272}.Release|x86.ActiveCfg = Release|Any CPU {338C3BE4-2E71-4F21-AD30-03FDBB47A272}.Release|x86.Build.0 = Release|Any CPU + {D9C82C0D-31D7-4888-B024-3CF3A4F54FE1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {D9C82C0D-31D7-4888-B024-3CF3A4F54FE1}.Debug|Any CPU.Build.0 = Debug|Any CPU + {D9C82C0D-31D7-4888-B024-3CF3A4F54FE1}.Debug|ARM.ActiveCfg = Debug|Any CPU + {D9C82C0D-31D7-4888-B024-3CF3A4F54FE1}.Debug|ARM.Build.0 = Debug|Any CPU + {D9C82C0D-31D7-4888-B024-3CF3A4F54FE1}.Debug|ARM64.ActiveCfg = Debug|Any CPU + {D9C82C0D-31D7-4888-B024-3CF3A4F54FE1}.Debug|ARM64.Build.0 = Debug|Any CPU + {D9C82C0D-31D7-4888-B024-3CF3A4F54FE1}.Debug|x64.ActiveCfg = Debug|Any CPU + {D9C82C0D-31D7-4888-B024-3CF3A4F54FE1}.Debug|x64.Build.0 = Debug|Any CPU + {D9C82C0D-31D7-4888-B024-3CF3A4F54FE1}.Debug|x86.ActiveCfg = Debug|Any CPU + {D9C82C0D-31D7-4888-B024-3CF3A4F54FE1}.Debug|x86.Build.0 = Debug|Any CPU + {D9C82C0D-31D7-4888-B024-3CF3A4F54FE1}.Release|Any CPU.ActiveCfg = Release|Any CPU + {D9C82C0D-31D7-4888-B024-3CF3A4F54FE1}.Release|Any CPU.Build.0 = Release|Any CPU + {D9C82C0D-31D7-4888-B024-3CF3A4F54FE1}.Release|ARM.ActiveCfg = Release|Any CPU + {D9C82C0D-31D7-4888-B024-3CF3A4F54FE1}.Release|ARM.Build.0 = Release|Any CPU + {D9C82C0D-31D7-4888-B024-3CF3A4F54FE1}.Release|ARM64.ActiveCfg = Release|Any CPU + {D9C82C0D-31D7-4888-B024-3CF3A4F54FE1}.Release|ARM64.Build.0 = Release|Any CPU + {D9C82C0D-31D7-4888-B024-3CF3A4F54FE1}.Release|x64.ActiveCfg = Release|Any CPU + {D9C82C0D-31D7-4888-B024-3CF3A4F54FE1}.Release|x64.Build.0 = Release|Any CPU + {D9C82C0D-31D7-4888-B024-3CF3A4F54FE1}.Release|x86.ActiveCfg = Release|Any CPU + {D9C82C0D-31D7-4888-B024-3CF3A4F54FE1}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -1204,6 +1226,7 @@ Global {3307BC1D-5D71-41C6-A1B3-B113B8242D08} = {F1AFFFA7-28FE-4770-BA48-10D76F3E59BC} {099B60FD-DAD6-4648-9DE2-8DBF9DCD9557} = {F1AFFFA7-28FE-4770-BA48-10D76F3E59BC} {338C3BE4-2E71-4F21-AD30-03FDBB47A272} = {B30036C4-D514-4E5B-A323-587A061772CE} + {D9C82C0D-31D7-4888-B024-3CF3A4F54FE1} = {B30036C4-D514-4E5B-A323-587A061772CE} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {5403B0C4-F244-4F73-A35C-FE664D0F4345} From b7688ef2a3fa9d528c80abcbe0ab99c35807c0de Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Mon, 9 Aug 2021 20:15:22 +0200 Subject: [PATCH 31/46] Fixed nullability attributes generation on .NET Standard 2.0 --- .../NullabilityAttributesGenerator.cs | 7 ++++++- .../Test_INotifyPropertyChangedAttribute.cs | 20 +++++++++++++++++++ .../UnitTests.NetCore.csproj | 1 + 3 files changed, 27 insertions(+), 1 deletion(-) diff --git a/Microsoft.Toolkit.Mvvm.SourceGenerators/Attributes/NullabilityAttributesGenerator.cs b/Microsoft.Toolkit.Mvvm.SourceGenerators/Attributes/NullabilityAttributesGenerator.cs index 5566ed21b59..cfb0f8b1233 100644 --- a/Microsoft.Toolkit.Mvvm.SourceGenerators/Attributes/NullabilityAttributesGenerator.cs +++ b/Microsoft.Toolkit.Mvvm.SourceGenerators/Attributes/NullabilityAttributesGenerator.cs @@ -34,7 +34,12 @@ public void Execute(GeneratorExecutionContext context) /// private void AddSourceCodeIfTypeIsNotPresent(GeneratorExecutionContext context, string typeFullName) { - if (context.Compilation.GetTypeByMetadataName(typeFullName) is not null) + // Check that the target attributes are not available in the consuming project. To ensure that + // this works fine both in .NET (Core) and .NET Standard implementations, we also need to check + // that the target types are defined in the reference assemblies for all target runtimes. This + // avoids issues on .NET Standard with Roslyn also seeing internal types from referenced assemblies. + if (context.Compilation.GetTypeByMetadataName(typeFullName) is + { ContainingModule: { MetadataName: "netstandard.dll" or "System.Runtime.dll" } }) { return; } diff --git a/UnitTests/UnitTests.NetCore/Mvvm/Test_INotifyPropertyChangedAttribute.cs b/UnitTests/UnitTests.NetCore/Mvvm/Test_INotifyPropertyChangedAttribute.cs index a59aecce691..a04822c9501 100644 --- a/UnitTests/UnitTests.NetCore/Mvvm/Test_INotifyPropertyChangedAttribute.cs +++ b/UnitTests/UnitTests.NetCore/Mvvm/Test_INotifyPropertyChangedAttribute.cs @@ -107,5 +107,25 @@ public partial class SampleModelWithINPCAndObservableProperties [ObservableProperty] private int y; } + + [TestCategory("Mvvm")] + [TestMethod] + public void Test_INotifyPropertyChanged_WithGeneratedProperties_ExternalNetStandard20Assembly() + { + Assert.IsTrue(typeof(INotifyPropertyChanged).IsAssignableFrom(typeof(NetStandard.SampleModelWithINPCAndObservableProperties))); + Assert.IsFalse(typeof(INotifyPropertyChanging).IsAssignableFrom(typeof(NetStandard.SampleModelWithINPCAndObservableProperties))); + + NetStandard.SampleModelWithINPCAndObservableProperties model = new(); + List eventArgs = new(); + + model.PropertyChanged += (s, e) => eventArgs.Add(e); + + model.X = 42; + model.Y = 66; + + Assert.AreEqual(eventArgs.Count, 2); + Assert.AreEqual(eventArgs[0].PropertyName, nameof(NetStandard.SampleModelWithINPCAndObservableProperties.X)); + Assert.AreEqual(eventArgs[1].PropertyName, nameof(NetStandard.SampleModelWithINPCAndObservableProperties.Y)); + } } } diff --git a/UnitTests/UnitTests.NetCore/UnitTests.NetCore.csproj b/UnitTests/UnitTests.NetCore/UnitTests.NetCore.csproj index c5bcf7a5da4..9921a87b1a9 100644 --- a/UnitTests/UnitTests.NetCore/UnitTests.NetCore.csproj +++ b/UnitTests/UnitTests.NetCore/UnitTests.NetCore.csproj @@ -10,6 +10,7 @@ + From 22a133b0e8a6e04ba515ca2dfa31dc64d2ddc432 Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Mon, 9 Aug 2021 20:22:19 +0200 Subject: [PATCH 32/46] Switched .NET Standard 2.0 check to use accessibility --- .../Attributes/NullabilityAttributesGenerator.cs | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/Microsoft.Toolkit.Mvvm.SourceGenerators/Attributes/NullabilityAttributesGenerator.cs b/Microsoft.Toolkit.Mvvm.SourceGenerators/Attributes/NullabilityAttributesGenerator.cs index cfb0f8b1233..a0c2d6ef8a2 100644 --- a/Microsoft.Toolkit.Mvvm.SourceGenerators/Attributes/NullabilityAttributesGenerator.cs +++ b/Microsoft.Toolkit.Mvvm.SourceGenerators/Attributes/NullabilityAttributesGenerator.cs @@ -36,10 +36,9 @@ private void AddSourceCodeIfTypeIsNotPresent(GeneratorExecutionContext context, { // Check that the target attributes are not available in the consuming project. To ensure that // this works fine both in .NET (Core) and .NET Standard implementations, we also need to check - // that the target types are defined in the reference assemblies for all target runtimes. This - // avoids issues on .NET Standard with Roslyn also seeing internal types from referenced assemblies. - if (context.Compilation.GetTypeByMetadataName(typeFullName) is - { ContainingModule: { MetadataName: "netstandard.dll" or "System.Runtime.dll" } }) + // that the target types are declared as public (we assume that in this case those types are from the BCL). + // This avoids issues on .NET Standard with Roslyn also seeing internal types from referenced assemblies. + if (context.Compilation.GetTypeByMetadataName(typeFullName) is { DeclaredAccessibility: Accessibility.Public }) { return; } From c7b55c6f4011d796174faad5a6645f1f5c6e0a63 Mon Sep 17 00:00:00 2001 From: Shane Weaver Date: Mon, 9 Aug 2021 15:50:05 -0700 Subject: [PATCH 33/46] Updated OnMaxTokensChanged method and improved MaxTokens test --- .../TokenizingTextBox.Properties.cs | 25 ++++++------------- .../Test_TokenizingTextBox_General.cs | 4 +++ 2 files changed, 12 insertions(+), 17 deletions(-) diff --git a/Microsoft.Toolkit.Uwp.UI.Controls.Input/TokenizingTextBox/TokenizingTextBox.Properties.cs b/Microsoft.Toolkit.Uwp.UI.Controls.Input/TokenizingTextBox/TokenizingTextBox.Properties.cs index 29521486d4e..20af01ea948 100644 --- a/Microsoft.Toolkit.Uwp.UI.Controls.Input/TokenizingTextBox/TokenizingTextBox.Properties.cs +++ b/Microsoft.Toolkit.Uwp.UI.Controls.Input/TokenizingTextBox/TokenizingTextBox.Properties.cs @@ -170,28 +170,19 @@ private static void OnMaxTokensChanged(DependencyObject d, DependencyPropertyCha { if (d is TokenizingTextBox ttb && ttb.ReadLocalValue(MaxTokensProperty) != DependencyProperty.UnsetValue && e.NewValue is int newMaxTokens) { - var tokenCount = ttb.Items.Count; - if (tokenCount > newMaxTokens) + var tokenCount = ttb._innerItemsSource.ItemsSource.Count; + if (tokenCount > 0 && tokenCount > newMaxTokens) { int tokensToRemove = tokenCount - Math.Max(newMaxTokens, 0); - var tokensRemoved = 0; // Start at the end, remove any extra tokens. - for (var i = ttb._innerItemsSource.Count - 1; i >= 0; --i) + for (var i = tokenCount; i >= tokenCount - tokensToRemove; --i) { - var item = ttb._innerItemsSource[i]; - if (item is not ITokenStringContainer) - { - // Force remove the items. No warning and no option to cancel. - ttb._innerItemsSource.Remove(item); - ttb.TokenItemRemoved?.Invoke(ttb, item); - - tokensRemoved++; - if (tokensRemoved == tokensToRemove) - { - break; - } - } + var token = ttb._innerItemsSource.ItemsSource[i - 1]; + + // Force remove the items. No warning and no option to cancel. + ttb._innerItemsSource.Remove(token); + ttb.TokenItemRemoved?.Invoke(ttb, token); } } } diff --git a/UnitTests/UnitTests.UWP/UI/Controls/Test_TokenizingTextBox_General.cs b/UnitTests/UnitTests.UWP/UI/Controls/Test_TokenizingTextBox_General.cs index 39a1764bb7b..25cdefac5ad 100644 --- a/UnitTests/UnitTests.UWP/UI/Controls/Test_TokenizingTextBox_General.cs +++ b/UnitTests/UnitTests.UWP/UI/Controls/Test_TokenizingTextBox_General.cs @@ -101,6 +101,10 @@ public void Test_MaxTokens() Assert.AreEqual(startingItemsCount + maxTokens, tokenBox.Items.Count, "Token Replace failed"); Assert.AreEqual("TokenItem1", tokenBox.Items[0]); Assert.AreEqual("TokenItem3", tokenBox.Items[1]); + + tokenBox.MaxTokens = 1; + + Assert.AreEqual(1, tokenBox.Items.Count); } } } \ No newline at end of file From 07f19d57835d228248531bac1d2b2a5fce18f8d5 Mon Sep 17 00:00:00 2001 From: Shane Weaver Date: Mon, 9 Aug 2021 16:04:20 -0700 Subject: [PATCH 34/46] Bug fixes --- .../TokenizingTextBox/TokenizingTextBox.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Microsoft.Toolkit.Uwp.UI.Controls.Input/TokenizingTextBox/TokenizingTextBox.cs b/Microsoft.Toolkit.Uwp.UI.Controls.Input/TokenizingTextBox/TokenizingTextBox.cs index 9eaf54bd651..827aa03e804 100644 --- a/Microsoft.Toolkit.Uwp.UI.Controls.Input/TokenizingTextBox/TokenizingTextBox.cs +++ b/Microsoft.Toolkit.Uwp.UI.Controls.Input/TokenizingTextBox/TokenizingTextBox.cs @@ -440,7 +440,7 @@ public async Task ClearAsync() internal async Task AddTokenAsync(object data, bool? atEnd = null) { - if (ReadLocalValue(MaxTokensProperty) != DependencyProperty.UnsetValue && MaxTokens <= 0) + if (ReadLocalValue(MaxTokensProperty) == DependencyProperty.UnsetValue || MaxTokens <= 0) { // No tokens for you return; @@ -465,7 +465,7 @@ internal async Task AddTokenAsync(object data, bool? atEnd = null) // If we've been typing in the last box, just add this to the end of our collection if (atEnd == true || _currentTextEdit == _lastTextEdit) { - if (ReadLocalValue(MaxTokensProperty) != DependencyProperty.UnsetValue && _innerItemsSource.ItemsSource.Count >= MaxTokens) + if (ReadLocalValue(MaxTokensProperty) != DependencyProperty.UnsetValue && _innerItemsSource.ItemsSource.Count > MaxTokens) { // Remove tokens from the end until below the max number. for (var i = _innerItemsSource.Count - 2; i >= 0; --i) @@ -493,7 +493,7 @@ internal async Task AddTokenAsync(object data, bool? atEnd = null) var edit = _currentTextEdit; var index = _innerItemsSource.IndexOf(edit); - if (ReadLocalValue(MaxTokensProperty) != DependencyProperty.UnsetValue && _innerItemsSource.ItemsSource.Count >= MaxTokens) + if (ReadLocalValue(MaxTokensProperty) != DependencyProperty.UnsetValue && _innerItemsSource.ItemsSource.Count > MaxTokens) { // Find the next token and remove it, until below the max number of tokens. for (var i = index; i < _innerItemsSource.Count; i++) From 0ae64383e63cfbd75314cc97aea5328412a0d4b7 Mon Sep 17 00:00:00 2001 From: Alexandre Zollinger Chohfi Date: Mon, 9 Aug 2021 16:13:03 -0700 Subject: [PATCH 35/46] Fix typo in ConnectionInformation class. --- .../Network/ConnectionInformation.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Microsoft.Toolkit.Uwp.Connectivity/Network/ConnectionInformation.cs b/Microsoft.Toolkit.Uwp.Connectivity/Network/ConnectionInformation.cs index aca168b6aac..d9537bf047a 100644 --- a/Microsoft.Toolkit.Uwp.Connectivity/Network/ConnectionInformation.cs +++ b/Microsoft.Toolkit.Uwp.Connectivity/Network/ConnectionInformation.cs @@ -135,9 +135,9 @@ public bool IsInternetOnMeteredConnection public byte? SignalStrength { get; private set; } /// - /// Gets signal strength for the current Internet Connection Profile. + /// Gets the network names associated with the current Internet Connection Profile. /// - /// value of + /// value of public IReadOnlyList NetworkNames { get @@ -146,4 +146,4 @@ public IReadOnlyList NetworkNames } } } -} \ No newline at end of file +} From 01c413b62ae5e6050b5ffb880b584a9a25470eee Mon Sep 17 00:00:00 2001 From: Shane Weaver Date: Mon, 9 Aug 2021 17:03:15 -0700 Subject: [PATCH 36/46] bugfix bug fixes --- .../TokenizingTextBox/TokenizingTextBox.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Microsoft.Toolkit.Uwp.UI.Controls.Input/TokenizingTextBox/TokenizingTextBox.cs b/Microsoft.Toolkit.Uwp.UI.Controls.Input/TokenizingTextBox/TokenizingTextBox.cs index 827aa03e804..9eaf54bd651 100644 --- a/Microsoft.Toolkit.Uwp.UI.Controls.Input/TokenizingTextBox/TokenizingTextBox.cs +++ b/Microsoft.Toolkit.Uwp.UI.Controls.Input/TokenizingTextBox/TokenizingTextBox.cs @@ -440,7 +440,7 @@ public async Task ClearAsync() internal async Task AddTokenAsync(object data, bool? atEnd = null) { - if (ReadLocalValue(MaxTokensProperty) == DependencyProperty.UnsetValue || MaxTokens <= 0) + if (ReadLocalValue(MaxTokensProperty) != DependencyProperty.UnsetValue && MaxTokens <= 0) { // No tokens for you return; @@ -465,7 +465,7 @@ internal async Task AddTokenAsync(object data, bool? atEnd = null) // If we've been typing in the last box, just add this to the end of our collection if (atEnd == true || _currentTextEdit == _lastTextEdit) { - if (ReadLocalValue(MaxTokensProperty) != DependencyProperty.UnsetValue && _innerItemsSource.ItemsSource.Count > MaxTokens) + if (ReadLocalValue(MaxTokensProperty) != DependencyProperty.UnsetValue && _innerItemsSource.ItemsSource.Count >= MaxTokens) { // Remove tokens from the end until below the max number. for (var i = _innerItemsSource.Count - 2; i >= 0; --i) @@ -493,7 +493,7 @@ internal async Task AddTokenAsync(object data, bool? atEnd = null) var edit = _currentTextEdit; var index = _innerItemsSource.IndexOf(edit); - if (ReadLocalValue(MaxTokensProperty) != DependencyProperty.UnsetValue && _innerItemsSource.ItemsSource.Count > MaxTokens) + if (ReadLocalValue(MaxTokensProperty) != DependencyProperty.UnsetValue && _innerItemsSource.ItemsSource.Count >= MaxTokens) { // Find the next token and remove it, until below the max number of tokens. for (var i = index; i < _innerItemsSource.Count; i++) From a7bb51b31c2b07f725b15989f452267e8e3f1690 Mon Sep 17 00:00:00 2001 From: Shane Weaver Date: Mon, 9 Aug 2021 17:09:25 -0700 Subject: [PATCH 37/46] Added binding to MaxTokens value in sample --- .../TokenizingTextBox/TokenizingTextBoxXaml.bind | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/TokenizingTextBox/TokenizingTextBoxXaml.bind b/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/TokenizingTextBox/TokenizingTextBoxXaml.bind index 46025c3b0ce..4877768f0c4 100644 --- a/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/TokenizingTextBox/TokenizingTextBoxXaml.bind +++ b/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/TokenizingTextBox/TokenizingTextBoxXaml.bind @@ -30,8 +30,11 @@ - + + + + + Date: Tue, 10 Aug 2021 09:55:05 -0700 Subject: [PATCH 38/46] Update Microsoft.Toolkit.Uwp.Connectivity/Network/ConnectionInformation.cs --- .../Network/ConnectionInformation.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Microsoft.Toolkit.Uwp.Connectivity/Network/ConnectionInformation.cs b/Microsoft.Toolkit.Uwp.Connectivity/Network/ConnectionInformation.cs index d9537bf047a..949e3c9ae91 100644 --- a/Microsoft.Toolkit.Uwp.Connectivity/Network/ConnectionInformation.cs +++ b/Microsoft.Toolkit.Uwp.Connectivity/Network/ConnectionInformation.cs @@ -137,7 +137,7 @@ public bool IsInternetOnMeteredConnection /// /// Gets the network names associated with the current Internet Connection Profile. /// - /// value of + /// value of public IReadOnlyList NetworkNames { get From 8b3be50b339d2a157a95287be3d2a1b4820b8a42 Mon Sep 17 00:00:00 2001 From: ArchieCoder Date: Tue, 10 Aug 2021 16:29:37 -0400 Subject: [PATCH 39/46] Fix StyleCop --- .../Keyboard/KeyDownTriggerBehavior.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/Microsoft.Toolkit.Uwp.UI.Behaviors/Keyboard/KeyDownTriggerBehavior.cs b/Microsoft.Toolkit.Uwp.UI.Behaviors/Keyboard/KeyDownTriggerBehavior.cs index 0480e7895e2..e0266db4556 100644 --- a/Microsoft.Toolkit.Uwp.UI.Behaviors/Keyboard/KeyDownTriggerBehavior.cs +++ b/Microsoft.Toolkit.Uwp.UI.Behaviors/Keyboard/KeyDownTriggerBehavior.cs @@ -15,7 +15,6 @@ namespace Microsoft.Toolkit.Uwp.UI.Behaviors [TypeConstraint(typeof(FrameworkElement))] public class KeyDownTriggerBehavior : Trigger { - /// /// Identifies the property. /// From d4633044b340bce262bd31b051a366c816719d0e Mon Sep 17 00:00:00 2001 From: Alexandre Zollinger Chohfi Date: Tue, 10 Aug 2021 13:47:06 -0700 Subject: [PATCH 40/46] Update Microsoft.Toolkit.Uwp.Connectivity/Network/ConnectionInformation.cs --- .../Network/ConnectionInformation.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Microsoft.Toolkit.Uwp.Connectivity/Network/ConnectionInformation.cs b/Microsoft.Toolkit.Uwp.Connectivity/Network/ConnectionInformation.cs index 949e3c9ae91..2e89c465442 100644 --- a/Microsoft.Toolkit.Uwp.Connectivity/Network/ConnectionInformation.cs +++ b/Microsoft.Toolkit.Uwp.Connectivity/Network/ConnectionInformation.cs @@ -137,7 +137,7 @@ public bool IsInternetOnMeteredConnection /// /// Gets the network names associated with the current Internet Connection Profile. /// - /// value of + /// value of public IReadOnlyList NetworkNames { get From c3903abf4ce1c5b24ac6c3335af828a2d409f584 Mon Sep 17 00:00:00 2001 From: Shane Weaver Date: Tue, 10 Aug 2021 16:34:56 -0700 Subject: [PATCH 41/46] Updated TokenizingTextBox.MaxTokens test --- .../TokenizingTextBox/TokenizingTextBox.Properties.cs | 2 +- .../UI/Controls/Test_TokenizingTextBox_General.cs | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/Microsoft.Toolkit.Uwp.UI.Controls.Input/TokenizingTextBox/TokenizingTextBox.Properties.cs b/Microsoft.Toolkit.Uwp.UI.Controls.Input/TokenizingTextBox/TokenizingTextBox.Properties.cs index 20af01ea948..ea162758e04 100644 --- a/Microsoft.Toolkit.Uwp.UI.Controls.Input/TokenizingTextBox/TokenizingTextBox.Properties.cs +++ b/Microsoft.Toolkit.Uwp.UI.Controls.Input/TokenizingTextBox/TokenizingTextBox.Properties.cs @@ -176,7 +176,7 @@ private static void OnMaxTokensChanged(DependencyObject d, DependencyPropertyCha int tokensToRemove = tokenCount - Math.Max(newMaxTokens, 0); // Start at the end, remove any extra tokens. - for (var i = tokenCount; i >= tokenCount - tokensToRemove; --i) + for (var i = tokenCount; i > tokenCount - tokensToRemove; --i) { var token = ttb._innerItemsSource.ItemsSource[i - 1]; diff --git a/UnitTests/UnitTests.UWP/UI/Controls/Test_TokenizingTextBox_General.cs b/UnitTests/UnitTests.UWP/UI/Controls/Test_TokenizingTextBox_General.cs index 25cdefac5ad..ae55c31d885 100644 --- a/UnitTests/UnitTests.UWP/UI/Controls/Test_TokenizingTextBox_General.cs +++ b/UnitTests/UnitTests.UWP/UI/Controls/Test_TokenizingTextBox_General.cs @@ -104,7 +104,8 @@ public void Test_MaxTokens() tokenBox.MaxTokens = 1; - Assert.AreEqual(1, tokenBox.Items.Count); + Assert.AreEqual(startingItemsCount + 1, tokenBox.Items.Count); + Assert.AreEqual("TokenItem1", tokenBox.Items[0]); } } } \ No newline at end of file From 5c1b35bd3fd45f34a1f626f5ea54c18d9354e1d8 Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Thu, 12 Aug 2021 12:57:08 +0200 Subject: [PATCH 42/46] Fixed links to related issue --- .../Mvvm/Test_INotifyPropertyChangedAttribute.cs | 2 +- .../SampleModelWithINPCAndObservableProperties.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/UnitTests/UnitTests.NetCore/Mvvm/Test_INotifyPropertyChangedAttribute.cs b/UnitTests/UnitTests.NetCore/Mvvm/Test_INotifyPropertyChangedAttribute.cs index a04822c9501..6c0583e282f 100644 --- a/UnitTests/UnitTests.NetCore/Mvvm/Test_INotifyPropertyChangedAttribute.cs +++ b/UnitTests/UnitTests.NetCore/Mvvm/Test_INotifyPropertyChangedAttribute.cs @@ -97,7 +97,7 @@ public void Test_INotifyPropertyChanged_WithGeneratedProperties() Assert.AreEqual(eventArgs[1].PropertyName, nameof(SampleModelWithINPCAndObservableProperties.Y)); } - // See https://github.com/CommunityToolkit/WindowsCommunityToolkit/issues/3665 + // See https://github.com/CommunityToolkit/WindowsCommunityToolkit/issues/4167 [INotifyPropertyChanged] public partial class SampleModelWithINPCAndObservableProperties { diff --git a/UnitTests/UnitTests.NetStandard/SampleModelWithINPCAndObservableProperties.cs b/UnitTests/UnitTests.NetStandard/SampleModelWithINPCAndObservableProperties.cs index e30b152ec38..d571eea9f44 100644 --- a/UnitTests/UnitTests.NetStandard/SampleModelWithINPCAndObservableProperties.cs +++ b/UnitTests/UnitTests.NetStandard/SampleModelWithINPCAndObservableProperties.cs @@ -7,7 +7,7 @@ namespace UnitTests.NetStandard { /// - /// See https://github.com/CommunityToolkit/WindowsCommunityToolkit/issues/3665. + /// See https://github.com/CommunityToolkit/WindowsCommunityToolkit/issues/4167. /// This model in particular is loaded from an external .NET Standard 2.0 assembly. /// [INotifyPropertyChanged] From 86cd4d6eb6d524583e9f254bdf56245cc1115baa Mon Sep 17 00:00:00 2001 From: Shane Weaver Date: Thu, 12 Aug 2021 15:59:02 -0700 Subject: [PATCH 43/46] Updated Max to Maximum and added token counter --- .../TokenizingTextBoxXaml.bind | 4 +- .../TokenizingTextBox.Properties.cs | 18 +++---- .../TokenizingTextBox/TokenizingTextBox.cs | 46 ++---------------- .../TokenizingTextBoxItem.AutoSuggestBox.cs | 47 +++++++++++++++++++ .../TokenizingTextBoxItem.AutoSuggestBox.xaml | 11 ++++- .../TokenizingTextBoxItem.cs | 1 - .../Test_TokenizingTextBox_General.cs | 18 +++++-- 7 files changed, 83 insertions(+), 62 deletions(-) diff --git a/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/TokenizingTextBox/TokenizingTextBoxXaml.bind b/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/TokenizingTextBox/TokenizingTextBoxXaml.bind index 4877768f0c4..fca7b60e1de 100644 --- a/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/TokenizingTextBox/TokenizingTextBoxXaml.bind +++ b/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/TokenizingTextBox/TokenizingTextBoxXaml.bind @@ -32,7 +32,7 @@ - + + MaximumTokens="3"> diff --git a/Microsoft.Toolkit.Uwp.UI.Controls.Input/TokenizingTextBox/TokenizingTextBox.Properties.cs b/Microsoft.Toolkit.Uwp.UI.Controls.Input/TokenizingTextBox/TokenizingTextBox.Properties.cs index ea162758e04..1a5a1e36b89 100644 --- a/Microsoft.Toolkit.Uwp.UI.Controls.Input/TokenizingTextBox/TokenizingTextBox.Properties.cs +++ b/Microsoft.Toolkit.Uwp.UI.Controls.Input/TokenizingTextBox/TokenizingTextBox.Properties.cs @@ -158,17 +158,17 @@ private static void TextPropertyChanged(DependencyObject d, DependencyPropertyCh new PropertyMetadata(false)); /// - /// Identifies the property. + /// Identifies the property. /// - public static readonly DependencyProperty MaxTokensProperty = DependencyProperty.Register( - nameof(MaxTokens), + public static readonly DependencyProperty MaximumTokensProperty = DependencyProperty.Register( + nameof(MaximumTokens), typeof(int), typeof(TokenizingTextBox), - new PropertyMetadata(null, OnMaxTokensChanged)); + new PropertyMetadata(null, OnMaximumTokensChanged)); - private static void OnMaxTokensChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + private static void OnMaximumTokensChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { - if (d is TokenizingTextBox ttb && ttb.ReadLocalValue(MaxTokensProperty) != DependencyProperty.UnsetValue && e.NewValue is int newMaxTokens) + if (d is TokenizingTextBox ttb && ttb.ReadLocalValue(MaximumTokensProperty) != DependencyProperty.UnsetValue && e.NewValue is int newMaxTokens) { var tokenCount = ttb._innerItemsSource.ItemsSource.Count; if (tokenCount > 0 && tokenCount > newMaxTokens) @@ -338,10 +338,10 @@ public string SelectedTokenText /// /// Gets or sets the maximum number of token results allowed at a time. /// - public int MaxTokens + public int MaximumTokens { - get => (int)GetValue(MaxTokensProperty); - set => SetValue(MaxTokensProperty, value); + get => (int)GetValue(MaximumTokensProperty); + set => SetValue(MaximumTokensProperty, value); } } } diff --git a/Microsoft.Toolkit.Uwp.UI.Controls.Input/TokenizingTextBox/TokenizingTextBox.cs b/Microsoft.Toolkit.Uwp.UI.Controls.Input/TokenizingTextBox/TokenizingTextBox.cs index 9eaf54bd651..981c37584dd 100644 --- a/Microsoft.Toolkit.Uwp.UI.Controls.Input/TokenizingTextBox/TokenizingTextBox.cs +++ b/Microsoft.Toolkit.Uwp.UI.Controls.Input/TokenizingTextBox/TokenizingTextBox.cs @@ -78,10 +78,10 @@ private void ItemsSource_PropertyChanged(DependencyObject sender, DependencyProp { _innerItemsSource = new InterspersedObservableCollection(ItemsSource); - if (ReadLocalValue(MaxTokensProperty) != DependencyProperty.UnsetValue && _innerItemsSource.ItemsSource.Count > MaxTokens) + if (ReadLocalValue(MaximumTokensProperty) != DependencyProperty.UnsetValue && _innerItemsSource.ItemsSource.Count > MaximumTokens) { // Reduce down to the max as necessary. - for (var i = _innerItemsSource.ItemsSource.Count - 1; i >= Math.Max(MaxTokens, 0); --i) + for (var i = _innerItemsSource.ItemsSource.Count - 1; i >= Math.Max(MaximumTokens, 0); --i) { _innerItemsSource.Remove(_innerItemsSource[i]); } @@ -440,7 +440,7 @@ public async Task ClearAsync() internal async Task AddTokenAsync(object data, bool? atEnd = null) { - if (ReadLocalValue(MaxTokensProperty) != DependencyProperty.UnsetValue && MaxTokens <= 0) + if (ReadLocalValue(MaximumTokensProperty) != DependencyProperty.UnsetValue && (MaximumTokens <= 0 || MaximumTokens <= _innerItemsSource.ItemsSource.Count)) { // No tokens for you return; @@ -465,26 +465,6 @@ internal async Task AddTokenAsync(object data, bool? atEnd = null) // If we've been typing in the last box, just add this to the end of our collection if (atEnd == true || _currentTextEdit == _lastTextEdit) { - if (ReadLocalValue(MaxTokensProperty) != DependencyProperty.UnsetValue && _innerItemsSource.ItemsSource.Count >= MaxTokens) - { - // Remove tokens from the end until below the max number. - for (var i = _innerItemsSource.Count - 2; i >= 0; --i) - { - var item = _innerItemsSource[i]; - if (item is not ITokenStringContainer) - { - _innerItemsSource.Remove(item); - TokenItemRemoved?.Invoke(this, item); - - // Keep going until we are below the max. - if (_innerItemsSource.ItemsSource.Count < MaxTokens) - { - break; - } - } - } - } - _innerItemsSource.InsertAt(_innerItemsSource.Count - 1, data); } else @@ -493,26 +473,6 @@ internal async Task AddTokenAsync(object data, bool? atEnd = null) var edit = _currentTextEdit; var index = _innerItemsSource.IndexOf(edit); - if (ReadLocalValue(MaxTokensProperty) != DependencyProperty.UnsetValue && _innerItemsSource.ItemsSource.Count >= MaxTokens) - { - // Find the next token and remove it, until below the max number of tokens. - for (var i = index; i < _innerItemsSource.Count; i++) - { - var item = _innerItemsSource[i]; - if (item is not ITokenStringContainer) - { - _innerItemsSource.Remove(item); - TokenItemRemoved?.Invoke(this, item); - - // Keep going until we are below the max. - if (_innerItemsSource.ItemsSource.Count < MaxTokens) - { - break; - } - } - } - } - // Insert our new data item at the location of our textbox _innerItemsSource.InsertAt(index, data); diff --git a/Microsoft.Toolkit.Uwp.UI.Controls.Input/TokenizingTextBox/TokenizingTextBoxItem.AutoSuggestBox.cs b/Microsoft.Toolkit.Uwp.UI.Controls.Input/TokenizingTextBox/TokenizingTextBoxItem.AutoSuggestBox.cs index ebd64de2539..77722b7a75d 100644 --- a/Microsoft.Toolkit.Uwp.UI.Controls.Input/TokenizingTextBox/TokenizingTextBoxItem.AutoSuggestBox.cs +++ b/Microsoft.Toolkit.Uwp.UI.Controls.Input/TokenizingTextBox/TokenizingTextBoxItem.AutoSuggestBox.cs @@ -4,10 +4,12 @@ using Windows.Foundation; using Windows.System; +using Windows.UI; using Windows.UI.Xaml; using Windows.UI.Xaml.Controls; using Windows.UI.Xaml.Data; using Windows.UI.Xaml.Input; +using Windows.UI.Xaml.Media; namespace Microsoft.Toolkit.Uwp.UI.Controls { @@ -16,9 +18,11 @@ namespace Microsoft.Toolkit.Uwp.UI.Controls /// [System.Diagnostics.CodeAnalysis.SuppressMessage("StyleCop.CSharp.ReadabilityRules", "SA1124:Do not use regions", Justification = "Organization")] [TemplatePart(Name = PART_AutoSuggestBox, Type = typeof(AutoSuggestBox))] //// String case + [TemplatePart(Name = PART_TokensCounter, Type = typeof(TextBlock))] public partial class TokenizingTextBoxItem { private const string PART_AutoSuggestBox = "PART_AutoSuggestBox"; + private const string PART_TokensCounter = "PART_TokensCounter"; private AutoSuggestBox _autoSuggestBox; @@ -231,6 +235,8 @@ private void AutoSuggestBox_GotFocus(object sender, RoutedEventArgs e) #region Inner TextBox private void OnASBLoaded(object sender, RoutedEventArgs e) { + UpdateTokensCounter(this); + // Local function for Selection changed void AutoSuggestTextBox_SelectionChanged(object box, RoutedEventArgs args) { @@ -329,6 +335,47 @@ private void AutoSuggestTextBox_PreviewKeyDown(object sender, KeyRoutedEventArgs Owner.SelectAllTokensAndText(); } } + + private void UpdateTokensCounter(TokenizingTextBoxItem ttbi) + { + var maxTokensCounter = (TextBlock)_autoSuggestBox?.FindDescendant(PART_TokensCounter); + if (maxTokensCounter == null) + { + return; + } + + void OnTokenCountChanged(TokenizingTextBox ttb, object value = null) + { + var itemsSource = ttb.ItemsSource as InterspersedObservableCollection; + var currentTokens = itemsSource.ItemsSource.Count; + var maxTokens = ttb.MaximumTokens; + + maxTokensCounter.Text = $"{currentTokens}/{maxTokens}"; + maxTokensCounter.Visibility = Visibility.Visible; + + maxTokensCounter.Foreground = (currentTokens == maxTokens) + ? new SolidColorBrush(Colors.Red) + : _autoSuggestBox.Foreground; + } + + ttbi.Owner.TokenItemAdded -= OnTokenCountChanged; + ttbi.Owner.TokenItemRemoved -= OnTokenCountChanged; + + // I would have like to compared to DependencyProperty.UnsetValue, but MaximumTokensProperty value is returning 0 even though we didn't set it! + // This means that the token counter will not show up for a specified maximum value of 0. However, it's a pretty uncommon scenario to offer a picker + // with no ability to add items. If the case does arrive where the ttb should be unusable by design, developers should disable the control instead or setting the maximum to 0. + if (Content is ITokenStringContainer str && str.IsLast && ttbi?.Owner != null && (int)ttbi.Owner.GetValue(TokenizingTextBox.MaximumTokensProperty) > 0) + { + ttbi.Owner.TokenItemAdded += OnTokenCountChanged; + ttbi.Owner.TokenItemRemoved += OnTokenCountChanged; + OnTokenCountChanged(ttbi.Owner); + } + else + { + maxTokensCounter.Visibility = Visibility.Collapsed; + maxTokensCounter.Text = string.Empty; + } + } #endregion } } \ No newline at end of file diff --git a/Microsoft.Toolkit.Uwp.UI.Controls.Input/TokenizingTextBox/TokenizingTextBoxItem.AutoSuggestBox.xaml b/Microsoft.Toolkit.Uwp.UI.Controls.Input/TokenizingTextBox/TokenizingTextBoxItem.AutoSuggestBox.xaml index e0b9725492f..26dd6f69844 100644 --- a/Microsoft.Toolkit.Uwp.UI.Controls.Input/TokenizingTextBox/TokenizingTextBoxItem.AutoSuggestBox.xaml +++ b/Microsoft.Toolkit.Uwp.UI.Controls.Input/TokenizingTextBox/TokenizingTextBoxItem.AutoSuggestBox.xaml @@ -137,6 +137,7 @@ + @@ -176,7 +177,7 @@ ZoomMode="Disabled" /> + + +