diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md index 510e2f03e7b..1bdcc56d20e 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -4,23 +4,26 @@ about: Create a report to help us fix something that isn't working as expected title: '' labels: "bug :bug:" 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 + 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,46 +33,52 @@ 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): + + + +NuGet Package(s): Package Version(s): Windows 10 Build Number: + - [ ] Fall Creators Update (16299) - [ ] April 2018 Update (17134) - [ ] 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: + - [ ] Fall Creators Update (16299) - [ ] April 2018 Update (17134) - [ ] October 2018 Update (17763) - [ ] May 2019 Update (18362) - [ ] May 2020 Update (19041) -- [ ] Insider Build (xxxxx) +- [ ] Insider Build ({build_number}) Device form factor: + - [ ] Desktop - [ ] Xbox - [ ] Surface Hub - [ ] IoT -Visual Studio -- [ ] 2017 (version: ) -- [ ] 2019 (version: ) -- [ ] 2019 Preview (version: ) +Visual Studio version: -``` +- [ ] 2017 (15.{minor_version}) +- [ ] 2019 (16.{minor_version}) +- [ ] 2022 (17.{minor_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 -## Fixes # - + ## PR Type + What kind of change does this PR introduce? + @@ -23,30 +25,30 @@ What kind of change does this PR introduce? - ## What is the current behavior? - + ## What is the new behavior? - + ## 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) + - [ ] 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 + + \ 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 6e784b3635d..f5e8ced4566 100644 --- a/Contributing.md +++ b/Contributing.md @@ -1,39 +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: -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. +## ❔ 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`. 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. +## πŸ› Fix a Bug + +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 + +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. -## 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 -## 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. +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: -* 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 New Feature -## Add or Improve Documentation :page_with_curl: +* 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 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. +## πŸš€ Create, Submit or Review Pull Request + +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 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 + +**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.Mvvm.SourceGenerators/Attributes/NullabilityAttributesGenerator.cs b/Microsoft.Toolkit.Mvvm.SourceGenerators/Attributes/NullabilityAttributesGenerator.cs index 5566ed21b59..a0c2d6ef8a2 100644 --- a/Microsoft.Toolkit.Mvvm.SourceGenerators/Attributes/NullabilityAttributesGenerator.cs +++ b/Microsoft.Toolkit.Mvvm.SourceGenerators/Attributes/NullabilityAttributesGenerator.cs @@ -34,7 +34,11 @@ 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 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; } diff --git a/Microsoft.Toolkit.Uwp.Connectivity/Network/ConnectionInformation.cs b/Microsoft.Toolkit.Uwp.Connectivity/Network/ConnectionInformation.cs index aca168b6aac..2e89c465442 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 +} 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.SampleApp/Microsoft.Toolkit.Uwp.SampleApp.csproj b/Microsoft.Toolkit.Uwp.SampleApp/Microsoft.Toolkit.Uwp.SampleApp.csproj index c94beaa13c1..df804382285 100644 --- a/Microsoft.Toolkit.Uwp.SampleApp/Microsoft.Toolkit.Uwp.SampleApp.csproj +++ b/Microsoft.Toolkit.Uwp.SampleApp/Microsoft.Toolkit.Uwp.SampleApp.csproj @@ -626,6 +626,7 @@ Designer + @@ -1479,6 +1480,7 @@ Visual C++ 2015 Runtime for Universal Windows Platform Apps + 14.0 diff --git a/Microsoft.Toolkit.Uwp.SampleApp/ReadMe.md b/Microsoft.Toolkit.Uwp.SampleApp/ReadMe.md index 6a059b6c637..98554a7f74c 100644 --- a/Microsoft.Toolkit.Uwp.SampleApp/ReadMe.md +++ b/Microsoft.Toolkit.Uwp.SampleApp/ReadMe.md @@ -1,100 +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 - - + + - + Foreground="Black" + Text="@[Text:String:Hey!]" + FontSize="@[FontSize:Slider:12:10-30]" + VerticalAlignment="@[Vertical Alignment:Enum:VerticalAlignment.Center]"> + ``` -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 -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.) +### 3. Have a '*Shallow Copy*' of your example in the sample page -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. +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. +### 4. For Events/Resource Templates: Have your sample page implement the **`IXamlRendererListener`** interface -```csharp +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. + +```cs var markdownText = control.FindChild("MarkdownText") as MarkdownTextBlock; if (markdownText != null) { @@ -102,13 +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); @@ -117,26 +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 [ @@ -148,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" } @@ -157,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: -> ``` -> repositories -> β”œβ”€β”€ WindowsCommunityToolkit -> β”œβ”€β”€ WindowsCommunityToolkitDocs -> ``` +Example, this folder structure works best: + +```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.Uwp.SampleApp/SamplePages/KeyDownTriggerBehavior/KeyDownTriggerBehaviorXaml.bind b/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/KeyDownTriggerBehavior/KeyDownTriggerBehaviorXaml.bind new file mode 100644 index 00000000000..25535c04303 --- /dev/null +++ b/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/KeyDownTriggerBehavior/KeyDownTriggerBehaviorXaml.bind @@ -0,0 +1,59 @@ + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/TokenizingTextBox/TokenizingTextBoxXaml.bind b/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/TokenizingTextBox/TokenizingTextBoxXaml.bind index b38058a507d..fca7b60e1de 100644 --- a/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/TokenizingTextBox/TokenizingTextBoxXaml.bind +++ b/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/TokenizingTextBox/TokenizingTextBoxXaml.bind @@ -30,8 +30,11 @@ - + + + + + + TokenDelimiter="," + MaximumTokens="3"> diff --git a/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/XamlOnlyPage.xaml b/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/XamlOnlyPage.xaml index 5beea3c5e25..c51455532c3 100644 --- a/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/XamlOnlyPage.xaml +++ b/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/XamlOnlyPage.xaml @@ -33,6 +33,7 @@ + diff --git a/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/samples.json b/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/samples.json index adc8674db85..6e9841a5a36 100644 --- a/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/samples.json +++ b/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/samples.json @@ -839,6 +839,15 @@ "Icon": "/Assets/Helpers.png", "DocumentationUrl": "https://raw.githubusercontent.com/MicrosoftDocs/WindowsCommunityToolkitDocs/master/docs/behaviors/AutoSelectBehavior.md" }, + { + "Name": "KeyDownTriggerBehavior", + "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": "/SamplePages/KeyDownTriggerBehavior/KeyDownTriggerBehaviorXaml.bind", + "Icon": "/Assets/Helpers.png", + "DocumentationUrl": "https://raw.githubusercontent.com/MicrosoftDocs/WindowsCommunityToolkitDocs/master/docs/behaviors/KeyDownTriggerBehavior.md" + }, { "Name": "Win2d Path Mini Language Parser", "Type": "CanvasPathGeometryPage", 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..e0266db4556 --- /dev/null +++ b/Microsoft.Toolkit.Uwp.UI.Behaviors/Keyboard/KeyDownTriggerBehavior.cs @@ -0,0 +1,62 @@ +// 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; +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 + { + /// + /// Identifies the property. + /// + public static readonly DependencyProperty KeyProperty = DependencyProperty.Register( + nameof(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); + } + + /// + 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); + } + } + } +} diff --git a/Microsoft.Toolkit.Uwp.UI.Controls.Core/VisualStudioToolsManifest.xml b/Microsoft.Toolkit.Uwp.UI.Controls.Core/VisualStudioToolsManifest.xml index 161131ea6dd..031faeee520 100644 --- a/Microsoft.Toolkit.Uwp.UI.Controls.Core/VisualStudioToolsManifest.xml +++ b/Microsoft.Toolkit.Uwp.UI.Controls.Core/VisualStudioToolsManifest.xml @@ -1,6 +1,8 @@ - + @@ -13,6 +15,7 @@ + diff --git a/Microsoft.Toolkit.Uwp.UI.Controls.DataGrid/VisualStudioToolsManifest.xml b/Microsoft.Toolkit.Uwp.UI.Controls.DataGrid/VisualStudioToolsManifest.xml index 761934998a6..0eef95ce961 100644 --- a/Microsoft.Toolkit.Uwp.UI.Controls.DataGrid/VisualStudioToolsManifest.xml +++ b/Microsoft.Toolkit.Uwp.UI.Controls.DataGrid/VisualStudioToolsManifest.xml @@ -1,7 +1,26 @@ - + + + + + + + + + + + + + + + + + + \ No newline at end of file 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..1a5a1e36b89 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,37 @@ private static void TextPropertyChanged(DependencyObject d, DependencyPropertyCh typeof(TokenizingTextBox), new PropertyMetadata(false)); + /// + /// Identifies the property. + /// + public static readonly DependencyProperty MaximumTokensProperty = DependencyProperty.Register( + nameof(MaximumTokens), + typeof(int), + typeof(TokenizingTextBox), + new PropertyMetadata(null, OnMaximumTokensChanged)); + + private static void OnMaximumTokensChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + 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) + { + int tokensToRemove = tokenCount - Math.Max(newMaxTokens, 0); + + // Start at the end, remove any extra tokens. + for (var i = tokenCount; i > tokenCount - tokensToRemove; --i) + { + 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); + } + } + } + } + /// /// Gets or sets the Style for the contained AutoSuggestBox template part. /// @@ -303,5 +334,14 @@ public string SelectedTokenText return PrepareSelectionForClipboard(); } } + + /// + /// Gets or sets the maximum number of token results allowed at a time. + /// + public int MaximumTokens + { + get => (int)GetValue(MaximumTokensProperty); + set => SetValue(MaximumTokensProperty, 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..3ddc778355e 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; @@ -77,6 +78,17 @@ private void ItemsSource_PropertyChanged(DependencyObject sender, DependencyProp if (ItemsSource != null && ItemsSource.GetType() != typeof(InterspersedObservableCollection)) { _innerItemsSource = new InterspersedObservableCollection(ItemsSource); + + if (ReadLocalValue(MaximumTokensProperty) != DependencyProperty.UnsetValue && _innerItemsSource.ItemsSource.Count >= MaximumTokens) + { + // Reduce down to below the max as necessary. + var endCount = MaximumTokens > 0 ? MaximumTokens : 0; + for (var i = _innerItemsSource.ItemsSource.Count - 1; i >= endCount; --i) + { + _innerItemsSource.Remove(_innerItemsSource[i]); + } + } + _currentTextEdit = _lastTextEdit = new PretokenStringContainer(true); _innerItemsSource.Insert(_innerItemsSource.Count, _currentTextEdit); ItemsSource = _innerItemsSource; @@ -278,18 +290,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 +442,12 @@ public async Task ClearAsync() internal async Task AddTokenAsync(object data, bool? atEnd = null) { + if (ReadLocalValue(MaximumTokensProperty) != DependencyProperty.UnsetValue && (MaximumTokens <= 0 || MaximumTokens <= _innerItemsSource.ItemsSource.Count)) + { + // No tokens for you + return; + } + if (data is string str && TokenItemAdding != null) { var tiaea = new TokenItemAddingEventArgs(str); @@ -486,6 +502,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..5f049a80149 --- /dev/null +++ b/Microsoft.Toolkit.Uwp.UI.Controls.Input/TokenizingTextBox/TokenizingTextBoxAutomationPeer.cs @@ -0,0 +1,131 @@ +ο»Ώ// 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.Automation.Provider; +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, IValueProvider + { + /// + /// Initializes a new instance of the class. + /// + /// + /// The that is associated with this . + /// + public TokenizingTextBoxAutomationPeer(TokenizingTextBox owner) + : base(owner) + { + } + + /// 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; + + /// Gets the value of the control. + /// The value of the control. + public string Value => this.OwningTokenizingTextBox.Text; + + private TokenizingTextBox OwningTokenizingTextBox + { + get + { + return Owner as TokenizingTextBox; + } + } + + /// 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; + } + + /// + /// 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 TokenizingTextBox + /// - TokenizingTextBox class name + /// + protected override string GetNameCore() + { + string name = this.OwningTokenizingTextBox.Name; + if (!string.IsNullOrWhiteSpace(name)) + { + return name; + } + + name = AutomationProperties.GetName(this.OwningTokenizingTextBox); + return !string.IsNullOrWhiteSpace(name) ? name : 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, + _ => 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; + } + } +} 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..5a95f5b4b45 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,44 @@ 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; + + if (Content is ITokenStringContainer str && str.IsLast && ttbi?.Owner != null && ttbi.Owner.ReadLocalValue(TokenizingTextBox.MaximumTokensProperty) != DependencyProperty.UnsetValue) + { + 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" /> + + +