From 00df29e0993759b33ee613fa1ab2205bb691e614 Mon Sep 17 00:00:00 2001 From: Vincent Date: Tue, 24 Mar 2020 17:40:29 +0100 Subject: [PATCH 001/595] removed bindable attribute --- Microsoft.Toolkit.Uwp.UI/Converters/DoubleToObjectConverter.cs | 1 - .../Converters/DoubleToVisibilityConverter.cs | 1 - Microsoft.Toolkit.Uwp.UI/Extensions/Markup/NullableBool.cs | 1 - Microsoft.Toolkit.Uwp.UI/Extensions/Markup/OnDevice.cs | 1 - 4 files changed, 4 deletions(-) diff --git a/Microsoft.Toolkit.Uwp.UI/Converters/DoubleToObjectConverter.cs b/Microsoft.Toolkit.Uwp.UI/Converters/DoubleToObjectConverter.cs index cadeeae4043..0c8e82a9029 100644 --- a/Microsoft.Toolkit.Uwp.UI/Converters/DoubleToObjectConverter.cs +++ b/Microsoft.Toolkit.Uwp.UI/Converters/DoubleToObjectConverter.cs @@ -15,7 +15,6 @@ namespace Microsoft.Toolkit.Uwp.UI.Converters /// Otherwise the logic looks for the value to be GreaterThan or LessThan the specified value. /// The ConverterParameter can be used to invert the logic. /// - [Bindable] public class DoubleToObjectConverter : DependencyObject, IValueConverter { /// diff --git a/Microsoft.Toolkit.Uwp.UI/Converters/DoubleToVisibilityConverter.cs b/Microsoft.Toolkit.Uwp.UI/Converters/DoubleToVisibilityConverter.cs index 9a88789b784..faabec3f372 100644 --- a/Microsoft.Toolkit.Uwp.UI/Converters/DoubleToVisibilityConverter.cs +++ b/Microsoft.Toolkit.Uwp.UI/Converters/DoubleToVisibilityConverter.cs @@ -10,7 +10,6 @@ namespace Microsoft.Toolkit.Uwp.UI.Converters /// /// This class converts a double value into a Visibility enumeration. /// - [Bindable] public class DoubleToVisibilityConverter : DoubleToObjectConverter { /// diff --git a/Microsoft.Toolkit.Uwp.UI/Extensions/Markup/NullableBool.cs b/Microsoft.Toolkit.Uwp.UI/Extensions/Markup/NullableBool.cs index cb1831ea4ab..3c30a52d843 100644 --- a/Microsoft.Toolkit.Uwp.UI/Extensions/Markup/NullableBool.cs +++ b/Microsoft.Toolkit.Uwp.UI/Extensions/Markup/NullableBool.cs @@ -16,7 +16,6 @@ namespace Microsoft.Toolkit.Uwp.UI.Extensions /// Custom which can provide nullable bool values. /// See https://wpdev.uservoice.com/forums/110705-universal-windows-platform/suggestions/17767198-nullable-dependency-properties. /// - [Bindable] [MarkupExtensionReturnType(ReturnType = typeof(bool?))] public class NullableBool : MarkupExtension { diff --git a/Microsoft.Toolkit.Uwp.UI/Extensions/Markup/OnDevice.cs b/Microsoft.Toolkit.Uwp.UI/Extensions/Markup/OnDevice.cs index 21fdf480344..3f2e9578816 100644 --- a/Microsoft.Toolkit.Uwp.UI/Extensions/Markup/OnDevice.cs +++ b/Microsoft.Toolkit.Uwp.UI/Extensions/Markup/OnDevice.cs @@ -19,7 +19,6 @@ namespace Microsoft.Toolkit.Uwp.UI.Extensions.Markup /// xmlns:helpers="using:Microsoft.Toolkit.Uwp.UI.Extensions.Markup" /> /// /// - [Bindable] public class OnDevice : MarkupExtension { /// From 834995db9519f39b6b073e76823148fc1855de68 Mon Sep 17 00:00:00 2001 From: Vincent Date: Tue, 24 Mar 2020 17:50:48 +0100 Subject: [PATCH 002/595] removed remaining bindable attributes --- Microsoft.Toolkit.Uwp.UI.Controls/UniformGrid/UniformGrid.cs | 1 - UnitTests/Extensions/Helpers/ObjectWithNullableBoolProperty.cs | 1 - 2 files changed, 2 deletions(-) diff --git a/Microsoft.Toolkit.Uwp.UI.Controls/UniformGrid/UniformGrid.cs b/Microsoft.Toolkit.Uwp.UI.Controls/UniformGrid/UniformGrid.cs index cdc7bc9a4b9..306e3159fe3 100644 --- a/Microsoft.Toolkit.Uwp.UI.Controls/UniformGrid/UniformGrid.cs +++ b/Microsoft.Toolkit.Uwp.UI.Controls/UniformGrid/UniformGrid.cs @@ -17,7 +17,6 @@ namespace Microsoft.Toolkit.Uwp.UI.Controls /// /// The UniformGrid control presents information within a Grid with even spacing. /// - [Bindable] public partial class UniformGrid : Grid { // Guard for 15063 as Grid Spacing only works on 16299+. diff --git a/UnitTests/Extensions/Helpers/ObjectWithNullableBoolProperty.cs b/UnitTests/Extensions/Helpers/ObjectWithNullableBoolProperty.cs index 47911801ea8..30c26cefb37 100644 --- a/UnitTests/Extensions/Helpers/ObjectWithNullableBoolProperty.cs +++ b/UnitTests/Extensions/Helpers/ObjectWithNullableBoolProperty.cs @@ -7,7 +7,6 @@ namespace UnitTests.Extensions.Helpers { - [Bindable] public class ObjectWithNullableBoolProperty : DependencyObject { public bool? NullableBool From 6cd8bcee4448c10c9c65574b4def9ba11e95775f Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Thu, 9 Apr 2020 16:34:50 +0200 Subject: [PATCH 003/595] Added IoC class --- Microsoft.Toolkit/Helpers/Ioc.cs | 164 +++++++++++++++++++++++++++++++ 1 file changed, 164 insertions(+) create mode 100644 Microsoft.Toolkit/Helpers/Ioc.cs diff --git a/Microsoft.Toolkit/Helpers/Ioc.cs b/Microsoft.Toolkit/Helpers/Ioc.cs new file mode 100644 index 00000000000..2023ea7c4c9 --- /dev/null +++ b/Microsoft.Toolkit/Helpers/Ioc.cs @@ -0,0 +1,164 @@ +using System; +using System.Diagnostics.CodeAnalysis; +using System.Diagnostics.Contracts; +using System.Runtime.CompilerServices; + +#nullable enable + +namespace Microsoft.Toolkit +{ + /// + /// An Inversion of Control container that can be used to register and access instances of types providing services. + /// It is focused on performance, and offers the ability to register service providers either through eagerly + /// produced instances, or by using the factory pattern. Accessing services is also thread-safe. + /// In order to use this helper, first define a service and a service provider, like so: + /// + /// public interface ILogger + /// { + /// void Log(string text); + /// } + /// + /// public class ConsoleLogger : ILogger + /// { + /// void Log(string text) => Console.WriteLine(text); + /// } + /// + /// Then, register your services at startup, or at some time before using them: + /// + /// IoC.Register<ILogger, ConsoleLogger>(); + /// + /// Finally, use the type to retrieve service instances to use: + /// + /// IoC.Resolve<ILogger>().Log("Hello world!"); + /// + /// The type will make sure to initialize your service instance if needed, or it will + /// throw an exception in case the requested service has not been registered yet. + /// + public static class Ioc + { + /// + /// Internal container type for services of type . + /// + /// The type of services to store in this type. + [SuppressMessage("StyleCop.CSharp.MaintainabilityRules", "SA1401", Justification = "Public fields for performance reasons")] + private static class Container + where T : class + { + /// + /// An used to synchronize accesses to the and fields. + /// + public static readonly object Lock = new object(); + + /// + /// The optional instance to produce instances of the service . + /// + public static Lazy? Lazy; + + /// + /// The current instance being produced, if available. + /// + public static T? Instance; + } + + /// + /// Registers a singleton instance of service through the type . + /// + /// The type of contract to register. + /// The type of service provider for type to register. + /// This method will create a new instance for future use. + public static void Register() + where TService : class + where TProvider : class, TService, new() + { + Register(new TProvider()); + } + + /// + /// Registers a singleton instance of service through the type . + /// + /// The type of contract to register. + /// The type of service provider for type to register. + /// The instance to register. + public static void Register(TProvider provider) + where TService : class + where TProvider : class, TService + { + Container.Instance = provider; + } + + /// + /// Registers a service through a factory. + /// + /// The type of contract to register. + /// The factory of instances implementing the contract to use. + public static void Register(Func factory) + where TService : class + { + Container.Lazy = new Lazy(factory); + } + + /// + /// Registers a service through a instance. + /// + /// The type of contract to register. + /// The instance used to create instances implementing the contract. + public static void Register(Lazy lazy) + where TService : class + { + Container.Lazy = lazy; + } + + /// + /// Resolves an instance of a registered type implementing the contract. + /// + /// The type of contract to look for. + /// An instance of a registered type implementing . + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static TService Resolve() + where TService : class + { + TService? service = Container.Instance; + + if (service is null) + { + return ResolveOrThrow(); + } + + return service; + } + + /// + /// Tries to resolve a instance with additional checks. + /// This method implements the slow path for , locking + /// to ensure thread-safety and invoking the available factory, if possible. + /// + /// The type of contract to look for. + /// An instance of a registered type implementing . + [MethodImpl(MethodImplOptions.NoInlining)] + private static TService ResolveOrThrow() + where TService : class + { + lock (Container.Lock) + { + TService? service = Container.Instance; + + // Check the instance field again for race conditions + if (!(service is null)) + { + return service; + } + + Lazy? lazy = Container.Lazy; + + // If no factory is available, the service hasn't been registered yet + if (lazy is null) + { + throw new InvalidOperationException($"Service {typeof(TService)} not initialized"); + } + + return Container.Instance = lazy.Value; + } + } + } +} From 8c25f0c22504c9be5bd1e292bee695bfdadb257d Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Thu, 9 Apr 2020 16:54:33 +0200 Subject: [PATCH 004/595] Improved IoC thread-safety --- Microsoft.Toolkit/Helpers/Ioc.cs | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/Microsoft.Toolkit/Helpers/Ioc.cs b/Microsoft.Toolkit/Helpers/Ioc.cs index 2023ea7c4c9..a8a5a996c44 100644 --- a/Microsoft.Toolkit/Helpers/Ioc.cs +++ b/Microsoft.Toolkit/Helpers/Ioc.cs @@ -10,7 +10,7 @@ namespace Microsoft.Toolkit /// /// An Inversion of Control container that can be used to register and access instances of types providing services. /// It is focused on performance, and offers the ability to register service providers either through eagerly - /// produced instances, or by using the factory pattern. Accessing services is also thread-safe. + /// produced instances, or by using the factory pattern. The type is also fully thread-safe. /// In order to use this helper, first define a service and a service provider, like so: /// /// public interface ILogger @@ -83,7 +83,10 @@ public static void Register(TProvider provider) where TService : class where TProvider : class, TService { - Container.Instance = provider; + lock (Container.Lock) + { + Container.Instance = provider; + } } /// @@ -94,7 +97,7 @@ public static void Register(TProvider provider) public static void Register(Func factory) where TService : class { - Container.Lazy = new Lazy(factory); + Register(new Lazy(factory)); } /// @@ -105,7 +108,10 @@ public static void Register(Func factory) public static void Register(Lazy lazy) where TService : class { - Container.Lazy = lazy; + lock (Container.Lock) + { + Container.Lazy = lazy; + } } /// From 2c349636eed25247dff38c6af6fe043af47a9489 Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Thu, 9 Apr 2020 16:56:28 +0200 Subject: [PATCH 005/595] Minor code refactoring --- Microsoft.Toolkit/Helpers/Ioc.cs | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/Microsoft.Toolkit/Helpers/Ioc.cs b/Microsoft.Toolkit/Helpers/Ioc.cs index a8a5a996c44..90b405fcbd2 100644 --- a/Microsoft.Toolkit/Helpers/Ioc.cs +++ b/Microsoft.Toolkit/Helpers/Ioc.cs @@ -70,18 +70,16 @@ public static void Register() where TService : class where TProvider : class, TService, new() { - Register(new TProvider()); + Register(new TProvider()); } /// - /// Registers a singleton instance of service through the type . + /// Registers a singleton instance of service . /// /// The type of contract to register. - /// The type of service provider for type to register. - /// The instance to register. - public static void Register(TProvider provider) + /// The instance to register. + public static void Register(TService provider) where TService : class - where TProvider : class, TService { lock (Container.Lock) { From 1a72762e35734ef48448e8f018506a067c63d7b2 Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Thu, 9 Apr 2020 17:01:03 +0200 Subject: [PATCH 006/595] Added IoC.IsRegistered() API --- Microsoft.Toolkit/Helpers/Ioc.cs | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/Microsoft.Toolkit/Helpers/Ioc.cs b/Microsoft.Toolkit/Helpers/Ioc.cs index 90b405fcbd2..ad65724be52 100644 --- a/Microsoft.Toolkit/Helpers/Ioc.cs +++ b/Microsoft.Toolkit/Helpers/Ioc.cs @@ -60,6 +60,29 @@ private static class Container public static T? Instance; } + /// + /// Checks whether or not a service of type has already been registered. + /// + /// The type of contract to check for registration. + /// if the service has already been registered, otherwise. + [Pure] + public static bool IsRegistered() + where TService : class + { + if (!(Container.Instance is null) || + !(Container.Lazy is null)) + { + return true; + } + + lock (Container.Lock) + { + return + !(Container.Instance is null) || + !(Container.Lazy is null); + } + } + /// /// Registers a singleton instance of service through the type . /// From 52a79f23f849af708eaa55e47c573ef59f66a803 Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Thu, 9 Apr 2020 17:26:48 +0200 Subject: [PATCH 007/595] Minor bug fixes --- Microsoft.Toolkit/Helpers/Ioc.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Microsoft.Toolkit/Helpers/Ioc.cs b/Microsoft.Toolkit/Helpers/Ioc.cs index ad65724be52..78a090b0097 100644 --- a/Microsoft.Toolkit/Helpers/Ioc.cs +++ b/Microsoft.Toolkit/Helpers/Ioc.cs @@ -106,6 +106,7 @@ public static void Register(TService provider) { lock (Container.Lock) { + Container.Lazy = null; Container.Instance = provider; } } @@ -132,6 +133,7 @@ public static void Register(Lazy lazy) lock (Container.Lock) { Container.Lazy = lazy; + Container.Instance = null; } } From 9dcca03451649da831a7840a4e308aa8e5f87058 Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Thu, 9 Apr 2020 17:45:33 +0200 Subject: [PATCH 008/595] Added Ioc.GetAllRegisteredServices() API --- Microsoft.Toolkit/Helpers/Ioc.cs | 40 ++++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/Microsoft.Toolkit/Helpers/Ioc.cs b/Microsoft.Toolkit/Helpers/Ioc.cs index 78a090b0097..b5df91f5059 100644 --- a/Microsoft.Toolkit/Helpers/Ioc.cs +++ b/Microsoft.Toolkit/Helpers/Ioc.cs @@ -1,6 +1,8 @@ using System; +using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.Diagnostics.Contracts; +using System.Linq; using System.Runtime.CompilerServices; #nullable enable @@ -36,6 +38,17 @@ namespace Microsoft.Toolkit /// public static class Ioc { + /// + /// The collection of currently registered service types. + /// + /// + /// This list is not used when retrieving registered instances through + /// the method, so it has no impact on + /// performances there. This is only used to allow users to retrieve all + /// the registered instances at any given time, or to unregister them all. + /// + private static readonly HashSet RegisteredTypes = new HashSet(); + /// /// Internal container type for services of type . /// @@ -104,8 +117,11 @@ public static void Register() public static void Register(TService provider) where TService : class { + lock (RegisteredTypes) lock (Container.Lock) { + RegisteredTypes.Add(typeof(TService)); + Container.Lazy = null; Container.Instance = provider; } @@ -130,13 +146,37 @@ public static void Register(Func factory) public static void Register(Lazy lazy) where TService : class { + lock (RegisteredTypes) lock (Container.Lock) { + RegisteredTypes.Add(typeof(TService)); + Container.Lazy = lazy; Container.Instance = null; } } + /// + /// Gets all the currently registered services. + /// + /// A collection of all the currently registered services. + /// + /// This will also cause the service to resolve instances of + /// registered services that were setup to use lazy initialization. + /// + [Pure] + public static IReadOnlyCollection GetAllRegisteredServices() + { + lock (RegisteredTypes) + { + return ( + from Type serviceType in RegisteredTypes + let resolver = typeof(Ioc).GetMethod(nameof(Resolve)) + let service = resolver.Invoke(null, null) + select service).ToArray(); + } + } + /// /// Resolves an instance of a registered type implementing the contract. /// From bfb8ccb9b082c9154a4646b982a146a615230b4e Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Thu, 9 Apr 2020 17:45:53 +0200 Subject: [PATCH 009/595] Minor code refactoring --- Microsoft.Toolkit/Helpers/Ioc.cs | 48 ++++++++++++++++---------------- 1 file changed, 24 insertions(+), 24 deletions(-) diff --git a/Microsoft.Toolkit/Helpers/Ioc.cs b/Microsoft.Toolkit/Helpers/Ioc.cs index b5df91f5059..c90c89a8c77 100644 --- a/Microsoft.Toolkit/Helpers/Ioc.cs +++ b/Microsoft.Toolkit/Helpers/Ioc.cs @@ -49,30 +49,6 @@ public static class Ioc /// private static readonly HashSet RegisteredTypes = new HashSet(); - /// - /// Internal container type for services of type . - /// - /// The type of services to store in this type. - [SuppressMessage("StyleCop.CSharp.MaintainabilityRules", "SA1401", Justification = "Public fields for performance reasons")] - private static class Container - where T : class - { - /// - /// An used to synchronize accesses to the and fields. - /// - public static readonly object Lock = new object(); - - /// - /// The optional instance to produce instances of the service . - /// - public static Lazy? Lazy; - - /// - /// The current instance being produced, if available. - /// - public static T? Instance; - } - /// /// Checks whether or not a service of type has already been registered. /// @@ -229,5 +205,29 @@ private static TService ResolveOrThrow() return Container.Instance = lazy.Value; } } + + /// + /// Internal container type for services of type . + /// + /// The type of services to store in this type. + [SuppressMessage("StyleCop.CSharp.MaintainabilityRules", "SA1401", Justification = "Public fields for performance reasons")] + private static class Container + where T : class + { + /// + /// An used to synchronize accesses to the and fields. + /// + public static readonly object Lock = new object(); + + /// + /// The optional instance to produce instances of the service . + /// + public static Lazy? Lazy; + + /// + /// The current instance being produced, if available. + /// + public static T? Instance; + } } } From a58ecdde49b3be878b050d3e6a2d8845a6c1d922 Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Thu, 9 Apr 2020 17:47:42 +0200 Subject: [PATCH 010/595] Added Ioc.Unregister() API --- Microsoft.Toolkit/Helpers/Ioc.cs | 37 +++++++++++++++++++++++--------- 1 file changed, 27 insertions(+), 10 deletions(-) diff --git a/Microsoft.Toolkit/Helpers/Ioc.cs b/Microsoft.Toolkit/Helpers/Ioc.cs index c90c89a8c77..36e2ecf3d40 100644 --- a/Microsoft.Toolkit/Helpers/Ioc.cs +++ b/Microsoft.Toolkit/Helpers/Ioc.cs @@ -52,7 +52,7 @@ public static class Ioc /// /// Checks whether or not a service of type has already been registered. /// - /// The type of contract to check for registration. + /// The type of service to check for registration. /// if the service has already been registered, otherwise. [Pure] public static bool IsRegistered() @@ -75,7 +75,7 @@ public static bool IsRegistered() /// /// Registers a singleton instance of service through the type . /// - /// The type of contract to register. + /// The type of service to register. /// The type of service provider for type to register. /// This method will create a new instance for future use. public static void Register() @@ -88,7 +88,7 @@ public static void Register() /// /// Registers a singleton instance of service . /// - /// The type of contract to register. + /// The type of service to register. /// The instance to register. public static void Register(TService provider) where TService : class @@ -106,8 +106,8 @@ public static void Register(TService provider) /// /// Registers a service through a factory. /// - /// The type of contract to register. - /// The factory of instances implementing the contract to use. + /// The type of service to register. + /// The factory of instances implementing the service to use. public static void Register(Func factory) where TService : class { @@ -117,8 +117,8 @@ public static void Register(Func factory) /// /// Registers a service through a instance. /// - /// The type of contract to register. - /// The instance used to create instances implementing the contract. + /// The type of service to register. + /// The instance used to create instances implementing the service. public static void Register(Lazy lazy) where TService : class { @@ -132,6 +132,23 @@ public static void Register(Lazy lazy) } } + /// + /// Unregisters a service of a specified type. + /// + /// The type of service to unregister. + public static void Unregister() + where TService : class + { + lock (RegisteredTypes) + lock (Container.Lock) + { + RegisteredTypes.Remove(typeof(TService)); + + Container.Lazy = null; + Container.Instance = null; + } + } + /// /// Gets all the currently registered services. /// @@ -154,9 +171,9 @@ from Type serviceType in RegisteredTypes } /// - /// Resolves an instance of a registered type implementing the contract. + /// Resolves an instance of a registered type implementing the service. /// - /// The type of contract to look for. + /// The type of service to look for. /// An instance of a registered type implementing . [Pure] [MethodImpl(MethodImplOptions.AggressiveInlining)] @@ -178,7 +195,7 @@ public static TService Resolve() /// This method implements the slow path for , locking /// to ensure thread-safety and invoking the available factory, if possible. /// - /// The type of contract to look for. + /// The type of service to look for. /// An instance of a registered type implementing . [MethodImpl(MethodImplOptions.NoInlining)] private static TService ResolveOrThrow() From 4554d3a0a0d1c0d41d07e47edca70d4f154132e8 Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Thu, 9 Apr 2020 18:00:01 +0200 Subject: [PATCH 011/595] Added Ioc.Reset() API, minor bug fixes --- Microsoft.Toolkit/Helpers/Ioc.cs | 30 +++++++++++++++++++++++++++++- 1 file changed, 29 insertions(+), 1 deletion(-) diff --git a/Microsoft.Toolkit/Helpers/Ioc.cs b/Microsoft.Toolkit/Helpers/Ioc.cs index 36e2ecf3d40..7ecef6a53f7 100644 --- a/Microsoft.Toolkit/Helpers/Ioc.cs +++ b/Microsoft.Toolkit/Helpers/Ioc.cs @@ -3,6 +3,7 @@ using System.Diagnostics.CodeAnalysis; using System.Diagnostics.Contracts; using System.Linq; +using System.Reflection; using System.Runtime.CompilerServices; #nullable enable @@ -149,6 +150,32 @@ public static void Unregister() } } + /// + /// Resets the internal state of the type and unregisters all services. + /// + public static void Reset() + { + lock (RegisteredTypes) + { + foreach (Type serviceType in RegisteredTypes) + { + Type containerType = typeof(Container<>).MakeGenericType(serviceType); + FieldInfo + lockField = containerType.GetField(nameof(Container.Lock)), + lazyField = containerType.GetField(nameof(Container.Lazy)), + instanceField = containerType.GetField(nameof(Container.Instance)); + + lock (lockField.GetValue(null)) + { + lazyField.SetValue(null, null); + instanceField.SetValue(null, null); + } + } + + RegisteredTypes.Clear(); + } + } + /// /// Gets all the currently registered services. /// @@ -165,7 +192,8 @@ public static IReadOnlyCollection GetAllRegisteredServices() return ( from Type serviceType in RegisteredTypes let resolver = typeof(Ioc).GetMethod(nameof(Resolve)) - let service = resolver.Invoke(null, null) + let resolverOfT = resolver.MakeGenericMethod(serviceType) + let service = resolverOfT.Invoke(null, null) select service).ToArray(); } } From 2cb2675ed7f80454c432255c16744203d7eb6501 Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Thu, 9 Apr 2020 18:48:18 +0200 Subject: [PATCH 012/595] Added tests for the Ioc class --- UnitTests/Helpers/Test_Ioc.cs | 67 +++++++++++++++++++++++++++++++++++ UnitTests/UnitTests.csproj | 1 + 2 files changed, 68 insertions(+) create mode 100644 UnitTests/Helpers/Test_Ioc.cs diff --git a/UnitTests/Helpers/Test_Ioc.cs b/UnitTests/Helpers/Test_Ioc.cs new file mode 100644 index 00000000000..8ab87d361b1 --- /dev/null +++ b/UnitTests/Helpers/Test_Ioc.cs @@ -0,0 +1,67 @@ +// 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.Linq; +using Microsoft.Toolkit; +using Microsoft.VisualStudio.TestTools.UnitTesting; + +namespace UnitTests.Helpers +{ + [TestClass] + public class Test_Ioc + { + [TestCategory("Helpers")] + [TestMethod] + public void Test_Ioc_SampleUsage() + { + Assert.IsFalse(Ioc.IsRegistered()); + Assert.AreEqual(Ioc.GetAllRegisteredServices().Count, 0); + + Ioc.Register(); + + Assert.IsTrue(Ioc.IsRegistered()); + + var services = Ioc.GetAllRegisteredServices(); + + Assert.AreEqual(services.Count, 1); + Assert.IsInstanceOfType(services.First(), typeof(INameService)); + Assert.IsInstanceOfType(services.First(), typeof(BobService)); + + Ioc.Unregister(); + + Assert.IsFalse(Ioc.IsRegistered()); + Assert.AreEqual(Ioc.GetAllRegisteredServices().Count, 0); + + Ioc.Register(() => new AliceService()); + + Assert.IsTrue(Ioc.IsRegistered()); + + services = Ioc.GetAllRegisteredServices(); + + Assert.AreEqual(services.Count, 1); + Assert.IsInstanceOfType(services.First(), typeof(INameService)); + Assert.IsInstanceOfType(services.First(), typeof(AliceService)); + + Ioc.Reset(); + + Assert.IsFalse(Ioc.IsRegistered()); + Assert.AreEqual(Ioc.GetAllRegisteredServices().Count, 0); + } + + public interface INameService + { + string GetName(); + } + + public class BobService : INameService + { + public string GetName() => "Bob"; + } + + public class AliceService : INameService + { + public string GetName() => "Alice"; + } + } +} diff --git a/UnitTests/UnitTests.csproj b/UnitTests/UnitTests.csproj index 45aec0e7e7c..96a02c7afcb 100644 --- a/UnitTests/UnitTests.csproj +++ b/UnitTests/UnitTests.csproj @@ -139,6 +139,7 @@ + From f337280a409d77448f45d1640f0b1bd999bbef73 Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Thu, 9 Apr 2020 19:33:44 +0200 Subject: [PATCH 013/595] Added Ioc.GetAllCreatedServices() API --- Microsoft.Toolkit/Helpers/Ioc.cs | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/Microsoft.Toolkit/Helpers/Ioc.cs b/Microsoft.Toolkit/Helpers/Ioc.cs index 7ecef6a53f7..9f40969530e 100644 --- a/Microsoft.Toolkit/Helpers/Ioc.cs +++ b/Microsoft.Toolkit/Helpers/Ioc.cs @@ -185,7 +185,7 @@ public static void Reset() /// registered services that were setup to use lazy initialization. /// [Pure] - public static IReadOnlyCollection GetAllRegisteredServices() + public static IReadOnlyCollection GetAllServices() { lock (RegisteredTypes) { @@ -198,6 +198,25 @@ from Type serviceType in RegisteredTypes } } + /// + /// Gets all the currently registered and instantiated services. + /// + /// A collection of all the currently registered and instantiated services. + [Pure] + public static IReadOnlyCollection GetAllCreatedServices() + { + lock (RegisteredTypes) + { + return ( + from Type serviceType in RegisteredTypes + let containerType = typeof(Container<>).MakeGenericType(serviceType) + let field = containerType.GetField(nameof(Container.Instance)) + let instance = field.GetValue(null) + where !(instance is null) + select instance).ToArray(); + } + } + /// /// Resolves an instance of a registered type implementing the service. /// From 4f65e956e129f1b530b719f0d16fead484c6c240 Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Thu, 9 Apr 2020 20:18:00 +0200 Subject: [PATCH 014/595] Minor code refactoring --- Microsoft.Toolkit/Helpers/Ioc.cs | 33 +++++++++++--------------------- 1 file changed, 11 insertions(+), 22 deletions(-) diff --git a/Microsoft.Toolkit/Helpers/Ioc.cs b/Microsoft.Toolkit/Helpers/Ioc.cs index 9f40969530e..3221f99d230 100644 --- a/Microsoft.Toolkit/Helpers/Ioc.cs +++ b/Microsoft.Toolkit/Helpers/Ioc.cs @@ -60,7 +60,7 @@ public static bool IsRegistered() where TService : class { if (!(Container.Instance is null) || - !(Container.Lazy is null)) + !(Container.Factory is null)) { return true; } @@ -69,7 +69,7 @@ public static bool IsRegistered() { return !(Container.Instance is null) || - !(Container.Lazy is null); + !(Container.Factory is null); } } @@ -99,7 +99,7 @@ public static void Register(TService provider) { RegisteredTypes.Add(typeof(TService)); - Container.Lazy = null; + Container.Factory = null; Container.Instance = provider; } } @@ -111,24 +111,13 @@ public static void Register(TService provider) /// The factory of instances implementing the service to use. public static void Register(Func factory) where TService : class - { - Register(new Lazy(factory)); - } - - /// - /// Registers a service through a instance. - /// - /// The type of service to register. - /// The instance used to create instances implementing the service. - public static void Register(Lazy lazy) - where TService : class { lock (RegisteredTypes) lock (Container.Lock) { RegisteredTypes.Add(typeof(TService)); - Container.Lazy = lazy; + Container.Factory = factory; Container.Instance = null; } } @@ -145,7 +134,7 @@ public static void Unregister() { RegisteredTypes.Remove(typeof(TService)); - Container.Lazy = null; + Container.Factory = null; Container.Instance = null; } } @@ -162,7 +151,7 @@ public static void Reset() Type containerType = typeof(Container<>).MakeGenericType(serviceType); FieldInfo lockField = containerType.GetField(nameof(Container.Lock)), - lazyField = containerType.GetField(nameof(Container.Lazy)), + lazyField = containerType.GetField(nameof(Container.Factory)), instanceField = containerType.GetField(nameof(Container.Instance)); lock (lockField.GetValue(null)) @@ -258,15 +247,15 @@ private static TService ResolveOrThrow() return service; } - Lazy? lazy = Container.Lazy; + Func? factory = Container.Factory; // If no factory is available, the service hasn't been registered yet - if (lazy is null) + if (factory is null) { throw new InvalidOperationException($"Service {typeof(TService)} not initialized"); } - return Container.Instance = lazy.Value; + return Container.Instance = factory(); } } @@ -284,9 +273,9 @@ private static class Container public static readonly object Lock = new object(); /// - /// The optional instance to produce instances of the service . + /// The optional instance to produce instances of the service . /// - public static Lazy? Lazy; + public static Func? Factory; /// /// The current instance being produced, if available. From df973eadeb7fa5774e390bd15c623b968e083253 Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Thu, 9 Apr 2020 20:21:16 +0200 Subject: [PATCH 015/595] Added Ioc.GetInstanceWithoutCaching() } } + /// + /// Creates an instance of a registered type implementing the service. + /// + /// The type of service to look for. + /// A new instance of a registered type implementing . + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static TService GetInstanceWithoutCaching() + where TService : class + { + lock (Container.Lock) + { + Func? factory = Container.Factory; + + if (!(factory is null)) + { + return factory(); + } + + TService? service = Container.Instance; + + if (!(service is null)) + { + return (TService)Activator.CreateInstance(service.GetType()); + } + + throw new InvalidOperationException($"Service {typeof(TService)} not initialized"); + } + } + /// /// Internal container type for services of type . /// From ce4b023cbcb57831c700fcd206d6d61076902ce1 Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Thu, 9 Apr 2020 20:47:41 +0200 Subject: [PATCH 016/595] Minor code refactorings --- Microsoft.Toolkit/Helpers/Ioc.cs | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/Microsoft.Toolkit/Helpers/Ioc.cs b/Microsoft.Toolkit/Helpers/Ioc.cs index 884c92fab3d..49572b0d193 100644 --- a/Microsoft.Toolkit/Helpers/Ioc.cs +++ b/Microsoft.Toolkit/Helpers/Ioc.cs @@ -32,7 +32,7 @@ namespace Microsoft.Toolkit /// /// Finally, use the type to retrieve service instances to use: /// - /// IoC.Resolve<ILogger>().Log("Hello world!"); + /// IoC.GetInstance<ILogger>().Log("Hello world!"); /// /// The type will make sure to initialize your service instance if needed, or it will /// throw an exception in case the requested service has not been registered yet. @@ -44,7 +44,7 @@ public static class Ioc /// /// /// This list is not used when retrieving registered instances through - /// the method, so it has no impact on + /// the method, so it has no impact on /// performances there. This is only used to allow users to retrieve all /// the registered instances at any given time, or to unregister them all. /// @@ -180,7 +180,7 @@ public static IReadOnlyCollection GetAllServices() { return ( from Type serviceType in RegisteredTypes - let resolver = typeof(Ioc).GetMethod(nameof(Resolve)) + let resolver = typeof(Ioc).GetMethod(nameof(GetInstance)) let resolverOfT = resolver.MakeGenericMethod(serviceType) let service = resolverOfT.Invoke(null, null) select service).ToArray(); @@ -213,14 +213,14 @@ from Type serviceType in RegisteredTypes /// An instance of a registered type implementing . [Pure] [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static TService Resolve() + public static TService GetInstance() where TService : class { TService? service = Container.Instance; if (service is null) { - return ResolveOrThrow(); + return GetInstanceOrThrow(); } return service; @@ -228,13 +228,13 @@ public static TService Resolve() /// /// Tries to resolve a instance with additional checks. - /// This method implements the slow path for , locking + /// This method implements the slow path for , locking /// to ensure thread-safety and invoking the available factory, if possible. /// /// The type of service to look for. /// An instance of a registered type implementing . [MethodImpl(MethodImplOptions.NoInlining)] - private static TService ResolveOrThrow() + private static TService GetInstanceOrThrow() where TService : class { lock (Container.Lock) From 155b9decbb793b8f46b6da418a86ff5867892ece Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Thu, 9 Apr 2020 20:55:34 +0200 Subject: [PATCH 017/595] Fixed an XML comment --- Microsoft.Toolkit/Helpers/Ioc.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Microsoft.Toolkit/Helpers/Ioc.cs b/Microsoft.Toolkit/Helpers/Ioc.cs index 49572b0d193..98e5642173d 100644 --- a/Microsoft.Toolkit/Helpers/Ioc.cs +++ b/Microsoft.Toolkit/Helpers/Ioc.cs @@ -298,7 +298,7 @@ private static class Container where T : class { /// - /// An used to synchronize accesses to the and fields. + /// An used to synchronize accesses to the and fields. /// public static readonly object Lock = new object(); From e409d94c7434234b3a1e0df0ef94c4aa19d1507d Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Thu, 9 Apr 2020 22:47:08 +0200 Subject: [PATCH 018/595] Fixed refactoring bugs --- UnitTests/Helpers/Test_Ioc.cs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/UnitTests/Helpers/Test_Ioc.cs b/UnitTests/Helpers/Test_Ioc.cs index 8ab87d361b1..b52709711a8 100644 --- a/UnitTests/Helpers/Test_Ioc.cs +++ b/UnitTests/Helpers/Test_Ioc.cs @@ -16,13 +16,13 @@ public class Test_Ioc public void Test_Ioc_SampleUsage() { Assert.IsFalse(Ioc.IsRegistered()); - Assert.AreEqual(Ioc.GetAllRegisteredServices().Count, 0); + Assert.AreEqual(Ioc.GetAllServices().Count, 0); Ioc.Register(); Assert.IsTrue(Ioc.IsRegistered()); - var services = Ioc.GetAllRegisteredServices(); + var services = Ioc.GetAllServices(); Assert.AreEqual(services.Count, 1); Assert.IsInstanceOfType(services.First(), typeof(INameService)); @@ -31,13 +31,13 @@ public void Test_Ioc_SampleUsage() Ioc.Unregister(); Assert.IsFalse(Ioc.IsRegistered()); - Assert.AreEqual(Ioc.GetAllRegisteredServices().Count, 0); + Assert.AreEqual(Ioc.GetAllServices().Count, 0); Ioc.Register(() => new AliceService()); Assert.IsTrue(Ioc.IsRegistered()); - services = Ioc.GetAllRegisteredServices(); + services = Ioc.GetAllServices(); Assert.AreEqual(services.Count, 1); Assert.IsInstanceOfType(services.First(), typeof(INameService)); @@ -46,7 +46,7 @@ public void Test_Ioc_SampleUsage() Ioc.Reset(); Assert.IsFalse(Ioc.IsRegistered()); - Assert.AreEqual(Ioc.GetAllRegisteredServices().Count, 0); + Assert.AreEqual(Ioc.GetAllServices().Count, 0); } public interface INameService From 46473303bbd1d515cf3385d1674be4f650687a77 Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Fri, 10 Apr 2020 13:02:56 +0200 Subject: [PATCH 019/595] Speed optimizations to Ioc.GetInstanceWithoutCaching() --- Microsoft.Toolkit/Helpers/Ioc.cs | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/Microsoft.Toolkit/Helpers/Ioc.cs b/Microsoft.Toolkit/Helpers/Ioc.cs index 98e5642173d..8b7041054d1 100644 --- a/Microsoft.Toolkit/Helpers/Ioc.cs +++ b/Microsoft.Toolkit/Helpers/Ioc.cs @@ -3,6 +3,7 @@ using System.Diagnostics.CodeAnalysis; using System.Diagnostics.Contracts; using System.Linq; +using System.Linq.Expressions; using System.Reflection; using System.Runtime.CompilerServices; @@ -282,7 +283,14 @@ public static TService GetInstanceWithoutCaching() if (!(service is null)) { - return (TService)Activator.CreateInstance(service.GetType()); + Type serviceType = service.GetType(); + Expression[] expressions = { Expression.New(serviceType) }; + Expression body = Expression.Block(serviceType, expressions); + factory = Expression.Lambda>(body).Compile(); + + Container.Factory = factory; + + return factory(); } throw new InvalidOperationException($"Service {typeof(TService)} not initialized"); From 1c7cfe7ef3c5320151c1321b04985810bfb306a7 Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Fri, 10 Apr 2020 13:26:07 +0200 Subject: [PATCH 020/595] Added Ioc.TryGetInstance API --- Microsoft.Toolkit/Helpers/Ioc.cs | 37 ++++++++++++++++++++++++++------ 1 file changed, 31 insertions(+), 6 deletions(-) diff --git a/Microsoft.Toolkit/Helpers/Ioc.cs b/Microsoft.Toolkit/Helpers/Ioc.cs index 8b7041054d1..e4e2d7d34e8 100644 --- a/Microsoft.Toolkit/Helpers/Ioc.cs +++ b/Microsoft.Toolkit/Helpers/Ioc.cs @@ -60,12 +60,6 @@ public static class Ioc public static bool IsRegistered() where TService : class { - if (!(Container.Instance is null) || - !(Container.Factory is null)) - { - return true; - } - lock (Container.Lock) { return @@ -207,6 +201,37 @@ from Type serviceType in RegisteredTypes } } + /// + /// Tries to resolve an instance of a registered type implementing the service. + /// + /// The type of service to look for. + /// An instance of a registered type implementing , if registered. + [Pure] + public static bool TryGetInstance(out TService? service) + where TService : class + { + lock (Container.Lock) + { + service = Container.Instance; + + if (!(service is null)) + { + return true; + } + + Func? factory = Container.Factory; + + if (factory is null) + { + return false; + } + + service = Container.Instance = factory(); + + return true; + } + } + /// /// Resolves an instance of a registered type implementing the service. /// From 6969545e58f115dc58a87e6bb420e6f056ef3073 Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Fri, 10 Apr 2020 13:27:38 +0200 Subject: [PATCH 021/595] Minor code refactoring --- Microsoft.Toolkit/{Helpers => Mvvm}/Ioc.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename Microsoft.Toolkit/{Helpers => Mvvm}/Ioc.cs (99%) diff --git a/Microsoft.Toolkit/Helpers/Ioc.cs b/Microsoft.Toolkit/Mvvm/Ioc.cs similarity index 99% rename from Microsoft.Toolkit/Helpers/Ioc.cs rename to Microsoft.Toolkit/Mvvm/Ioc.cs index e4e2d7d34e8..8a2cc3d14f9 100644 --- a/Microsoft.Toolkit/Helpers/Ioc.cs +++ b/Microsoft.Toolkit/Mvvm/Ioc.cs @@ -9,7 +9,7 @@ #nullable enable -namespace Microsoft.Toolkit +namespace Microsoft.Toolkit.Mvvm { /// /// An Inversion of Control container that can be used to register and access instances of types providing services. From 5c88279629b94d53d923b4a28be0281748db3e15 Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Fri, 10 Apr 2020 13:34:30 +0200 Subject: [PATCH 022/595] Fixed unit tests --- UnitTests/{Helpers => Mvvm}/Test_Ioc.cs | 6 +++--- UnitTests/UnitTests.csproj | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) rename UnitTests/{Helpers => Mvvm}/Test_Ioc.cs (95%) diff --git a/UnitTests/Helpers/Test_Ioc.cs b/UnitTests/Mvvm/Test_Ioc.cs similarity index 95% rename from UnitTests/Helpers/Test_Ioc.cs rename to UnitTests/Mvvm/Test_Ioc.cs index b52709711a8..45b56d7657f 100644 --- a/UnitTests/Helpers/Test_Ioc.cs +++ b/UnitTests/Mvvm/Test_Ioc.cs @@ -3,15 +3,15 @@ // See the LICENSE file in the project root for more information. using System.Linq; -using Microsoft.Toolkit; +using Microsoft.Toolkit.Mvvm; using Microsoft.VisualStudio.TestTools.UnitTesting; -namespace UnitTests.Helpers +namespace UnitTests.Mvvm { [TestClass] public class Test_Ioc { - [TestCategory("Helpers")] + [TestCategory("Mvvm")] [TestMethod] public void Test_Ioc_SampleUsage() { diff --git a/UnitTests/UnitTests.csproj b/UnitTests/UnitTests.csproj index 96a02c7afcb..094aa2cd257 100644 --- a/UnitTests/UnitTests.csproj +++ b/UnitTests/UnitTests.csproj @@ -139,7 +139,7 @@ - + From 3b13c9ef22401bdf139fad6c57c32cf59eecb329 Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Mon, 13 Apr 2020 18:11:03 +0200 Subject: [PATCH 023/595] Added empty Microsoft.Toolkit.Mvvm package --- .../Microsoft.Toolkit.Mvvm.csproj | 17 ++++++++++ Windows Community Toolkit.sln | 32 +++++++++++++++++++ 2 files changed, 49 insertions(+) create mode 100644 Microsoft.Toolkit.Mvvm/Microsoft.Toolkit.Mvvm.csproj diff --git a/Microsoft.Toolkit.Mvvm/Microsoft.Toolkit.Mvvm.csproj b/Microsoft.Toolkit.Mvvm/Microsoft.Toolkit.Mvvm.csproj new file mode 100644 index 00000000000..44a61dd23d5 --- /dev/null +++ b/Microsoft.Toolkit.Mvvm/Microsoft.Toolkit.Mvvm.csproj @@ -0,0 +1,17 @@ + + + + netstandard2.0 + 8.0 + Windows Community Toolkit Mvvm .NET Standard + + This package includes Mvvm .NET Standard code only helpers such as: + - Ioc: Provides a fast and minimalistic implementation of an IoC container. + + UWP Toolkit Windows Mvvm Ioc extensions helpers + + + Full + + + diff --git a/Windows Community Toolkit.sln b/Windows Community Toolkit.sln index 2a562708db3..72a27b4a0b1 100644 --- a/Windows Community Toolkit.sln +++ b/Windows Community Toolkit.sln @@ -90,6 +90,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GazeInputTest", "GazeInputT EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Toolkit.Uwp.UI.Media", "Microsoft.Toolkit.Uwp.UI.Media\Microsoft.Toolkit.Uwp.UI.Media.csproj", "{75F9EE44-3EFA-47BC-AEDD-351B9834A0AF}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.Toolkit.Mvvm", "Microsoft.Toolkit.Mvvm\Microsoft.Toolkit.Mvvm.csproj", "{D82AE6E1-E612-434E-ACB2-363EE48738D3}" +EndProject Global GlobalSection(SharedMSBuildProjectFiles) = preSolution UnitTests\UnitTests.Notifications.Shared\UnitTests.Notifications.Shared.projitems*{982cc826-aacd-4855-9075-430bb6ce40a9}*SharedItemsImports = 13 @@ -804,6 +806,36 @@ Global {75F9EE44-3EFA-47BC-AEDD-351B9834A0AF}.Release|x64.Build.0 = Release|Any CPU {75F9EE44-3EFA-47BC-AEDD-351B9834A0AF}.Release|x86.ActiveCfg = Release|Any CPU {75F9EE44-3EFA-47BC-AEDD-351B9834A0AF}.Release|x86.Build.0 = Release|Any CPU + {D82AE6E1-E612-434E-ACB2-363EE48738D3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {D82AE6E1-E612-434E-ACB2-363EE48738D3}.Debug|Any CPU.Build.0 = Debug|Any CPU + {D82AE6E1-E612-434E-ACB2-363EE48738D3}.Debug|ARM.ActiveCfg = Debug|Any CPU + {D82AE6E1-E612-434E-ACB2-363EE48738D3}.Debug|ARM.Build.0 = Debug|Any CPU + {D82AE6E1-E612-434E-ACB2-363EE48738D3}.Debug|ARM64.ActiveCfg = Debug|Any CPU + {D82AE6E1-E612-434E-ACB2-363EE48738D3}.Debug|ARM64.Build.0 = Debug|Any CPU + {D82AE6E1-E612-434E-ACB2-363EE48738D3}.Debug|x64.ActiveCfg = Debug|Any CPU + {D82AE6E1-E612-434E-ACB2-363EE48738D3}.Debug|x64.Build.0 = Debug|Any CPU + {D82AE6E1-E612-434E-ACB2-363EE48738D3}.Debug|x86.ActiveCfg = Debug|Any CPU + {D82AE6E1-E612-434E-ACB2-363EE48738D3}.Debug|x86.Build.0 = Debug|Any CPU + {D82AE6E1-E612-434E-ACB2-363EE48738D3}.Native|Any CPU.ActiveCfg = Debug|Any CPU + {D82AE6E1-E612-434E-ACB2-363EE48738D3}.Native|Any CPU.Build.0 = Debug|Any CPU + {D82AE6E1-E612-434E-ACB2-363EE48738D3}.Native|ARM.ActiveCfg = Debug|Any CPU + {D82AE6E1-E612-434E-ACB2-363EE48738D3}.Native|ARM.Build.0 = Debug|Any CPU + {D82AE6E1-E612-434E-ACB2-363EE48738D3}.Native|ARM64.ActiveCfg = Debug|Any CPU + {D82AE6E1-E612-434E-ACB2-363EE48738D3}.Native|ARM64.Build.0 = Debug|Any CPU + {D82AE6E1-E612-434E-ACB2-363EE48738D3}.Native|x64.ActiveCfg = Debug|Any CPU + {D82AE6E1-E612-434E-ACB2-363EE48738D3}.Native|x64.Build.0 = Debug|Any CPU + {D82AE6E1-E612-434E-ACB2-363EE48738D3}.Native|x86.ActiveCfg = Debug|Any CPU + {D82AE6E1-E612-434E-ACB2-363EE48738D3}.Native|x86.Build.0 = Debug|Any CPU + {D82AE6E1-E612-434E-ACB2-363EE48738D3}.Release|Any CPU.ActiveCfg = Release|Any CPU + {D82AE6E1-E612-434E-ACB2-363EE48738D3}.Release|Any CPU.Build.0 = Release|Any CPU + {D82AE6E1-E612-434E-ACB2-363EE48738D3}.Release|ARM.ActiveCfg = Release|Any CPU + {D82AE6E1-E612-434E-ACB2-363EE48738D3}.Release|ARM.Build.0 = Release|Any CPU + {D82AE6E1-E612-434E-ACB2-363EE48738D3}.Release|ARM64.ActiveCfg = Release|Any CPU + {D82AE6E1-E612-434E-ACB2-363EE48738D3}.Release|ARM64.Build.0 = Release|Any CPU + {D82AE6E1-E612-434E-ACB2-363EE48738D3}.Release|x64.ActiveCfg = Release|Any CPU + {D82AE6E1-E612-434E-ACB2-363EE48738D3}.Release|x64.Build.0 = Release|Any CPU + {D82AE6E1-E612-434E-ACB2-363EE48738D3}.Release|x86.ActiveCfg = Release|Any CPU + {D82AE6E1-E612-434E-ACB2-363EE48738D3}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE From ed5b3a11832ee52c7f09f1b164b266e5e1825217 Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Mon, 13 Apr 2020 18:12:17 +0200 Subject: [PATCH 024/595] Moved Ioc class to separate package --- {Microsoft.Toolkit/Mvvm => Microsoft.Toolkit.Mvvm}/Ioc.cs | 0 UnitTests/UnitTests.csproj | 4 ++++ 2 files changed, 4 insertions(+) rename {Microsoft.Toolkit/Mvvm => Microsoft.Toolkit.Mvvm}/Ioc.cs (100%) diff --git a/Microsoft.Toolkit/Mvvm/Ioc.cs b/Microsoft.Toolkit.Mvvm/Ioc.cs similarity index 100% rename from Microsoft.Toolkit/Mvvm/Ioc.cs rename to Microsoft.Toolkit.Mvvm/Ioc.cs diff --git a/UnitTests/UnitTests.csproj b/UnitTests/UnitTests.csproj index 094aa2cd257..51926253c1a 100644 --- a/UnitTests/UnitTests.csproj +++ b/UnitTests/UnitTests.csproj @@ -201,6 +201,10 @@ + + {d82ae6e1-e612-434e-acb2-363ee48738d3} + Microsoft.Toolkit.Mvvm + {42ca4935-54be-42ea-ac19-992378c08de6} Microsoft.Toolkit.Parsers From 47f1373ee4017e334af390ffd3adf603d98fe44a Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Mon, 13 Apr 2020 18:38:24 +0200 Subject: [PATCH 025/595] Added ObservableObject class --- .../Microsoft.Toolkit.Mvvm.csproj | 3 +- Microsoft.Toolkit.Mvvm/ObservableObject.cs | 86 +++++++++++++++++++ 2 files changed, 88 insertions(+), 1 deletion(-) create mode 100644 Microsoft.Toolkit.Mvvm/ObservableObject.cs diff --git a/Microsoft.Toolkit.Mvvm/Microsoft.Toolkit.Mvvm.csproj b/Microsoft.Toolkit.Mvvm/Microsoft.Toolkit.Mvvm.csproj index 44a61dd23d5..784abc8b8b4 100644 --- a/Microsoft.Toolkit.Mvvm/Microsoft.Toolkit.Mvvm.csproj +++ b/Microsoft.Toolkit.Mvvm/Microsoft.Toolkit.Mvvm.csproj @@ -1,8 +1,9 @@ - + netstandard2.0 8.0 + enable Windows Community Toolkit Mvvm .NET Standard This package includes Mvvm .NET Standard code only helpers such as: diff --git a/Microsoft.Toolkit.Mvvm/ObservableObject.cs b/Microsoft.Toolkit.Mvvm/ObservableObject.cs new file mode 100644 index 00000000000..07e9c64f122 --- /dev/null +++ b/Microsoft.Toolkit.Mvvm/ObservableObject.cs @@ -0,0 +1,86 @@ +// 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. + +// Original file header: +// **************************************************************************** +// +// Copyright © GalaSoft Laurent Bugnion 2011-2016 +// +// **************************************************************************** +// Laurent Bugnion +// laurent@galasoft.ch +// 10.4.2011 +// GalaSoft.MvvmLight.Messaging +// http://www.mvvmlight.net +// +// See license.txt in this project or http://www.galasoft.ch/license_MIT.txt +// +// **************************************************************************** + +using System.Collections.Generic; +using System.ComponentModel; +using System.Runtime.CompilerServices; + +namespace Microsoft.Toolkit.Mvvm +{ + /// + /// A base class for objects of which the properties must be observable. + /// + public abstract class ObservableObject : INotifyPropertyChanged, INotifyPropertyChanging + { + /// + public event PropertyChangedEventHandler? PropertyChanged; + + /// + public event PropertyChangingEventHandler? PropertyChanging; + + /// + /// Raises the event if needed. + /// + /// (optional) The name of the property that changed. + protected virtual void RaisePropertyChanged([CallerMemberName] string propertyName = null!) + { + PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); + } + + /// + /// Raises the event if needed. + /// + /// (optional) The name of the property that changed. + protected virtual void RaisePropertyChanging([CallerMemberName] string propertyName = null!) + { + PropertyChanging?.Invoke(this, new PropertyChangingEventArgs(propertyName)); + } + + /// + /// Compares the current and new values for a given property. If the value has changed, + /// raises the event, updates the property with the new + /// value, then raises the event. + /// + /// The type of the property that changed. + /// The field storing the property's value. + /// The property's value after the change occurred. + /// (optional) The name of the property that changed. + /// if the property was changed, otherwise. + /// + /// The and events are not raised + /// if the current and new value for the target property are the same. + /// + protected bool Set(ref T field, T newValue, [CallerMemberName] string propertyName = null!) + { + if (EqualityComparer.Default.Equals(field, newValue)) + { + return false; + } + + this.RaisePropertyChanging(propertyName); + + field = newValue; + + this.RaisePropertyChanged(propertyName); + + return true; + } + } +} \ No newline at end of file From bcd2674e8f99c8b6484aecda598e7fadd634b7c8 Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Mon, 13 Apr 2020 18:40:28 +0200 Subject: [PATCH 026/595] Minor code style tweaks --- Microsoft.Toolkit.Mvvm/Ioc.cs | 6 +++++- Microsoft.Toolkit.Mvvm/ObservableObject.cs | 2 ++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/Microsoft.Toolkit.Mvvm/Ioc.cs b/Microsoft.Toolkit.Mvvm/Ioc.cs index 8a2cc3d14f9..bb5ccce4675 100644 --- a/Microsoft.Toolkit.Mvvm/Ioc.cs +++ b/Microsoft.Toolkit.Mvvm/Ioc.cs @@ -1,4 +1,8 @@ -using System; +// 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.Diagnostics.CodeAnalysis; using System.Diagnostics.Contracts; diff --git a/Microsoft.Toolkit.Mvvm/ObservableObject.cs b/Microsoft.Toolkit.Mvvm/ObservableObject.cs index 07e9c64f122..be6697c40d7 100644 --- a/Microsoft.Toolkit.Mvvm/ObservableObject.cs +++ b/Microsoft.Toolkit.Mvvm/ObservableObject.cs @@ -2,6 +2,8 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. +#pragma warning disable SA1512 + // Original file header: // **************************************************************************** // From 08dfe8ae2e6176b876e8051960c75efe513a16c5 Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Mon, 13 Apr 2020 18:54:26 +0200 Subject: [PATCH 027/595] Minor code refactorings Changed the property raise methods to follow ObservableCollection naming convention (cc. @Aminator) --- Microsoft.Toolkit.Mvvm/ObservableObject.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Microsoft.Toolkit.Mvvm/ObservableObject.cs b/Microsoft.Toolkit.Mvvm/ObservableObject.cs index be6697c40d7..ecf629edb75 100644 --- a/Microsoft.Toolkit.Mvvm/ObservableObject.cs +++ b/Microsoft.Toolkit.Mvvm/ObservableObject.cs @@ -41,7 +41,7 @@ public abstract class ObservableObject : INotifyPropertyChanged, INotifyProperty /// Raises the event if needed. /// /// (optional) The name of the property that changed. - protected virtual void RaisePropertyChanged([CallerMemberName] string propertyName = null!) + protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null!) { PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); } @@ -50,7 +50,7 @@ protected virtual void RaisePropertyChanged([CallerMemberName] string propertyNa /// Raises the event if needed. /// /// (optional) The name of the property that changed. - protected virtual void RaisePropertyChanging([CallerMemberName] string propertyName = null!) + protected virtual void OnPropertyChanging([CallerMemberName] string propertyName = null!) { PropertyChanging?.Invoke(this, new PropertyChangingEventArgs(propertyName)); } @@ -76,11 +76,11 @@ protected bool Set(ref T field, T newValue, [CallerMemberName] string propert return false; } - this.RaisePropertyChanging(propertyName); + this.OnPropertyChanging(propertyName); field = newValue; - this.RaisePropertyChanged(propertyName); + this.OnPropertyChanged(propertyName); return true; } From ad613c3f1b91919e826775452e68803fe507e80f Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Mon, 13 Apr 2020 19:32:04 +0200 Subject: [PATCH 028/595] Added RelayCommand type --- Microsoft.Toolkit.Mvvm/RelayCommand.cs | 95 ++++++++++++++++++++++++++ 1 file changed, 95 insertions(+) create mode 100644 Microsoft.Toolkit.Mvvm/RelayCommand.cs diff --git a/Microsoft.Toolkit.Mvvm/RelayCommand.cs b/Microsoft.Toolkit.Mvvm/RelayCommand.cs new file mode 100644 index 00000000000..578e6638495 --- /dev/null +++ b/Microsoft.Toolkit.Mvvm/RelayCommand.cs @@ -0,0 +1,95 @@ +// 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. + +#pragma warning disable SA1512 + +// +// Copyright © GalaSoft Laurent Bugnion 2009-2016 +// +// **************************************************************************** +// Laurent Bugnion +// laurent@galasoft.ch +// 22.4.2009 +// GalaSoft.MvvmLight +// http://www.mvvmlight.net +// +// See license.txt in this project or http://www.galasoft.ch/license_MIT.txt +// +// **************************************************************************** +// This class was developed by Josh Smith (http://joshsmithonwpf.wordpress.com) and +// slightly modified with his permission. +// **************************************************************************** + +using System; +using System.Runtime.CompilerServices; +using System.Windows.Input; + +namespace Microsoft.Toolkit.Mvvm +{ + /// + /// A command whose sole purpose is to relay its functionality to other + /// objects by invoking delegates. The default return value for the + /// method is . This type does not allow you to accept command parameters + /// in the and callback methods. + /// + public sealed class RelayCommand : ICommand + { + /// + /// The to invoke when is used. + /// + private readonly Action execute; + + /// + /// The optional action to invoke when is used. + /// + private readonly Func? canExecute; + + /// + public event EventHandler? CanExecuteChanged; + + /// + /// Initializes a new instance of the class that can always execute. + /// + /// The execution logic. + public RelayCommand(Action execute) + : this(execute, null) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The execution logic. + /// The execution status logic. + public RelayCommand(Action execute, Func? canExecute) + { + this.execute = execute; + this.canExecute = canExecute; + } + + /// + /// Raises the event. + /// + public void RaiseCanExecuteChanged() + { + CanExecuteChanged?.Invoke(this, EventArgs.Empty); + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool CanExecute(object parameter) + { + return this.canExecute?.Invoke() != false; + } + + /// + public void Execute(object parameter) + { + if (CanExecute(parameter)) + { + this.execute(); + } + } + } +} \ No newline at end of file From f93ae4f60845d2ab0d5eb36401181a5395f0f9cb Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Mon, 13 Apr 2020 19:38:54 +0200 Subject: [PATCH 029/595] Added the RelayCommand type --- Microsoft.Toolkit.Mvvm/RelayCommand{T}.cs | 118 ++++++++++++++++++++++ 1 file changed, 118 insertions(+) create mode 100644 Microsoft.Toolkit.Mvvm/RelayCommand{T}.cs diff --git a/Microsoft.Toolkit.Mvvm/RelayCommand{T}.cs b/Microsoft.Toolkit.Mvvm/RelayCommand{T}.cs new file mode 100644 index 00000000000..56104e01b09 --- /dev/null +++ b/Microsoft.Toolkit.Mvvm/RelayCommand{T}.cs @@ -0,0 +1,118 @@ +// 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. + +#pragma warning disable SA1512 + +// +// Copyright © GalaSoft Laurent Bugnion 2009-2016 +// +// **************************************************************************** +// Laurent Bugnion +// laurent@galasoft.ch +// 22.4.2009 +// GalaSoft.MvvmLight +// http://www.mvvmlight.net +// +// See license.txt in this project or http://www.galasoft.ch/license_MIT.txt +// +// **************************************************************************** +// This class was developed by Josh Smith (http://joshsmithonwpf.wordpress.com) and +// slightly modified with his permission. +// **************************************************************************** + +using System; +using System.Runtime.CompilerServices; +using System.Windows.Input; + +namespace Microsoft.Toolkit.Mvvm +{ + /// + /// A generic command whose sole purpose is to relay its functionality to other + /// objects by invoking delegates. The default return value for the CanExecute + /// method is . This class allows you to accept command parameters + /// in the and callback methods. + /// + /// The type of parameter being passed as input to the callbacks. + public sealed class RelayCommand : ICommand + { + /// + /// The to invoke when is used. + /// + private readonly Action execute; + + /// + /// The optional action to invoke when is used. + /// + private readonly Func? canExecute; + + /// + public event EventHandler? CanExecuteChanged; + + /// + /// Initializes a new instance of the class that can always execute. + /// + /// The execution logic. + public RelayCommand(Action execute) + : this(execute, null) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The execution logic. + /// The execution status logic. + public RelayCommand(Action execute, Func? canExecute) + { + this.execute = execute; + this.canExecute = canExecute; + } + + /// + /// Raises the event. + /// + public void RaiseCanExecuteChanged() + { + CanExecuteChanged?.Invoke(this, EventArgs.Empty); + } + + /// + /// Provides a strongly-typed variant of . + /// + /// The input parameter. + /// Whether or not the current command can be executed. + /// Use this overload to avoid boxing, if is a value type. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool CanExecute(T parameter) + { + return this.canExecute?.Invoke(parameter) != false; + } + + /// + public bool CanExecute(object parameter) + { + return CanExecute((T)parameter); + } + + /// + /// Provides a strongly-typed variant of . + /// + /// The input parameter. + /// Use this overload to avoid boxing, if is a value type. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Execute(T parameter) + { + if (CanExecute(parameter)) + { + this.execute(parameter); + } + } + + /// + public void Execute(object parameter) + { + Execute((T)parameter); + } + } +} \ No newline at end of file From 1e75231a0547f749551f1a51fa89f2b2d265dd73 Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Mon, 13 Apr 2020 19:41:54 +0200 Subject: [PATCH 030/595] Minor code refactoring --- Microsoft.Toolkit.Mvvm/{ => Commands}/RelayCommand.cs | 2 +- Microsoft.Toolkit.Mvvm/{ => Commands}/RelayCommand{T}.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) rename Microsoft.Toolkit.Mvvm/{ => Commands}/RelayCommand.cs (98%) rename Microsoft.Toolkit.Mvvm/{ => Commands}/RelayCommand{T}.cs (99%) diff --git a/Microsoft.Toolkit.Mvvm/RelayCommand.cs b/Microsoft.Toolkit.Mvvm/Commands/RelayCommand.cs similarity index 98% rename from Microsoft.Toolkit.Mvvm/RelayCommand.cs rename to Microsoft.Toolkit.Mvvm/Commands/RelayCommand.cs index 578e6638495..c2fe13c80ca 100644 --- a/Microsoft.Toolkit.Mvvm/RelayCommand.cs +++ b/Microsoft.Toolkit.Mvvm/Commands/RelayCommand.cs @@ -25,7 +25,7 @@ using System.Runtime.CompilerServices; using System.Windows.Input; -namespace Microsoft.Toolkit.Mvvm +namespace Microsoft.Toolkit.Mvvm.Commands { /// /// A command whose sole purpose is to relay its functionality to other diff --git a/Microsoft.Toolkit.Mvvm/RelayCommand{T}.cs b/Microsoft.Toolkit.Mvvm/Commands/RelayCommand{T}.cs similarity index 99% rename from Microsoft.Toolkit.Mvvm/RelayCommand{T}.cs rename to Microsoft.Toolkit.Mvvm/Commands/RelayCommand{T}.cs index 56104e01b09..262f56152ce 100644 --- a/Microsoft.Toolkit.Mvvm/RelayCommand{T}.cs +++ b/Microsoft.Toolkit.Mvvm/Commands/RelayCommand{T}.cs @@ -25,7 +25,7 @@ using System.Runtime.CompilerServices; using System.Windows.Input; -namespace Microsoft.Toolkit.Mvvm +namespace Microsoft.Toolkit.Mvvm.Commands { /// /// A generic command whose sole purpose is to relay its functionality to other From 79f4f4b6e351a2c891602ee6fdef3fc626778b85 Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Tue, 14 Apr 2020 16:46:58 +0200 Subject: [PATCH 031/595] Ported minimal DictionarySlim type from CoreFXLabs --- .../DictionarySlim{TKey,TValue}.cs | 246 ++++++++++++++++++ 1 file changed, 246 insertions(+) create mode 100644 Microsoft.Toolkit.Mvvm/Microsoft.Collections.Extensions/DictionarySlim{TKey,TValue}.cs diff --git a/Microsoft.Toolkit.Mvvm/Microsoft.Collections.Extensions/DictionarySlim{TKey,TValue}.cs b/Microsoft.Toolkit.Mvvm/Microsoft.Collections.Extensions/DictionarySlim{TKey,TValue}.cs new file mode 100644 index 00000000000..c9acfd2a39f --- /dev/null +++ b/Microsoft.Toolkit.Mvvm/Microsoft.Collections.Extensions/DictionarySlim{TKey,TValue}.cs @@ -0,0 +1,246 @@ +// 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; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Runtime.CompilerServices; + +namespace Microsoft.Collections.Extensions +{ + /// + /// A lightweight Dictionary with three principal differences compared to + /// + /// 1) It is possible to do "get or add" in a single lookup using . For + /// values that are value types, this also saves a copy of the value. + /// 2) It assumes it is cheap to equate values. + /// 3) It assumes the keys implement or else Equals() and they are cheap and sufficient. + /// + /// The type of keys in the dictionary. + /// The type of values in the dictionary. + /// + /// 1) This avoids having to do separate lookups ( + /// followed by . + /// There is not currently an API exposed to get a value by ref without adding if the key is not present. + /// 2) This means it can save space by not storing hash codes. + /// 3) This means it can avoid storing a comparer, and avoid the likely virtual call to a comparer. + /// + [DebuggerDisplay("Count = {Count}")] + internal sealed class DictionarySlim + where TKey : struct, IEquatable + { + // See info in CoreFX labs for how this works + private static readonly Entry[] InitialEntries = new Entry[1]; + private int _count; + private int _freeList = -1; + private int[] _buckets; + private Entry[] _entries; + + private struct Entry + { + public TKey Key; + public TValue Value; + public int Next; + } + + /// + /// Initializes a new instance of the class. + /// + public DictionarySlim() + { + _buckets = new int[1]; + _entries = InitialEntries; + } + + /// + /// Gets the count of entries in the dictionary. + /// + public int Count => _count; + + /// + /// Clears the dictionary. Note that this invalidates any active enumerators. + /// + public void Clear() + { + _count = 0; + _freeList = -1; + _buckets = new int[1]; + _entries = InitialEntries; + } + + /// + public bool Remove(TKey key) + { + Entry[] entries = _entries; + int bucketIndex = key.GetHashCode() & (_buckets.Length - 1); + int entryIndex = _buckets[bucketIndex] - 1; + + int lastIndex = -1; + while (entryIndex != -1) + { + Entry candidate = entries[entryIndex]; + if (candidate.Key.Equals(key)) + { + if (lastIndex != -1) + { + entries[lastIndex].Next = candidate.Next; + } + else + { + _buckets[bucketIndex] = candidate.Next + 1; + } + + entries[entryIndex] = default; + + entries[entryIndex].Next = -3 - _freeList; + _freeList = entryIndex; + + _count--; + return true; + } + + lastIndex = entryIndex; + entryIndex = candidate.Next; + } + + return false; + } + + /// + /// Gets the value for the specified key, or, if the key is not present, + /// adds an entry and returns the value by ref. This makes it possible to + /// add or update a value in a single look up operation. + /// + /// Key to look for + /// Reference to the new or existing value + public ref TValue GetOrAddValueRef(TKey key) + { + Entry[] entries = _entries; + int bucketIndex = key.GetHashCode() & (_buckets.Length - 1); + + for (int i = _buckets[bucketIndex] - 1; + (uint)i < (uint)entries.Length; i = entries[i].Next) + { + if (key.Equals(entries[i].Key)) + { + return ref entries[i].Value; + } + } + + return ref AddKey(key, bucketIndex); + } + + [MethodImpl(MethodImplOptions.NoInlining)] + private ref TValue AddKey(TKey key, int bucketIndex) + { + Entry[] entries = _entries; + int entryIndex; + if (_freeList != -1) + { + entryIndex = _freeList; + _freeList = -3 - entries[_freeList].Next; + } + else + { + if (_count == entries.Length || entries.Length == 1) + { + entries = Resize(); + bucketIndex = key.GetHashCode() & (_buckets.Length - 1); + } + + entryIndex = _count; + } + + entries[entryIndex].Key = key; + entries[entryIndex].Next = _buckets[bucketIndex] - 1; + + _buckets[bucketIndex] = entryIndex + 1; + _count++; + + return ref entries[entryIndex].Value; + } + + /// + /// Resizes the current dictionary to reduce the number of collisions + /// + [MethodImpl(MethodImplOptions.NoInlining)] + private Entry[] Resize() + { + int count = _count; + int newSize = _entries.Length * 2; + + if ((uint)newSize > (uint)int.MaxValue) + { + throw new InvalidOperationException("Max capacity exceeded"); + } + + var entries = new Entry[newSize]; + + Array.Copy(_entries, 0, entries, 0, count); + + var newBuckets = new int[entries.Length]; + + while (count-- > 0) + { + int bucketIndex = entries[count].Key.GetHashCode() & (newBuckets.Length - 1); + entries[count].Next = newBuckets[bucketIndex] - 1; + newBuckets[bucketIndex] = count + 1; + } + + _buckets = newBuckets; + _entries = entries; + + return entries; + } + + /// + public Enumerator GetEnumerator() => new Enumerator(this); + + /// + /// Enumerator for . + /// + public ref struct Enumerator + { + private readonly DictionarySlim _dictionary; + private int _index; + private int _count; + private KeyValuePair _current; + + internal Enumerator(DictionarySlim dictionary) + { + _dictionary = dictionary; + _index = 0; + _count = _dictionary._count; + _current = default; + } + + /// + public bool MoveNext() + { + if (_count == 0) + { + _current = default; + return false; + } + + _count--; + + while (_dictionary._entries[_index].Next < -1) + { + _index++; + } + + _current = new KeyValuePair( + _dictionary._entries[_index].Key, + _dictionary._entries[_index++].Value); + return true; + } + + /// + public KeyValuePair Current => _current; + } + } +} \ No newline at end of file From bb7db868e678248a2f49933d4dc80c1bfd0130c1 Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Tue, 14 Apr 2020 16:54:26 +0200 Subject: [PATCH 032/595] Added the IDictionary interface --- .../DictionarySlim{TKey,TValue}.cs | 4 ++-- .../IDictionary{TKey}.cs | 16 ++++++++++++++++ 2 files changed, 18 insertions(+), 2 deletions(-) create mode 100644 Microsoft.Toolkit.Mvvm/Microsoft.Collections.Extensions/IDictionary{TKey}.cs diff --git a/Microsoft.Toolkit.Mvvm/Microsoft.Collections.Extensions/DictionarySlim{TKey,TValue}.cs b/Microsoft.Toolkit.Mvvm/Microsoft.Collections.Extensions/DictionarySlim{TKey,TValue}.cs index c9acfd2a39f..2c1862ac2e6 100644 --- a/Microsoft.Toolkit.Mvvm/Microsoft.Collections.Extensions/DictionarySlim{TKey,TValue}.cs +++ b/Microsoft.Toolkit.Mvvm/Microsoft.Collections.Extensions/DictionarySlim{TKey,TValue}.cs @@ -29,7 +29,7 @@ namespace Microsoft.Collections.Extensions /// 3) This means it can avoid storing a comparer, and avoid the likely virtual call to a comparer. /// [DebuggerDisplay("Count = {Count}")] - internal sealed class DictionarySlim + internal sealed class DictionarySlim : IDictionary where TKey : struct, IEquatable { // See info in CoreFX labs for how this works @@ -71,7 +71,7 @@ public void Clear() _entries = InitialEntries; } - /// + /// public bool Remove(TKey key) { Entry[] entries = _entries; diff --git a/Microsoft.Toolkit.Mvvm/Microsoft.Collections.Extensions/IDictionary{TKey}.cs b/Microsoft.Toolkit.Mvvm/Microsoft.Collections.Extensions/IDictionary{TKey}.cs new file mode 100644 index 00000000000..e4976feb47f --- /dev/null +++ b/Microsoft.Toolkit.Mvvm/Microsoft.Collections.Extensions/IDictionary{TKey}.cs @@ -0,0 +1,16 @@ +using System; +using System.Collections.Generic; + +namespace Microsoft.Collections.Extensions +{ + /// + /// An interface providing value-invariant access to a instance. + /// + /// The type of keys in the dictionary. + internal interface IDictionary + where TKey : struct, IEquatable + { + /// + bool Remove(TKey key); + } +} From 8963f3a4a47bc2fb523a2e8d44e45cfbc355a820 Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Tue, 14 Apr 2020 18:10:37 +0200 Subject: [PATCH 033/595] Initial draft of the Messenger class --- Microsoft.Toolkit.Mvvm/Messenger.cs | 332 ++++++++++++++++++ .../Microsoft.Toolkit.Mvvm.csproj | 4 + 2 files changed, 336 insertions(+) create mode 100644 Microsoft.Toolkit.Mvvm/Messenger.cs diff --git a/Microsoft.Toolkit.Mvvm/Messenger.cs b/Microsoft.Toolkit.Mvvm/Messenger.cs new file mode 100644 index 00000000000..70bf07a7def --- /dev/null +++ b/Microsoft.Toolkit.Mvvm/Messenger.cs @@ -0,0 +1,332 @@ +using System; +using System.Buffers; +using System.Collections.Generic; +using System.Runtime.CompilerServices; +using Microsoft.Collections.Extensions; + +namespace Microsoft.Toolkit.Mvvm +{ + /// + /// A type implementing a messaging system between objects. + /// + public static class Messenger + { + /// + /// The collection of currently registered recipients, with a link to their linked message receivers. + /// + /// + /// This collection is used to allow reflection-free access to all the existing + /// registered recipients from , so that all the + /// existing handlers can be removed without having to dynamically create the + /// generic types for the containers of the various dictionaries mapping the handlers. + /// + private static readonly DictionarySlim>> RecipientsMap + = new DictionarySlim>>(); + + /// + /// Registers a recipient for a given type of message. + /// + /// The type of message to receive. + /// The recipient that will receive the messages. + /// The to invoke when a message is received. + public static void Register(object recipient, Action action) + { + Register(recipient, default(Unit), action); + } + + /// + /// Registers a recipient for a given type of message. + /// + /// The type of message to receive. + /// The type of token to use to pick the messages to receive. + /// The recipient that will receive the messages. + /// A token used to determine the receiving channel to use. + /// The to invoke when a message is received. + public static void Register(object recipient, TToken token, Action action) + { + lock (RecipientsMap) + { + // Get the registration list for this recipient + var values = Container.Values; + var key = new Recipient(recipient); + ref var list = ref values.GetOrAddValueRef(key); + + if (list is null) + { + list = new List>(); + } + + // Add the new registration entry + list.Add(new Entry(action, token)); + + // Make sure this registration map is tracked for the current recipient + ref var set = ref RecipientsMap.GetOrAddValueRef(key); + + if (set is null) + { + set = new HashSet>(); + } + + set.Add(values); + } + } + + /// + /// Unregisters a recipient from messages of a given type. + /// + /// The type of message to stop receiving. + /// The recipient to unregister. + public static void Unregister(object recipient) + { + Unregister(recipient, default); + } + + /// + /// Unregisters a recipient from messages of a given type. + /// + /// The type of message to stop receiving. + /// The type of token to identify what channel to unregister from. + /// The recipient to unregister. + /// The token to use to identify which handlers to unregister. + public static void Unregister(object recipient, TToken token) + { + lock (RecipientsMap) + { + // Get the registration list (same as above) + var values = Container.Values; + var key = new Recipient(recipient); + ref var list = ref values.GetOrAddValueRef(key); + + if (list is null) + { + return; + } + + // Remove all entries with a matching token + list.RemoveAll(entry => EqualityComparer.Default.Equals(entry.Token, token)); + + /* If the list is empty, it means that the current recipient has no remaining + * registered handlers for the current combination, regardless, + * of the specific token value (ie. the channel used to receive messages of that type). + * We can remove the list entirely from this map, and remove the link to the map itself + * to the static mapping between existing registered recipients. */ + if (list.Count == 0) + { + values.Remove(key); + + ref var set = ref RecipientsMap.GetOrAddValueRef(key); + + set.Remove(values); + + if (set.Count == 0) + { + RecipientsMap.Remove(key); + } + } + } + } + + /// + /// Unregisters a recipient from all registered messages. + /// + /// The recipient to unregister. + public static void Unregister(object recipient) + { + lock (RecipientsMap) + { + // If the recipient has no registered messages at all, ignore + var key = new Recipient(recipient); + ref var set = ref RecipientsMap.GetOrAddValueRef(key); + + if (set is null || set.Count == 0) + { + return; + } + + // Removes all the lists of registered handlers for the recipient + foreach (var map in set) + { + map.Remove(key); + } + + // Remove the associated set in the static map + RecipientsMap.Remove(key); + } + } + + /// + /// Sends a message of the specified type to all registered recipients. + /// + /// The type of message to send. + /// The message to send. + public static void Send(TMessage message) + { + Send(message, default(Unit)); + } + + /// + /// Sends a message of the specified type to all registered recipients. + /// + /// The type of message to send. + /// The type of token to identify what channel to use to send the message. + /// The message to send. + /// The token indicating what channel to use. + public static void Send(TMessage message, TToken token) + { + Entry[] entries; + int length = 0; + + lock (RecipientsMap) + { + /* We need to make a local copy of the currently registered handlers, + * since users might try to unregister (or register) new handlers from + * inside one of the currently existing handlers. We can use memory pooling + * to reuse arrays, to minimize the average memory usage. In practice, + * we usually just need to pay the small overhead of copying the items. */ + var values = Container.Values; + + // Count the total number of recipients + foreach (var pair in values) + { + length += pair.Value.Count; + } + + // Rent the array to copy handlers to + entries = ArrayPool>.Shared.Rent(length); + + /* Copy the handlers to the local collection. + * Both types being enumerate expose a struct enumerator, + * so we're not actually allocating the enumerator here. */ + int i = 0; + foreach (var pair in values) + { + foreach (var entry in pair.Value) + { + entries[i++] = entry; + } + } + } + + try + { + // Invoke all the necessary handlers on the local copy of entries + foreach (var entry in entries.AsSpan(0, length)) + { + if (EqualityComparer.Default.Equals(token, entry.Token)) + { + entry.Action(message); + } + } + } + finally + { + ArrayPool>.Shared.Return(entries, true); + } + } + + /// + /// A container class for registered recipients. + /// + /// The type of message to receive. + /// The type of token to use to pick the messages to receive. + private static class Container + { + /// + /// The mapping of currently registered recipients for the combination of types + /// and , along with + /// their registered actions and tokens. Each recipient has an associated + /// since it could have a number of handlers registered in parallel, each using a different + /// registration channel specified by the token in use. Using a + /// for the mapping allows for quick access to all the registered entries for each recipient. + /// + public static readonly DictionarySlim>> Values + = new DictionarySlim>>(); + } + + /// + /// A simple type representing a recipient. + /// + /// + /// This type is used to enable fast indexing in each mapping dictionary, + /// since it acts as an external override for the and + /// methods for arbitrary objects, removing both + /// the virtual call and preventing instances overriding those methods in this context. + /// Using this type guarantees that all the equality operations are always only done + /// based on reference equality for each registered recipient, regardless of its type. + /// + private readonly struct Recipient : IEquatable + { + /// + /// The registered recipient. + /// + private readonly object target; + + /// + /// Initializes a new instance of the struct. + /// + /// The target recipient instance. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Recipient(object target) + { + this.target = target; + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool Equals(Recipient other) + { + return ReferenceEquals(this.target, other.target); + } + + /// + public override bool Equals(object? obj) + { + return obj is Recipient other && Equals(other); + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public override int GetHashCode() + { + return RuntimeHelpers.GetHashCode(this.target); + } + } + + /// + /// An empty type representing a generic token with no specific value. + /// + private struct Unit + { + } + + /// + /// A type representing a pair of message and token used for registration. + /// + /// The type of message to receive. + /// The type of token to use to pick the messages to receive. + private readonly struct Entry + { + /// + /// The to invoke when a message is receive. + /// + public readonly Action Action; + + /// + /// The token used for the message registration. + /// + public readonly TToken Token; + + /// + /// Initializes a new instance of the struct. + /// + /// The to invoke when a message is receive. + /// The token used for the message registration. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Entry(Action action, TToken token) + { + this.Action = action; + this.Token = token; + } + } + } +} diff --git a/Microsoft.Toolkit.Mvvm/Microsoft.Toolkit.Mvvm.csproj b/Microsoft.Toolkit.Mvvm/Microsoft.Toolkit.Mvvm.csproj index 784abc8b8b4..ee24e73b38b 100644 --- a/Microsoft.Toolkit.Mvvm/Microsoft.Toolkit.Mvvm.csproj +++ b/Microsoft.Toolkit.Mvvm/Microsoft.Toolkit.Mvvm.csproj @@ -15,4 +15,8 @@ Full + + + + From 2e95d488d44894df11dcc25296089add2883fcab Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Tue, 14 Apr 2020 18:13:04 +0200 Subject: [PATCH 034/595] Added missing file headers --- Microsoft.Toolkit.Mvvm/Messenger.cs | 6 +++++- .../Microsoft.Collections.Extensions/IDictionary{TKey}.cs | 6 +++++- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/Microsoft.Toolkit.Mvvm/Messenger.cs b/Microsoft.Toolkit.Mvvm/Messenger.cs index 70bf07a7def..8ea9ecc202d 100644 --- a/Microsoft.Toolkit.Mvvm/Messenger.cs +++ b/Microsoft.Toolkit.Mvvm/Messenger.cs @@ -1,4 +1,8 @@ -using System; +// 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.Buffers; using System.Collections.Generic; using System.Runtime.CompilerServices; diff --git a/Microsoft.Toolkit.Mvvm/Microsoft.Collections.Extensions/IDictionary{TKey}.cs b/Microsoft.Toolkit.Mvvm/Microsoft.Collections.Extensions/IDictionary{TKey}.cs index e4976feb47f..9ab5e595ff7 100644 --- a/Microsoft.Toolkit.Mvvm/Microsoft.Collections.Extensions/IDictionary{TKey}.cs +++ b/Microsoft.Toolkit.Mvvm/Microsoft.Collections.Extensions/IDictionary{TKey}.cs @@ -1,4 +1,8 @@ -using System; +// 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; namespace Microsoft.Collections.Extensions From 805d9402d7b2f88b0ac949dfbf93deff438cd132 Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Tue, 14 Apr 2020 20:08:52 +0200 Subject: [PATCH 035/595] Code tweaks and performance improvements --- Microsoft.Toolkit.Mvvm/Messenger.cs | 137 ++++++++++-------- .../DictionarySlim{TKey,TValue}.cs | 2 +- .../IDictionary{TKey}.cs | 2 +- 3 files changed, 81 insertions(+), 60 deletions(-) diff --git a/Microsoft.Toolkit.Mvvm/Messenger.cs b/Microsoft.Toolkit.Mvvm/Messenger.cs index 8ea9ecc202d..da74a8c66d6 100644 --- a/Microsoft.Toolkit.Mvvm/Messenger.cs +++ b/Microsoft.Toolkit.Mvvm/Messenger.cs @@ -33,6 +33,7 @@ private static readonly DictionarySlim /// The type of message to receive. /// The recipient that will receive the messages. /// The to invoke when a message is received. + /// Thrown when trying to register the same message twice. public static void Register(object recipient, Action action) { Register(recipient, default(Unit), action); @@ -46,25 +47,34 @@ public static void Register(object recipient, Action action) /// The recipient that will receive the messages. /// A token used to determine the receiving channel to use. /// The to invoke when a message is received. + /// Thrown when trying to register the same message twice. public static void Register(object recipient, TToken token, Action action) + where TToken : notnull, IEquatable { lock (RecipientsMap) { // Get the registration list for this recipient var values = Container.Values; var key = new Recipient(recipient); - ref var list = ref values.GetOrAddValueRef(key); + ref DictionarySlim> map = ref values.GetOrAddValueRef(key); - if (list is null) + if (map is null) { - list = new List>(); + map = new DictionarySlim>(); } // Add the new registration entry - list.Add(new Entry(action, token)); + ref Action handler = ref map.GetOrAddValueRef(token); + + if (!(handler is null)) + { + ThrowInvalidOperationExceptionForDuplicateRegistration(); + } + + handler = action; // Make sure this registration map is tracked for the current recipient - ref var set = ref RecipientsMap.GetOrAddValueRef(key); + ref HashSet> set = ref RecipientsMap.GetOrAddValueRef(key); if (set is null) { @@ -80,6 +90,7 @@ public static void Register(object recipient, TToken token, Ac /// /// The type of message to stop receiving. /// The recipient to unregister. + /// If the recipient has no registered handler, this method does nothing. public static void Unregister(object recipient) { Unregister(recipient, default); @@ -92,33 +103,35 @@ public static void Unregister(object recipient) /// The type of token to identify what channel to unregister from. /// The recipient to unregister. /// The token to use to identify which handlers to unregister. + /// If the recipient has no registered handler, this method does nothing. public static void Unregister(object recipient, TToken token) + where TToken : notnull, IEquatable { lock (RecipientsMap) { // Get the registration list (same as above) var values = Container.Values; var key = new Recipient(recipient); - ref var list = ref values.GetOrAddValueRef(key); + ref DictionarySlim> map = ref values.GetOrAddValueRef(key); - if (list is null) + if (map is null) { return; } - // Remove all entries with a matching token - list.RemoveAll(entry => EqualityComparer.Default.Equals(entry.Token, token)); + // Remove the target handler + map.Remove(token); - /* If the list is empty, it means that the current recipient has no remaining + /* If the map is empty, it means that the current recipient has no remaining * registered handlers for the current combination, regardless, * of the specific token value (ie. the channel used to receive messages of that type). - * We can remove the list entirely from this map, and remove the link to the map itself + * We can remove the map entirely from this container, and remove the link to the map itself * to the static mapping between existing registered recipients. */ - if (list.Count == 0) + if (map.Count == 0) { values.Remove(key); - ref var set = ref RecipientsMap.GetOrAddValueRef(key); + ref HashSet> set = ref RecipientsMap.GetOrAddValueRef(key); set.Remove(values); @@ -140,15 +153,15 @@ public static void Unregister(object recipient) { // If the recipient has no registered messages at all, ignore var key = new Recipient(recipient); - ref var set = ref RecipientsMap.GetOrAddValueRef(key); + ref HashSet> set = ref RecipientsMap.GetOrAddValueRef(key); - if (set is null || set.Count == 0) + if (set is null) { return; } // Removes all the lists of registered handlers for the recipient - foreach (var map in set) + foreach (IDictionary map in set) { map.Remove(key); } @@ -176,9 +189,10 @@ public static void Send(TMessage message) /// The message to send. /// The token indicating what channel to use. public static void Send(TMessage message, TToken token) + where TToken : notnull, IEquatable { - Entry[] entries; - int length = 0; + Action[] entries; + int i = 0; lock (RecipientsMap) { @@ -189,24 +203,32 @@ public static void Send(TMessage message, TToken token) * we usually just need to pay the small overhead of copying the items. */ var values = Container.Values; - // Count the total number of recipients + // Count the total number of recipients (including with different tokens) foreach (var pair in values) { - length += pair.Value.Count; + i += pair.Value.Count; } // Rent the array to copy handlers to - entries = ArrayPool>.Shared.Rent(length); + entries = ArrayPool>.Shared.Rent(i); /* Copy the handlers to the local collection. * Both types being enumerate expose a struct enumerator, - * so we're not actually allocating the enumerator here. */ - int i = 0; + * so we're not actually allocating the enumerator here. + * The array is oversized at this point, since it also includes + * handlers for different tokens. We can reuse the same variable + * to count the number of matching handlers to invoke later on. + * This will be the array slice with valid actions in the rented buffer. */ + i = 0; foreach (var pair in values) { foreach (var entry in pair.Value) { - entries[i++] = entry; + // Only select the ones with a matching token + if (EqualityComparer.Default.Equals(entry.Key, token)) + { + entries[i++] = entry.Value; + } } } } @@ -214,17 +236,17 @@ public static void Send(TMessage message, TToken token) try { // Invoke all the necessary handlers on the local copy of entries - foreach (var entry in entries.AsSpan(0, length)) + foreach (var entry in entries.AsSpan(0, i)) { - if (EqualityComparer.Default.Equals(token, entry.Token)) - { - entry.Action(message); - } + entry(message); } } finally { - ArrayPool>.Shared.Return(entries, true); + // Remove references to avoid leaks coming from the shader memory pool + entries.AsSpan(0, i).Clear(); + + ArrayPool>.Shared.Return(entries, true); } } @@ -234,6 +256,7 @@ public static void Send(TMessage message, TToken token) /// The type of message to receive. /// The type of token to use to pick the messages to receive. private static class Container + where TToken : notnull, IEquatable { /// /// The mapping of currently registered recipients for the combination of types @@ -243,8 +266,8 @@ private static class Container /// registration channel specified by the token in use. Using a /// for the mapping allows for quick access to all the registered entries for each recipient. /// - public static readonly DictionarySlim>> Values - = new DictionarySlim>>(); + public static readonly DictionarySlim>> Values + = new DictionarySlim>>(); } /// @@ -299,38 +322,36 @@ public override int GetHashCode() /// /// An empty type representing a generic token with no specific value. /// - private struct Unit - { - } - - /// - /// A type representing a pair of message and token used for registration. - /// - /// The type of message to receive. - /// The type of token to use to pick the messages to receive. - private readonly struct Entry + private struct Unit : IEquatable { - /// - /// The to invoke when a message is receive. - /// - public readonly Action Action; + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool Equals(Unit other) + { + return true; + } - /// - /// The token used for the message registration. - /// - public readonly TToken Token; + /// + public override bool Equals(object? obj) + { + return obj is Unit; + } - /// - /// Initializes a new instance of the struct. - /// - /// The to invoke when a message is receive. - /// The token used for the message registration. + /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public Entry(Action action, TToken token) + public override int GetHashCode() { - this.Action = action; - this.Token = token; + return 0; } } + + /// + /// Throws an when trying to add a duplicate handler. + /// + [MethodImpl(MethodImplOptions.NoInlining)] + private static void ThrowInvalidOperationExceptionForDuplicateRegistration() + { + throw new InvalidOperationException("The target recipient has already subscribed to the target message"); + } } } diff --git a/Microsoft.Toolkit.Mvvm/Microsoft.Collections.Extensions/DictionarySlim{TKey,TValue}.cs b/Microsoft.Toolkit.Mvvm/Microsoft.Collections.Extensions/DictionarySlim{TKey,TValue}.cs index 2c1862ac2e6..2e11250adc6 100644 --- a/Microsoft.Toolkit.Mvvm/Microsoft.Collections.Extensions/DictionarySlim{TKey,TValue}.cs +++ b/Microsoft.Toolkit.Mvvm/Microsoft.Collections.Extensions/DictionarySlim{TKey,TValue}.cs @@ -30,7 +30,7 @@ namespace Microsoft.Collections.Extensions /// [DebuggerDisplay("Count = {Count}")] internal sealed class DictionarySlim : IDictionary - where TKey : struct, IEquatable + where TKey : notnull, IEquatable { // See info in CoreFX labs for how this works private static readonly Entry[] InitialEntries = new Entry[1]; diff --git a/Microsoft.Toolkit.Mvvm/Microsoft.Collections.Extensions/IDictionary{TKey}.cs b/Microsoft.Toolkit.Mvvm/Microsoft.Collections.Extensions/IDictionary{TKey}.cs index 9ab5e595ff7..95e4c62a749 100644 --- a/Microsoft.Toolkit.Mvvm/Microsoft.Collections.Extensions/IDictionary{TKey}.cs +++ b/Microsoft.Toolkit.Mvvm/Microsoft.Collections.Extensions/IDictionary{TKey}.cs @@ -12,7 +12,7 @@ namespace Microsoft.Collections.Extensions /// /// The type of keys in the dictionary. internal interface IDictionary - where TKey : struct, IEquatable + where TKey : notnull, IEquatable { /// bool Remove(TKey key); From 5eacb127100f970c37403617fd4d0e333f237900 Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Tue, 14 Apr 2020 20:09:26 +0200 Subject: [PATCH 036/595] Minor code style tweaks --- Microsoft.Toolkit.Mvvm/Messenger.cs | 56 ++++++++++++++--------------- 1 file changed, 28 insertions(+), 28 deletions(-) diff --git a/Microsoft.Toolkit.Mvvm/Messenger.cs b/Microsoft.Toolkit.Mvvm/Messenger.cs index da74a8c66d6..20ad8c1a685 100644 --- a/Microsoft.Toolkit.Mvvm/Messenger.cs +++ b/Microsoft.Toolkit.Mvvm/Messenger.cs @@ -85,6 +85,34 @@ public static void Register(object recipient, TToken token, Ac } } + /// + /// Unregisters a recipient from all registered messages. + /// + /// The recipient to unregister. + public static void Unregister(object recipient) + { + lock (RecipientsMap) + { + // If the recipient has no registered messages at all, ignore + var key = new Recipient(recipient); + ref HashSet> set = ref RecipientsMap.GetOrAddValueRef(key); + + if (set is null) + { + return; + } + + // Removes all the lists of registered handlers for the recipient + foreach (IDictionary map in set) + { + map.Remove(key); + } + + // Remove the associated set in the static map + RecipientsMap.Remove(key); + } + } + /// /// Unregisters a recipient from messages of a given type. /// @@ -143,34 +171,6 @@ public static void Unregister(object recipient, TToken token) } } - /// - /// Unregisters a recipient from all registered messages. - /// - /// The recipient to unregister. - public static void Unregister(object recipient) - { - lock (RecipientsMap) - { - // If the recipient has no registered messages at all, ignore - var key = new Recipient(recipient); - ref HashSet> set = ref RecipientsMap.GetOrAddValueRef(key); - - if (set is null) - { - return; - } - - // Removes all the lists of registered handlers for the recipient - foreach (IDictionary map in set) - { - map.Remove(key); - } - - // Remove the associated set in the static map - RecipientsMap.Remove(key); - } - } - /// /// Sends a message of the specified type to all registered recipients. /// From ae6ff2400160bb3ddf2c5f426af322ce7d046cf4 Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Tue, 14 Apr 2020 20:13:45 +0200 Subject: [PATCH 037/595] Added DictionarySlim.ContainsKey method --- .../DictionarySlim{TKey,TValue}.cs | 30 ++++++++++++------- 1 file changed, 19 insertions(+), 11 deletions(-) diff --git a/Microsoft.Toolkit.Mvvm/Microsoft.Collections.Extensions/DictionarySlim{TKey,TValue}.cs b/Microsoft.Toolkit.Mvvm/Microsoft.Collections.Extensions/DictionarySlim{TKey,TValue}.cs index 2e11250adc6..917e96843db 100644 --- a/Microsoft.Toolkit.Mvvm/Microsoft.Collections.Extensions/DictionarySlim{TKey,TValue}.cs +++ b/Microsoft.Toolkit.Mvvm/Microsoft.Collections.Extensions/DictionarySlim{TKey,TValue}.cs @@ -60,15 +60,21 @@ public DictionarySlim() /// public int Count => _count; - /// - /// Clears the dictionary. Note that this invalidates any active enumerators. - /// - public void Clear() + /// + public bool ContainsKey(TKey key) { - _count = 0; - _freeList = -1; - _buckets = new int[1]; - _entries = InitialEntries; + Entry[] entries = _entries; + + for (int i = _buckets[key.GetHashCode() & (_buckets.Length - 1)] - 1; + (uint)i < (uint)entries.Length; i = entries[i].Next) + { + if (key.Equals(entries[i].Key)) + { + return true; + } + } + + return false; } /// @@ -77,8 +83,8 @@ public bool Remove(TKey key) Entry[] entries = _entries; int bucketIndex = key.GetHashCode() & (_buckets.Length - 1); int entryIndex = _buckets[bucketIndex] - 1; - int lastIndex = -1; + while (entryIndex != -1) { Entry candidate = entries[entryIndex]; @@ -122,7 +128,8 @@ public ref TValue GetOrAddValueRef(TKey key) int bucketIndex = key.GetHashCode() & (_buckets.Length - 1); for (int i = _buckets[bucketIndex] - 1; - (uint)i < (uint)entries.Length; i = entries[i].Next) + (uint)i < (uint)entries.Length; + i = entries[i].Next) { if (key.Equals(entries[i].Key)) { @@ -138,6 +145,7 @@ private ref TValue AddKey(TKey key, int bucketIndex) { Entry[] entries = _entries; int entryIndex; + if (_freeList != -1) { entryIndex = _freeList; @@ -172,7 +180,7 @@ private Entry[] Resize() int count = _count; int newSize = _entries.Length * 2; - if ((uint)newSize > (uint)int.MaxValue) + if ((uint)newSize > int.MaxValue) { throw new InvalidOperationException("Max capacity exceeded"); } From 0f7f2ac716e02b8534b8cd5fb18adcfda369aa36 Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Tue, 14 Apr 2020 20:15:56 +0200 Subject: [PATCH 038/595] Added Messenger.IsRegistered methods --- Microsoft.Toolkit.Mvvm/Messenger.cs | 34 +++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/Microsoft.Toolkit.Mvvm/Messenger.cs b/Microsoft.Toolkit.Mvvm/Messenger.cs index 20ad8c1a685..c27ec01560a 100644 --- a/Microsoft.Toolkit.Mvvm/Messenger.cs +++ b/Microsoft.Toolkit.Mvvm/Messenger.cs @@ -5,6 +5,7 @@ using System; using System.Buffers; using System.Collections.Generic; +using System.Diagnostics.Contracts; using System.Runtime.CompilerServices; using Microsoft.Collections.Extensions; @@ -27,6 +28,39 @@ public static class Messenger private static readonly DictionarySlim>> RecipientsMap = new DictionarySlim>>(); + /// + /// Checks whether or not a given recipient has already been registered for a message. + /// + /// The type of message to check for the given recipient. + /// The target recipient to check the registration for. + /// Whether or not has already been registered for the specified message. + [Pure] + public static bool IsRegistered(object recipient) + { + return IsRegistered(recipient, default); + } + + /// + /// Checks whether or not a given recipient has already been registered for a message. + /// + /// The type of message to check for the given recipient. + /// The type of token to check the channel for. + /// The target recipient to check the registration for. + /// The token used to identify the target channel to check. + /// Whether or not has already been registered for the specified message. + [Pure] + public static bool IsRegistered(object recipient, TToken token) + where TToken : notnull, IEquatable + { + lock (RecipientsMap) + { + var values = Container.Values; + var key = new Recipient(recipient); + + return values.ContainsKey(key); + } + } + /// /// Registers a recipient for a given type of message. /// From d0b110bdf23e05d02e8650749aebd468caa61973 Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Tue, 14 Apr 2020 22:18:47 +0200 Subject: [PATCH 039/595] Added IReadOnlyDictionary interface --- .../DictionarySlim{TKey,TValue}.cs | 34 ++++++++++++++++++- .../IReadOnlyDictionary{TKey,TValue}.cs | 25 ++++++++++++++ 2 files changed, 58 insertions(+), 1 deletion(-) create mode 100644 Microsoft.Toolkit.Mvvm/Microsoft.Collections.Extensions/IReadOnlyDictionary{TKey,TValue}.cs diff --git a/Microsoft.Toolkit.Mvvm/Microsoft.Collections.Extensions/DictionarySlim{TKey,TValue}.cs b/Microsoft.Toolkit.Mvvm/Microsoft.Collections.Extensions/DictionarySlim{TKey,TValue}.cs index 917e96843db..5d1be4ed294 100644 --- a/Microsoft.Toolkit.Mvvm/Microsoft.Collections.Extensions/DictionarySlim{TKey,TValue}.cs +++ b/Microsoft.Toolkit.Mvvm/Microsoft.Collections.Extensions/DictionarySlim{TKey,TValue}.cs @@ -29,7 +29,7 @@ namespace Microsoft.Collections.Extensions /// 3) This means it can avoid storing a comparer, and avoid the likely virtual call to a comparer. /// [DebuggerDisplay("Count = {Count}")] - internal sealed class DictionarySlim : IDictionary + internal sealed class DictionarySlim : IDictionary, IReadOnlyDictionary where TKey : notnull, IEquatable { // See info in CoreFX labs for how this works @@ -60,6 +60,29 @@ public DictionarySlim() /// public int Count => _count; + /// + public TValue this[TKey key] + { + get + { + Entry[] entries = _entries; + + for (int i = _buckets[key.GetHashCode() & (_buckets.Length - 1)] - 1; + (uint)i < (uint)entries.Length; + i = entries[i].Next) + { + if (key.Equals(entries[i].Key)) + { + return entries[i].Value; + } + } + + ThrowArgumentExceptionForKeyNotFound(key); + + return default!; + } + } + /// public bool ContainsKey(TKey key) { @@ -250,5 +273,14 @@ public bool MoveNext() /// public KeyValuePair Current => _current; } + + /// + /// Throws an when trying to load an element with a missing key. + /// + [MethodImpl(MethodImplOptions.NoInlining)] + private static void ThrowArgumentExceptionForKeyNotFound(TKey key) + { + throw new ArgumentException($"The target key {key} was not present in the dictionary"); + } } } \ No newline at end of file diff --git a/Microsoft.Toolkit.Mvvm/Microsoft.Collections.Extensions/IReadOnlyDictionary{TKey,TValue}.cs b/Microsoft.Toolkit.Mvvm/Microsoft.Collections.Extensions/IReadOnlyDictionary{TKey,TValue}.cs new file mode 100644 index 00000000000..4ecc749f735 --- /dev/null +++ b/Microsoft.Toolkit.Mvvm/Microsoft.Collections.Extensions/IReadOnlyDictionary{TKey,TValue}.cs @@ -0,0 +1,25 @@ +// 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; + +namespace Microsoft.Collections.Extensions +{ + /// + /// An interface providing value-invariant access to a instance. + /// + /// The type of keys in the dictionary. + /// The type of values in the dictionary. + internal interface IReadOnlyDictionary + where TKey : notnull, IEquatable + { + /// + /// Gets the value with the specified key. + /// + /// The key to look for + /// The returned value + /// Thrown if the key wasn't present. + TValue this[TKey key] { get; } + } +} From 43c7202eba49b2a8aa6d70a399a2b2635b34a406 Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Tue, 14 Apr 2020 22:32:22 +0200 Subject: [PATCH 040/595] Fixed some XML docs --- .../DictionarySlim{TKey,TValue}.cs | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/Microsoft.Toolkit.Mvvm/Microsoft.Collections.Extensions/DictionarySlim{TKey,TValue}.cs b/Microsoft.Toolkit.Mvvm/Microsoft.Collections.Extensions/DictionarySlim{TKey,TValue}.cs index 5d1be4ed294..d8a5b1b721d 100644 --- a/Microsoft.Toolkit.Mvvm/Microsoft.Collections.Extensions/DictionarySlim{TKey,TValue}.cs +++ b/Microsoft.Toolkit.Mvvm/Microsoft.Collections.Extensions/DictionarySlim{TKey,TValue}.cs @@ -2,11 +2,15 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. +#pragma warning disable SA1512 + +// The DictionarySlim type is originally from CoreFX labs, see +// the source repository on GitHub at https://github.com/dotnet/corefxlab. + using System; using System.Collections; using System.Collections.Generic; using System.Diagnostics; -using System.Linq; using System.Runtime.CompilerServices; namespace Microsoft.Collections.Extensions @@ -14,10 +18,9 @@ namespace Microsoft.Collections.Extensions /// /// A lightweight Dictionary with three principal differences compared to /// - /// 1) It is possible to do "get or add" in a single lookup using . For - /// values that are value types, this also saves a copy of the value. + /// 1) It is possible to do "get or add" in a single lookup. For value types, this also saves a copy of the value. /// 2) It assumes it is cheap to equate values. - /// 3) It assumes the keys implement or else Equals() and they are cheap and sufficient. + /// 3) It assumes the keys implement and they are cheap and sufficient. /// /// The type of keys in the dictionary. /// The type of values in the dictionary. From 45c794d63a1ba31333a5b8042ba6fbd01eeb43b8 Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Tue, 14 Apr 2020 22:42:00 +0200 Subject: [PATCH 041/595] Fixed a bug with new values being created when not needed --- Microsoft.Toolkit.Mvvm/Messenger.cs | 5 ++-- .../DictionarySlim{TKey,TValue}.cs | 27 +++++++++++++++++++ .../IReadOnlyDictionary{TKey,TValue}.cs | 4 +-- 3 files changed, 31 insertions(+), 5 deletions(-) diff --git a/Microsoft.Toolkit.Mvvm/Messenger.cs b/Microsoft.Toolkit.Mvvm/Messenger.cs index c27ec01560a..8ec660095ca 100644 --- a/Microsoft.Toolkit.Mvvm/Messenger.cs +++ b/Microsoft.Toolkit.Mvvm/Messenger.cs @@ -174,9 +174,8 @@ public static void Unregister(object recipient, TToken token) // Get the registration list (same as above) var values = Container.Values; var key = new Recipient(recipient); - ref DictionarySlim> map = ref values.GetOrAddValueRef(key); - if (map is null) + if (!values.TryGetValue(key, out DictionarySlim> map)) { return; } @@ -193,7 +192,7 @@ public static void Unregister(object recipient, TToken token) { values.Remove(key); - ref HashSet> set = ref RecipientsMap.GetOrAddValueRef(key); + HashSet> set = RecipientsMap[key]; set.Remove(values); diff --git a/Microsoft.Toolkit.Mvvm/Microsoft.Collections.Extensions/DictionarySlim{TKey,TValue}.cs b/Microsoft.Toolkit.Mvvm/Microsoft.Collections.Extensions/DictionarySlim{TKey,TValue}.cs index d8a5b1b721d..b1b2f299cad 100644 --- a/Microsoft.Toolkit.Mvvm/Microsoft.Collections.Extensions/DictionarySlim{TKey,TValue}.cs +++ b/Microsoft.Toolkit.Mvvm/Microsoft.Collections.Extensions/DictionarySlim{TKey,TValue}.cs @@ -103,6 +103,33 @@ public bool ContainsKey(TKey key) return false; } + /// + /// Gets the value if present for the specified key. + /// + /// The key to look for. + /// The value found, otherwise . + /// Whether or not the key was present. + public bool TryGetValue(TKey key, out TValue value) + { + Entry[] entries = _entries; + + for (int i = _buckets[key.GetHashCode() & (_buckets.Length - 1)] - 1; + (uint)i < (uint)entries.Length; + i = entries[i].Next) + { + if (key.Equals(entries[i].Key)) + { + value = entries[i].Value; + + return true; + } + } + + value = default!; + + return false; + } + /// public bool Remove(TKey key) { diff --git a/Microsoft.Toolkit.Mvvm/Microsoft.Collections.Extensions/IReadOnlyDictionary{TKey,TValue}.cs b/Microsoft.Toolkit.Mvvm/Microsoft.Collections.Extensions/IReadOnlyDictionary{TKey,TValue}.cs index 4ecc749f735..4fffd657f1b 100644 --- a/Microsoft.Toolkit.Mvvm/Microsoft.Collections.Extensions/IReadOnlyDictionary{TKey,TValue}.cs +++ b/Microsoft.Toolkit.Mvvm/Microsoft.Collections.Extensions/IReadOnlyDictionary{TKey,TValue}.cs @@ -17,8 +17,8 @@ internal interface IReadOnlyDictionary /// /// Gets the value with the specified key. /// - /// The key to look for - /// The returned value + /// The key to look for. + /// The returned value. /// Thrown if the key wasn't present. TValue this[TKey key] { get; } } From 6c68dcfcf57b95f9c9fccd4789ba0e33844e8a6c Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Tue, 14 Apr 2020 23:02:39 +0200 Subject: [PATCH 042/595] Renamed some interfaces to avoid naming collisions --- Microsoft.Toolkit.Mvvm/Messenger.cs | 14 +++++++------- .../DictionarySlim{TKey,TValue}.cs | 2 +- ...,TValue}.cs => IDictionarySlim{TKey,TValue}.cs} | 9 +++++---- ...ictionary{TKey}.cs => IDictionarySlim{TKey}.cs} | 6 +++--- 4 files changed, 16 insertions(+), 15 deletions(-) rename Microsoft.Toolkit.Mvvm/Microsoft.Collections.Extensions/{IReadOnlyDictionary{TKey,TValue}.cs => IDictionarySlim{TKey,TValue}.cs} (63%) rename Microsoft.Toolkit.Mvvm/Microsoft.Collections.Extensions/{IDictionary{TKey}.cs => IDictionarySlim{TKey}.cs} (65%) diff --git a/Microsoft.Toolkit.Mvvm/Messenger.cs b/Microsoft.Toolkit.Mvvm/Messenger.cs index 8ec660095ca..069885cb6bd 100644 --- a/Microsoft.Toolkit.Mvvm/Messenger.cs +++ b/Microsoft.Toolkit.Mvvm/Messenger.cs @@ -25,8 +25,8 @@ public static class Messenger /// existing handlers can be removed without having to dynamically create the /// generic types for the containers of the various dictionaries mapping the handlers. /// - private static readonly DictionarySlim>> RecipientsMap - = new DictionarySlim>>(); + private static readonly DictionarySlim>> RecipientsMap + = new DictionarySlim>>(); /// /// Checks whether or not a given recipient has already been registered for a message. @@ -108,11 +108,11 @@ public static void Register(object recipient, TToken token, Ac handler = action; // Make sure this registration map is tracked for the current recipient - ref HashSet> set = ref RecipientsMap.GetOrAddValueRef(key); + ref HashSet> set = ref RecipientsMap.GetOrAddValueRef(key); if (set is null) { - set = new HashSet>(); + set = new HashSet>(); } set.Add(values); @@ -129,7 +129,7 @@ public static void Unregister(object recipient) { // If the recipient has no registered messages at all, ignore var key = new Recipient(recipient); - ref HashSet> set = ref RecipientsMap.GetOrAddValueRef(key); + ref HashSet> set = ref RecipientsMap.GetOrAddValueRef(key); if (set is null) { @@ -137,7 +137,7 @@ public static void Unregister(object recipient) } // Removes all the lists of registered handlers for the recipient - foreach (IDictionary map in set) + foreach (IDictionarySlim map in set) { map.Remove(key); } @@ -192,7 +192,7 @@ public static void Unregister(object recipient, TToken token) { values.Remove(key); - HashSet> set = RecipientsMap[key]; + HashSet> set = RecipientsMap[key]; set.Remove(values); diff --git a/Microsoft.Toolkit.Mvvm/Microsoft.Collections.Extensions/DictionarySlim{TKey,TValue}.cs b/Microsoft.Toolkit.Mvvm/Microsoft.Collections.Extensions/DictionarySlim{TKey,TValue}.cs index b1b2f299cad..59039a084ba 100644 --- a/Microsoft.Toolkit.Mvvm/Microsoft.Collections.Extensions/DictionarySlim{TKey,TValue}.cs +++ b/Microsoft.Toolkit.Mvvm/Microsoft.Collections.Extensions/DictionarySlim{TKey,TValue}.cs @@ -32,7 +32,7 @@ namespace Microsoft.Collections.Extensions /// 3) This means it can avoid storing a comparer, and avoid the likely virtual call to a comparer. /// [DebuggerDisplay("Count = {Count}")] - internal sealed class DictionarySlim : IDictionary, IReadOnlyDictionary + internal sealed class DictionarySlim : IDictionarySlim, IDictionarySlim where TKey : notnull, IEquatable { // See info in CoreFX labs for how this works diff --git a/Microsoft.Toolkit.Mvvm/Microsoft.Collections.Extensions/IReadOnlyDictionary{TKey,TValue}.cs b/Microsoft.Toolkit.Mvvm/Microsoft.Collections.Extensions/IDictionarySlim{TKey,TValue}.cs similarity index 63% rename from Microsoft.Toolkit.Mvvm/Microsoft.Collections.Extensions/IReadOnlyDictionary{TKey,TValue}.cs rename to Microsoft.Toolkit.Mvvm/Microsoft.Collections.Extensions/IDictionarySlim{TKey,TValue}.cs index 4fffd657f1b..79dc6350f79 100644 --- a/Microsoft.Toolkit.Mvvm/Microsoft.Collections.Extensions/IReadOnlyDictionary{TKey,TValue}.cs +++ b/Microsoft.Toolkit.Mvvm/Microsoft.Collections.Extensions/IDictionarySlim{TKey,TValue}.cs @@ -7,11 +7,12 @@ namespace Microsoft.Collections.Extensions { /// - /// An interface providing value-invariant access to a instance. + /// An interface providing key type contravariant and value type covariant access + /// to a instance. /// - /// The type of keys in the dictionary. - /// The type of values in the dictionary. - internal interface IReadOnlyDictionary + /// The contravariant type of keys in the dictionary. + /// The covariant type of values in the dictionary. + internal interface IDictionarySlim where TKey : notnull, IEquatable { /// diff --git a/Microsoft.Toolkit.Mvvm/Microsoft.Collections.Extensions/IDictionary{TKey}.cs b/Microsoft.Toolkit.Mvvm/Microsoft.Collections.Extensions/IDictionarySlim{TKey}.cs similarity index 65% rename from Microsoft.Toolkit.Mvvm/Microsoft.Collections.Extensions/IDictionary{TKey}.cs rename to Microsoft.Toolkit.Mvvm/Microsoft.Collections.Extensions/IDictionarySlim{TKey}.cs index 95e4c62a749..bd41933f0a8 100644 --- a/Microsoft.Toolkit.Mvvm/Microsoft.Collections.Extensions/IDictionary{TKey}.cs +++ b/Microsoft.Toolkit.Mvvm/Microsoft.Collections.Extensions/IDictionarySlim{TKey}.cs @@ -8,10 +8,10 @@ namespace Microsoft.Collections.Extensions { /// - /// An interface providing value-invariant access to a instance. + /// An interface providing key type contravariant access to a instance. /// - /// The type of keys in the dictionary. - internal interface IDictionary + /// The contravariant type of keys in the dictionary. + internal interface IDictionarySlim where TKey : notnull, IEquatable { /// From 852e85e2c1053ba0beca50110e2c859e9690e31e Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Wed, 15 Apr 2020 00:15:05 +0200 Subject: [PATCH 043/595] Added Messenger.Unregister() method --- Microsoft.Toolkit.Mvvm/Messenger.cs | 86 ++++++++++++++++++- .../DictionarySlim{TKey,TValue}.cs | 6 +- .../IDictionarySlim{TKey,TValue}.cs | 2 +- .../IDictionarySlim{TKey}.cs | 5 ++ 4 files changed, 93 insertions(+), 6 deletions(-) diff --git a/Microsoft.Toolkit.Mvvm/Messenger.cs b/Microsoft.Toolkit.Mvvm/Messenger.cs index 069885cb6bd..40fc41aee27 100644 --- a/Microsoft.Toolkit.Mvvm/Messenger.cs +++ b/Microsoft.Toolkit.Mvvm/Messenger.cs @@ -158,6 +158,90 @@ public static void Unregister(object recipient) Unregister(recipient, default); } + /// + /// Unregisters a recipient from messages on a specific channel. + /// + /// The type of token to identify what channel to unregister from. + /// The recipient to unregister. + /// The token to use to identify which handlers to unregister. + /// If the recipient has no registered handler, this method does nothing. + public static void Unregister(object recipient, TToken token) + where TToken : notnull, IEquatable + { + lock (RecipientsMap) + { + // Get the shared set of mappings for the recipient, if present + var key = new Recipient(recipient); + + if (!RecipientsMap.TryGetValue(key, out HashSet> set)) + { + return; + } + + /* Copy the candidate mappings for the target recipient to a local + * array, as we can't modify the contents of the set while iterating it. + * The rented buffer is oversized and will also include mappings for + * handlers of messages that are registered through a different token. */ + var maps = ArrayPool>>.Shared.Rent(set.Count); + int i = 0; + + try + { + // Select the items with the same token type + foreach (IDictionarySlim item in set) + { + if (item is IDictionarySlim> map) + { + maps[i++] = map; + } + } + + /* Iterate through all the local maps. These are all the currently + * existing maps of handlers for messages of any given type, with a token + * of the current type, for the target recipient. We heavily rely on + * interfaces here to be able to iterate through all the available mappings + * without having to know the concrete type in advance, and without having + * to deal with reflection: we can just check if the type of the closed interface + * matches with the token type currently in use, and operate on those instances. */ + foreach (IDictionarySlim> map in maps.AsSpan(0, i)) + { + IDictionarySlim holder = map[key]; + + /* Remove the registered handler for the input token, + * for the current message type (unknown from here). */ + holder.Remove(token); + + if (holder.Count == 0) + { + // If the map is empty, remove the recipient entirely from its container + map.Remove(key); + + if (map.Count == 0) + { + /* If no handlers are left at all for the recipient, across all + * message types and token types, remove the set of mappings + * entirely for the current recipient, and lost the strong + * reference to it as well. This is the same situation that + * would've been achieved by just calling Unregister(recipient). */ + set.Remove(map); + + if (set.Count == 0) + { + RecipientsMap.Remove(key); + } + } + } + } + } + finally + { + maps.AsSpan(0, i).Clear(); + + ArrayPool>>.Shared.Return(maps); + } + } + } + /// /// Unregisters a recipient from messages of a given type. /// @@ -279,7 +363,7 @@ public static void Send(TMessage message, TToken token) // Remove references to avoid leaks coming from the shader memory pool entries.AsSpan(0, i).Clear(); - ArrayPool>.Shared.Return(entries, true); + ArrayPool>.Shared.Return(entries); } } diff --git a/Microsoft.Toolkit.Mvvm/Microsoft.Collections.Extensions/DictionarySlim{TKey,TValue}.cs b/Microsoft.Toolkit.Mvvm/Microsoft.Collections.Extensions/DictionarySlim{TKey,TValue}.cs index 59039a084ba..589a073a931 100644 --- a/Microsoft.Toolkit.Mvvm/Microsoft.Collections.Extensions/DictionarySlim{TKey,TValue}.cs +++ b/Microsoft.Toolkit.Mvvm/Microsoft.Collections.Extensions/DictionarySlim{TKey,TValue}.cs @@ -32,7 +32,7 @@ namespace Microsoft.Collections.Extensions /// 3) This means it can avoid storing a comparer, and avoid the likely virtual call to a comparer. /// [DebuggerDisplay("Count = {Count}")] - internal sealed class DictionarySlim : IDictionarySlim, IDictionarySlim + internal sealed class DictionarySlim : IDictionarySlim where TKey : notnull, IEquatable { // See info in CoreFX labs for how this works @@ -58,9 +58,7 @@ public DictionarySlim() _entries = InitialEntries; } - /// - /// Gets the count of entries in the dictionary. - /// + /// public int Count => _count; /// diff --git a/Microsoft.Toolkit.Mvvm/Microsoft.Collections.Extensions/IDictionarySlim{TKey,TValue}.cs b/Microsoft.Toolkit.Mvvm/Microsoft.Collections.Extensions/IDictionarySlim{TKey,TValue}.cs index 79dc6350f79..106cf8daf5f 100644 --- a/Microsoft.Toolkit.Mvvm/Microsoft.Collections.Extensions/IDictionarySlim{TKey,TValue}.cs +++ b/Microsoft.Toolkit.Mvvm/Microsoft.Collections.Extensions/IDictionarySlim{TKey,TValue}.cs @@ -12,7 +12,7 @@ namespace Microsoft.Collections.Extensions /// /// The contravariant type of keys in the dictionary. /// The covariant type of values in the dictionary. - internal interface IDictionarySlim + internal interface IDictionarySlim : IDictionarySlim where TKey : notnull, IEquatable { /// diff --git a/Microsoft.Toolkit.Mvvm/Microsoft.Collections.Extensions/IDictionarySlim{TKey}.cs b/Microsoft.Toolkit.Mvvm/Microsoft.Collections.Extensions/IDictionarySlim{TKey}.cs index bd41933f0a8..33fcfec7adc 100644 --- a/Microsoft.Toolkit.Mvvm/Microsoft.Collections.Extensions/IDictionarySlim{TKey}.cs +++ b/Microsoft.Toolkit.Mvvm/Microsoft.Collections.Extensions/IDictionarySlim{TKey}.cs @@ -14,6 +14,11 @@ namespace Microsoft.Collections.Extensions internal interface IDictionarySlim where TKey : notnull, IEquatable { + /// + /// Gets the count of entries in the dictionary. + /// + int Count { get; } + /// bool Remove(TKey key); } From 3af3aff993f035304c91def72c3dc39f7d6cba9e Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Wed, 15 Apr 2020 00:54:26 +0200 Subject: [PATCH 044/595] Added more comments in the Messenger class --- Microsoft.Toolkit.Mvvm/Messenger.cs | 30 ++++++++++++++++++++++++++--- 1 file changed, 27 insertions(+), 3 deletions(-) diff --git a/Microsoft.Toolkit.Mvvm/Messenger.cs b/Microsoft.Toolkit.Mvvm/Messenger.cs index 40fc41aee27..b8b63b74258 100644 --- a/Microsoft.Toolkit.Mvvm/Messenger.cs +++ b/Microsoft.Toolkit.Mvvm/Messenger.cs @@ -16,14 +16,38 @@ namespace Microsoft.Toolkit.Mvvm /// public static class Messenger { + // The Messenger class uses the following logic to link stored instances together: + // -------------------------------------------------------------------------------------------------------- + // DictionarySlim>> RecipientsMap + // | / \ / + // \_________________/_____ \__/____[*]IDictionarySlim + // / \ / ______________/ + // / \ / / + // Container.DictionarySlim>> Values + // -------------------------------------------------------------------------------------------------------- + // Each combination of results in a concrete Container<,> type, which holds the + // mapping to registered recipients to handlers. The handlers are stored in a > + // dictionary, so that each recipient can have up to one registered handler for a given token, for each + // message type. Each existing recipient is also stored in the main recipients map, along with a set of + // all the existing dictionaries of handlers for that recipient (for all message types and token types). + // A recipient is stored in the main map as long as it has at least one registered handler in any of the + // existing mappings for every message/token type combination. The shared map is used to access the set + // of all registered handlers for a given recipient, without having to know in advance the type of message + // or token being used for the registration, and without having to use reflection. Note that each dictionary + // stored in the associated set for each recipient also implements IDictionarySlim, with + // any token type currently in use by that recipient. This allows to retrieve the type-closed mappings + // of registered handlers with a given token type, for any message type, for every receiver, again without + // having to use reflection. This shared map is used to unregister messages from a given recipients either + // unconditionally, by message type, by token, or for a specific pair of message type and token value. + /// /// The collection of currently registered recipients, with a link to their linked message receivers. /// /// /// This collection is used to allow reflection-free access to all the existing - /// registered recipients from , so that all the - /// existing handlers can be removed without having to dynamically create the - /// generic types for the containers of the various dictionaries mapping the handlers. + /// registered recipients from and other overloads, + /// so that all the existing handlers can be removed without having to dynamically create + /// the generic types for the containers of the various dictionaries mapping the handlers. /// private static readonly DictionarySlim>> RecipientsMap = new DictionarySlim>>(); From ea68036dc73079d34599a53ad55c26efd5cec726 Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Wed, 15 Apr 2020 01:05:49 +0200 Subject: [PATCH 045/595] Added DictionarySlim.Clear method --- .../DictionarySlim{TKey,TValue}.cs | 14 ++++++++++++-- .../HashHelpers.cs | 17 +++++++++++++++++ .../IDictionarySlim{TKey}.cs | 5 +++++ 3 files changed, 34 insertions(+), 2 deletions(-) create mode 100644 Microsoft.Toolkit.Mvvm/Microsoft.Collections.Extensions/HashHelpers.cs diff --git a/Microsoft.Toolkit.Mvvm/Microsoft.Collections.Extensions/DictionarySlim{TKey,TValue}.cs b/Microsoft.Toolkit.Mvvm/Microsoft.Collections.Extensions/DictionarySlim{TKey,TValue}.cs index 589a073a931..62f442ec8e9 100644 --- a/Microsoft.Toolkit.Mvvm/Microsoft.Collections.Extensions/DictionarySlim{TKey,TValue}.cs +++ b/Microsoft.Toolkit.Mvvm/Microsoft.Collections.Extensions/DictionarySlim{TKey,TValue}.cs @@ -54,7 +54,7 @@ private struct Entry /// public DictionarySlim() { - _buckets = new int[1]; + _buckets = HashHelpers.SizeOneIntArray; _entries = InitialEntries; } @@ -84,13 +84,23 @@ public TValue this[TKey key] } } + /// + public void Clear() + { + _count = 0; + _freeList = -1; + _buckets = HashHelpers.SizeOneIntArray; + _entries = InitialEntries; + } + /// public bool ContainsKey(TKey key) { Entry[] entries = _entries; for (int i = _buckets[key.GetHashCode() & (_buckets.Length - 1)] - 1; - (uint)i < (uint)entries.Length; i = entries[i].Next) + (uint)i < (uint)entries.Length; + i = entries[i].Next) { if (key.Equals(entries[i].Key)) { diff --git a/Microsoft.Toolkit.Mvvm/Microsoft.Collections.Extensions/HashHelpers.cs b/Microsoft.Toolkit.Mvvm/Microsoft.Collections.Extensions/HashHelpers.cs new file mode 100644 index 00000000000..14fb3abe766 --- /dev/null +++ b/Microsoft.Toolkit.Mvvm/Microsoft.Collections.Extensions/HashHelpers.cs @@ -0,0 +1,17 @@ +// 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. + +namespace Microsoft.Collections.Extensions +{ + /// + /// A helper class for . + /// + internal static class HashHelpers + { + /// + /// Gets an array of type of size 1. + /// + internal static readonly int[] SizeOneIntArray = new int[1]; + } +} \ No newline at end of file diff --git a/Microsoft.Toolkit.Mvvm/Microsoft.Collections.Extensions/IDictionarySlim{TKey}.cs b/Microsoft.Toolkit.Mvvm/Microsoft.Collections.Extensions/IDictionarySlim{TKey}.cs index 33fcfec7adc..c9cf27067ba 100644 --- a/Microsoft.Toolkit.Mvvm/Microsoft.Collections.Extensions/IDictionarySlim{TKey}.cs +++ b/Microsoft.Toolkit.Mvvm/Microsoft.Collections.Extensions/IDictionarySlim{TKey}.cs @@ -21,5 +21,10 @@ internal interface IDictionarySlim /// bool Remove(TKey key); + + /// + /// Clears the current dictionary. + /// + void Clear(); } } From 7ab96f3dd4749a5c36f2e878ed0c76a580babccd Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Wed, 15 Apr 2020 01:08:42 +0200 Subject: [PATCH 046/595] Added Messenger.Reset method --- Microsoft.Toolkit.Mvvm/Messenger.cs | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/Microsoft.Toolkit.Mvvm/Messenger.cs b/Microsoft.Toolkit.Mvvm/Messenger.cs index b8b63b74258..745328fd369 100644 --- a/Microsoft.Toolkit.Mvvm/Messenger.cs +++ b/Microsoft.Toolkit.Mvvm/Messenger.cs @@ -391,6 +391,27 @@ public static void Send(TMessage message, TToken token) } } + /// + /// Resets the class and unregisters all the existing recipients. + /// + public static void Reset() + { + lock (RecipientsMap) + { + foreach (var pair in RecipientsMap) + { + // Clear all the typed maps, as they're assigned to static fields + foreach (var map in pair.Value) + { + map.Clear(); + } + } + + // Clear the shared map too + RecipientsMap.Clear(); + } + } + /// /// A container class for registered recipients. /// From 6dc71d0188c8f4b159d5d44a1c2757313ddd5c27 Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Wed, 15 Apr 2020 01:45:50 +0200 Subject: [PATCH 047/595] Added initial Messenger unit tests --- UnitTests/Mvvm/Test_Messenger.cs | 99 ++++++++++++++++++++++++++++++++ UnitTests/UnitTests.csproj | 1 + 2 files changed, 100 insertions(+) create mode 100644 UnitTests/Mvvm/Test_Messenger.cs diff --git a/UnitTests/Mvvm/Test_Messenger.cs b/UnitTests/Mvvm/Test_Messenger.cs new file mode 100644 index 00000000000..df046747fa1 --- /dev/null +++ b/UnitTests/Mvvm/Test_Messenger.cs @@ -0,0 +1,99 @@ +// 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; +using Microsoft.VisualStudio.TestTools.UnitTesting; + +namespace UnitTests.Mvvm +{ + [TestClass] + public class Test_Messenger + { + [TestCategory("Mvvm")] + [TestMethod] + public void Test_Ioc_IsRegistered_Register_Send_UnregisterOfTMessage_WithNoToken() + { + object a = new object(); + + Assert.IsFalse(Messenger.IsRegistered(a)); + + string result = null; + Messenger.Register(a, m => result = m.Text); + + Assert.IsTrue(Messenger.IsRegistered(a)); + + Messenger.Send(new MessageA { Text = nameof(MessageA) }); + + Assert.AreEqual(result, nameof(MessageA)); + + Messenger.Unregister(a); + + Assert.IsFalse(Messenger.IsRegistered(a)); + + result = null; + Messenger.Send(new MessageA { Text = nameof(MessageA) }); + + Assert.IsNull(result); + } + + [TestCategory("Mvvm")] + [TestMethod] + public void Test_Ioc_IsRegistered_Register_Send_UnregisterRecipient_WithNoToken() + { + object a = new object(); + + Assert.IsFalse(Messenger.IsRegistered(a)); + + string result = null; + Messenger.Register(a, m => result = m.Text); + + Assert.IsTrue(Messenger.IsRegistered(a)); + + Messenger.Send(new MessageA { Text = nameof(MessageA) }); + + Assert.AreEqual(result, nameof(MessageA)); + + Messenger.Unregister(a); + + Assert.IsFalse(Messenger.IsRegistered(a)); + + result = null; + Messenger.Send(new MessageA { Text = nameof(MessageA) }); + + Assert.IsNull(result); + } + + [TestCategory("Mvvm")] + [TestMethod] + public void Test_Ioc_IsRegistered_Register_Send_UnregisterOfTMessage_WithToken() + { + object a = new object(); + + Assert.IsFalse(Messenger.IsRegistered(a)); + + string result = null; + Messenger.Register(a, nameof(MessageA), m => result = m.Text); + + Assert.IsTrue(Messenger.IsRegistered(a, nameof(MessageA))); + + Messenger.Send(new MessageA { Text = nameof(MessageA) }, nameof(MessageA)); + + Assert.AreEqual(result, nameof(MessageA)); + + Messenger.Unregister(a, nameof(MessageA)); + + Assert.IsFalse(Messenger.IsRegistered(a, nameof(MessageA))); + + result = null; + Messenger.Send(new MessageA { Text = nameof(MessageA) }, nameof(MessageA)); + + Assert.IsNull(result); + } + + public sealed class MessageA + { + public string Text; + } + } +} diff --git a/UnitTests/UnitTests.csproj b/UnitTests/UnitTests.csproj index 51926253c1a..f947137c2aa 100644 --- a/UnitTests/UnitTests.csproj +++ b/UnitTests/UnitTests.csproj @@ -139,6 +139,7 @@ + From 8c2cced5eae67de5b1933fd8ef2093b47ae6f27a Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Wed, 15 Apr 2020 02:25:41 +0200 Subject: [PATCH 048/595] Minor code style tweaks --- UnitTests/Mvvm/Test_Messenger.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/UnitTests/Mvvm/Test_Messenger.cs b/UnitTests/Mvvm/Test_Messenger.cs index df046747fa1..21e0f2e8346 100644 --- a/UnitTests/Mvvm/Test_Messenger.cs +++ b/UnitTests/Mvvm/Test_Messenger.cs @@ -93,7 +93,7 @@ public void Test_Ioc_IsRegistered_Register_Send_UnregisterOfTMessage_WithToken() public sealed class MessageA { - public string Text; + public string Text { get; set; } } } } From bca38f5760fb22fb6c3404e31fcc487125b0e5ff Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Wed, 15 Apr 2020 13:03:11 +0200 Subject: [PATCH 049/595] Code refactoring --- Microsoft.Toolkit.Mvvm/{ => Messaging}/Messenger.cs | 2 +- .../DictionarySlim{TKey,TValue}.cs | 0 .../Microsoft.Collections.Extensions/HashHelpers.cs | 0 .../IDictionarySlim{TKey,TValue}.cs | 0 .../Microsoft.Collections.Extensions/IDictionarySlim{TKey}.cs | 0 UnitTests/Mvvm/Test_Messenger.cs | 2 +- 6 files changed, 2 insertions(+), 2 deletions(-) rename Microsoft.Toolkit.Mvvm/{ => Messaging}/Messenger.cs (99%) rename Microsoft.Toolkit.Mvvm/{ => Messaging}/Microsoft.Collections.Extensions/DictionarySlim{TKey,TValue}.cs (100%) rename Microsoft.Toolkit.Mvvm/{ => Messaging}/Microsoft.Collections.Extensions/HashHelpers.cs (100%) rename Microsoft.Toolkit.Mvvm/{ => Messaging}/Microsoft.Collections.Extensions/IDictionarySlim{TKey,TValue}.cs (100%) rename Microsoft.Toolkit.Mvvm/{ => Messaging}/Microsoft.Collections.Extensions/IDictionarySlim{TKey}.cs (100%) diff --git a/Microsoft.Toolkit.Mvvm/Messenger.cs b/Microsoft.Toolkit.Mvvm/Messaging/Messenger.cs similarity index 99% rename from Microsoft.Toolkit.Mvvm/Messenger.cs rename to Microsoft.Toolkit.Mvvm/Messaging/Messenger.cs index 745328fd369..60ba2d5e132 100644 --- a/Microsoft.Toolkit.Mvvm/Messenger.cs +++ b/Microsoft.Toolkit.Mvvm/Messaging/Messenger.cs @@ -9,7 +9,7 @@ using System.Runtime.CompilerServices; using Microsoft.Collections.Extensions; -namespace Microsoft.Toolkit.Mvvm +namespace Microsoft.Toolkit.Mvvm.Messaging { /// /// A type implementing a messaging system between objects. diff --git a/Microsoft.Toolkit.Mvvm/Microsoft.Collections.Extensions/DictionarySlim{TKey,TValue}.cs b/Microsoft.Toolkit.Mvvm/Messaging/Microsoft.Collections.Extensions/DictionarySlim{TKey,TValue}.cs similarity index 100% rename from Microsoft.Toolkit.Mvvm/Microsoft.Collections.Extensions/DictionarySlim{TKey,TValue}.cs rename to Microsoft.Toolkit.Mvvm/Messaging/Microsoft.Collections.Extensions/DictionarySlim{TKey,TValue}.cs diff --git a/Microsoft.Toolkit.Mvvm/Microsoft.Collections.Extensions/HashHelpers.cs b/Microsoft.Toolkit.Mvvm/Messaging/Microsoft.Collections.Extensions/HashHelpers.cs similarity index 100% rename from Microsoft.Toolkit.Mvvm/Microsoft.Collections.Extensions/HashHelpers.cs rename to Microsoft.Toolkit.Mvvm/Messaging/Microsoft.Collections.Extensions/HashHelpers.cs diff --git a/Microsoft.Toolkit.Mvvm/Microsoft.Collections.Extensions/IDictionarySlim{TKey,TValue}.cs b/Microsoft.Toolkit.Mvvm/Messaging/Microsoft.Collections.Extensions/IDictionarySlim{TKey,TValue}.cs similarity index 100% rename from Microsoft.Toolkit.Mvvm/Microsoft.Collections.Extensions/IDictionarySlim{TKey,TValue}.cs rename to Microsoft.Toolkit.Mvvm/Messaging/Microsoft.Collections.Extensions/IDictionarySlim{TKey,TValue}.cs diff --git a/Microsoft.Toolkit.Mvvm/Microsoft.Collections.Extensions/IDictionarySlim{TKey}.cs b/Microsoft.Toolkit.Mvvm/Messaging/Microsoft.Collections.Extensions/IDictionarySlim{TKey}.cs similarity index 100% rename from Microsoft.Toolkit.Mvvm/Microsoft.Collections.Extensions/IDictionarySlim{TKey}.cs rename to Microsoft.Toolkit.Mvvm/Messaging/Microsoft.Collections.Extensions/IDictionarySlim{TKey}.cs diff --git a/UnitTests/Mvvm/Test_Messenger.cs b/UnitTests/Mvvm/Test_Messenger.cs index 21e0f2e8346..fd44a47fdf3 100644 --- a/UnitTests/Mvvm/Test_Messenger.cs +++ b/UnitTests/Mvvm/Test_Messenger.cs @@ -2,7 +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 Microsoft.Toolkit.Mvvm; +using Microsoft.Toolkit.Mvvm.Messaging; using Microsoft.VisualStudio.TestTools.UnitTesting; namespace UnitTests.Mvvm From de0d2ba607f479b2036535a27dfd56037b80b7d8 Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Wed, 15 Apr 2020 13:23:32 +0200 Subject: [PATCH 050/595] Added Messenger APIs with no input message --- Microsoft.Toolkit.Mvvm/Messaging/Messenger.cs | 41 +++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/Microsoft.Toolkit.Mvvm/Messaging/Messenger.cs b/Microsoft.Toolkit.Mvvm/Messaging/Messenger.cs index 60ba2d5e132..e77b1821c3b 100644 --- a/Microsoft.Toolkit.Mvvm/Messaging/Messenger.cs +++ b/Microsoft.Toolkit.Mvvm/Messaging/Messenger.cs @@ -60,6 +60,7 @@ private static readonly DictionarySlimWhether or not has already been registered for the specified message. [Pure] public static bool IsRegistered(object recipient) + where TMessage : class { return IsRegistered(recipient, default); } @@ -74,6 +75,7 @@ public static bool IsRegistered(object recipient) /// Whether or not has already been registered for the specified message. [Pure] public static bool IsRegistered(object recipient, TToken token) + where TMessage : class where TToken : notnull, IEquatable { lock (RecipientsMap) @@ -93,6 +95,7 @@ public static bool IsRegistered(object recipient, TToken token /// The to invoke when a message is received. /// Thrown when trying to register the same message twice. public static void Register(object recipient, Action action) + where TMessage : class { Register(recipient, default(Unit), action); } @@ -107,6 +110,7 @@ public static void Register(object recipient, Action action) /// The to invoke when a message is received. /// Thrown when trying to register the same message twice. public static void Register(object recipient, TToken token, Action action) + where TMessage : class where TToken : notnull, IEquatable { lock (RecipientsMap) @@ -178,6 +182,7 @@ public static void Unregister(object recipient) /// The recipient to unregister. /// If the recipient has no registered handler, this method does nothing. public static void Unregister(object recipient) + where TMessage : class { Unregister(recipient, default); } @@ -275,6 +280,7 @@ public static void Unregister(object recipient, TToken token) /// The token to use to identify which handlers to unregister. /// If the recipient has no registered handler, this method does nothing. public static void Unregister(object recipient, TToken token) + where TMessage : class where TToken : notnull, IEquatable { lock (RecipientsMap) @@ -312,16 +318,49 @@ public static void Unregister(object recipient, TToken token) } } + /// + /// Sends a message of the specified type to all registered recipients. + /// + /// The type of message to send. + /// + /// This method is a shorthand for when the + /// message type exposes a parameterless constructor: it will automatically create + /// a new instance and send that to its recipients. + /// + public static void Send() + where TMessage : class, new() + { + Send(new TMessage(), default(Unit)); + } + /// /// Sends a message of the specified type to all registered recipients. /// /// The type of message to send. /// The message to send. public static void Send(TMessage message) + where TMessage : class { Send(message, default(Unit)); } + /// + /// Sends a message of the specified type to all registered recipients. + /// + /// The type of message to send. + /// The type of token to identify what channel to use to send the message. + /// The token indicating what channel to use. + /// + /// This method will automatically create a new instance + /// just like , and then send it to the right recipients. + /// + public static void Send(TToken token) + where TMessage : class, new() + where TToken : notnull, IEquatable + { + Send(new TMessage(), token); + } + /// /// Sends a message of the specified type to all registered recipients. /// @@ -330,6 +369,7 @@ public static void Send(TMessage message) /// The message to send. /// The token indicating what channel to use. public static void Send(TMessage message, TToken token) + where TMessage : class where TToken : notnull, IEquatable { Action[] entries; @@ -418,6 +458,7 @@ public static void Reset() /// The type of message to receive. /// The type of token to use to pick the messages to receive. private static class Container + where TMessage : class where TToken : notnull, IEquatable { /// From 6d22101d87d2d14a155d55925dab85f36b54b0ee Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Wed, 15 Apr 2020 14:07:35 +0200 Subject: [PATCH 051/595] Added RequestMessageBase type --- .../Messages/RequestMessageBase{T}.cs | 72 +++++++++++++++++++ 1 file changed, 72 insertions(+) create mode 100644 Microsoft.Toolkit.Mvvm/Messaging/Messages/RequestMessageBase{T}.cs diff --git a/Microsoft.Toolkit.Mvvm/Messaging/Messages/RequestMessageBase{T}.cs b/Microsoft.Toolkit.Mvvm/Messaging/Messages/RequestMessageBase{T}.cs new file mode 100644 index 00000000000..522c9cc6575 --- /dev/null +++ b/Microsoft.Toolkit.Mvvm/Messaging/Messages/RequestMessageBase{T}.cs @@ -0,0 +1,72 @@ +using System; +using System.Runtime.CompilerServices; + +#pragma warning disable CS8618 + +namespace Microsoft.Toolkit.Mvvm.Messaging.Messages +{ + /// + /// A base for request messages. + /// + /// The type of request to make. + public abstract class RequestMessageBase + { + /// + /// An used to synchronize access to and . + /// + private readonly object dummy = new object(); + + private T result; + + /// + /// Gets the message response. + /// + public T Result + { + get + { + lock (this.dummy) + { + if (!this.IsResponseReceived) + { + ThrowInvalidOperationExceptionForDuplicateReporResult(); + } + + return this.result; + } + } + } + + /// + /// Gets a value indicating whether a result has already been assigned to this instance. + /// + public bool IsResponseReceived { get; private set; } + + /// + /// Reports a result for the current request message. + /// + /// The result to report for the message. + public void ReportResult(T result) + { + lock (this.dummy) + { + if (this.IsResponseReceived) + { + ThrowInvalidOperationExceptionForDuplicateReporResult(); + } + + this.IsResponseReceived = true; + this.result = result; + } + } + + /// + /// Throws an when is called twice. + /// + [MethodImpl(MethodImplOptions.NoInlining)] + private static void ThrowInvalidOperationExceptionForDuplicateReporResult() + { + throw new InvalidOperationException("A result has already been reported for the current message"); + } + } +} From 2cd0a2017a993e5cce7972c4b4276f35ee9ae0af Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Wed, 15 Apr 2020 14:17:50 +0200 Subject: [PATCH 052/595] Added Messenger request methods --- Microsoft.Toolkit.Mvvm/Messaging/Messenger.cs | 86 +++++++++++++++++++ 1 file changed, 86 insertions(+) diff --git a/Microsoft.Toolkit.Mvvm/Messaging/Messenger.cs b/Microsoft.Toolkit.Mvvm/Messaging/Messenger.cs index e77b1821c3b..dcae86fb08e 100644 --- a/Microsoft.Toolkit.Mvvm/Messaging/Messenger.cs +++ b/Microsoft.Toolkit.Mvvm/Messaging/Messenger.cs @@ -8,6 +8,7 @@ using System.Diagnostics.Contracts; using System.Runtime.CompilerServices; using Microsoft.Collections.Extensions; +using Microsoft.Toolkit.Mvvm.Messaging.Messages; namespace Microsoft.Toolkit.Mvvm.Messaging { @@ -431,6 +432,82 @@ public static void Send(TMessage message, TToken token) } } + /// + /// Sends a request of the specified type to all registered recipients, and returns the received response. + /// + /// The type of request message to send. + /// The type of response to expect. + /// The response value for the given message. + /// Thrown if no response is received for the message. + /// + /// This method is a shorthand for when the + /// message type exposes a parameterless constructor: it will automatically create + /// a new instance and send that to its recipients. + /// + public static TResult Request() + where TMessage : RequestMessageBase, new() + { + return Request(new TMessage(), default); + } + + /// + /// Sends a request of the specified type to all registered recipients, and returns the received response. + /// + /// The type of request message to send. + /// The type of response to expect. + /// The message to send. + /// The response value for the given message. + /// Thrown if no response is received for the message. + public static TResult Request(TMessage message) + where TMessage : RequestMessageBase + { + return Request(message, default); + } + + /// + /// Sends a request of the specified type to all registered recipients, and returns the received response. + /// + /// The type of request message to send. + /// The type of response to expect. + /// The type of token to identify what channel to use to send the message. + /// The token indicating what channel to use. + /// The response value for the given message. + /// Thrown if no response is received for the message. + /// + /// This method will automatically create a new instance + /// just like , and then send it to the right recipients. + /// + public static TResult Request(TToken token) + where TMessage : RequestMessageBase, new() + where TToken : notnull, IEquatable + { + return Request(new TMessage(), token); + } + + /// + /// Sends a request of the specified type to all registered recipients, and returns the received response. + /// + /// The type of request message to send. + /// The type of response to expect. + /// The type of token to identify what channel to use to send the message. + /// The request message to send. + /// The token indicating what channel to use. + /// The response value for the given message. + /// Thrown if no response is received for the message. + public static TResult Request(TMessage message, TToken token) + where TMessage : RequestMessageBase + where TToken : notnull, IEquatable + { + Send(message, token); + + if (!message.IsResponseReceived) + { + ThrowInvalidOperationExceptionForNoResponseReceived(); + } + + return message.Result; + } + /// /// Resets the class and unregisters all the existing recipients. /// @@ -556,5 +633,14 @@ private static void ThrowInvalidOperationExceptionForDuplicateRegistration() { throw new InvalidOperationException("The target recipient has already subscribed to the target message"); } + + /// + /// Throws an when trying to add a duplicate handler. + /// + [MethodImpl(MethodImplOptions.NoInlining)] + private static void ThrowInvalidOperationExceptionForNoResponseReceived() + { + throw new InvalidOperationException("No response was received for the given request message"); + } } } From 595474c905f012db9300111d478cd26136250b55 Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Wed, 15 Apr 2020 14:24:44 +0200 Subject: [PATCH 053/595] Minor code refactoring --- .../{RequestMessageBase{T}.cs => RequestMessage{T}.cs} | 4 ++-- Microsoft.Toolkit.Mvvm/Messaging/Messenger.cs | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) rename Microsoft.Toolkit.Mvvm/Messaging/Messages/{RequestMessageBase{T}.cs => RequestMessage{T}.cs} (93%) diff --git a/Microsoft.Toolkit.Mvvm/Messaging/Messages/RequestMessageBase{T}.cs b/Microsoft.Toolkit.Mvvm/Messaging/Messages/RequestMessage{T}.cs similarity index 93% rename from Microsoft.Toolkit.Mvvm/Messaging/Messages/RequestMessageBase{T}.cs rename to Microsoft.Toolkit.Mvvm/Messaging/Messages/RequestMessage{T}.cs index 522c9cc6575..37d29fdc9e4 100644 --- a/Microsoft.Toolkit.Mvvm/Messaging/Messages/RequestMessageBase{T}.cs +++ b/Microsoft.Toolkit.Mvvm/Messaging/Messages/RequestMessage{T}.cs @@ -6,10 +6,10 @@ namespace Microsoft.Toolkit.Mvvm.Messaging.Messages { /// - /// A base for request messages. + /// A for request messages, which can either be used directly or through derived classes. /// /// The type of request to make. - public abstract class RequestMessageBase + public class RequestMessage { /// /// An used to synchronize access to and . diff --git a/Microsoft.Toolkit.Mvvm/Messaging/Messenger.cs b/Microsoft.Toolkit.Mvvm/Messaging/Messenger.cs index dcae86fb08e..12c5eb2e0ad 100644 --- a/Microsoft.Toolkit.Mvvm/Messaging/Messenger.cs +++ b/Microsoft.Toolkit.Mvvm/Messaging/Messenger.cs @@ -445,7 +445,7 @@ public static void Send(TMessage message, TToken token) /// a new instance and send that to its recipients. /// public static TResult Request() - where TMessage : RequestMessageBase, new() + where TMessage : RequestMessage, new() { return Request(new TMessage(), default); } @@ -459,7 +459,7 @@ public static TResult Request() /// The response value for the given message. /// Thrown if no response is received for the message. public static TResult Request(TMessage message) - where TMessage : RequestMessageBase + where TMessage : RequestMessage { return Request(message, default); } @@ -478,7 +478,7 @@ public static TResult Request(TMessage message) /// just like , and then send it to the right recipients. /// public static TResult Request(TToken token) - where TMessage : RequestMessageBase, new() + where TMessage : RequestMessage, new() where TToken : notnull, IEquatable { return Request(new TMessage(), token); @@ -495,7 +495,7 @@ public static TResult Request(TToken token) /// The response value for the given message. /// Thrown if no response is received for the message. public static TResult Request(TMessage message, TToken token) - where TMessage : RequestMessageBase + where TMessage : RequestMessage where TToken : notnull, IEquatable { Send(message, token); From 82bf497ad691604b3403787d2b2a4360a999e183 Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Wed, 15 Apr 2020 14:31:37 +0200 Subject: [PATCH 054/595] Added PropertyChangedMessage type --- .../Messages/PropertyChangedMessage{T}.cs | 66 +++++++++++++++++++ 1 file changed, 66 insertions(+) create mode 100644 Microsoft.Toolkit.Mvvm/Messaging/Messages/PropertyChangedMessage{T}.cs diff --git a/Microsoft.Toolkit.Mvvm/Messaging/Messages/PropertyChangedMessage{T}.cs b/Microsoft.Toolkit.Mvvm/Messaging/Messages/PropertyChangedMessage{T}.cs new file mode 100644 index 00000000000..8b0f57fc2e0 --- /dev/null +++ b/Microsoft.Toolkit.Mvvm/Messaging/Messages/PropertyChangedMessage{T}.cs @@ -0,0 +1,66 @@ +// 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. + +#pragma warning disable SA1512 + +// Original file header: +// **************************************************************************** +// +// Copyright © GalaSoft Laurent Bugnion 2009-2016 +// +// **************************************************************************** +// Laurent Bugnion +// laurent@galasoft.ch +// 13.4.2009 +// GalaSoft.MvvmLight.Messaging +// http://www.mvvmlight.net +// +// See license.txt in this project or http://www.galasoft.ch/license_MIT.txt +// +// **************************************************************************** + +namespace Microsoft.Toolkit.Mvvm.Messaging.Messages +{ + /// + /// A message used to broadcast property changes in observable objects. + /// + /// The type of the property to broadcast the change for. + public class PropertyChangedMessage + { + /// + /// Initializes a new instance of the class. + /// + /// The original sender of the broadcast message. + /// The name of the property that changed. + /// The value that the property had before the change. + /// The value that the property has after the change. + public PropertyChangedMessage(object sender, string propertyName, T oldValue, T newValue) + { + this.Sender = sender; + this.PropertyName = propertyName; + this.OldValue = oldValue; + this.NewValue = newValue; + } + + /// + /// Gets the original sender of the broadcast message. + /// + public object Sender { get; } + + /// + /// Gets the name of the property that changed. + /// + public string PropertyName { get; } + + /// + /// Gets the value that the property had before the change. + /// + public T OldValue { get; } + + /// + /// Gets the value that the property has after the change. + /// + public T NewValue { get; } + } +} \ No newline at end of file From 82946f9841a82d6310f1b440add7ad290bdbbc9b Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Wed, 15 Apr 2020 15:34:17 +0200 Subject: [PATCH 055/595] Added ViewModelBase class --- Microsoft.Toolkit.Mvvm/ViewModelBase.cs | 132 ++++++++++++++++++++++++ 1 file changed, 132 insertions(+) create mode 100644 Microsoft.Toolkit.Mvvm/ViewModelBase.cs diff --git a/Microsoft.Toolkit.Mvvm/ViewModelBase.cs b/Microsoft.Toolkit.Mvvm/ViewModelBase.cs new file mode 100644 index 00000000000..4e1ddd293d9 --- /dev/null +++ b/Microsoft.Toolkit.Mvvm/ViewModelBase.cs @@ -0,0 +1,132 @@ +// 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. + +#pragma warning disable SA1512 + +// Original file header: +// **************************************************************************** +// +// Copyright © GalaSoft Laurent Bugnion 2009-2016 +// +// **************************************************************************** +// Laurent Bugnion +// laurent@galasoft.ch +// 22.4.2009 +// GalaSoft.MvvmLight +// http://www.mvvmlight.net +// +// See license.txt in this project or http://www.galasoft.ch/license_MIT.txt +// +// BL0014 +// **************************************************************************** + +using System.Runtime.CompilerServices; +using Microsoft.Toolkit.Mvvm.Messaging; +using Microsoft.Toolkit.Mvvm.Messaging.Messages; + +namespace Microsoft.Toolkit.Mvvm +{ + /// + /// A base class for viewmodels. + /// + public abstract class ViewModelBase : ObservableObject + { + private bool isActive; + + /// + /// Gets or sets a value indicating whether the current view model is currently active. + /// + public bool IsActive + { + get => this.isActive; + set + { + if (Set(ref this.isActive, value)) + { + if (value) + { + OnActivate(); + } + else + { + OnDeactivate(); + } + } + } + } + + /// + /// Raised whenever the property is set to . + /// Use this method to register to messages and do other initialization for this instance. + /// + protected virtual void OnActivate() + { + } + + /// + /// Raised whenever the property is set to . + /// Use this method to unregister from messages and do general cleanup for this instance. + /// + /// The base implementation unregisters all messages for this recipient (with no token). + protected virtual void OnDeactivate() + { + Messenger.Unregister(this); + } + + /// + /// Broadcasts a with the specified + /// parameters, without using any particular token (so using the default channel). + /// + /// The type of the property that changed. + /// The value of the property before it changed. + /// The value of the property after it changed. + /// The name of the property that changed. + /// + /// You should override this method if you wish to customize the channel being + /// used to send the message (eg. if you need to use a specific token for the channel). + /// + protected virtual void Broadcast(T oldValue, T newValue, string propertyName) + { + var message = new PropertyChangedMessage(this, propertyName, oldValue, newValue); + + Messenger.Send(message); + } + + /// + /// Compares the current and new values for a given property. If the value has changed, + /// raises the event, updates the property with + /// the new value, then raises the event. + /// + /// The type of the property that changed. + /// The field storing the property's value. + /// The property's value after the change occurred. + /// If , will also be invoked. + /// (optional) The name of the property that changed. + /// if the property was changed, otherwise. + /// + /// This method is just like , just with the addition + /// of the parameter. As such, following the behavior of the base method, the + /// and events + /// are not raised if the current and new value for the target property are the same. + /// + protected bool Set(ref T field, T newValue, bool broadcast, [CallerMemberName] string propertyName = null!) + { + if (!broadcast) + { + return Set(ref field, newValue, propertyName); + } + + T oldValue = field; + + if (Set(ref field, newValue, propertyName)) + { + Broadcast(oldValue, newValue, propertyName); + + return true; + } + + return false; + } + } +} \ No newline at end of file From e549aef8ba7f52fc7c3c4f18fd9d5ce641407978 Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Wed, 15 Apr 2020 17:50:01 +0200 Subject: [PATCH 056/595] The Messenger class is not static anymore --- Microsoft.Toolkit.Mvvm/Messaging/Messenger.cs | 191 +++++++++++------- .../DictionarySlim{TKey,TValue}.cs | 2 +- Microsoft.Toolkit.Mvvm/ViewModelBase.cs | 4 +- UnitTests/Mvvm/Test_Messenger.cs | 42 ++-- 4 files changed, 145 insertions(+), 94 deletions(-) diff --git a/Microsoft.Toolkit.Mvvm/Messaging/Messenger.cs b/Microsoft.Toolkit.Mvvm/Messaging/Messenger.cs index 12c5eb2e0ad..af413350680 100644 --- a/Microsoft.Toolkit.Mvvm/Messaging/Messenger.cs +++ b/Microsoft.Toolkit.Mvvm/Messaging/Messenger.cs @@ -15,31 +15,42 @@ namespace Microsoft.Toolkit.Mvvm.Messaging /// /// A type implementing a messaging system between objects. /// - public static class Messenger + public sealed class Messenger { // The Messenger class uses the following logic to link stored instances together: // -------------------------------------------------------------------------------------------------------- - // DictionarySlim>> RecipientsMap - // | / \ / - // \_________________/_____ \__/____[*]IDictionarySlim - // / \ / ______________/ - // / \ / / - // Container.DictionarySlim>> Values + // DictionarySlim>> recipientsMap + // | \ / + // | \____/_____[*]IDictionarySlim + // | ____________________________/ \____ + // | / \ + // DictionarySlim>> Values + // / / / + // __(T1)______________/_______________/ / + // / ____(T2)______/ / + // / / / + // / / ________(as object)_____________/ + // / / / + // DictionarySlim<(Type, Type), object> typesMap; // -------------------------------------------------------------------------------------------------------- - // Each combination of results in a concrete Container<,> type, which holds the - // mapping to registered recipients to handlers. The handlers are stored in a > + // Each combination of results in a concrete Mapping type, which holds + // the references from registered recipients to handlers. The handlers are stored in a > // dictionary, so that each recipient can have up to one registered handler for a given token, for each - // message type. Each existing recipient is also stored in the main recipients map, along with a set of - // all the existing dictionaries of handlers for that recipient (for all message types and token types). - // A recipient is stored in the main map as long as it has at least one registered handler in any of the - // existing mappings for every message/token type combination. The shared map is used to access the set - // of all registered handlers for a given recipient, without having to know in advance the type of message - // or token being used for the registration, and without having to use reflection. Note that each dictionary - // stored in the associated set for each recipient also implements IDictionarySlim, with - // any token type currently in use by that recipient. This allows to retrieve the type-closed mappings - // of registered handlers with a given token type, for any message type, for every receiver, again without - // having to use reflection. This shared map is used to unregister messages from a given recipients either - // unconditionally, by message type, by token, or for a specific pair of message type and token value. + // message type. Each mapping is stored in the types map, which associates to each pair of concrete types the + // mapping instances. Each instances is just exposed as an object, as each will be a closed type over a different + // combination of TMessage and TToken generic type parameters. Each existing recipient is also stored in the main + // recipients map, along with a set of all the existing dictionaries of handlers for that recipient (for all + // message types and token types). A recipient is stored in the main map as long as it has at least one + // registered handler in any of the existing mappings for every message/token type combination. + // The shared map is used to access the set of all registered handlers for a given recipient, without having + // to know in advance the type of message or token being used for the registration, and without having to + // use reflection. This is the same approach used in the types map, just with a more specific exposed reference + // type as in this case we have at least some partial knowledge of the generic type parameters of the mapped types. + // Note that each dictionary stored in the associated set for each recipient also implements + // IDictionarySlim, with any token type currently in use by that recipient. This allows to retrieve + // the type-closed mappings of registered handlers with a given token type, for an message type, for every receiver, + // again without having to use reflection. This shared map is used to unregister messages from a given recipients + // either unconditionally, by message type, by token, or for a specific pair of message type and token value. /// /// The collection of currently registered recipients, with a link to their linked message receivers. @@ -50,9 +61,25 @@ public static class Messenger /// so that all the existing handlers can be removed without having to dynamically create /// the generic types for the containers of the various dictionaries mapping the handlers. /// - private static readonly DictionarySlim>> RecipientsMap + private readonly DictionarySlim>> recipientsMap = new DictionarySlim>>(); + /// + /// The instance for types combination. + /// + /// + /// The values are just of type as we don't know the type parameters in advance. + /// Each method relies on to get the type-safe instance + /// of the class for each pair of generic arguments in use. + /// + private readonly DictionarySlim<(Type, Type), object> typesMap + = new DictionarySlim<(Type, Type), object>(); + + /// + /// Gets the default instance. + /// + public static Messenger Default { get; } = new Messenger(); + /// /// Checks whether or not a given recipient has already been registered for a message. /// @@ -60,7 +87,7 @@ private static readonly DictionarySlimThe target recipient to check the registration for. /// Whether or not has already been registered for the specified message. [Pure] - public static bool IsRegistered(object recipient) + public bool IsRegistered(object recipient) where TMessage : class { return IsRegistered(recipient, default); @@ -75,13 +102,13 @@ public static bool IsRegistered(object recipient) /// The token used to identify the target channel to check. /// Whether or not has already been registered for the specified message. [Pure] - public static bool IsRegistered(object recipient, TToken token) + public bool IsRegistered(object recipient, TToken token) where TMessage : class where TToken : notnull, IEquatable { - lock (RecipientsMap) + lock (this.recipientsMap) { - var values = Container.Values; + Mapping values = GetMapping(); var key = new Recipient(recipient); return values.ContainsKey(key); @@ -95,7 +122,7 @@ public static bool IsRegistered(object recipient, TToken token /// The recipient that will receive the messages. /// The to invoke when a message is received. /// Thrown when trying to register the same message twice. - public static void Register(object recipient, Action action) + public void Register(object recipient, Action action) where TMessage : class { Register(recipient, default(Unit), action); @@ -110,14 +137,14 @@ public static void Register(object recipient, Action action) /// A token used to determine the receiving channel to use. /// The to invoke when a message is received. /// Thrown when trying to register the same message twice. - public static void Register(object recipient, TToken token, Action action) + public void Register(object recipient, TToken token, Action action) where TMessage : class where TToken : notnull, IEquatable { - lock (RecipientsMap) + lock (this.recipientsMap) { // Get the registration list for this recipient - var values = Container.Values; + Mapping values = GetMapping(); var key = new Recipient(recipient); ref DictionarySlim> map = ref values.GetOrAddValueRef(key); @@ -137,7 +164,7 @@ public static void Register(object recipient, TToken token, Ac handler = action; // Make sure this registration map is tracked for the current recipient - ref HashSet> set = ref RecipientsMap.GetOrAddValueRef(key); + ref HashSet> set = ref this.recipientsMap.GetOrAddValueRef(key); if (set is null) { @@ -152,13 +179,13 @@ public static void Register(object recipient, TToken token, Ac /// Unregisters a recipient from all registered messages. /// /// The recipient to unregister. - public static void Unregister(object recipient) + public void Unregister(object recipient) { - lock (RecipientsMap) + lock (this.recipientsMap) { // If the recipient has no registered messages at all, ignore var key = new Recipient(recipient); - ref HashSet> set = ref RecipientsMap.GetOrAddValueRef(key); + ref HashSet> set = ref this.recipientsMap.GetOrAddValueRef(key); if (set is null) { @@ -172,7 +199,7 @@ public static void Unregister(object recipient) } // Remove the associated set in the static map - RecipientsMap.Remove(key); + this.recipientsMap.Remove(key); } } @@ -182,7 +209,7 @@ public static void Unregister(object recipient) /// The type of message to stop receiving. /// The recipient to unregister. /// If the recipient has no registered handler, this method does nothing. - public static void Unregister(object recipient) + public void Unregister(object recipient) where TMessage : class { Unregister(recipient, default); @@ -195,15 +222,15 @@ public static void Unregister(object recipient) /// The recipient to unregister. /// The token to use to identify which handlers to unregister. /// If the recipient has no registered handler, this method does nothing. - public static void Unregister(object recipient, TToken token) + public void Unregister(object recipient, TToken token) where TToken : notnull, IEquatable { - lock (RecipientsMap) + lock (this.recipientsMap) { // Get the shared set of mappings for the recipient, if present var key = new Recipient(recipient); - if (!RecipientsMap.TryGetValue(key, out HashSet> set)) + if (!this.recipientsMap.TryGetValue(key, out HashSet> set)) { return; } @@ -257,7 +284,7 @@ public static void Unregister(object recipient, TToken token) if (set.Count == 0) { - RecipientsMap.Remove(key); + this.recipientsMap.Remove(key); } } } @@ -280,14 +307,14 @@ public static void Unregister(object recipient, TToken token) /// The recipient to unregister. /// The token to use to identify which handlers to unregister. /// If the recipient has no registered handler, this method does nothing. - public static void Unregister(object recipient, TToken token) + public void Unregister(object recipient, TToken token) where TMessage : class where TToken : notnull, IEquatable { - lock (RecipientsMap) + lock (this.recipientsMap) { // Get the registration list (same as above) - var values = Container.Values; + Mapping values = GetMapping(); var key = new Recipient(recipient); if (!values.TryGetValue(key, out DictionarySlim> map)) @@ -307,13 +334,13 @@ public static void Unregister(object recipient, TToken token) { values.Remove(key); - HashSet> set = RecipientsMap[key]; + HashSet> set = this.recipientsMap[key]; set.Remove(values); if (set.Count == 0) { - RecipientsMap.Remove(key); + this.recipientsMap.Remove(key); } } } @@ -328,7 +355,7 @@ public static void Unregister(object recipient, TToken token) /// message type exposes a parameterless constructor: it will automatically create /// a new instance and send that to its recipients. /// - public static void Send() + public void Send() where TMessage : class, new() { Send(new TMessage(), default(Unit)); @@ -339,7 +366,7 @@ public static void Send() /// /// The type of message to send. /// The message to send. - public static void Send(TMessage message) + public void Send(TMessage message) where TMessage : class { Send(message, default(Unit)); @@ -355,7 +382,7 @@ public static void Send(TMessage message) /// This method will automatically create a new instance /// just like , and then send it to the right recipients. /// - public static void Send(TToken token) + public void Send(TToken token) where TMessage : class, new() where TToken : notnull, IEquatable { @@ -369,21 +396,21 @@ public static void Send(TToken token) /// The type of token to identify what channel to use to send the message. /// The message to send. /// The token indicating what channel to use. - public static void Send(TMessage message, TToken token) + public void Send(TMessage message, TToken token) where TMessage : class where TToken : notnull, IEquatable { Action[] entries; int i = 0; - lock (RecipientsMap) + lock (this.recipientsMap) { /* We need to make a local copy of the currently registered handlers, * since users might try to unregister (or register) new handlers from * inside one of the currently existing handlers. We can use memory pooling * to reuse arrays, to minimize the average memory usage. In practice, * we usually just need to pay the small overhead of copying the items. */ - var values = Container.Values; + Mapping values = GetMapping(); // Count the total number of recipients (including with different tokens) foreach (var pair in values) @@ -444,7 +471,7 @@ public static void Send(TMessage message, TToken token) /// message type exposes a parameterless constructor: it will automatically create /// a new instance and send that to its recipients. /// - public static TResult Request() + public TResult Request() where TMessage : RequestMessage, new() { return Request(new TMessage(), default); @@ -458,7 +485,7 @@ public static TResult Request() /// The message to send. /// The response value for the given message. /// Thrown if no response is received for the message. - public static TResult Request(TMessage message) + public TResult Request(TMessage message) where TMessage : RequestMessage { return Request(message, default); @@ -477,7 +504,7 @@ public static TResult Request(TMessage message) /// This method will automatically create a new instance /// just like , and then send it to the right recipients. /// - public static TResult Request(TToken token) + public TResult Request(TToken token) where TMessage : RequestMessage, new() where TToken : notnull, IEquatable { @@ -494,7 +521,7 @@ public static TResult Request(TToken token) /// The token indicating what channel to use. /// The response value for the given message. /// Thrown if no response is received for the message. - public static TResult Request(TMessage message, TToken token) + public TResult Request(TMessage message, TToken token) where TMessage : RequestMessage where TToken : notnull, IEquatable { @@ -511,11 +538,11 @@ public static TResult Request(TMessage message, TToke /// /// Resets the class and unregisters all the existing recipients. /// - public static void Reset() + public void Reset() { - lock (RecipientsMap) + lock (this.recipientsMap) { - foreach (var pair in RecipientsMap) + foreach (var pair in this.recipientsMap) { // Clear all the typed maps, as they're assigned to static fields foreach (var map in pair.Value) @@ -525,29 +552,53 @@ public static void Reset() } // Clear the shared map too - RecipientsMap.Clear(); + this.recipientsMap.Clear(); + } + } + + /// + /// Gets the instance of currently registered recipients for the + /// combination of types and , along with + /// their registered actions and tokens. Each recipient has an associated + /// mapping each token value to a specific handler on that communication channel, if available. + /// Using a pair of instances for the two indirect mappings allows for + /// quick access to all the handlers per recipient, and to the handler per token for each recipient. + /// + /// The type of message to send. + /// The type of token to identify what channel to use to send the message. + /// A instance with the requested type arguments. + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private Mapping GetMapping() + where TMessage : class + where TToken : notnull, IEquatable + { + ref object target = ref this.typesMap.GetOrAddValueRef((typeof(TMessage), typeof(TToken))); + + if (target is null) + { + target = new Mapping(); } + + /* This method is the only one handling values in the types map, and here we + * are sure that the object reference we have points to an instance of the right + * type. Using an unsafe cast skips two conditional branches and is faster. */ + return Unsafe.As>(target); } /// - /// A container class for registered recipients. + /// A mapping type representing a link to recipients and their view of handlers per communication channel. /// /// The type of message to receive. /// The type of token to use to pick the messages to receive. - private static class Container + /// + /// This type is defined for simplicity and as a workaround for the lack of support for using type aliases + /// over open generic types in C# (using type aliases can only be used for concrete, closed types). + /// + private sealed class Mapping : DictionarySlim>> where TMessage : class where TToken : notnull, IEquatable { - /// - /// The mapping of currently registered recipients for the combination of types - /// and , along with - /// their registered actions and tokens. Each recipient has an associated - /// since it could have a number of handlers registered in parallel, each using a different - /// registration channel specified by the token in use. Using a - /// for the mapping allows for quick access to all the registered entries for each recipient. - /// - public static readonly DictionarySlim>> Values - = new DictionarySlim>>(); } /// diff --git a/Microsoft.Toolkit.Mvvm/Messaging/Microsoft.Collections.Extensions/DictionarySlim{TKey,TValue}.cs b/Microsoft.Toolkit.Mvvm/Messaging/Microsoft.Collections.Extensions/DictionarySlim{TKey,TValue}.cs index 62f442ec8e9..30173f203ed 100644 --- a/Microsoft.Toolkit.Mvvm/Messaging/Microsoft.Collections.Extensions/DictionarySlim{TKey,TValue}.cs +++ b/Microsoft.Toolkit.Mvvm/Messaging/Microsoft.Collections.Extensions/DictionarySlim{TKey,TValue}.cs @@ -32,7 +32,7 @@ namespace Microsoft.Collections.Extensions /// 3) This means it can avoid storing a comparer, and avoid the likely virtual call to a comparer. /// [DebuggerDisplay("Count = {Count}")] - internal sealed class DictionarySlim : IDictionarySlim + internal class DictionarySlim : IDictionarySlim where TKey : notnull, IEquatable { // See info in CoreFX labs for how this works diff --git a/Microsoft.Toolkit.Mvvm/ViewModelBase.cs b/Microsoft.Toolkit.Mvvm/ViewModelBase.cs index 4e1ddd293d9..86f02477646 100644 --- a/Microsoft.Toolkit.Mvvm/ViewModelBase.cs +++ b/Microsoft.Toolkit.Mvvm/ViewModelBase.cs @@ -71,7 +71,7 @@ protected virtual void OnActivate() /// The base implementation unregisters all messages for this recipient (with no token). protected virtual void OnDeactivate() { - Messenger.Unregister(this); + Messenger.Default.Unregister(this); } /// @@ -90,7 +90,7 @@ protected virtual void Broadcast(T oldValue, T newValue, string propertyName) { var message = new PropertyChangedMessage(this, propertyName, oldValue, newValue); - Messenger.Send(message); + Messenger.Default.Send(message); } /// diff --git a/UnitTests/Mvvm/Test_Messenger.cs b/UnitTests/Mvvm/Test_Messenger.cs index fd44a47fdf3..fdc15b625ee 100644 --- a/UnitTests/Mvvm/Test_Messenger.cs +++ b/UnitTests/Mvvm/Test_Messenger.cs @@ -16,23 +16,23 @@ public void Test_Ioc_IsRegistered_Register_Send_UnregisterOfTMessage_WithNoToken { object a = new object(); - Assert.IsFalse(Messenger.IsRegistered(a)); + Assert.IsFalse(Messenger.Default.IsRegistered(a)); string result = null; - Messenger.Register(a, m => result = m.Text); + Messenger.Default.Register(a, m => result = m.Text); - Assert.IsTrue(Messenger.IsRegistered(a)); + Assert.IsTrue(Messenger.Default.IsRegistered(a)); - Messenger.Send(new MessageA { Text = nameof(MessageA) }); + Messenger.Default.Send(new MessageA { Text = nameof(MessageA) }); Assert.AreEqual(result, nameof(MessageA)); - Messenger.Unregister(a); + Messenger.Default.Unregister(a); - Assert.IsFalse(Messenger.IsRegistered(a)); + Assert.IsFalse(Messenger.Default.IsRegistered(a)); result = null; - Messenger.Send(new MessageA { Text = nameof(MessageA) }); + Messenger.Default.Send(new MessageA { Text = nameof(MessageA) }); Assert.IsNull(result); } @@ -43,23 +43,23 @@ public void Test_Ioc_IsRegistered_Register_Send_UnregisterRecipient_WithNoToken( { object a = new object(); - Assert.IsFalse(Messenger.IsRegistered(a)); + Assert.IsFalse(Messenger.Default.IsRegistered(a)); string result = null; - Messenger.Register(a, m => result = m.Text); + Messenger.Default.Register(a, m => result = m.Text); - Assert.IsTrue(Messenger.IsRegistered(a)); + Assert.IsTrue(Messenger.Default.IsRegistered(a)); - Messenger.Send(new MessageA { Text = nameof(MessageA) }); + Messenger.Default.Send(new MessageA { Text = nameof(MessageA) }); Assert.AreEqual(result, nameof(MessageA)); - Messenger.Unregister(a); + Messenger.Default.Unregister(a); - Assert.IsFalse(Messenger.IsRegistered(a)); + Assert.IsFalse(Messenger.Default.IsRegistered(a)); result = null; - Messenger.Send(new MessageA { Text = nameof(MessageA) }); + Messenger.Default.Send(new MessageA { Text = nameof(MessageA) }); Assert.IsNull(result); } @@ -70,23 +70,23 @@ public void Test_Ioc_IsRegistered_Register_Send_UnregisterOfTMessage_WithToken() { object a = new object(); - Assert.IsFalse(Messenger.IsRegistered(a)); + Assert.IsFalse(Messenger.Default.IsRegistered(a)); string result = null; - Messenger.Register(a, nameof(MessageA), m => result = m.Text); + Messenger.Default.Register(a, nameof(MessageA), m => result = m.Text); - Assert.IsTrue(Messenger.IsRegistered(a, nameof(MessageA))); + Assert.IsTrue(Messenger.Default.IsRegistered(a, nameof(MessageA))); - Messenger.Send(new MessageA { Text = nameof(MessageA) }, nameof(MessageA)); + Messenger.Default.Send(new MessageA { Text = nameof(MessageA) }, nameof(MessageA)); Assert.AreEqual(result, nameof(MessageA)); - Messenger.Unregister(a, nameof(MessageA)); + Messenger.Default.Unregister(a, nameof(MessageA)); - Assert.IsFalse(Messenger.IsRegistered(a, nameof(MessageA))); + Assert.IsFalse(Messenger.Default.IsRegistered(a, nameof(MessageA))); result = null; - Messenger.Send(new MessageA { Text = nameof(MessageA) }, nameof(MessageA)); + Messenger.Default.Send(new MessageA { Text = nameof(MessageA) }, nameof(MessageA)); Assert.IsNull(result); } From 1121fbefe956e60c742e350355f7f5bc60b2f463 Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Wed, 15 Apr 2020 17:56:55 +0200 Subject: [PATCH 057/595] Added messaging customization to ViewModelBase --- Microsoft.Toolkit.Mvvm/ViewModelBase.cs | 34 +++++++++++++++++++++++-- 1 file changed, 32 insertions(+), 2 deletions(-) diff --git a/Microsoft.Toolkit.Mvvm/ViewModelBase.cs b/Microsoft.Toolkit.Mvvm/ViewModelBase.cs index 86f02477646..800cdadf81f 100644 --- a/Microsoft.Toolkit.Mvvm/ViewModelBase.cs +++ b/Microsoft.Toolkit.Mvvm/ViewModelBase.cs @@ -32,6 +32,36 @@ namespace Microsoft.Toolkit.Mvvm /// public abstract class ViewModelBase : ObservableObject { + /// + /// The optional instance to use. + /// + private readonly Messenger? messenger; + + /// + /// Initializes a new instance of the class. + /// + protected ViewModelBase() + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The instance to use to send messages. + /// + /// If is , the + /// instance will be used to send message (eg. from or ). + /// + protected ViewModelBase(Messenger messenger) + { + this.messenger = messenger; + } + + /// + /// Gets the instance in use. + /// + protected Messenger Messenger => this.messenger ?? Messenger.Default; + private bool isActive; /// @@ -71,7 +101,7 @@ protected virtual void OnActivate() /// The base implementation unregisters all messages for this recipient (with no token). protected virtual void OnDeactivate() { - Messenger.Default.Unregister(this); + Messenger.Unregister(this); } /// @@ -90,7 +120,7 @@ protected virtual void Broadcast(T oldValue, T newValue, string propertyName) { var message = new PropertyChangedMessage(this, propertyName, oldValue, newValue); - Messenger.Default.Send(message); + Messenger.Send(message); } /// From 1e3e1e4fbc18fe01819be3dcf6f31d74a77ff30f Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Wed, 15 Apr 2020 22:23:36 +0200 Subject: [PATCH 058/595] The Ioc class is not static anymore --- Microsoft.Toolkit.Mvvm/Ioc.cs | 373 +++++++++++++++++++++------------- UnitTests/Mvvm/Test_Ioc.cs | 41 ++-- 2 files changed, 256 insertions(+), 158 deletions(-) diff --git a/Microsoft.Toolkit.Mvvm/Ioc.cs b/Microsoft.Toolkit.Mvvm/Ioc.cs index bb5ccce4675..ee472b8161c 100644 --- a/Microsoft.Toolkit.Mvvm/Ioc.cs +++ b/Microsoft.Toolkit.Mvvm/Ioc.cs @@ -3,13 +3,11 @@ // See the LICENSE file in the project root for more information. using System; -using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.Diagnostics.Contracts; -using System.Linq; using System.Linq.Expressions; -using System.Reflection; using System.Runtime.CompilerServices; +using Microsoft.Collections.Extensions; #nullable enable @@ -33,27 +31,26 @@ namespace Microsoft.Toolkit.Mvvm /// /// Then, register your services at startup, or at some time before using them: /// - /// IoC.Register<ILogger, ConsoleLogger>(); + /// Ioc.Default.Register<ILogger, ConsoleLogger>(); /// /// Finally, use the type to retrieve service instances to use: /// - /// IoC.GetInstance<ILogger>().Log("Hello world!"); + /// Ioc.Default.GetInstance<ILogger>().Log("Hello world!"); /// /// The type will make sure to initialize your service instance if needed, or it will /// throw an exception in case the requested service has not been registered yet. /// - public static class Ioc + public sealed class Ioc { /// - /// The collection of currently registered service types. + /// The instance for each registered type. /// - /// - /// This list is not used when retrieving registered instances through - /// the method, so it has no impact on - /// performances there. This is only used to allow users to retrieve all - /// the registered instances at any given time, or to unregister them all. - /// - private static readonly HashSet RegisteredTypes = new HashSet(); + private readonly DictionarySlim typesMap = new DictionarySlim(); + + /// + /// Gets the default instance. + /// + public static Ioc Default { get; } = new Ioc(); /// /// Checks whether or not a service of type has already been registered. @@ -61,14 +58,12 @@ public static class Ioc /// The type of service to check for registration. /// if the service has already been registered, otherwise. [Pure] - public static bool IsRegistered() + public bool IsRegistered() where TService : class { - lock (Container.Lock) + lock (this.typesMap) { - return - !(Container.Instance is null) || - !(Container.Factory is null); + return TryGetContainer(out _); } } @@ -78,7 +73,7 @@ public static bool IsRegistered() /// The type of service to register. /// The type of service provider for type to register. /// This method will create a new instance for future use. - public static void Register() + public void Register() where TService : class where TProvider : class, TService, new() { @@ -90,16 +85,15 @@ public static void Register() /// /// The type of service to register. /// The instance to register. - public static void Register(TService provider) + public void Register(TService provider) where TService : class { - lock (RegisteredTypes) - lock (Container.Lock) + lock (this.typesMap) { - RegisteredTypes.Add(typeof(TService)); + Container container = GetContainer(); - Container.Factory = null; - Container.Instance = provider; + container.Factory = null; + container.Instance = provider; } } @@ -108,16 +102,15 @@ public static void Register(TService provider) /// /// The type of service to register. /// The factory of instances implementing the service to use. - public static void Register(Func factory) + public void Register(Func factory) where TService : class { - lock (RegisteredTypes) - lock (Container.Lock) + lock (this.typesMap) { - RegisteredTypes.Add(typeof(TService)); + Container container = GetContainer(); - Container.Factory = factory; - Container.Instance = null; + container.Factory = factory; + container.Instance = null; } } @@ -125,83 +118,88 @@ public static void Register(Func factory) /// Unregisters a service of a specified type. /// /// The type of service to unregister. - public static void Unregister() + public void Unregister() where TService : class { - lock (RegisteredTypes) - lock (Container.Lock) + lock (this.typesMap) { - RegisteredTypes.Remove(typeof(TService)); + var key = new Key(typeof(TService)); - Container.Factory = null; - Container.Instance = null; + this.typesMap.Remove(key); } } /// /// Resets the internal state of the type and unregisters all services. /// - public static void Reset() + public void Reset() { - lock (RegisteredTypes) + lock (this.typesMap) { - foreach (Type serviceType in RegisteredTypes) - { - Type containerType = typeof(Container<>).MakeGenericType(serviceType); - FieldInfo - lockField = containerType.GetField(nameof(Container.Lock)), - lazyField = containerType.GetField(nameof(Container.Factory)), - instanceField = containerType.GetField(nameof(Container.Instance)); - - lock (lockField.GetValue(null)) - { - lazyField.SetValue(null, null); - instanceField.SetValue(null, null); - } - } - - RegisteredTypes.Clear(); + this.typesMap.Clear(); } } /// /// Gets all the currently registered services. /// - /// A collection of all the currently registered services. + /// A collection of all the currently registered services. /// /// This will also cause the service to resolve instances of /// registered services that were setup to use lazy initialization. /// [Pure] - public static IReadOnlyCollection GetAllServices() + public ReadOnlyMemory GetAllServices() { - lock (RegisteredTypes) + lock (this.typesMap) { - return ( - from Type serviceType in RegisteredTypes - let resolver = typeof(Ioc).GetMethod(nameof(GetInstance)) - let resolverOfT = resolver.MakeGenericMethod(serviceType) - let service = resolverOfT.Invoke(null, null) - select service).ToArray(); + if (this.typesMap.Count == 0) + { + return Array.Empty(); + } + + object[] services = new object[this.typesMap.Count]; + + int i = 0; + foreach (var pair in this.typesMap) + { + services[i++] = pair.Value.Instance ?? pair.Value.Factory!(); + } + + return services; } } /// /// Gets all the currently registered and instantiated services. /// - /// A collection of all the currently registered and instantiated services. + /// A collection of all the currently registered and instantiated services. [Pure] - public static IReadOnlyCollection GetAllCreatedServices() + public ReadOnlyMemory GetAllCreatedServices() { - lock (RegisteredTypes) + lock (this.typesMap) { - return ( - from Type serviceType in RegisteredTypes - let containerType = typeof(Container<>).MakeGenericType(serviceType) - let field = containerType.GetField(nameof(Container.Instance)) - let instance = field.GetValue(null) - where !(instance is null) - select instance).ToArray(); + if (this.typesMap.Count == 0) + { + return Array.Empty(); + } + + object[] services = new object[this.typesMap.Count]; + + int i = 0; + foreach (var pair in this.typesMap) + { + object? service = pair.Value.Instance; + + if (service is null) + { + continue; + } + + services[i++] = service; + } + + return services.AsMemory(0, i); } } @@ -211,26 +209,19 @@ from Type serviceType in RegisteredTypes /// The type of service to look for. /// An instance of a registered type implementing , if registered. [Pure] - public static bool TryGetInstance(out TService? service) + public bool TryGetInstance(out TService? service) where TService : class { - lock (Container.Lock) + lock (this.typesMap) { - service = Container.Instance; - - if (!(service is null)) + if (!TryGetContainer(out Container? container)) { - return true; - } - - Func? factory = Container.Factory; + service = null; - if (factory is null) - { return false; } - service = Container.Instance = factory(); + service = container!.Instance ?? container.Factory?.Invoke(); return true; } @@ -241,88 +232,151 @@ public static bool TryGetInstance(out TService? service) /// /// The type of service to look for. /// An instance of a registered type implementing . + /// Thrown if the requested service was not registered. [Pure] [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static TService GetInstance() + public TService GetInstance() where TService : class { - TService? service = Container.Instance; - - if (service is null) + if (!TryGetInstance(out TService? service)) { - return GetInstanceOrThrow(); + ThrowInvalidOperationExceptionOnServiceNotRegistered(); } - return service; + return service!; } /// - /// Tries to resolve a instance with additional checks. - /// This method implements the slow path for , locking - /// to ensure thread-safety and invoking the available factory, if possible. + /// Creates an instance of a registered type implementing the service. /// /// The type of service to look for. - /// An instance of a registered type implementing . - [MethodImpl(MethodImplOptions.NoInlining)] - private static TService GetInstanceOrThrow() + /// A new instance of a registered type implementing . + [Pure] + public TService GetInstanceWithoutCaching() where TService : class { - lock (Container.Lock) + lock (this.typesMap) { - TService? service = Container.Instance; + if (!TryGetContainer(out Container? container)) + { + ThrowInvalidOperationExceptionOnServiceNotRegistered(); + } + + Func? factory = container!.Factory; - // Check the instance field again for race conditions - if (!(service is null)) + if (!(factory is null)) { - return service; + return factory(); } - Func? factory = Container.Factory; + TService service = container.Instance!; - // If no factory is available, the service hasn't been registered yet - if (factory is null) + if (service.GetType().GetConstructor(Type.EmptyTypes) is null) { - throw new InvalidOperationException($"Service {typeof(TService)} not initialized"); + ThrowInvalidOperationExceptionOnServiceWithNoConstructor(service.GetType()); } - return Container.Instance = factory(); + Type serviceType = service.GetType(); + Expression[] expressions = { Expression.New(serviceType) }; + Expression body = Expression.Block(serviceType, expressions); + factory = Expression.Lambda>(body).Compile(); + + // Cache for later use + container.Factory = factory; + + return factory(); } } /// - /// Creates an instance of a registered type implementing the service. + /// Tries to get a instance for a specific service type. /// /// The type of service to look for. - /// A new instance of a registered type implementing . + /// The resulting , if found. + /// Whether or not the container was present. [Pure] [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static TService GetInstanceWithoutCaching() + private bool TryGetContainer(out Container? container) where TService : class { - lock (Container.Lock) + var key = new Key(typeof(TService)); + + if (this.typesMap.TryGetValue(key, out IContainer local)) { - Func? factory = Container.Factory; + // We can use a fast cast here as the type is always known + container = Unsafe.As>(local); - if (!(factory is null)) - { - return factory(); - } + return true; + } - TService? service = Container.Instance; + container = null; - if (!(service is null)) - { - Type serviceType = service.GetType(); - Expression[] expressions = { Expression.New(serviceType) }; - Expression body = Expression.Block(serviceType, expressions); - factory = Expression.Lambda>(body).Compile(); + return false; + } - Container.Factory = factory; + /// + /// Gets the instance for a specific service type. + /// + /// The type of service to look for. + /// A instance with the requested type argument. + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private Container GetContainer() + where TService : class + { + var key = new Key(typeof(TService)); + ref IContainer target = ref this.typesMap.GetOrAddValueRef(key); - return factory(); - } + if (target is null) + { + target = new Container(); + } + + return Unsafe.As>(target); + } + + /// + /// A simple type representing a registered type. + /// + /// + /// This type is used to enable fast indexing in the type mapping, and is used to externally + /// inject the interface to . + /// + private readonly struct Key : IEquatable + { + /// + /// The registered type. + /// + private readonly Type type; + + /// + /// Initializes a new instance of the struct. + /// + /// The input instance. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Key(Type type) + { + this.type = type; + } - throw new InvalidOperationException($"Service {typeof(TService)} not initialized"); + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool Equals(Key other) + { + return ReferenceEquals(this.type, other.type); + } + + /// + public override bool Equals(object? obj) + { + return obj is Key other && Equals(other); + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public override int GetHashCode() + { + return RuntimeHelpers.GetHashCode(this.type); } } @@ -330,24 +384,69 @@ public static TService GetInstanceWithoutCaching() /// Internal container type for services of type . /// /// The type of services to store in this type. - [SuppressMessage("StyleCop.CSharp.MaintainabilityRules", "SA1401", Justification = "Public fields for performance reasons")] - private static class Container + [SuppressMessage("StyleCop.CSharp.MaintainabilityRules", "SA1401", Justification = "The type is private")] + private sealed class Container : IContainer where T : class { - /// - /// An used to synchronize accesses to the and fields. - /// - public static readonly object Lock = new object(); + /// + public object Lock { get; } = new object(); /// /// The optional instance to produce instances of the service . /// - public static Func? Factory; + public Func? Factory; /// /// The current instance being produced, if available. /// - public static T? Instance; + public T? Instance; + + /// + Func? IContainer.Factory => this.Factory; + + /// + object? IContainer.Instance => this.Instance; + } + + /// + /// A interface for the type. + /// + private interface IContainer + { + /// + /// Gets an used to synchronize accesses to the and fields. + /// + object Lock { get; } + + /// + /// Gets the optional instance to produce instances of the service. + /// + Func? Factory { get; } + + /// + /// Gets the current instance being produced, if available. + /// + object? Instance { get; } + } + + /// + /// Throws an when a service of a given type is not found. + /// + /// The type of service not found. + [MethodImpl(MethodImplOptions.NoInlining)] + private static void ThrowInvalidOperationExceptionOnServiceNotRegistered() + { + throw new InvalidOperationException($"Service {typeof(TService)} not registered"); + } + + /// + /// Throws an when a service doesn't expose a public parametrless constructor. + /// + /// The type of service not found. + [MethodImpl(MethodImplOptions.NoInlining)] + private static void ThrowInvalidOperationExceptionOnServiceWithNoConstructor(Type type) + { + throw new InvalidOperationException($"Type {type} implementing service {typeof(TService)} does not expose a public constructor"); } } } diff --git a/UnitTests/Mvvm/Test_Ioc.cs b/UnitTests/Mvvm/Test_Ioc.cs index 45b56d7657f..7a4b13cb0c2 100644 --- a/UnitTests/Mvvm/Test_Ioc.cs +++ b/UnitTests/Mvvm/Test_Ioc.cs @@ -2,7 +2,6 @@ // 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.Linq; using Microsoft.Toolkit.Mvvm; using Microsoft.VisualStudio.TestTools.UnitTesting; @@ -15,38 +14,38 @@ public class Test_Ioc [TestMethod] public void Test_Ioc_SampleUsage() { - Assert.IsFalse(Ioc.IsRegistered()); - Assert.AreEqual(Ioc.GetAllServices().Count, 0); + Assert.IsFalse(Ioc.Default.IsRegistered()); + Assert.AreEqual(Ioc.Default.GetAllServices().Length, 0); - Ioc.Register(); + Ioc.Default.Register(); - Assert.IsTrue(Ioc.IsRegistered()); + Assert.IsTrue(Ioc.Default.IsRegistered()); - var services = Ioc.GetAllServices(); + var services = Ioc.Default.GetAllServices(); - Assert.AreEqual(services.Count, 1); - Assert.IsInstanceOfType(services.First(), typeof(INameService)); - Assert.IsInstanceOfType(services.First(), typeof(BobService)); + Assert.AreEqual(services.Length, 1); + Assert.IsInstanceOfType(services.Span[0], typeof(INameService)); + Assert.IsInstanceOfType(services.Span[0], typeof(BobService)); - Ioc.Unregister(); + Ioc.Default.Unregister(); - Assert.IsFalse(Ioc.IsRegistered()); - Assert.AreEqual(Ioc.GetAllServices().Count, 0); + Assert.IsFalse(Ioc.Default.IsRegistered()); + Assert.AreEqual(Ioc.Default.GetAllServices().Length, 0); - Ioc.Register(() => new AliceService()); + Ioc.Default.Register(() => new AliceService()); - Assert.IsTrue(Ioc.IsRegistered()); + Assert.IsTrue(Ioc.Default.IsRegistered()); - services = Ioc.GetAllServices(); + services = Ioc.Default.GetAllServices(); - Assert.AreEqual(services.Count, 1); - Assert.IsInstanceOfType(services.First(), typeof(INameService)); - Assert.IsInstanceOfType(services.First(), typeof(AliceService)); + Assert.AreEqual(services.Length, 1); + Assert.IsInstanceOfType(services.Span[0], typeof(INameService)); + Assert.IsInstanceOfType(services.Span[0], typeof(AliceService)); - Ioc.Reset(); + Ioc.Default.Reset(); - Assert.IsFalse(Ioc.IsRegistered()); - Assert.AreEqual(Ioc.GetAllServices().Count, 0); + Assert.IsFalse(Ioc.Default.IsRegistered()); + Assert.AreEqual(Ioc.Default.GetAllServices().Length, 0); } public interface INameService From 7396d08320344274c8597783f17d41d15fc9e4ed Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Wed, 15 Apr 2020 22:29:07 +0200 Subject: [PATCH 059/595] Added dependency injection of service provider in ViewModelBase --- Microsoft.Toolkit.Mvvm/ViewModelBase.cs | 38 ++++++++++++++++++++++--- 1 file changed, 34 insertions(+), 4 deletions(-) diff --git a/Microsoft.Toolkit.Mvvm/ViewModelBase.cs b/Microsoft.Toolkit.Mvvm/ViewModelBase.cs index 800cdadf81f..2026ba61017 100644 --- a/Microsoft.Toolkit.Mvvm/ViewModelBase.cs +++ b/Microsoft.Toolkit.Mvvm/ViewModelBase.cs @@ -37,9 +37,18 @@ public abstract class ViewModelBase : ObservableObject /// private readonly Messenger? messenger; + /// + /// The optional instance to use. + /// + private readonly Ioc? ioc; + /// /// Initializes a new instance of the class. /// + /// + /// This constructor will produce an instance that will use the + /// and services to perform operations. + /// protected ViewModelBase() { } @@ -48,20 +57,41 @@ protected ViewModelBase() /// Initializes a new instance of the class. /// /// The instance to use to send messages. - /// - /// If is , the - /// instance will be used to send message (eg. from or ). - /// protected ViewModelBase(Messenger messenger) { this.messenger = messenger; } + /// + /// Initializes a new instance of the class. + /// + /// The optional instance to use to retrieve services. + protected ViewModelBase(Ioc ioc) + { + this.ioc = ioc; + } + + /// + /// Initializes a new instance of the class. + /// + /// The instance to use to send messages. + /// The optional instance to use to retrieve services. + protected ViewModelBase(Messenger messenger, Ioc ioc) + { + this.messenger = messenger; + this.ioc = ioc; + } + /// /// Gets the instance in use. /// protected Messenger Messenger => this.messenger ?? Messenger.Default; + /// + /// Gets the instance in use. + /// + protected Ioc Ioc => this.ioc ?? Ioc.Default; + private bool isActive; /// From 6fa643b5ef9aa5281b413878b3c80fa1052d18c8 Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Wed, 15 Apr 2020 22:32:25 +0200 Subject: [PATCH 060/595] Code refactoring --- Microsoft.Toolkit.Mvvm/{ => Services}/Ioc.cs | 2 +- Microsoft.Toolkit.Mvvm/ViewModelBase.cs | 9 +++++---- UnitTests/Mvvm/Test_Ioc.cs | 2 +- 3 files changed, 7 insertions(+), 6 deletions(-) rename Microsoft.Toolkit.Mvvm/{ => Services}/Ioc.cs (99%) diff --git a/Microsoft.Toolkit.Mvvm/Ioc.cs b/Microsoft.Toolkit.Mvvm/Services/Ioc.cs similarity index 99% rename from Microsoft.Toolkit.Mvvm/Ioc.cs rename to Microsoft.Toolkit.Mvvm/Services/Ioc.cs index ee472b8161c..796a5f06c0f 100644 --- a/Microsoft.Toolkit.Mvvm/Ioc.cs +++ b/Microsoft.Toolkit.Mvvm/Services/Ioc.cs @@ -11,7 +11,7 @@ #nullable enable -namespace Microsoft.Toolkit.Mvvm +namespace Microsoft.Toolkit.Mvvm.Services { /// /// An Inversion of Control container that can be used to register and access instances of types providing services. diff --git a/Microsoft.Toolkit.Mvvm/ViewModelBase.cs b/Microsoft.Toolkit.Mvvm/ViewModelBase.cs index 2026ba61017..4cfc92d4ecf 100644 --- a/Microsoft.Toolkit.Mvvm/ViewModelBase.cs +++ b/Microsoft.Toolkit.Mvvm/ViewModelBase.cs @@ -24,6 +24,7 @@ using System.Runtime.CompilerServices; using Microsoft.Toolkit.Mvvm.Messaging; using Microsoft.Toolkit.Mvvm.Messaging.Messages; +using Microsoft.Toolkit.Mvvm.Services; namespace Microsoft.Toolkit.Mvvm { @@ -47,7 +48,7 @@ public abstract class ViewModelBase : ObservableObject /// /// /// This constructor will produce an instance that will use the - /// and services to perform operations. + /// and services to perform operations. /// protected ViewModelBase() { @@ -65,7 +66,7 @@ protected ViewModelBase(Messenger messenger) /// /// Initializes a new instance of the class. /// - /// The optional instance to use to retrieve services. + /// The optional instance to use to retrieve services. protected ViewModelBase(Ioc ioc) { this.ioc = ioc; @@ -75,7 +76,7 @@ protected ViewModelBase(Ioc ioc) /// Initializes a new instance of the class. /// /// The instance to use to send messages. - /// The optional instance to use to retrieve services. + /// The optional instance to use to retrieve services. protected ViewModelBase(Messenger messenger, Ioc ioc) { this.messenger = messenger; @@ -88,7 +89,7 @@ protected ViewModelBase(Messenger messenger, Ioc ioc) protected Messenger Messenger => this.messenger ?? Messenger.Default; /// - /// Gets the instance in use. + /// Gets the instance in use. /// protected Ioc Ioc => this.ioc ?? Ioc.Default; diff --git a/UnitTests/Mvvm/Test_Ioc.cs b/UnitTests/Mvvm/Test_Ioc.cs index 7a4b13cb0c2..7ddd33f668c 100644 --- a/UnitTests/Mvvm/Test_Ioc.cs +++ b/UnitTests/Mvvm/Test_Ioc.cs @@ -2,7 +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 Microsoft.Toolkit.Mvvm; +using Microsoft.Toolkit.Mvvm.Services; using Microsoft.VisualStudio.TestTools.UnitTesting; namespace UnitTests.Mvvm From c40fc586f0b19a28e8918c250ea0b08b14e3b02d Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Wed, 15 Apr 2020 22:36:36 +0200 Subject: [PATCH 061/595] Added IIoc interface --- Microsoft.Toolkit.Mvvm/Services/IIoc.cs | 112 ++++++++++++++++++++++++ Microsoft.Toolkit.Mvvm/Services/Ioc.cs | 75 +++------------- Microsoft.Toolkit.Mvvm/ViewModelBase.cs | 14 +-- 3 files changed, 131 insertions(+), 70 deletions(-) create mode 100644 Microsoft.Toolkit.Mvvm/Services/IIoc.cs diff --git a/Microsoft.Toolkit.Mvvm/Services/IIoc.cs b/Microsoft.Toolkit.Mvvm/Services/IIoc.cs new file mode 100644 index 00000000000..1da7338c581 --- /dev/null +++ b/Microsoft.Toolkit.Mvvm/Services/IIoc.cs @@ -0,0 +1,112 @@ +// 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.Diagnostics.Contracts; +using System.Runtime.CompilerServices; + +#nullable enable + +namespace Microsoft.Toolkit.Mvvm.Services +{ + /// + /// An interface for a type implementing an Inversion of Control service provider. + /// + public interface IIoc + { + /// + /// Checks whether or not a service of type has already been registered. + /// + /// The type of service to check for registration. + /// if the service has already been registered, otherwise. + [Pure] + bool IsRegistered() + where TService : class; + + /// + /// Registers a singleton instance of service through the type . + /// + /// The type of service to register. + /// The type of service provider for type to register. + /// This method will create a new instance for future use. + void Register() + where TService : class + where TProvider : class, TService, new(); + + /// + /// Registers a singleton instance of service . + /// + /// The type of service to register. + /// The instance to register. + void Register(TService provider) + where TService : class; + + /// + /// Registers a service through a factory. + /// + /// The type of service to register. + /// The factory of instances implementing the service to use. + void Register(Func factory) + where TService : class; + + /// + /// Unregisters a service of a specified type. + /// + /// The type of service to unregister. + void Unregister() + where TService : class; + + /// + /// Resets the internal state of the type and unregisters all services. + /// + void Reset(); + + /// + /// Gets all the currently registered services. + /// + /// A collection of all the currently registered services. + /// + /// This will also cause the service to resolve instances of + /// registered services that were setup to use lazy initialization. + /// + [Pure] + ReadOnlyMemory GetAllServices(); + + /// + /// Gets all the currently registered and instantiated services. + /// + /// A collection of all the currently registered and instantiated services. + [Pure] + ReadOnlyMemory GetAllCreatedServices(); + + /// + /// Tries to resolve an instance of a registered type implementing the service. + /// + /// The type of service to look for. + /// An instance of a registered type implementing , if registered. + [Pure] + bool TryGetInstance(out TService? service) + where TService : class; + + /// + /// Resolves an instance of a registered type implementing the service. + /// + /// The type of service to look for. + /// An instance of a registered type implementing . + /// Thrown if the requested service was not registered. + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining)] + TService GetInstance() + where TService : class; + + /// + /// Creates an instance of a registered type implementing the service. + /// + /// The type of service to look for. + /// A new instance of a registered type implementing . + [Pure] + TService GetInstanceWithoutCaching() + where TService : class; + } +} diff --git a/Microsoft.Toolkit.Mvvm/Services/Ioc.cs b/Microsoft.Toolkit.Mvvm/Services/Ioc.cs index 796a5f06c0f..5a28a23441c 100644 --- a/Microsoft.Toolkit.Mvvm/Services/Ioc.cs +++ b/Microsoft.Toolkit.Mvvm/Services/Ioc.cs @@ -40,7 +40,7 @@ namespace Microsoft.Toolkit.Mvvm.Services /// The type will make sure to initialize your service instance if needed, or it will /// throw an exception in case the requested service has not been registered yet. /// - public sealed class Ioc + public sealed class Ioc : IIoc { /// /// The instance for each registered type. @@ -52,12 +52,7 @@ public sealed class Ioc /// public static Ioc Default { get; } = new Ioc(); - /// - /// Checks whether or not a service of type has already been registered. - /// - /// The type of service to check for registration. - /// if the service has already been registered, otherwise. - [Pure] + /// public bool IsRegistered() where TService : class { @@ -67,12 +62,7 @@ public bool IsRegistered() } } - /// - /// Registers a singleton instance of service through the type . - /// - /// The type of service to register. - /// The type of service provider for type to register. - /// This method will create a new instance for future use. + /// public void Register() where TService : class where TProvider : class, TService, new() @@ -80,11 +70,7 @@ public void Register() Register(new TProvider()); } - /// - /// Registers a singleton instance of service . - /// - /// The type of service to register. - /// The instance to register. + /// public void Register(TService provider) where TService : class { @@ -97,11 +83,7 @@ public void Register(TService provider) } } - /// - /// Registers a service through a factory. - /// - /// The type of service to register. - /// The factory of instances implementing the service to use. + /// public void Register(Func factory) where TService : class { @@ -114,10 +96,7 @@ public void Register(Func factory) } } - /// - /// Unregisters a service of a specified type. - /// - /// The type of service to unregister. + /// public void Unregister() where TService : class { @@ -129,9 +108,7 @@ public void Unregister() } } - /// - /// Resets the internal state of the type and unregisters all services. - /// + /// public void Reset() { lock (this.typesMap) @@ -140,15 +117,7 @@ public void Reset() } } - /// - /// Gets all the currently registered services. - /// - /// A collection of all the currently registered services. - /// - /// This will also cause the service to resolve instances of - /// registered services that were setup to use lazy initialization. - /// - [Pure] + /// public ReadOnlyMemory GetAllServices() { lock (this.typesMap) @@ -170,11 +139,7 @@ public ReadOnlyMemory GetAllServices() } } - /// - /// Gets all the currently registered and instantiated services. - /// - /// A collection of all the currently registered and instantiated services. - [Pure] + /// public ReadOnlyMemory GetAllCreatedServices() { lock (this.typesMap) @@ -203,12 +168,7 @@ public ReadOnlyMemory GetAllCreatedServices() } } - /// - /// Tries to resolve an instance of a registered type implementing the service. - /// - /// The type of service to look for. - /// An instance of a registered type implementing , if registered. - [Pure] + /// public bool TryGetInstance(out TService? service) where TService : class { @@ -227,13 +187,7 @@ public bool TryGetInstance(out TService? service) } } - /// - /// Resolves an instance of a registered type implementing the service. - /// - /// The type of service to look for. - /// An instance of a registered type implementing . - /// Thrown if the requested service was not registered. - [Pure] + /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public TService GetInstance() where TService : class @@ -246,12 +200,7 @@ public TService GetInstance() return service!; } - /// - /// Creates an instance of a registered type implementing the service. - /// - /// The type of service to look for. - /// A new instance of a registered type implementing . - [Pure] + /// public TService GetInstanceWithoutCaching() where TService : class { diff --git a/Microsoft.Toolkit.Mvvm/ViewModelBase.cs b/Microsoft.Toolkit.Mvvm/ViewModelBase.cs index 4cfc92d4ecf..1f02a244daa 100644 --- a/Microsoft.Toolkit.Mvvm/ViewModelBase.cs +++ b/Microsoft.Toolkit.Mvvm/ViewModelBase.cs @@ -39,9 +39,9 @@ public abstract class ViewModelBase : ObservableObject private readonly Messenger? messenger; /// - /// The optional instance to use. + /// The optional instance to use. /// - private readonly Ioc? ioc; + private readonly IIoc? ioc; /// /// Initializes a new instance of the class. @@ -66,8 +66,8 @@ protected ViewModelBase(Messenger messenger) /// /// Initializes a new instance of the class. /// - /// The optional instance to use to retrieve services. - protected ViewModelBase(Ioc ioc) + /// The optional instance to use to retrieve services. + protected ViewModelBase(IIoc ioc) { this.ioc = ioc; } @@ -76,8 +76,8 @@ protected ViewModelBase(Ioc ioc) /// Initializes a new instance of the class. /// /// The instance to use to send messages. - /// The optional instance to use to retrieve services. - protected ViewModelBase(Messenger messenger, Ioc ioc) + /// The optional instance to use to retrieve services. + protected ViewModelBase(Messenger messenger, IIoc ioc) { this.messenger = messenger; this.ioc = ioc; @@ -91,7 +91,7 @@ protected ViewModelBase(Messenger messenger, Ioc ioc) /// /// Gets the instance in use. /// - protected Ioc Ioc => this.ioc ?? Ioc.Default; + protected IIoc Ioc => this.ioc ?? Services.Ioc.Default; private bool isActive; From dd7e973b6e6ffda9f1d370efb30ddddf6d20913c Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Wed, 15 Apr 2020 22:45:09 +0200 Subject: [PATCH 062/595] Added IMessenger interface --- .../Messaging/IMessenger.cs | 150 ++++++++++++++++++ Microsoft.Toolkit.Mvvm/Messaging/Messenger.cs | 101 ++---------- Microsoft.Toolkit.Mvvm/ViewModelBase.cs | 18 +-- 3 files changed, 172 insertions(+), 97 deletions(-) create mode 100644 Microsoft.Toolkit.Mvvm/Messaging/IMessenger.cs diff --git a/Microsoft.Toolkit.Mvvm/Messaging/IMessenger.cs b/Microsoft.Toolkit.Mvvm/Messaging/IMessenger.cs new file mode 100644 index 00000000000..871f629e3e1 --- /dev/null +++ b/Microsoft.Toolkit.Mvvm/Messaging/IMessenger.cs @@ -0,0 +1,150 @@ +// 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.Buffers; +using System.Collections.Generic; +using System.Diagnostics.Contracts; + +namespace Microsoft.Toolkit.Mvvm.Messaging +{ + /// + /// An interface for a type providing the ability to exchange messages between different objects. + /// + public interface IMessenger + { + /// + /// Checks whether or not a given recipient has already been registered for a message. + /// + /// The type of message to check for the given recipient. + /// The target recipient to check the registration for. + /// Whether or not has already been registered for the specified message. + [Pure] + bool IsRegistered(object recipient) + where TMessage : class; + + /// + /// Checks whether or not a given recipient has already been registered for a message. + /// + /// The type of message to check for the given recipient. + /// The type of token to check the channel for. + /// The target recipient to check the registration for. + /// The token used to identify the target channel to check. + /// Whether or not has already been registered for the specified message. + [Pure] + bool IsRegistered(object recipient, TToken token) + where TMessage : class + where TToken : notnull, IEquatable; + + /// + /// Registers a recipient for a given type of message. + /// + /// The type of message to receive. + /// The recipient that will receive the messages. + /// The to invoke when a message is received. + /// Thrown when trying to register the same message twice. + void Register(object recipient, Action action) + where TMessage : class; + + /// + /// Registers a recipient for a given type of message. + /// + /// The type of message to receive. + /// The type of token to use to pick the messages to receive. + /// The recipient that will receive the messages. + /// A token used to determine the receiving channel to use. + /// The to invoke when a message is received. + /// Thrown when trying to register the same message twice. + void Register(object recipient, TToken token, Action action) + where TMessage : class + where TToken : notnull, IEquatable; + + /// + /// Unregisters a recipient from all registered messages. + /// + /// The recipient to unregister. + void Unregister(object recipient); + + /// + /// Unregisters a recipient from messages of a given type. + /// + /// The type of message to stop receiving. + /// The recipient to unregister. + /// If the recipient has no registered handler, this method does nothing. + void Unregister(object recipient) + where TMessage : class; + + /// + /// Unregisters a recipient from messages on a specific channel. + /// + /// The type of token to identify what channel to unregister from. + /// The recipient to unregister. + /// The token to use to identify which handlers to unregister. + /// If the recipient has no registered handler, this method does nothing. + void Unregister(object recipient, TToken token) + where TToken : notnull, IEquatable; + + /// + /// Unregisters a recipient from messages of a given type. + /// + /// The type of message to stop receiving. + /// The type of token to identify what channel to unregister from. + /// The recipient to unregister. + /// The token to use to identify which handlers to unregister. + /// If the recipient has no registered handler, this method does nothing. + void Unregister(object recipient, TToken token) + where TMessage : class + where TToken : notnull, IEquatable; + + /// + /// Sends a message of the specified type to all registered recipients. + /// + /// The type of message to send. + /// + /// This method is a shorthand for when the + /// message type exposes a parameterless constructor: it will automatically create + /// a new instance and send that to its recipients. + /// + void Send() + where TMessage : class, new(); + + /// + /// Sends a message of the specified type to all registered recipients. + /// + /// The type of message to send. + /// The message to send. + void Send(TMessage message) + where TMessage : class; + + /// + /// Sends a message of the specified type to all registered recipients. + /// + /// The type of message to send. + /// The type of token to identify what channel to use to send the message. + /// The token indicating what channel to use. + /// + /// This method will automatically create a new instance + /// just like , and then send it to the right recipients. + /// + void Send(TToken token) + where TMessage : class, new() + where TToken : notnull, IEquatable; + + /// + /// Sends a message of the specified type to all registered recipients. + /// + /// The type of message to send. + /// The type of token to identify what channel to use to send the message. + /// The message to send. + /// The token indicating what channel to use. + void Send(TMessage message, TToken token) + where TMessage : class + where TToken : notnull, IEquatable; + + /// + /// Resets the class and unregisters all the existing recipients. + /// + void Reset(); + } +} diff --git a/Microsoft.Toolkit.Mvvm/Messaging/Messenger.cs b/Microsoft.Toolkit.Mvvm/Messaging/Messenger.cs index af413350680..468a5e0aee1 100644 --- a/Microsoft.Toolkit.Mvvm/Messaging/Messenger.cs +++ b/Microsoft.Toolkit.Mvvm/Messaging/Messenger.cs @@ -15,7 +15,7 @@ namespace Microsoft.Toolkit.Mvvm.Messaging /// /// A type implementing a messaging system between objects. /// - public sealed class Messenger + public sealed class Messenger : IMessenger { // The Messenger class uses the following logic to link stored instances together: // -------------------------------------------------------------------------------------------------------- @@ -80,27 +80,14 @@ private readonly DictionarySlim>> /// public static Messenger Default { get; } = new Messenger(); - /// - /// Checks whether or not a given recipient has already been registered for a message. - /// - /// The type of message to check for the given recipient. - /// The target recipient to check the registration for. - /// Whether or not has already been registered for the specified message. - [Pure] + /// public bool IsRegistered(object recipient) where TMessage : class { return IsRegistered(recipient, default); } - /// - /// Checks whether or not a given recipient has already been registered for a message. - /// - /// The type of message to check for the given recipient. - /// The type of token to check the channel for. - /// The target recipient to check the registration for. - /// The token used to identify the target channel to check. - /// Whether or not has already been registered for the specified message. + /// [Pure] public bool IsRegistered(object recipient, TToken token) where TMessage : class @@ -115,28 +102,14 @@ public bool IsRegistered(object recipient, TToken token) } } - /// - /// Registers a recipient for a given type of message. - /// - /// The type of message to receive. - /// The recipient that will receive the messages. - /// The to invoke when a message is received. - /// Thrown when trying to register the same message twice. + /// public void Register(object recipient, Action action) where TMessage : class { Register(recipient, default(Unit), action); } - /// - /// Registers a recipient for a given type of message. - /// - /// The type of message to receive. - /// The type of token to use to pick the messages to receive. - /// The recipient that will receive the messages. - /// A token used to determine the receiving channel to use. - /// The to invoke when a message is received. - /// Thrown when trying to register the same message twice. + /// public void Register(object recipient, TToken token, Action action) where TMessage : class where TToken : notnull, IEquatable @@ -175,10 +148,7 @@ public void Register(object recipient, TToken token, Action - /// Unregisters a recipient from all registered messages. - /// - /// The recipient to unregister. + /// public void Unregister(object recipient) { lock (this.recipientsMap) @@ -203,25 +173,14 @@ public void Unregister(object recipient) } } - /// - /// Unregisters a recipient from messages of a given type. - /// - /// The type of message to stop receiving. - /// The recipient to unregister. - /// If the recipient has no registered handler, this method does nothing. + /// public void Unregister(object recipient) where TMessage : class { Unregister(recipient, default); } - /// - /// Unregisters a recipient from messages on a specific channel. - /// - /// The type of token to identify what channel to unregister from. - /// The recipient to unregister. - /// The token to use to identify which handlers to unregister. - /// If the recipient has no registered handler, this method does nothing. + /// public void Unregister(object recipient, TToken token) where TToken : notnull, IEquatable { @@ -299,14 +258,7 @@ public void Unregister(object recipient, TToken token) } } - /// - /// Unregisters a recipient from messages of a given type. - /// - /// The type of message to stop receiving. - /// The type of token to identify what channel to unregister from. - /// The recipient to unregister. - /// The token to use to identify which handlers to unregister. - /// If the recipient has no registered handler, this method does nothing. + /// public void Unregister(object recipient, TToken token) where TMessage : class where TToken : notnull, IEquatable @@ -346,42 +298,21 @@ public void Unregister(object recipient, TToken token) } } - /// - /// Sends a message of the specified type to all registered recipients. - /// - /// The type of message to send. - /// - /// This method is a shorthand for when the - /// message type exposes a parameterless constructor: it will automatically create - /// a new instance and send that to its recipients. - /// + /// public void Send() where TMessage : class, new() { Send(new TMessage(), default(Unit)); } - /// - /// Sends a message of the specified type to all registered recipients. - /// - /// The type of message to send. - /// The message to send. + /// public void Send(TMessage message) where TMessage : class { Send(message, default(Unit)); } - /// - /// Sends a message of the specified type to all registered recipients. - /// - /// The type of message to send. - /// The type of token to identify what channel to use to send the message. - /// The token indicating what channel to use. - /// - /// This method will automatically create a new instance - /// just like , and then send it to the right recipients. - /// + /// public void Send(TToken token) where TMessage : class, new() where TToken : notnull, IEquatable @@ -389,13 +320,7 @@ public void Send(TToken token) Send(new TMessage(), token); } - /// - /// Sends a message of the specified type to all registered recipients. - /// - /// The type of message to send. - /// The type of token to identify what channel to use to send the message. - /// The message to send. - /// The token indicating what channel to use. + /// public void Send(TMessage message, TToken token) where TMessage : class where TToken : notnull, IEquatable diff --git a/Microsoft.Toolkit.Mvvm/ViewModelBase.cs b/Microsoft.Toolkit.Mvvm/ViewModelBase.cs index 1f02a244daa..9702c5f0a89 100644 --- a/Microsoft.Toolkit.Mvvm/ViewModelBase.cs +++ b/Microsoft.Toolkit.Mvvm/ViewModelBase.cs @@ -34,9 +34,9 @@ namespace Microsoft.Toolkit.Mvvm public abstract class ViewModelBase : ObservableObject { /// - /// The optional instance to use. + /// The optional instance to use. /// - private readonly Messenger? messenger; + private readonly IMessenger? messenger; /// /// The optional instance to use. @@ -57,8 +57,8 @@ protected ViewModelBase() /// /// Initializes a new instance of the class. /// - /// The instance to use to send messages. - protected ViewModelBase(Messenger messenger) + /// The instance to use to send messages. + protected ViewModelBase(IMessenger messenger) { this.messenger = messenger; } @@ -75,21 +75,21 @@ protected ViewModelBase(IIoc ioc) /// /// Initializes a new instance of the class. /// - /// The instance to use to send messages. + /// The instance to use to send messages. /// The optional instance to use to retrieve services. - protected ViewModelBase(Messenger messenger, IIoc ioc) + protected ViewModelBase(IMessenger messenger, IIoc ioc) { this.messenger = messenger; this.ioc = ioc; } /// - /// Gets the instance in use. + /// Gets the instance in use. /// - protected Messenger Messenger => this.messenger ?? Messenger.Default; + protected IMessenger Messenger => this.messenger ?? Messaging.Messenger.Default; /// - /// Gets the instance in use. + /// Gets the instance in use. /// protected IIoc Ioc => this.ioc ?? Services.Ioc.Default; From fc638226d3515c4efe1c55bf1e5640f4ac85c926 Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Wed, 15 Apr 2020 22:54:48 +0200 Subject: [PATCH 063/595] Moved request methods to extensions --- .../Messaging/IMessengerExtensions.cs | 105 ++++++++++++++++++ Microsoft.Toolkit.Mvvm/Messaging/Messenger.cs | 92 +-------------- 2 files changed, 107 insertions(+), 90 deletions(-) create mode 100644 Microsoft.Toolkit.Mvvm/Messaging/IMessengerExtensions.cs diff --git a/Microsoft.Toolkit.Mvvm/Messaging/IMessengerExtensions.cs b/Microsoft.Toolkit.Mvvm/Messaging/IMessengerExtensions.cs new file mode 100644 index 00000000000..0538576faa8 --- /dev/null +++ b/Microsoft.Toolkit.Mvvm/Messaging/IMessengerExtensions.cs @@ -0,0 +1,105 @@ +// 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.Runtime.CompilerServices; +using Microsoft.Toolkit.Mvvm.Messaging.Messages; + +namespace Microsoft.Toolkit.Mvvm.Messaging +{ + /// + /// Extensions for the type. + /// + public static class IMessengerExtensions + { + /// + /// Sends a request of the specified type to all registered recipients, and returns the received response. + /// + /// The type of request message to send. + /// The type of response to expect. + /// The instance to use to send the request. + /// The response value for the given message. + /// Thrown if no response is received for the message. + /// + /// This method is a shorthand for when the + /// message type exposes a parameterless constructor: it will automatically create + /// a new request message and use that to perform the request. + /// + public static TResult Request(this IMessenger messenger) + where TMessage : RequestMessage, new() + { + return Request(messenger, new TMessage(), default); + } + + /// + /// Sends a request of the specified type to all registered recipients, and returns the received response. + /// + /// The type of request message to send. + /// The type of response to expect. + /// The instance to use to send the request. + /// The message to send. + /// The response value for the given message. + /// Thrown if no response is received for the message. + public static TResult Request(this IMessenger messenger, TMessage message) + where TMessage : RequestMessage + { + return Request(messenger, message, default); + } + + /// + /// Sends a request of the specified type to all registered recipients, and returns the received response. + /// + /// The type of request message to send. + /// The type of response to expect. + /// The type of token to identify what channel to use to send the message. + /// The instance to use to send the request. + /// The token indicating what channel to use. + /// The response value for the given message. + /// Thrown if no response is received for the message. + /// + /// This method will automatically create a new instance + /// just like , and then send it to the right recipients. + /// + public static TResult Request(this IMessenger messenger, TToken token) + where TMessage : RequestMessage, new() + where TToken : notnull, IEquatable + { + return Request(messenger, new TMessage(), token); + } + + /// + /// Sends a request of the specified type to all registered recipients, and returns the received response. + /// + /// The type of request message to send. + /// The type of response to expect. + /// The type of token to identify what channel to use to send the message. + /// The instance to use to send the request. + /// The request message to send. + /// The token indicating what channel to use. + /// The response value for the given message. + /// Thrown if no response is received for the message. + public static TResult Request(this IMessenger messenger, TMessage message, TToken token) + where TMessage : RequestMessage + where TToken : notnull, IEquatable + { + messenger.Send(message, token); + + if (!message.IsResponseReceived) + { + ThrowInvalidOperationExceptionForNoResponseReceived(); + } + + return message.Result; + } + + /// + /// Throws an when trying to add a duplicate handler. + /// + [MethodImpl(MethodImplOptions.NoInlining)] + private static void ThrowInvalidOperationExceptionForNoResponseReceived() + { + throw new InvalidOperationException("No response was received for the given request message"); + } + } +} diff --git a/Microsoft.Toolkit.Mvvm/Messaging/Messenger.cs b/Microsoft.Toolkit.Mvvm/Messaging/Messenger.cs index 468a5e0aee1..00ea5a27157 100644 --- a/Microsoft.Toolkit.Mvvm/Messaging/Messenger.cs +++ b/Microsoft.Toolkit.Mvvm/Messaging/Messenger.cs @@ -8,7 +8,6 @@ using System.Diagnostics.Contracts; using System.Runtime.CompilerServices; using Microsoft.Collections.Extensions; -using Microsoft.Toolkit.Mvvm.Messaging.Messages; namespace Microsoft.Toolkit.Mvvm.Messaging { @@ -384,85 +383,7 @@ public void Send(TMessage message, TToken token) } } - /// - /// Sends a request of the specified type to all registered recipients, and returns the received response. - /// - /// The type of request message to send. - /// The type of response to expect. - /// The response value for the given message. - /// Thrown if no response is received for the message. - /// - /// This method is a shorthand for when the - /// message type exposes a parameterless constructor: it will automatically create - /// a new instance and send that to its recipients. - /// - public TResult Request() - where TMessage : RequestMessage, new() - { - return Request(new TMessage(), default); - } - - /// - /// Sends a request of the specified type to all registered recipients, and returns the received response. - /// - /// The type of request message to send. - /// The type of response to expect. - /// The message to send. - /// The response value for the given message. - /// Thrown if no response is received for the message. - public TResult Request(TMessage message) - where TMessage : RequestMessage - { - return Request(message, default); - } - - /// - /// Sends a request of the specified type to all registered recipients, and returns the received response. - /// - /// The type of request message to send. - /// The type of response to expect. - /// The type of token to identify what channel to use to send the message. - /// The token indicating what channel to use. - /// The response value for the given message. - /// Thrown if no response is received for the message. - /// - /// This method will automatically create a new instance - /// just like , and then send it to the right recipients. - /// - public TResult Request(TToken token) - where TMessage : RequestMessage, new() - where TToken : notnull, IEquatable - { - return Request(new TMessage(), token); - } - - /// - /// Sends a request of the specified type to all registered recipients, and returns the received response. - /// - /// The type of request message to send. - /// The type of response to expect. - /// The type of token to identify what channel to use to send the message. - /// The request message to send. - /// The token indicating what channel to use. - /// The response value for the given message. - /// Thrown if no response is received for the message. - public TResult Request(TMessage message, TToken token) - where TMessage : RequestMessage - where TToken : notnull, IEquatable - { - Send(message, token); - - if (!message.IsResponseReceived) - { - ThrowInvalidOperationExceptionForNoResponseReceived(); - } - - return message.Result; - } - - /// - /// Resets the class and unregisters all the existing recipients. - /// + /// public void Reset() { lock (this.recipientsMap) @@ -578,7 +499,7 @@ public override int GetHashCode() /// /// An empty type representing a generic token with no specific value. /// - private struct Unit : IEquatable + internal struct Unit : IEquatable { /// [MethodImpl(MethodImplOptions.AggressiveInlining)] @@ -609,14 +530,5 @@ private static void ThrowInvalidOperationExceptionForDuplicateRegistration() { throw new InvalidOperationException("The target recipient has already subscribed to the target message"); } - - /// - /// Throws an when trying to add a duplicate handler. - /// - [MethodImpl(MethodImplOptions.NoInlining)] - private static void ThrowInvalidOperationExceptionForNoResponseReceived() - { - throw new InvalidOperationException("No response was received for the given request message"); - } } } From 5b1258bad8a8cd92ba3940cceb87909eab085e04 Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Wed, 15 Apr 2020 23:02:11 +0200 Subject: [PATCH 064/595] Code refactoring --- .../{ => ComponentModel}/ObservableObject.cs | 6 +++--- .../{ => ComponentModel}/ViewModelBase.cs | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) rename Microsoft.Toolkit.Mvvm/{ => ComponentModel}/ObservableObject.cs (93%) rename Microsoft.Toolkit.Mvvm/{ => ComponentModel}/ViewModelBase.cs (99%) diff --git a/Microsoft.Toolkit.Mvvm/ObservableObject.cs b/Microsoft.Toolkit.Mvvm/ComponentModel/ObservableObject.cs similarity index 93% rename from Microsoft.Toolkit.Mvvm/ObservableObject.cs rename to Microsoft.Toolkit.Mvvm/ComponentModel/ObservableObject.cs index ecf629edb75..263f2698f6b 100644 --- a/Microsoft.Toolkit.Mvvm/ObservableObject.cs +++ b/Microsoft.Toolkit.Mvvm/ComponentModel/ObservableObject.cs @@ -24,7 +24,7 @@ using System.ComponentModel; using System.Runtime.CompilerServices; -namespace Microsoft.Toolkit.Mvvm +namespace Microsoft.Toolkit.Mvvm.ComponentModel { /// /// A base class for objects of which the properties must be observable. @@ -43,7 +43,7 @@ public abstract class ObservableObject : INotifyPropertyChanged, INotifyProperty /// (optional) The name of the property that changed. protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null!) { - PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); + this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); } /// @@ -52,7 +52,7 @@ protected virtual void OnPropertyChanged([CallerMemberName] string propertyName /// (optional) The name of the property that changed. protected virtual void OnPropertyChanging([CallerMemberName] string propertyName = null!) { - PropertyChanging?.Invoke(this, new PropertyChangingEventArgs(propertyName)); + this.PropertyChanging?.Invoke(this, new PropertyChangingEventArgs(propertyName)); } /// diff --git a/Microsoft.Toolkit.Mvvm/ViewModelBase.cs b/Microsoft.Toolkit.Mvvm/ComponentModel/ViewModelBase.cs similarity index 99% rename from Microsoft.Toolkit.Mvvm/ViewModelBase.cs rename to Microsoft.Toolkit.Mvvm/ComponentModel/ViewModelBase.cs index 9702c5f0a89..8e29bb62002 100644 --- a/Microsoft.Toolkit.Mvvm/ViewModelBase.cs +++ b/Microsoft.Toolkit.Mvvm/ComponentModel/ViewModelBase.cs @@ -26,7 +26,7 @@ using Microsoft.Toolkit.Mvvm.Messaging.Messages; using Microsoft.Toolkit.Mvvm.Services; -namespace Microsoft.Toolkit.Mvvm +namespace Microsoft.Toolkit.Mvvm.ComponentModel { /// /// A base class for viewmodels. From 59a6a87cc0db6d086233a4d98ae4ae238bd01f4e Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Thu, 16 Apr 2020 00:13:16 +0200 Subject: [PATCH 065/595] Added tests for ObservableObject --- UnitTests/Mvvm/Test_ObservableObject.cs | 56 +++++++++++++++++++++++++ UnitTests/UnitTests.csproj | 1 + 2 files changed, 57 insertions(+) create mode 100644 UnitTests/Mvvm/Test_ObservableObject.cs diff --git a/UnitTests/Mvvm/Test_ObservableObject.cs b/UnitTests/Mvvm/Test_ObservableObject.cs new file mode 100644 index 00000000000..742935b4b13 --- /dev/null +++ b/UnitTests/Mvvm/Test_ObservableObject.cs @@ -0,0 +1,56 @@ +// 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.ComponentModel; +using Microsoft.Toolkit.Mvvm.ComponentModel; +using Microsoft.VisualStudio.TestTools.UnitTesting; + +namespace UnitTests.Mvvm +{ + [TestClass] + public class Test_ObservableObject + { + [TestCategory("Mvvm")] + [TestMethod] + public void Test_ObservableObject_Events() + { + var model = new SampleModel(); + + (PropertyChangingEventArgs, int) changing = default; + (PropertyChangedEventArgs, int) changed = default; + + model.PropertyChanging += (s, e) => + { + Assert.AreSame(model, s); + + changing = (e, model.Data); + }; + + model.PropertyChanged += (s, e) => + { + Assert.AreSame(model, s); + + changed = (e, model.Data); + }; + + model.Data = 42; + + Assert.AreEqual(changing.Item1?.PropertyName, nameof(SampleModel.Data)); + Assert.AreEqual(changing.Item2, 0); + Assert.AreEqual(changed.Item1?.PropertyName, nameof(SampleModel.Data)); + Assert.AreEqual(changed.Item2, 42); + } + + public class SampleModel : ObservableObject + { + private T data; + + public T Data + { + get => data; + set => Set(ref data, value); + } + } + } +} diff --git a/UnitTests/UnitTests.csproj b/UnitTests/UnitTests.csproj index f947137c2aa..15872ab48a5 100644 --- a/UnitTests/UnitTests.csproj +++ b/UnitTests/UnitTests.csproj @@ -139,6 +139,7 @@ + From 245745bc4393b3a73d5bae3d5ed12d0ca7703ffe Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Thu, 16 Apr 2020 00:28:18 +0200 Subject: [PATCH 066/595] Added tests for ViewModelBase --- UnitTests/Mvvm/Test_ViewModelBase.cs | 113 +++++++++++++++++++++++++++ UnitTests/UnitTests.csproj | 1 + 2 files changed, 114 insertions(+) create mode 100644 UnitTests/Mvvm/Test_ViewModelBase.cs diff --git a/UnitTests/Mvvm/Test_ViewModelBase.cs b/UnitTests/Mvvm/Test_ViewModelBase.cs new file mode 100644 index 00000000000..650bea05e86 --- /dev/null +++ b/UnitTests/Mvvm/Test_ViewModelBase.cs @@ -0,0 +1,113 @@ +// 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; +using Microsoft.Toolkit.Mvvm.Messaging; +using Microsoft.Toolkit.Mvvm.Messaging.Messages; +using Microsoft.Toolkit.Mvvm.Services; +using Microsoft.VisualStudio.TestTools.UnitTesting; + +namespace UnitTests.Mvvm +{ + [TestClass] + public class Test_ViewModelBase + { + [TestCategory("Mvvm")] + [TestMethod] + public void Test_ViewModelBase_Activation() + { + var viewmodel = new SomeViewModel(); + + Assert.IsFalse(viewmodel.IsActivatedCheck); + + viewmodel.IsActive = true; + + Assert.IsTrue(viewmodel.IsActivatedCheck); + + viewmodel.IsActive = false; + + Assert.IsFalse(viewmodel.IsActivatedCheck); + } + + [TestCategory("Mvvm")] + [TestMethod] + public void Test_ViewModelBase_Defaults() + { + var viewmodel = new SomeViewModel(); + + Assert.AreSame(viewmodel.CurrentMessenger, Messenger.Default); + Assert.AreSame(viewmodel.CurrentIoc, Ioc.Default); + } + + [TestCategory("Mvvm")] + [TestMethod] + public void Test_ViewModelBase_Injection() + { + var messenger = new Messenger(); + var ioc = new Ioc(); + var viewmodel = new SomeViewModel(messenger, ioc); + + Assert.AreSame(viewmodel.CurrentMessenger, messenger); + Assert.AreSame(viewmodel.CurrentIoc, ioc); + } + + [TestCategory("Mvvm")] + [TestMethod] + public void Test_ViewModelBase_Broadcast() + { + var messenger = new Messenger(); + var viewmodel = new SomeViewModel(messenger, null); + + PropertyChangedMessage message = null; + + messenger.Register>(messenger, m => message = m); + + viewmodel.Data = 42; + + Assert.IsNotNull(message); + Assert.AreSame(message.Sender, viewmodel); + Assert.AreEqual(message.OldValue, 0); + Assert.AreEqual(message.NewValue, 42); + Assert.AreEqual(message.PropertyName, nameof(SomeViewModel.Data)); + } + + public class SomeViewModel : ViewModelBase + { + public SomeViewModel() + { + } + + public SomeViewModel(IMessenger messenger, IIoc ioc) + : base(messenger, ioc) + { + } + + public IIoc CurrentIoc => Ioc; + + public IMessenger CurrentMessenger => Messenger; + + private T data; + + public T Data + { + get => data; + set => Set(ref data, value, true); + } + + public bool IsActivatedCheck { get; private set; } + + protected override void OnActivate() + { + IsActivatedCheck = true; + } + + protected override void OnDeactivate() + { + base.OnDeactivate(); + + IsActivatedCheck = false; + } + } + } +} diff --git a/UnitTests/UnitTests.csproj b/UnitTests/UnitTests.csproj index 15872ab48a5..3cfafb33b90 100644 --- a/UnitTests/UnitTests.csproj +++ b/UnitTests/UnitTests.csproj @@ -139,6 +139,7 @@ + From fd3aba31bec1fabb88c9bb101fa4783086306d5f Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Thu, 16 Apr 2020 00:47:20 +0200 Subject: [PATCH 067/595] Added tests for relay commands --- UnitTests/Mvvm/Test_RelayCommand.cs | 83 ++++++++++++++++++++++++++ UnitTests/Mvvm/Test_RelayCommand{T}.cs | 69 +++++++++++++++++++++ UnitTests/UnitTests.csproj | 2 + 3 files changed, 154 insertions(+) create mode 100644 UnitTests/Mvvm/Test_RelayCommand.cs create mode 100644 UnitTests/Mvvm/Test_RelayCommand{T}.cs diff --git a/UnitTests/Mvvm/Test_RelayCommand.cs b/UnitTests/Mvvm/Test_RelayCommand.cs new file mode 100644 index 00000000000..c0229fc3806 --- /dev/null +++ b/UnitTests/Mvvm/Test_RelayCommand.cs @@ -0,0 +1,83 @@ +// 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 Microsoft.Toolkit.Mvvm.Commands; +using Microsoft.VisualStudio.TestTools.UnitTesting; + +namespace UnitTests.Mvvm +{ + [TestClass] + public class Test_RelayCommand + { + [TestCategory("Mvvm")] + [TestMethod] + public void Test_RelayCommand_AlwaysEnabled() + { + int ticks = 0; + + var command = new RelayCommand(() => ticks++); + + Assert.IsTrue(command.CanExecute(null)); + Assert.IsTrue(command.CanExecute(new object())); + + (object, EventArgs) args = default; + + command.CanExecuteChanged += (s, e) => args = (s, e); + + command.RaiseCanExecuteChanged(); + + Assert.AreSame(args.Item1, command); + Assert.AreSame(args.Item2, EventArgs.Empty); + + command.Execute(null); + + Assert.AreEqual(ticks, 1); + + command.Execute(new object()); + + Assert.AreEqual(ticks, 2); + } + + [TestCategory("Mvvm")] + [TestMethod] + public void Test_RelayCommand_WithCanExecuteFunctionTrue() + { + int ticks = 0; + + var command = new RelayCommand(() => ticks++, () => true); + + Assert.IsTrue(command.CanExecute(null)); + Assert.IsTrue(command.CanExecute(new object())); + + command.Execute(null); + + Assert.AreEqual(ticks, 1); + + command.Execute(new object()); + + Assert.AreEqual(ticks, 2); + } + + [TestCategory("Mvvm")] + [TestMethod] + public void Test_RelayCommand_WithCanExecuteFunctionFalse() + { + int ticks = 0; + + var command = new RelayCommand(() => ticks++, () => false); + + Assert.IsFalse(command.CanExecute(null)); + Assert.IsFalse(command.CanExecute(new object())); + + command.Execute(null); + + Assert.AreEqual(ticks, 0); + + command.Execute(new object()); + + Assert.AreEqual(ticks, 0); + } + } +} diff --git a/UnitTests/Mvvm/Test_RelayCommand{T}.cs b/UnitTests/Mvvm/Test_RelayCommand{T}.cs new file mode 100644 index 00000000000..63c8b4f4190 --- /dev/null +++ b/UnitTests/Mvvm/Test_RelayCommand{T}.cs @@ -0,0 +1,69 @@ +// 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.Diagnostics.CodeAnalysis; +using Microsoft.Toolkit.Mvvm.Commands; +using Microsoft.VisualStudio.TestTools.UnitTesting; + +namespace UnitTests.Mvvm +{ + [TestClass] + [SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1649", Justification = "Generic type")] + public class Test_RelayCommandOfT + { + [TestCategory("Mvvm")] + [TestMethod] + public void Test_RelayCommandOfT_AlwaysEnabled() + { + string text = string.Empty; + + var command = new RelayCommand(s => text = s); + + Assert.IsTrue(command.CanExecute("Text")); + Assert.IsTrue(command.CanExecute(null)); + + Assert.ThrowsException(() => command.CanExecute(new object())); + + (object, EventArgs) args = default; + + command.CanExecuteChanged += (s, e) => args = (s, e); + + command.RaiseCanExecuteChanged(); + + Assert.AreSame(args.Item1, command); + Assert.AreSame(args.Item2, EventArgs.Empty); + + command.Execute("Hello"); + + Assert.AreEqual(text, "Hello"); + + command.Execute(null); + + Assert.AreEqual(text, null); + } + + [TestCategory("Mvvm")] + [TestMethod] + public void Test_RelayCommand_WithCanExecuteFunction() + { + string text = string.Empty; + + var command = new RelayCommand(s => text = s, s => s != null); + + Assert.IsTrue(command.CanExecute("Text")); + Assert.IsFalse(command.CanExecute(null)); + + Assert.ThrowsException(() => command.CanExecute(new object())); + + command.Execute("Hello"); + + Assert.AreEqual(text, "Hello"); + + command.Execute(null); + + Assert.AreEqual(text, "Hello"); + } + } +} diff --git a/UnitTests/UnitTests.csproj b/UnitTests/UnitTests.csproj index 3cfafb33b90..2f729c3c07e 100644 --- a/UnitTests/UnitTests.csproj +++ b/UnitTests/UnitTests.csproj @@ -139,6 +139,8 @@ + + From c0a863bb7b6e53ddff3f7099a5d0e65d66c6e873 Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Thu, 16 Apr 2020 01:28:14 +0200 Subject: [PATCH 068/595] Messenger XML comments improved --- Microsoft.Toolkit.Mvvm/Messaging/Messenger.cs | 21 ++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/Microsoft.Toolkit.Mvvm/Messaging/Messenger.cs b/Microsoft.Toolkit.Mvvm/Messaging/Messenger.cs index 00ea5a27157..58c3155ef94 100644 --- a/Microsoft.Toolkit.Mvvm/Messaging/Messenger.cs +++ b/Microsoft.Toolkit.Mvvm/Messaging/Messenger.cs @@ -12,7 +12,26 @@ namespace Microsoft.Toolkit.Mvvm.Messaging { /// - /// A type implementing a messaging system between objects. + /// A type that can be used to exchange messages between different objects. + /// This can be useful to decouple different modules of an application without having to keep strong + /// references to types being referenced. It is also possible to send messages to specific channels, uniquely + /// identified by a token, and to have different messengers in different sections of an applications. + /// In order to use the functionalities, first define a message type, like so: + /// + /// public sealed class LoginCompletedMessage { } + /// + /// Then, register your a recipient for this message: + /// + /// Messenger.Default.Register<LoginCompletedMessage>(this, m => + /// { + /// // Handle the message here... + /// }); + /// + /// Finally, send a message when needed, like so: + /// + /// Messenger.Default.Send<LoginCompletedMessage>(); + /// + /// For info on the other available features, check the interface. /// public sealed class Messenger : IMessenger { From 2b38baca34bc43926bfe6631074734bb299ce197 Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Thu, 16 Apr 2020 13:05:05 +0200 Subject: [PATCH 069/595] Added ObservableObject.SetAndNotifyOnCompletion method --- .../ComponentModel/ObservableObject.cs | 84 +++++++++++++++++++ UnitTests/Mvvm/Test_ObservableObject.cs | 56 +++++++++++++ 2 files changed, 140 insertions(+) diff --git a/Microsoft.Toolkit.Mvvm/ComponentModel/ObservableObject.cs b/Microsoft.Toolkit.Mvvm/ComponentModel/ObservableObject.cs index 263f2698f6b..a7d698c8c67 100644 --- a/Microsoft.Toolkit.Mvvm/ComponentModel/ObservableObject.cs +++ b/Microsoft.Toolkit.Mvvm/ComponentModel/ObservableObject.cs @@ -20,9 +20,13 @@ // // **************************************************************************** +using System; using System.Collections.Generic; using System.ComponentModel; +using System.Linq.Expressions; +using System.Reflection; using System.Runtime.CompilerServices; +using System.Threading.Tasks; namespace Microsoft.Toolkit.Mvvm.ComponentModel { @@ -84,5 +88,85 @@ protected bool Set(ref T field, T newValue, [CallerMemberName] string propert return true; } + + /// + /// Compares the current and new values for a given field (which should be the backing + /// field for a property). If the value has changed, raises the + /// event, updates the field and then raises the event. + /// The behavior mirrors that of , with the difference being that this method + /// will also monitor the new value of the property (a generic ) and will also + /// raise the again for the target property when it completes. + /// This can be used to update bindings observing that or any of its properties. + /// + /// The type of to set and monitor. + /// An returning the field to update. + /// The property's value after the change occurred. + /// (optional) The name of the property that changed. + /// if the property was changed, otherwise. + /// + /// The and events are not raised + /// if the current and new value for the target property are the same. + /// + protected bool SetAndNotifyOnCompletion(Expression> field, TTask newValue, [CallerMemberName] string propertyName = null!) + where TTask : Task + { + // Get the target field to set + if (!((field.Body as MemberExpression)?.Member is FieldInfo fieldInfo)) + { + ThrowArgumentExceptionForInvalidFieldExpression(); + + // This is never executed, as the method above always throws + return false; + } + + TTask oldTask = (TTask)fieldInfo.GetValue(this); + + if (EqualityComparer.Default.Equals(oldTask, newValue)) + { + return false; + } + + this.OnPropertyChanging(propertyName); + + fieldInfo.SetValue(this, newValue); + + OnPropertyChanged(propertyName); + + /* We use a local async function here so that the main method can + * remain synchronous and return a value that can be immediately + * used by the caller. This mirrors Set(ref T, T, string). */ + async Task MonitorTaskAsync() + { + try + { + // Await the task and ignore any exceptions + await newValue; + } + catch + { + } + + TTask currentTask = (TTask)fieldInfo.GetValue(this); + + // Only notify if the property hasn't changed + if (ReferenceEquals(newValue, currentTask)) + { + OnPropertyChanged(propertyName); + } + } + + _ = MonitorTaskAsync(); + + return true; + } + + /// + /// Throws an when a given is invalid for a property field. + /// + [MethodImpl(MethodImplOptions.NoInlining)] + private static void ThrowArgumentExceptionForInvalidFieldExpression() + { + throw new ArgumentException("The given expression must be in the form () => field"); + } } } \ No newline at end of file diff --git a/UnitTests/Mvvm/Test_ObservableObject.cs b/UnitTests/Mvvm/Test_ObservableObject.cs index 742935b4b13..453fbe1da5a 100644 --- a/UnitTests/Mvvm/Test_ObservableObject.cs +++ b/UnitTests/Mvvm/Test_ObservableObject.cs @@ -3,6 +3,7 @@ // See the LICENSE file in the project root for more information. using System.ComponentModel; +using System.Threading.Tasks; using Microsoft.Toolkit.Mvvm.ComponentModel; using Microsoft.VisualStudio.TestTools.UnitTesting; @@ -52,5 +53,60 @@ public T Data set => Set(ref data, value); } } + + [TestCategory("Mvvm")] + [TestMethod] + public async Task Test_ObservableObject_NotifyTask() + { + var model = new SampleModelWithTask(); + var tcs = new TaskCompletionSource(); + var task = tcs.Task; + + (PropertyChangingEventArgs, Task) changing = default; + (PropertyChangedEventArgs, Task) changed = default; + + model.PropertyChanging += (s, e) => + { + Assert.AreSame(model, s); + + changing = (e, model.Data); + }; + + model.PropertyChanged += (s, e) => + { + Assert.AreSame(model, s); + + changed = (e, model.Data); + }; + + model.Data = task; + + Assert.IsFalse(task.IsCompleted); + Assert.AreEqual(changing.Item1?.PropertyName, nameof(SampleModelWithTask.Data)); + Assert.IsNull(changing.Item2); + Assert.AreEqual(changed.Item1?.PropertyName, nameof(SampleModelWithTask.Data)); + Assert.AreSame(changed.Item2, task); + + changed = default; + + tcs.SetResult(42); + + await Task.Delay(100); // Time for the notification to dispatch + + Assert.IsTrue(task.IsCompleted); + Assert.AreEqual(changed.Item1?.PropertyName, nameof(SampleModel.Data)); + Assert.AreSame(changed.Item2, task); + } + + public class SampleModelWithTask : ObservableObject + { + private Task data; + + public Task Data + { + get => data; + set => SetAndNotifyOnCompletion(() => data, value); + } + } } } From 22aa752726a50296d31ff550cf6a2fd2d0da3ee2 Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Thu, 16 Apr 2020 15:39:03 +0200 Subject: [PATCH 070/595] Fixed a comment --- Microsoft.Toolkit.Mvvm/Messaging/Messenger.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Microsoft.Toolkit.Mvvm/Messaging/Messenger.cs b/Microsoft.Toolkit.Mvvm/Messaging/Messenger.cs index 58c3155ef94..eaf681dfc77 100644 --- a/Microsoft.Toolkit.Mvvm/Messaging/Messenger.cs +++ b/Microsoft.Toolkit.Mvvm/Messaging/Messenger.cs @@ -42,7 +42,7 @@ public sealed class Messenger : IMessenger // | \____/_____[*]IDictionarySlim // | ____________________________/ \____ // | / \ - // DictionarySlim>> Values + // DictionarySlim>> mapping // / / / // __(T1)______________/_______________/ / // / ____(T2)______/ / From 53982ad32fe8d16f7912586d6688e00b4fd22050 Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Thu, 16 Apr 2020 15:57:48 +0200 Subject: [PATCH 071/595] Removed unnecessary using directives --- Microsoft.Toolkit.Mvvm/Messaging/IMessenger.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/Microsoft.Toolkit.Mvvm/Messaging/IMessenger.cs b/Microsoft.Toolkit.Mvvm/Messaging/IMessenger.cs index 871f629e3e1..811a3419779 100644 --- a/Microsoft.Toolkit.Mvvm/Messaging/IMessenger.cs +++ b/Microsoft.Toolkit.Mvvm/Messaging/IMessenger.cs @@ -3,8 +3,6 @@ // See the LICENSE file in the project root for more information. using System; -using System.Buffers; -using System.Collections.Generic; using System.Diagnostics.Contracts; namespace Microsoft.Toolkit.Mvvm.Messaging From 774ed79a5f9e752cd838f20dcd51ec40c5f3f321 Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Thu, 16 Apr 2020 18:32:30 +0200 Subject: [PATCH 072/595] Fixed an incorrect comment --- Microsoft.Toolkit.Mvvm/Messaging/Messenger.cs | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/Microsoft.Toolkit.Mvvm/Messaging/Messenger.cs b/Microsoft.Toolkit.Mvvm/Messaging/Messenger.cs index eaf681dfc77..72dc3079a43 100644 --- a/Microsoft.Toolkit.Mvvm/Messaging/Messenger.cs +++ b/Microsoft.Toolkit.Mvvm/Messaging/Messenger.cs @@ -37,11 +37,12 @@ public sealed class Messenger : IMessenger { // The Messenger class uses the following logic to link stored instances together: // -------------------------------------------------------------------------------------------------------- - // DictionarySlim>> recipientsMap + // DictionarySlim>> recipientsMap; // | \ / - // | \____/_____[*]IDictionarySlim - // | ____________________________/ \____ - // | / \ + // | \____/_____[*]IDictionarySlim> + // | ___________________________/ \____ / + // | / ___________________________________\__________________/ + // | / / \ // DictionarySlim>> mapping // / / / // __(T1)______________/_______________/ / From 61248d2f65fdd6c1040e5f7d82036b6df95b2aef Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Thu, 16 Apr 2020 18:46:46 +0200 Subject: [PATCH 073/595] Minor code tweaks --- Microsoft.Toolkit.Mvvm/Commands/RelayCommand.cs | 4 ++-- Microsoft.Toolkit.Mvvm/Commands/RelayCommand{T}.cs | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Microsoft.Toolkit.Mvvm/Commands/RelayCommand.cs b/Microsoft.Toolkit.Mvvm/Commands/RelayCommand.cs index c2fe13c80ca..4b7238923f1 100644 --- a/Microsoft.Toolkit.Mvvm/Commands/RelayCommand.cs +++ b/Microsoft.Toolkit.Mvvm/Commands/RelayCommand.cs @@ -53,8 +53,8 @@ public sealed class RelayCommand : ICommand /// /// The execution logic. public RelayCommand(Action execute) - : this(execute, null) { + this.execute = execute; } /// @@ -62,7 +62,7 @@ public RelayCommand(Action execute) /// /// The execution logic. /// The execution status logic. - public RelayCommand(Action execute, Func? canExecute) + public RelayCommand(Action execute, Func canExecute) { this.execute = execute; this.canExecute = canExecute; diff --git a/Microsoft.Toolkit.Mvvm/Commands/RelayCommand{T}.cs b/Microsoft.Toolkit.Mvvm/Commands/RelayCommand{T}.cs index 262f56152ce..5825d7d8605 100644 --- a/Microsoft.Toolkit.Mvvm/Commands/RelayCommand{T}.cs +++ b/Microsoft.Toolkit.Mvvm/Commands/RelayCommand{T}.cs @@ -54,8 +54,8 @@ public sealed class RelayCommand : ICommand /// /// The execution logic. public RelayCommand(Action execute) - : this(execute, null) { + this.execute = execute; } /// @@ -63,7 +63,7 @@ public RelayCommand(Action execute) /// /// The execution logic. /// The execution status logic. - public RelayCommand(Action execute, Func? canExecute) + public RelayCommand(Action execute, Func canExecute) { this.execute = execute; this.canExecute = canExecute; From 58f3c6472a4da9427165803fa17215788b202c00 Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Thu, 16 Apr 2020 18:49:06 +0200 Subject: [PATCH 074/595] Added AsyncRelayCommand type --- .../Commands/AsyncRelayCommand.cs | 90 +++++++++++++++++++ 1 file changed, 90 insertions(+) create mode 100644 Microsoft.Toolkit.Mvvm/Commands/AsyncRelayCommand.cs diff --git a/Microsoft.Toolkit.Mvvm/Commands/AsyncRelayCommand.cs b/Microsoft.Toolkit.Mvvm/Commands/AsyncRelayCommand.cs new file mode 100644 index 00000000000..33288d7197d --- /dev/null +++ b/Microsoft.Toolkit.Mvvm/Commands/AsyncRelayCommand.cs @@ -0,0 +1,90 @@ +// 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.Runtime.CompilerServices; +using System.Threading.Tasks; +using System.Windows.Input; +using Microsoft.Toolkit.Mvvm.ComponentModel; + +namespace Microsoft.Toolkit.Mvvm.Commands +{ + /// + /// A command that mirrors the functionality of , with the addition of + /// accepting a returning a as the execute + /// action, and providing a property that notifies changes when + /// is invoked and when the returned completes. + /// + public sealed class AsyncRelayCommand : ObservableObject, ICommand + { + /// + /// The to invoke when is used. + /// + private readonly Func execute; + + /// + /// The optional action to invoke when is used. + /// + private readonly Func? canExecute; + + /// + public event EventHandler? CanExecuteChanged; + + /// + /// Initializes a new instance of the class that can always execute. + /// + /// The execution logic. + public AsyncRelayCommand(Func execute) + { + this.execute = execute; + } + + /// + /// Initializes a new instance of the class that can always execute. + /// + /// The execution logic. + /// The execution status logic. + public AsyncRelayCommand(Func execute, Func canExecute) + { + this.execute = execute; + this.canExecute = canExecute; + } + + private Task? executionTask; + + /// + /// Gets the last scheduled , if available. + /// This property notifies a change when the completes. + /// + public Task? ExecutionTask + { + get => this.executionTask; + private set => SetAndNotifyOnCompletion(() => this.executionTask, value); + } + + /// + /// Raises the event. + /// + public void RaiseCanExecuteChanged() + { + CanExecuteChanged?.Invoke(this, EventArgs.Empty); + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool CanExecute(object parameter) + { + return this.canExecute?.Invoke() != false; + } + + /// + public void Execute(object parameter) + { + if (CanExecute(parameter)) + { + ExecutionTask = this.execute(); + } + } + } +} \ No newline at end of file From 29daf8e4b3ec9d7f7a8fd5070e4130b532ada815 Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Thu, 16 Apr 2020 19:05:11 +0200 Subject: [PATCH 075/595] Added AsyncRelayCommand type --- .../Commands/AsyncRelayCommand{T}.cs | 114 ++++++++++++++++++ 1 file changed, 114 insertions(+) create mode 100644 Microsoft.Toolkit.Mvvm/Commands/AsyncRelayCommand{T}.cs diff --git a/Microsoft.Toolkit.Mvvm/Commands/AsyncRelayCommand{T}.cs b/Microsoft.Toolkit.Mvvm/Commands/AsyncRelayCommand{T}.cs new file mode 100644 index 00000000000..1f0d88294b3 --- /dev/null +++ b/Microsoft.Toolkit.Mvvm/Commands/AsyncRelayCommand{T}.cs @@ -0,0 +1,114 @@ +// 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.Runtime.CompilerServices; +using System.Threading.Tasks; +using System.Windows.Input; +using Microsoft.Toolkit.Mvvm.ComponentModel; + +namespace Microsoft.Toolkit.Mvvm.Commands +{ + /// + /// A generic command that mirrors the functionality of , with the addition + /// of accepting a returning a as the execute + /// action, and providing a property that notifies changes when + /// is invoked and when the returned completes. + /// + /// The type of parameter being passed as input to the callbacks. + public sealed class AsyncRelayCommand : ObservableObject, ICommand + { + /// + /// The to invoke when is used. + /// + private readonly Func execute; + + /// + /// The optional action to invoke when is used. + /// + private readonly Func? canExecute; + + /// + public event EventHandler? CanExecuteChanged; + + /// + /// Initializes a new instance of the class that can always execute. + /// + /// The execution logic. + public AsyncRelayCommand(Func execute) + { + this.execute = execute; + } + + /// + /// Initializes a new instance of the class that can always execute. + /// + /// The execution logic. + /// The execution status logic. + public AsyncRelayCommand(Func execute, Func canExecute) + { + this.execute = execute; + this.canExecute = canExecute; + } + + private Task? executionTask; + + /// + /// Gets the last scheduled , if available. + /// This property notifies a change when the completes.Th + /// + public Task? ExecutionTask + { + get => this.executionTask; + private set => SetAndNotifyOnCompletion(() => this.executionTask, value); + } + + /// + /// Raises the event. + /// + public void RaiseCanExecuteChanged() + { + CanExecuteChanged?.Invoke(this, EventArgs.Empty); + } + + /// + /// Provides a strongly-typed variant of . + /// + /// The input parameter. + /// Whether or not the current command can be executed. + /// Use this overload to avoid boxing, if is a value type. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool CanExecute(T parameter) + { + return this.canExecute?.Invoke(parameter) != false; + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool CanExecute(object parameter) + { + return CanExecute((T)parameter); + } + + /// + /// Provides a strongly-typed variant of . + /// + /// The input parameter. + /// Use this overload to avoid boxing, if is a value type. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Execute(T parameter) + { + if (CanExecute(parameter)) + { + ExecutionTask = this.execute(parameter); + } + } + + /// + public void Execute(object parameter) + { + Execute((T)parameter); + } + } +} \ No newline at end of file From 9c85a3c76ad3576d4f1d091443d644c91373dc0d Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Thu, 16 Apr 2020 19:11:49 +0200 Subject: [PATCH 076/595] Bug fixes in ObservableObject.SetAndNotifyOnCompletion --- .../ComponentModel/ObservableObject.cs | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/Microsoft.Toolkit.Mvvm/ComponentModel/ObservableObject.cs b/Microsoft.Toolkit.Mvvm/ComponentModel/ObservableObject.cs index a7d698c8c67..ff8abc33176 100644 --- a/Microsoft.Toolkit.Mvvm/ComponentModel/ObservableObject.cs +++ b/Microsoft.Toolkit.Mvvm/ComponentModel/ObservableObject.cs @@ -107,7 +107,7 @@ protected bool Set(ref T field, T newValue, [CallerMemberName] string propert /// The and events are not raised /// if the current and new value for the target property are the same. /// - protected bool SetAndNotifyOnCompletion(Expression> field, TTask newValue, [CallerMemberName] string propertyName = null!) + protected bool SetAndNotifyOnCompletion(Expression> field, TTask? newValue, [CallerMemberName] string propertyName = null!) where TTask : Task { // Get the target field to set @@ -119,9 +119,9 @@ protected bool SetAndNotifyOnCompletion(Expression> field, TT return false; } - TTask oldTask = (TTask)fieldInfo.GetValue(this); + TTask? oldTask = (TTask?)fieldInfo.GetValue(this); - if (EqualityComparer.Default.Equals(oldTask, newValue)) + if (EqualityComparer.Default.Equals(oldTask, newValue)) { return false; } @@ -132,6 +132,11 @@ protected bool SetAndNotifyOnCompletion(Expression> field, TT OnPropertyChanged(propertyName); + if (newValue is null) + { + return true; + } + /* We use a local async function here so that the main method can * remain synchronous and return a value that can be immediately * used by the caller. This mirrors Set(ref T, T, string). */ @@ -146,7 +151,7 @@ async Task MonitorTaskAsync() { } - TTask currentTask = (TTask)fieldInfo.GetValue(this); + TTask? currentTask = (TTask?)fieldInfo.GetValue(this); // Only notify if the property hasn't changed if (ReferenceEquals(newValue, currentTask)) From 61dd57d984de6aa72d7566dc31793f5ae37adcdc Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Thu, 16 Apr 2020 19:19:58 +0200 Subject: [PATCH 077/595] Added ICommand interface --- .../Commands/AsyncRelayCommand{T}.cs | 16 +++--------- .../Commands/ICommand{T}.cs | 26 +++++++++++++++++++ .../Commands/RelayCommand{T}.cs | 16 +++--------- 3 files changed, 32 insertions(+), 26 deletions(-) create mode 100644 Microsoft.Toolkit.Mvvm/Commands/ICommand{T}.cs diff --git a/Microsoft.Toolkit.Mvvm/Commands/AsyncRelayCommand{T}.cs b/Microsoft.Toolkit.Mvvm/Commands/AsyncRelayCommand{T}.cs index 1f0d88294b3..b58e713ddf4 100644 --- a/Microsoft.Toolkit.Mvvm/Commands/AsyncRelayCommand{T}.cs +++ b/Microsoft.Toolkit.Mvvm/Commands/AsyncRelayCommand{T}.cs @@ -5,7 +5,6 @@ using System; using System.Runtime.CompilerServices; using System.Threading.Tasks; -using System.Windows.Input; using Microsoft.Toolkit.Mvvm.ComponentModel; namespace Microsoft.Toolkit.Mvvm.Commands @@ -17,7 +16,7 @@ namespace Microsoft.Toolkit.Mvvm.Commands /// is invoked and when the returned completes. /// /// The type of parameter being passed as input to the callbacks. - public sealed class AsyncRelayCommand : ObservableObject, ICommand + public sealed class AsyncRelayCommand : ObservableObject, ICommand { /// /// The to invoke when is used. @@ -72,12 +71,7 @@ public void RaiseCanExecuteChanged() CanExecuteChanged?.Invoke(this, EventArgs.Empty); } - /// - /// Provides a strongly-typed variant of . - /// - /// The input parameter. - /// Whether or not the current command can be executed. - /// Use this overload to avoid boxing, if is a value type. + /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public bool CanExecute(T parameter) { @@ -91,11 +85,7 @@ public bool CanExecute(object parameter) return CanExecute((T)parameter); } - /// - /// Provides a strongly-typed variant of . - /// - /// The input parameter. - /// Use this overload to avoid boxing, if is a value type. + /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public void Execute(T parameter) { diff --git a/Microsoft.Toolkit.Mvvm/Commands/ICommand{T}.cs b/Microsoft.Toolkit.Mvvm/Commands/ICommand{T}.cs new file mode 100644 index 00000000000..6eb2e2e29db --- /dev/null +++ b/Microsoft.Toolkit.Mvvm/Commands/ICommand{T}.cs @@ -0,0 +1,26 @@ +using System.Windows.Input; + +namespace Microsoft.Toolkit.Mvvm.Commands +{ + /// + /// A generic interface representing a more specific version of . + /// + /// The type used as argument for the interface methods. + public interface ICommand : ICommand + { + /// + /// Provides a strongly-typed variant of . + /// + /// The input parameter. + /// Whether or not the current command can be executed. + /// Use this overload to avoid boxing, if is a value type. + bool CanExecute(T parameter); + + /// + /// Provides a strongly-typed variant of . + /// + /// The input parameter. + /// Use this overload to avoid boxing, if is a value type. + void Execute(T parameter); + } +} diff --git a/Microsoft.Toolkit.Mvvm/Commands/RelayCommand{T}.cs b/Microsoft.Toolkit.Mvvm/Commands/RelayCommand{T}.cs index 5825d7d8605..9d630534e77 100644 --- a/Microsoft.Toolkit.Mvvm/Commands/RelayCommand{T}.cs +++ b/Microsoft.Toolkit.Mvvm/Commands/RelayCommand{T}.cs @@ -23,7 +23,6 @@ using System; using System.Runtime.CompilerServices; -using System.Windows.Input; namespace Microsoft.Toolkit.Mvvm.Commands { @@ -34,7 +33,7 @@ namespace Microsoft.Toolkit.Mvvm.Commands /// in the and callback methods. /// /// The type of parameter being passed as input to the callbacks. - public sealed class RelayCommand : ICommand + public sealed class RelayCommand : ICommand { /// /// The to invoke when is used. @@ -77,12 +76,7 @@ public void RaiseCanExecuteChanged() CanExecuteChanged?.Invoke(this, EventArgs.Empty); } - /// - /// Provides a strongly-typed variant of . - /// - /// The input parameter. - /// Whether or not the current command can be executed. - /// Use this overload to avoid boxing, if is a value type. + /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public bool CanExecute(T parameter) { @@ -95,11 +89,7 @@ public bool CanExecute(object parameter) return CanExecute((T)parameter); } - /// - /// Provides a strongly-typed variant of . - /// - /// The input parameter. - /// Use this overload to avoid boxing, if is a value type. + /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public void Execute(T parameter) { From 7ea0d3173e036f11ec41556fe35801f60082fc20 Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Thu, 16 Apr 2020 20:49:27 +0200 Subject: [PATCH 078/595] Code refactoring, removed build warnings --- .../Commands/AsyncRelayCommand.cs | 2 +- .../Commands/AsyncRelayCommand{T}.cs | 2 +- .../ComponentModel/ObservableObject.cs | 43 ++++++++++++------- UnitTests/Mvvm/Test_ObservableObject.cs | 2 +- 4 files changed, 31 insertions(+), 18 deletions(-) diff --git a/Microsoft.Toolkit.Mvvm/Commands/AsyncRelayCommand.cs b/Microsoft.Toolkit.Mvvm/Commands/AsyncRelayCommand.cs index 33288d7197d..16baf7b0ea4 100644 --- a/Microsoft.Toolkit.Mvvm/Commands/AsyncRelayCommand.cs +++ b/Microsoft.Toolkit.Mvvm/Commands/AsyncRelayCommand.cs @@ -60,7 +60,7 @@ public AsyncRelayCommand(Func execute, Func canExecute) public Task? ExecutionTask { get => this.executionTask; - private set => SetAndNotifyOnCompletion(() => this.executionTask, value); + private set => SetAndNotifyOnCompletion(ref executionTask, () => executionTask, value); } /// diff --git a/Microsoft.Toolkit.Mvvm/Commands/AsyncRelayCommand{T}.cs b/Microsoft.Toolkit.Mvvm/Commands/AsyncRelayCommand{T}.cs index b58e713ddf4..e6bfd021e69 100644 --- a/Microsoft.Toolkit.Mvvm/Commands/AsyncRelayCommand{T}.cs +++ b/Microsoft.Toolkit.Mvvm/Commands/AsyncRelayCommand{T}.cs @@ -60,7 +60,7 @@ public AsyncRelayCommand(Func execute, Func canExecute) public Task? ExecutionTask { get => this.executionTask; - private set => SetAndNotifyOnCompletion(() => this.executionTask, value); + private set => SetAndNotifyOnCompletion(ref executionTask, () => executionTask, value); } /// diff --git a/Microsoft.Toolkit.Mvvm/ComponentModel/ObservableObject.cs b/Microsoft.Toolkit.Mvvm/ComponentModel/ObservableObject.cs index ff8abc33176..a74eda2a1a0 100644 --- a/Microsoft.Toolkit.Mvvm/ComponentModel/ObservableObject.cs +++ b/Microsoft.Toolkit.Mvvm/ComponentModel/ObservableObject.cs @@ -97,9 +97,23 @@ protected bool Set(ref T field, T newValue, [CallerMemberName] string propert /// will also monitor the new value of the property (a generic ) and will also /// raise the again for the target property when it completes. /// This can be used to update bindings observing that or any of its properties. + /// Here is a sample property declaration using this method: + /// + /// private Task myTask; + /// + /// public Task MyTask + /// { + /// get => myTask; + /// private set => SetAndNotifyOnCompletion(ref myTask, () => myTask, value); + /// } + /// /// /// The type of to set and monitor. - /// An returning the field to update. + /// The field storing the property's value. + /// + /// An returning the field to update. This is needed to be + /// able to raise the to notify the completion of the input task. + /// /// The property's value after the change occurred. /// (optional) The name of the property that changed. /// if the property was changed, otherwise. @@ -107,28 +121,17 @@ protected bool Set(ref T field, T newValue, [CallerMemberName] string propert /// The and events are not raised /// if the current and new value for the target property are the same. /// - protected bool SetAndNotifyOnCompletion(Expression> field, TTask? newValue, [CallerMemberName] string propertyName = null!) + protected bool SetAndNotifyOnCompletion(ref TTask? field, Expression> expr, TTask? newValue, [CallerMemberName] string propertyName = null!) where TTask : Task { - // Get the target field to set - if (!((field.Body as MemberExpression)?.Member is FieldInfo fieldInfo)) - { - ThrowArgumentExceptionForInvalidFieldExpression(); - - // This is never executed, as the method above always throws - return false; - } - - TTask? oldTask = (TTask?)fieldInfo.GetValue(this); - - if (EqualityComparer.Default.Equals(oldTask, newValue)) + if (EqualityComparer.Default.Equals(field, newValue)) { return false; } this.OnPropertyChanging(propertyName); - fieldInfo.SetValue(this, newValue); + field = newValue; OnPropertyChanged(propertyName); @@ -137,6 +140,16 @@ protected bool SetAndNotifyOnCompletion(Expression> field, T return true; } + /* Get the target field to set. This is needed because we can't + * capture the ref field in a closure (for the async method). */ + if (!((expr.Body as MemberExpression)?.Member is FieldInfo fieldInfo)) + { + ThrowArgumentExceptionForInvalidFieldExpression(); + + // This is never executed, as the method above always throws + return false; + } + /* We use a local async function here so that the main method can * remain synchronous and return a value that can be immediately * used by the caller. This mirrors Set(ref T, T, string). */ diff --git a/UnitTests/Mvvm/Test_ObservableObject.cs b/UnitTests/Mvvm/Test_ObservableObject.cs index 453fbe1da5a..9d1f5deee57 100644 --- a/UnitTests/Mvvm/Test_ObservableObject.cs +++ b/UnitTests/Mvvm/Test_ObservableObject.cs @@ -105,7 +105,7 @@ public class SampleModelWithTask : ObservableObject public Task Data { get => data; - set => SetAndNotifyOnCompletion(() => data, value); + set => SetAndNotifyOnCompletion(ref data, () => data, value); } } } From 6e9025802b973f8baa7aae762ec391f91970bbe0 Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Thu, 16 Apr 2020 21:12:46 +0200 Subject: [PATCH 079/595] Code refactoring, added the IRelayCommand interface --- .../Commands/AsyncRelayCommand.cs | 8 +++----- .../Commands/AsyncRelayCommand{T}.cs | 7 +++---- .../Commands/Interfaces/IRelayCommand.cs | 16 ++++++++++++++++ .../IRelayCommand{T}.cs} | 6 +++--- Microsoft.Toolkit.Mvvm/Commands/RelayCommand.cs | 8 +++----- .../Commands/RelayCommand{T}.cs | 7 +++---- 6 files changed, 31 insertions(+), 21 deletions(-) create mode 100644 Microsoft.Toolkit.Mvvm/Commands/Interfaces/IRelayCommand.cs rename Microsoft.Toolkit.Mvvm/Commands/{ICommand{T}.cs => Interfaces/IRelayCommand{T}.cs} (87%) diff --git a/Microsoft.Toolkit.Mvvm/Commands/AsyncRelayCommand.cs b/Microsoft.Toolkit.Mvvm/Commands/AsyncRelayCommand.cs index 16baf7b0ea4..6727f45ca70 100644 --- a/Microsoft.Toolkit.Mvvm/Commands/AsyncRelayCommand.cs +++ b/Microsoft.Toolkit.Mvvm/Commands/AsyncRelayCommand.cs @@ -5,7 +5,7 @@ using System; using System.Runtime.CompilerServices; using System.Threading.Tasks; -using System.Windows.Input; +using Microsoft.Toolkit.Mvvm.Commands.Interfaces; using Microsoft.Toolkit.Mvvm.ComponentModel; namespace Microsoft.Toolkit.Mvvm.Commands @@ -16,7 +16,7 @@ namespace Microsoft.Toolkit.Mvvm.Commands /// action, and providing a property that notifies changes when /// is invoked and when the returned completes. /// - public sealed class AsyncRelayCommand : ObservableObject, ICommand + public sealed class AsyncRelayCommand : ObservableObject, IRelayCommand { /// /// The to invoke when is used. @@ -63,9 +63,7 @@ public Task? ExecutionTask private set => SetAndNotifyOnCompletion(ref executionTask, () => executionTask, value); } - /// - /// Raises the event. - /// + /// public void RaiseCanExecuteChanged() { CanExecuteChanged?.Invoke(this, EventArgs.Empty); diff --git a/Microsoft.Toolkit.Mvvm/Commands/AsyncRelayCommand{T}.cs b/Microsoft.Toolkit.Mvvm/Commands/AsyncRelayCommand{T}.cs index e6bfd021e69..3884195849c 100644 --- a/Microsoft.Toolkit.Mvvm/Commands/AsyncRelayCommand{T}.cs +++ b/Microsoft.Toolkit.Mvvm/Commands/AsyncRelayCommand{T}.cs @@ -5,6 +5,7 @@ using System; using System.Runtime.CompilerServices; using System.Threading.Tasks; +using Microsoft.Toolkit.Mvvm.Commands.Interfaces; using Microsoft.Toolkit.Mvvm.ComponentModel; namespace Microsoft.Toolkit.Mvvm.Commands @@ -16,7 +17,7 @@ namespace Microsoft.Toolkit.Mvvm.Commands /// is invoked and when the returned completes. /// /// The type of parameter being passed as input to the callbacks. - public sealed class AsyncRelayCommand : ObservableObject, ICommand + public sealed class AsyncRelayCommand : ObservableObject, IRelayCommand { /// /// The to invoke when is used. @@ -63,9 +64,7 @@ public Task? ExecutionTask private set => SetAndNotifyOnCompletion(ref executionTask, () => executionTask, value); } - /// - /// Raises the event. - /// + /// public void RaiseCanExecuteChanged() { CanExecuteChanged?.Invoke(this, EventArgs.Empty); diff --git a/Microsoft.Toolkit.Mvvm/Commands/Interfaces/IRelayCommand.cs b/Microsoft.Toolkit.Mvvm/Commands/Interfaces/IRelayCommand.cs new file mode 100644 index 00000000000..3e6503c0b45 --- /dev/null +++ b/Microsoft.Toolkit.Mvvm/Commands/Interfaces/IRelayCommand.cs @@ -0,0 +1,16 @@ +using System.Windows.Input; + +namespace Microsoft.Toolkit.Mvvm.Commands.Interfaces +{ + /// + /// An interface expanding with the ability to raise + /// the event externally. + /// + public interface IRelayCommand : ICommand + { + /// + /// Raises the event. + /// + void RaiseCanExecuteChanged(); + } +} diff --git a/Microsoft.Toolkit.Mvvm/Commands/ICommand{T}.cs b/Microsoft.Toolkit.Mvvm/Commands/Interfaces/IRelayCommand{T}.cs similarity index 87% rename from Microsoft.Toolkit.Mvvm/Commands/ICommand{T}.cs rename to Microsoft.Toolkit.Mvvm/Commands/Interfaces/IRelayCommand{T}.cs index 6eb2e2e29db..72b4707d3f9 100644 --- a/Microsoft.Toolkit.Mvvm/Commands/ICommand{T}.cs +++ b/Microsoft.Toolkit.Mvvm/Commands/Interfaces/IRelayCommand{T}.cs @@ -1,12 +1,12 @@ using System.Windows.Input; -namespace Microsoft.Toolkit.Mvvm.Commands +namespace Microsoft.Toolkit.Mvvm.Commands.Interfaces { /// - /// A generic interface representing a more specific version of . + /// A generic interface representing a more specific version of . /// /// The type used as argument for the interface methods. - public interface ICommand : ICommand + public interface IRelayCommand : IRelayCommand { /// /// Provides a strongly-typed variant of . diff --git a/Microsoft.Toolkit.Mvvm/Commands/RelayCommand.cs b/Microsoft.Toolkit.Mvvm/Commands/RelayCommand.cs index 4b7238923f1..5b83be8a631 100644 --- a/Microsoft.Toolkit.Mvvm/Commands/RelayCommand.cs +++ b/Microsoft.Toolkit.Mvvm/Commands/RelayCommand.cs @@ -23,7 +23,7 @@ using System; using System.Runtime.CompilerServices; -using System.Windows.Input; +using Microsoft.Toolkit.Mvvm.Commands.Interfaces; namespace Microsoft.Toolkit.Mvvm.Commands { @@ -33,7 +33,7 @@ namespace Microsoft.Toolkit.Mvvm.Commands /// method is . This type does not allow you to accept command parameters /// in the and callback methods. /// - public sealed class RelayCommand : ICommand + public sealed class RelayCommand : IRelayCommand { /// /// The to invoke when is used. @@ -68,9 +68,7 @@ public RelayCommand(Action execute, Func canExecute) this.canExecute = canExecute; } - /// - /// Raises the event. - /// + /// public void RaiseCanExecuteChanged() { CanExecuteChanged?.Invoke(this, EventArgs.Empty); diff --git a/Microsoft.Toolkit.Mvvm/Commands/RelayCommand{T}.cs b/Microsoft.Toolkit.Mvvm/Commands/RelayCommand{T}.cs index 9d630534e77..9c0ba22a0d5 100644 --- a/Microsoft.Toolkit.Mvvm/Commands/RelayCommand{T}.cs +++ b/Microsoft.Toolkit.Mvvm/Commands/RelayCommand{T}.cs @@ -23,6 +23,7 @@ using System; using System.Runtime.CompilerServices; +using Microsoft.Toolkit.Mvvm.Commands.Interfaces; namespace Microsoft.Toolkit.Mvvm.Commands { @@ -33,7 +34,7 @@ namespace Microsoft.Toolkit.Mvvm.Commands /// in the and callback methods. /// /// The type of parameter being passed as input to the callbacks. - public sealed class RelayCommand : ICommand + public sealed class RelayCommand : IRelayCommand { /// /// The to invoke when is used. @@ -68,9 +69,7 @@ public RelayCommand(Action execute, Func canExecute) this.canExecute = canExecute; } - /// - /// Raises the event. - /// + /// public void RaiseCanExecuteChanged() { CanExecuteChanged?.Invoke(this, EventArgs.Empty); From 971640d9ae8551bcc29a262860da01daec5c7bd8 Mon Sep 17 00:00:00 2001 From: Vincent Date: Fri, 17 Apr 2020 00:06:15 +0200 Subject: [PATCH 080/595] fix build pipeling --- UnitTests/UnitTestApp.xaml | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/UnitTests/UnitTestApp.xaml b/UnitTests/UnitTestApp.xaml index c38cc7fbf26..40cd3de0047 100644 --- a/UnitTests/UnitTestApp.xaml +++ b/UnitTests/UnitTestApp.xaml @@ -1,5 +1,17 @@  + RequestedTheme="Light"> + + + + - + + + + + + + + + + + + + + + - - + - - - + + - - - + + - - - + + - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + - - - + + - - - + + - - - + + - - - + + - - - - - - - - - - - - - - - - - From 3916d5a62bd9cb362db97050baaf4004a4f3a1ef Mon Sep 17 00:00:00 2001 From: Vincent Gromfeld Date: Mon, 29 Jun 2020 16:43:42 +0200 Subject: [PATCH 219/595] add show(object) --- .../InAppNotificationPage.xaml.cs | 14 +++++++++-- .../InAppNotificationXaml.bind | 12 +++++++++- .../InAppNotification/InAppNotification.cs | 24 +++++++++++++++++-- 3 files changed, 45 insertions(+), 5 deletions(-) diff --git a/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/InAppNotification/InAppNotificationPage.xaml.cs b/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/InAppNotification/InAppNotificationPage.xaml.cs index 6a5f814e15a..cb35082fd6f 100644 --- a/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/InAppNotification/InAppNotificationPage.xaml.cs +++ b/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/InAppNotification/InAppNotificationPage.xaml.cs @@ -2,10 +2,11 @@ // 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.Windows.Input; using Microsoft.Toolkit.Uwp.UI.Controls; using Microsoft.Toolkit.Uwp.UI.Extensions; +using System; +using System.Collections.Generic; +using System.Windows.Input; using Windows.UI.Xaml; using Windows.UI.Xaml.Controls; @@ -57,6 +58,15 @@ private void Load() _exampleInAppNotification?.Show(GetRandomText(), NotificationDuration); }); + SampleController.Current.RegisterNewCommand("Show notification with object", (sender, args) => + { + _exampleVSCodeInAppNotification?.Dismiss(); + SetDefaultControlTemplate(); + + var random = new Random(); + _exampleInAppNotification?.Show(new KeyValuePair(random.Next(1, 10), GetRandomText()), NotificationDuration); + }); + SampleController.Current.RegisterNewCommand("Show notification with buttons (without DataTemplate)", (sender, args) => { _exampleVSCodeInAppNotification?.Dismiss(); diff --git a/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/InAppNotification/InAppNotificationXaml.bind b/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/InAppNotification/InAppNotificationXaml.bind index d3be2911f64..812d93e65cd 100644 --- a/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/InAppNotification/InAppNotificationXaml.bind +++ b/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/InAppNotification/InAppNotificationXaml.bind @@ -378,7 +378,17 @@ AnimationDuration="@[AnimationDuration:TimeSpan:100:0-5000]" VerticalOffset="@[VerticalOffset:DoubleSlider:100.0:-200.0-200.0]" HorizontalOffset="@[HorizontalOffset:DoubleSlider:0.0:-200.0-200.0]" - StackMode="@[StackMode:Enum:StackMode.Replace]" /> + StackMode="@[StackMode:Enum:StackMode.Replace]"> + + + + + + + + + + - /// Show notification using the current template + /// Show notification using the current content. /// /// Displayed duration of the notification in ms (less or equal 0 means infinite duration) public void Show(int duration = 0) @@ -131,7 +131,7 @@ public void Show(UIElement element, int duration = 0) } /// - /// Show notification using DataTemplate as the content of the notification + /// Show notification using as the content of the notification /// /// DataTemplate used as the content of the notification /// Displayed duration of the notification in ms (less or equal 0 means infinite duration) @@ -145,6 +145,22 @@ public void Show(DataTemplate dataTemplate, int duration = 0) Show(notificationOptions); } + /// + /// Show notification using as the content of the notification. + /// The will be displayed with the current . + /// + /// The content of the notification + /// Displayed duration of the notification in ms (less or equal 0 means infinite duration) + public void Show(object content, int duration = 0) + { + var notificationOptions = new NotificationOptions + { + Duration = duration, + Content = content + }; + Show(notificationOptions); + } + /// /// Dismiss the notification /// @@ -229,6 +245,10 @@ private void UpdateContent(NotificationOptions notificationOptions) _contentProvider.ContentTemplate = dataTemplate; _contentProvider.Content = null; break; + case object content: + _contentProvider.ContentTemplate = ContentTemplate; + _contentProvider.Content = content; + break; } } From 8eb02a4dbf2d1e27de709bd521878f9779a6d4c0 Mon Sep 17 00:00:00 2001 From: Vincent Gromfeld Date: Tue, 30 Jun 2020 11:31:24 +0200 Subject: [PATCH 220/595] Add resources to allow light templating --- .../Styles/MSEdgeNotificationStyle.xaml | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/Microsoft.Toolkit.Uwp.UI.Controls/InAppNotification/Styles/MSEdgeNotificationStyle.xaml b/Microsoft.Toolkit.Uwp.UI.Controls/InAppNotification/Styles/MSEdgeNotificationStyle.xaml index 63e376f0b62..638e773ffcd 100644 --- a/Microsoft.Toolkit.Uwp.UI.Controls/InAppNotification/Styles/MSEdgeNotificationStyle.xaml +++ b/Microsoft.Toolkit.Uwp.UI.Controls/InAppNotification/Styles/MSEdgeNotificationStyle.xaml @@ -20,11 +20,14 @@ + 40 + 24,0,0,0 + - + - + diff --git a/Microsoft.Toolkit.Uwp.UI.Controls/InAppNotification/Styles/MSEdgeNotificationStyle.xaml b/Microsoft.Toolkit.Uwp.UI.Controls/InAppNotification/Styles/MSEdgeNotificationStyle.xaml index 63e376f0b62..d3983c49800 100644 --- a/Microsoft.Toolkit.Uwp.UI.Controls/InAppNotification/Styles/MSEdgeNotificationStyle.xaml +++ b/Microsoft.Toolkit.Uwp.UI.Controls/InAppNotification/Styles/MSEdgeNotificationStyle.xaml @@ -34,6 +34,7 @@ diff --git a/Microsoft.Toolkit.Uwp.UI.Controls/Strings/en-US/Resources.resw b/Microsoft.Toolkit.Uwp.UI.Controls/Strings/en-US/Resources.resw index 4235b160a2b..9da0e56d058 100644 --- a/Microsoft.Toolkit.Uwp.UI.Controls/Strings/en-US/Resources.resw +++ b/Microsoft.Toolkit.Uwp.UI.Controls/Strings/en-US/Resources.resw @@ -205,13 +205,13 @@ GridSpliter Narrator Resource for GridSplitter control - - New notification - Narrator resource for a new notification using the InAppNotification + + Dismiss + The automation name for the dismiss button of the InAppNotification control. - + Notification - Name property for InAppNotification + The landmark name for the InAppNotification control. It is said by the narrator when using landmark navigation. Close tab From 7b755a8601168abe80f2ad213ba45c12d6f93708 Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Wed, 1 Jul 2020 12:57:09 +0200 Subject: [PATCH 223/595] Minor tweaks to AsyncCollectionRequestMessage --- .../AsyncCollectionRequestMessage{T}.cs | 31 ++++++++++++++----- 1 file changed, 24 insertions(+), 7 deletions(-) diff --git a/Microsoft.Toolkit.Mvvm/Messaging/Messages/AsyncCollectionRequestMessage{T}.cs b/Microsoft.Toolkit.Mvvm/Messaging/Messages/AsyncCollectionRequestMessage{T}.cs index afcc7842dd2..afebef687c8 100644 --- a/Microsoft.Toolkit.Mvvm/Messaging/Messages/AsyncCollectionRequestMessage{T}.cs +++ b/Microsoft.Toolkit.Mvvm/Messaging/Messages/AsyncCollectionRequestMessage{T}.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; using System.Collections.Concurrent; using System.Collections.Generic; using System.ComponentModel; @@ -17,12 +18,12 @@ namespace Microsoft.Toolkit.Mvvm.Messaging.Messages /// The type of request to make. public class AsyncCollectionRequestMessage : IAsyncEnumerable { - private readonly ConcurrentBag> responses = new ConcurrentBag>(); - /// - /// Gets the message responses. + /// The collection of received replies. We accept both instance, representing already running + /// operations that can be executed in parallel, or instances, which can be used so that multiple + /// asynchronous operations are only started sequentially from and do not overlap in time. /// - public IReadOnlyCollection> Responses => this.responses; + private readonly ConcurrentBag<(Task?, Func>?)> responses = new ConcurrentBag<(Task?, Func>?)>(); /// /// The instance used to link the token passed to @@ -77,7 +78,16 @@ public void Reply(T response) /// The response to use to reply to the request message. public void Reply(Task response) { - this.responses.Add(response); + this.responses.Add((response, null)); + } + + /// + /// Replies to the current request message. + /// + /// The response to use to reply to the request message. + public void Reply(Func> response) + { + this.responses.Add((null, response)); } /// @@ -90,14 +100,21 @@ public async IAsyncEnumerator GetAsyncEnumerator(CancellationToken cancellati cancellationToken.Register(this.cancellationTokenSource.Cancel); } - foreach (var task in this.responses) + foreach (var (task, func) in this.responses) { if (cancellationToken.IsCancellationRequested) { yield break; } - yield return await task.ConfigureAwait(false); + if (!(task is null)) + { + yield return await task.ConfigureAwait(false); + } + else + { + yield return await func!(cancellationToken).ConfigureAwait(false); + } } } } From 4a1ec142226b836d2f696972dc2feb5b42185161 Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Wed, 1 Jul 2020 13:06:57 +0200 Subject: [PATCH 224/595] Added GetResponsesAsync API to AsyncCollectionRequestMessage --- .../AsyncCollectionRequestMessage{T}.cs | 23 +++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/Microsoft.Toolkit.Mvvm/Messaging/Messages/AsyncCollectionRequestMessage{T}.cs b/Microsoft.Toolkit.Mvvm/Messaging/Messages/AsyncCollectionRequestMessage{T}.cs index afebef687c8..6d34f512162 100644 --- a/Microsoft.Toolkit.Mvvm/Messaging/Messages/AsyncCollectionRequestMessage{T}.cs +++ b/Microsoft.Toolkit.Mvvm/Messaging/Messages/AsyncCollectionRequestMessage{T}.cs @@ -90,6 +90,29 @@ public void Reply(Func> response) this.responses.Add((null, response)); } + /// + /// Gets the collection of received response items. + /// + /// A value to stop the operation. + /// The collection of received response items. + [Pure] + public async Task> GetResponsesAsync(CancellationToken cancellationToken = default) + { + if (cancellationToken.CanBeCanceled) + { + cancellationToken.Register(this.cancellationTokenSource.Cancel); + } + + List results = new List(this.responses.Count); + + await foreach (var response in this.WithCancellation(cancellationToken)) + { + results.Add(response); + } + + return results; + } + /// [Pure] [EditorBrowsable(EditorBrowsableState.Never)] From 573bbad3166d2f3a4eba0f5421e837bf087217fb Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Wed, 1 Jul 2020 16:01:25 +0200 Subject: [PATCH 225/595] Fixed a bug in the unit tests --- .../UnitTests.Shared/Mvvm/Test_Messenger.Request.cs | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/UnitTests/UnitTests.Shared/Mvvm/Test_Messenger.Request.cs b/UnitTests/UnitTests.Shared/Mvvm/Test_Messenger.Request.cs index 7038852a048..07fef8d9300 100644 --- a/UnitTests/UnitTests.Shared/Mvvm/Test_Messenger.Request.cs +++ b/UnitTests/UnitTests.Shared/Mvvm/Test_Messenger.Request.cs @@ -207,7 +207,7 @@ public class NumbersCollectionRequestMessage : CollectionRequestMessage [TestCategory("Mvvm")] [TestMethod] - public void Test_Messenger_AsyncCollectionRequestMessage_Ok_NoReplies() + public async Task Test_Messenger_AsyncCollectionRequestMessage_Ok_NoReplies() { var messenger = new Messenger(); var recipient = new object(); @@ -218,7 +218,7 @@ void Receive(AsyncNumbersCollectionRequestMessage m) messenger.Register(recipient, Receive); - var results = messenger.Send().Responses; + var results = await messenger.Send().GetResponsesAsync(); Assert.AreEqual(results.Count, 0); } @@ -231,7 +231,8 @@ public async Task Test_Messenger_AsyncCollectionRequestMessage_Ok_MultipleReplie object recipient1 = new object(), recipient2 = new object(), - recipient3 = new object(); + recipient3 = new object(), + recipient4 = new object(); async Task GetNumberAsync() { @@ -243,10 +244,12 @@ async Task GetNumberAsync() void Receive1(AsyncNumbersCollectionRequestMessage m) => m.Reply(1); void Receive2(AsyncNumbersCollectionRequestMessage m) => m.Reply(Task.FromResult(2)); void Receive3(AsyncNumbersCollectionRequestMessage m) => m.Reply(GetNumberAsync()); + void Receive4(AsyncNumbersCollectionRequestMessage m) => m.Reply(_ => GetNumberAsync()); messenger.Register(recipient1, Receive1); messenger.Register(recipient2, Receive2); messenger.Register(recipient3, Receive3); + messenger.Register(recipient4, Receive4); List responses = new List(); @@ -255,7 +258,7 @@ async Task GetNumberAsync() responses.Add(response); } - CollectionAssert.AreEquivalent(responses, new[] { 1, 2, 3 }); + CollectionAssert.AreEquivalent(responses, new[] { 1, 2, 3, 3 }); } public class AsyncNumbersCollectionRequestMessage : AsyncCollectionRequestMessage From 9643ee7d70d1384ea06ab14e2b21b33d74756ef2 Mon Sep 17 00:00:00 2001 From: Vincent Gromfeld Date: Thu, 2 Jul 2020 09:02:01 +0200 Subject: [PATCH 226/595] add SystemControlMSEdgeNotificationButtonBorderThickness resource --- .../InAppNotification/Styles/MSEdgeNotificationStyle.xaml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Microsoft.Toolkit.Uwp.UI.Controls/InAppNotification/Styles/MSEdgeNotificationStyle.xaml b/Microsoft.Toolkit.Uwp.UI.Controls/InAppNotification/Styles/MSEdgeNotificationStyle.xaml index 74a0ddf6ddc..37a6a9b974d 100644 --- a/Microsoft.Toolkit.Uwp.UI.Controls/InAppNotification/Styles/MSEdgeNotificationStyle.xaml +++ b/Microsoft.Toolkit.Uwp.UI.Controls/InAppNotification/Styles/MSEdgeNotificationStyle.xaml @@ -24,6 +24,7 @@ 24,0,0,0 18 Center + 2 - + - + diff --git a/Microsoft.Toolkit.Uwp.UI.Controls/InAppNotification/Styles/MSEdgeNotificationStyle.xaml b/Microsoft.Toolkit.Uwp.UI.Controls/InAppNotification/Styles/MSEdgeNotificationStyle.xaml index daffd3dda8e..87b217d0d13 100644 --- a/Microsoft.Toolkit.Uwp.UI.Controls/InAppNotification/Styles/MSEdgeNotificationStyle.xaml +++ b/Microsoft.Toolkit.Uwp.UI.Controls/InAppNotification/Styles/MSEdgeNotificationStyle.xaml @@ -8,18 +8,26 @@ out below --> - - - - + + + + - - - - + + + + @@ -64,8 +72,10 @@ - - + + From 9b082377e2725a7fce2e8c760de2af930fb8bd3d Mon Sep 17 00:00:00 2001 From: Vincent Gromfeld Date: Thu, 2 Jul 2020 10:09:36 +0200 Subject: [PATCH 229/595] add missing timer stop --- .../InAppNotification/InAppNotification.cs | 22 ++++++++++--------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/Microsoft.Toolkit.Uwp.UI.Controls/InAppNotification/InAppNotification.cs b/Microsoft.Toolkit.Uwp.UI.Controls/InAppNotification/InAppNotification.cs index 03df7373b67..de838e989b0 100644 --- a/Microsoft.Toolkit.Uwp.UI.Controls/InAppNotification/InAppNotification.cs +++ b/Microsoft.Toolkit.Uwp.UI.Controls/InAppNotification/InAppNotification.cs @@ -80,16 +80,6 @@ protected override void OnApplyTemplate() /// Displayed duration of the notification in ms (less or equal 0 means infinite duration) public void Show(int duration = 0) { - _dismissTimer.Stop(); - - var eventArgs = new InAppNotificationOpeningEventArgs(); - Opening?.Invoke(this, eventArgs); - - if (eventArgs.Cancel) - { - return; - } - // We keep our current content var notificationOptions = new NotificationOptions { @@ -258,6 +248,14 @@ private void UpdateContent(NotificationOptions notificationOptions) /// Information about the notification to display private void Show(NotificationOptions notificationOptions) { + var eventArgs = new InAppNotificationOpeningEventArgs(); + Opening?.Invoke(this, eventArgs); + + if (eventArgs.Cancel) + { + return; + } + var shouldDisplayImmediately = true; switch (StackMode) { @@ -288,6 +286,10 @@ private void Show(NotificationOptions notificationOptions) _dismissTimer.Interval = TimeSpan.FromMilliseconds(notificationOptions.Duration); _dismissTimer.Start(); } + else + { + _dismissTimer.Stop(); + } } } } From e44951b3765048a9376e6b2204ad7dd464a6c5cc Mon Sep 17 00:00:00 2001 From: Vincent Gromfeld Date: Thu, 2 Jul 2020 14:01:00 +0200 Subject: [PATCH 230/595] fix unit tests --- UnitTests/UnitTests.UWP/UnitTestApp.xaml | 31 ++++++++++++++---------- 1 file changed, 18 insertions(+), 13 deletions(-) diff --git a/UnitTests/UnitTests.UWP/UnitTestApp.xaml b/UnitTests/UnitTests.UWP/UnitTestApp.xaml index ab5fb38f2f8..877955a71f2 100644 --- a/UnitTests/UnitTests.UWP/UnitTestApp.xaml +++ b/UnitTests/UnitTests.UWP/UnitTestApp.xaml @@ -1,28 +1,27 @@  + RequestedTheme="Light"> - + - - + + - + - + - + + + - - - - \ No newline at end of file diff --git a/Microsoft.Toolkit.Uwp.UI.Controls/Microsoft.Toolkit.Uwp.UI.Controls.csproj b/Microsoft.Toolkit.Uwp.UI.Controls/Microsoft.Toolkit.Uwp.UI.Controls.csproj index 6174415e0f3..9b240477475 100644 --- a/Microsoft.Toolkit.Uwp.UI.Controls/Microsoft.Toolkit.Uwp.UI.Controls.csproj +++ b/Microsoft.Toolkit.Uwp.UI.Controls/Microsoft.Toolkit.Uwp.UI.Controls.csproj @@ -17,7 +17,6 @@ - GridSplitter: A the control that redistributes space between columns or rows of a Grid control. - HeaderedContentControl: Provides a header to content. - HeaderedItemsControl: Provides a header to items. - - HeaderedTextBlock: Provide a header for read-only text. - ImageCropper: ImageCropper control allows user to crop image freely. - ImageEx: Images are downloaded asynchronously showing a load indicator and can be stored in a local cache. - InAppNotification: Show local notifications in your application. diff --git a/Microsoft.Toolkit.Uwp.UI.Controls/Microsoft.Toolkit.Uwp.UI.Controls.csproj.DotSettings b/Microsoft.Toolkit.Uwp.UI.Controls/Microsoft.Toolkit.Uwp.UI.Controls.csproj.DotSettings index 0dd5cea5b9e..8a263d98309 100644 --- a/Microsoft.Toolkit.Uwp.UI.Controls/Microsoft.Toolkit.Uwp.UI.Controls.csproj.DotSettings +++ b/Microsoft.Toolkit.Uwp.UI.Controls/Microsoft.Toolkit.Uwp.UI.Controls.csproj.DotSettings @@ -5,7 +5,6 @@ True True True - True True True True diff --git a/Microsoft.Toolkit.Uwp.UI.Controls/Strings/en-US/Resources.resw b/Microsoft.Toolkit.Uwp.UI.Controls/Strings/en-US/Resources.resw index 66cce5b66ab..d3b003e8b80 100644 --- a/Microsoft.Toolkit.Uwp.UI.Controls/Strings/en-US/Resources.resw +++ b/Microsoft.Toolkit.Uwp.UI.Controls/Strings/en-US/Resources.resw @@ -213,14 +213,6 @@ Notification The landmark name for the InAppNotification control. It is said by the narrator when using landmark navigation. - - Close tab - Narrator Resource for TabView Close Button. - - - Close tab - Tooltip for TabView Close Button. - Remove Label for TokenizingTextBox MenuFlyout 'Remove' option. diff --git a/Microsoft.Toolkit.Uwp.UI.Controls/TabView/TabClosingEventArgs.cs b/Microsoft.Toolkit.Uwp.UI.Controls/TabView/TabClosingEventArgs.cs deleted file mode 100644 index 893776ff0be..00000000000 --- a/Microsoft.Toolkit.Uwp.UI.Controls/TabView/TabClosingEventArgs.cs +++ /dev/null @@ -1,36 +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.ComponentModel; - -namespace Microsoft.Toolkit.Uwp.UI.Controls -{ - /// - /// Event arguments for event. - /// - public class TabClosingEventArgs : CancelEventArgs - { - /// - /// Initializes a new instance of the class. - /// - /// Item being closed. - /// container being closed. - public TabClosingEventArgs(object item, TabViewItem tab) - { - Item = item; - Tab = tab; - } - - /// - /// Gets the Item being closed. - /// - public object Item { get; private set; } - - /// - /// Gets the Tab being closed. - /// - public TabViewItem Tab { get; private set; } - } -} \ No newline at end of file diff --git a/Microsoft.Toolkit.Uwp.UI.Controls/TabView/TabDraggedOutsideEventArgs.cs b/Microsoft.Toolkit.Uwp.UI.Controls/TabView/TabDraggedOutsideEventArgs.cs deleted file mode 100644 index dcf351aab9b..00000000000 --- a/Microsoft.Toolkit.Uwp.UI.Controls/TabView/TabDraggedOutsideEventArgs.cs +++ /dev/null @@ -1,35 +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; - -namespace Microsoft.Toolkit.Uwp.UI.Controls -{ - /// - /// A class used by the TabDraggedOutside Event - /// - public class TabDraggedOutsideEventArgs : EventArgs - { - /// - /// Initializes a new instance of the class. - /// - /// data context of element dragged - /// container being dragged. - public TabDraggedOutsideEventArgs(object item, TabViewItem tab) - { - Item = item; - Tab = tab; - } - - /// - /// Gets or sets the Item/Data Context of the item being dragged outside of the . - /// - public object Item { get; set; } - - /// - /// Gets the Tab being dragged outside of the . - /// - public TabViewItem Tab { get; private set; } - } -} diff --git a/Microsoft.Toolkit.Uwp.UI.Controls/TabView/TabView.HeaderLayout.cs b/Microsoft.Toolkit.Uwp.UI.Controls/TabView/TabView.HeaderLayout.cs deleted file mode 100644 index 16885700b35..00000000000 --- a/Microsoft.Toolkit.Uwp.UI.Controls/TabView/TabView.HeaderLayout.cs +++ /dev/null @@ -1,221 +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.Linq; -using Windows.UI.Xaml; - -namespace Microsoft.Toolkit.Uwp.UI.Controls -{ - /// - /// TabView methods related to calculating the width of the Headers. - /// - public partial class TabView - { - // Attached property for storing widths of tabs if set by other means during layout. - private static double GetOriginalWidth(TabViewItem obj) - { - return (double)obj.GetValue(OriginalWidthProperty); - } - - private static void SetOriginalWidth(TabViewItem obj, double value) - { - obj.SetValue(OriginalWidthProperty, value); - } - - // Using a DependencyProperty as the backing store for MyProperty. This enables animation, styling, binding, etc... - private static readonly DependencyProperty OriginalWidthProperty = - DependencyProperty.RegisterAttached("OriginalWidth", typeof(double), typeof(TabView), new PropertyMetadata(null)); - - private static void OnLayoutEffectingPropertyChanged(DependencyObject sender, DependencyPropertyChangedEventArgs args) - { - var tabview = sender as TabView; - if (tabview != null && tabview._hasLoaded) - { - tabview.TabView_SizeChanged(tabview, null); - } - } - - private void TabView_SizeChanged(object sender, SizeChangedEventArgs e) - { - // We need to do this calculation here in Size Changed as the - // Columns don't have their Actual Size calculated in Measure or Arrange. - if (_hasLoaded && _tabViewContainer != null) - { - // Look for our special columns to calculate size of other 'stuff' - var taken = _tabViewContainer.ColumnDefinitions.Sum(cd => GetIgnoreColumn(cd) ? 0 : cd.ActualWidth); - - // Get the column we want to work on for available space - var tabc = _tabViewContainer.ColumnDefinitions.FirstOrDefault(cd => GetConstrainColumn(cd)); - if (tabc != null) - { - var available = ActualWidth - taken; - var required = 0.0; - var mintabwidth = double.MaxValue; - - if (TabWidthBehavior == TabWidthMode.Actual) - { - if (_tabScroller != null) - { - // If we have a scroll container, get its size. - required = _tabScroller.ExtentWidth; - } - - // Restore original widths - foreach (var item in Items) - { - var tab = ContainerFromItem(item) as TabViewItem; - if (tab == null) - { - continue; // container not generated yet - } - - if (tab.ReadLocalValue(OriginalWidthProperty) != DependencyProperty.UnsetValue) - { - tab.Width = GetOriginalWidth(tab); - } - } - } - else if (available > 0) - { - // Calculate the width for each tab from the provider and determine how much space they take. - foreach (var item in Items) - { - var tab = ContainerFromItem(item) as TabViewItem; - if (tab == null) - { - continue; // container not generated yet - } - - mintabwidth = Math.Min(mintabwidth, tab.MinWidth); - - double width = double.NaN; - - switch (TabWidthBehavior) - { - case TabWidthMode.Equal: - width = ProvideEqualWidth(tab, item, available); - break; - case TabWidthMode.Compact: - width = ProvideCompactWidth(tab, item, available); - break; - } - - if (tab.ReadLocalValue(OriginalWidthProperty) == DependencyProperty.UnsetValue) - { - SetOriginalWidth(tab, tab.Width); - } - - if (width > double.Epsilon) - { - tab.Width = width; - required += Math.Max(Math.Min(width, tab.MaxWidth), tab.MinWidth); - } - else - { - tab.Width = GetOriginalWidth(tab); - required += tab.ActualWidth; - } - } - } - else - { - // Fix negative bounds. - available = 0.0; - - // Still need to determine a 'minimum' width (if available) - // TODO: Consolidate this logic with above better? - foreach (var item in Items) - { - var tab = ContainerFromItem(item) as TabViewItem; - if (tab == null) - { - continue; // container not generated yet - } - - mintabwidth = Math.Min(mintabwidth, tab.MinWidth); - } - } - - if (!(mintabwidth < double.MaxValue)) - { - mintabwidth = 0.0; // No Containers, no visual, 0 size. - } - - if (available > mintabwidth) - { - // Constrain the column based on our required and available space - tabc.MaxWidth = available; - } - - //// TODO: If it's less, should we move the selected tab to only be the one shown by default? - - if (available <= mintabwidth || Math.Abs(available - mintabwidth) < double.Epsilon) - { - tabc.Width = new GridLength(mintabwidth); - } - else if (required >= available) - { - // Fix size as we don't have enough space for all the tabs. - tabc.Width = new GridLength(available); - } - else - { - // We haven't filled up our space, so we want to expand to take as much as needed. - tabc.Width = GridLength.Auto; - } - } - } - } - - private double ProvideEqualWidth(TabViewItem tab, object item, double availableWidth) - { - if (double.IsNaN(SelectedTabWidth)) - { - if (Items.Count <= 1) - { - return availableWidth; - } - - return Math.Max(tab.MinWidth, availableWidth / Items.Count); - } - else if (Items.Count() <= 1) - { - // Default case of a single tab, make it full size. - return Math.Min(SelectedTabWidth, availableWidth); - } - else - { - var width = (availableWidth - SelectedTabWidth) / (Items.Count - 1); - - // Constrain between Min and Selected (Max) - if (width < tab.MinWidth) - { - width = tab.MinWidth; - } - else if (width > SelectedTabWidth) - { - width = SelectedTabWidth; - } - - // If it's selected make it full size, otherwise whatever the size should be. - return tab.IsSelected - ? Math.Min(SelectedTabWidth, availableWidth) - : width; - } - } - - private double ProvideCompactWidth(TabViewItem tab, object item, double availableWidth) - { - // If we're selected and have a value for that, then just return that. - if (tab.IsSelected && !double.IsNaN(SelectedTabWidth)) - { - return SelectedTabWidth; - } - - // Otherwise use min size. - return tab.MinWidth; - } - } -} diff --git a/Microsoft.Toolkit.Uwp.UI.Controls/TabView/TabView.ItemSources.cs b/Microsoft.Toolkit.Uwp.UI.Controls/TabView/TabView.ItemSources.cs deleted file mode 100644 index 61f61623b6f..00000000000 --- a/Microsoft.Toolkit.Uwp.UI.Controls/TabView/TabView.ItemSources.cs +++ /dev/null @@ -1,115 +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.Reflection; -using Windows.Foundation.Collections; -using Windows.UI.Xaml; -using Windows.UI.Xaml.Controls.Primitives; - -namespace Microsoft.Toolkit.Uwp.UI.Controls -{ - /// - /// TabView methods related to tracking Items and ItemsSource changes. - /// - public partial class TabView - { - // Temporary tracking of previous collections for removing events. - private MethodInfo _removeItemsSourceMethod; - - /// - protected override void OnItemsChanged(object e) - { - IVectorChangedEventArgs args = (IVectorChangedEventArgs)e; - - base.OnItemsChanged(e); - - if (args.CollectionChange == CollectionChange.ItemRemoved && SelectedIndex == -1) - { - // If we remove the selected item we should select the previous item - int startIndex = (int)args.Index + 1; - if (startIndex > Items.Count) - { - startIndex = 0; - } - - SelectedIndex = FindNextTabIndex(startIndex, -1); - } - - // Update Sizing (in case there are less items now) - TabView_SizeChanged(this, null); - } - - private void ItemContainerGenerator_ItemsChanged(object sender, ItemsChangedEventArgs e) - { - var action = (CollectionChange)e.Action; - if (action == CollectionChange.Reset) - { - // Reset collection to reload later. - _hasLoaded = false; - } - } - - private void SetInitialSelection() - { - if (SelectedItem == null) - { - // If we have an index, but didn't get the selection, make the selection - if (SelectedIndex >= 0 && SelectedIndex < Items.Count) - { - SelectedItem = Items[SelectedIndex]; - } - - // Otherwise, select the first item by default - else if (Items.Count >= 1) - { - SelectedItem = Items[0]; - } - } - } - - // Finds the next visible & enabled tab index. - private int FindNextTabIndex(int startIndex, int direction) - { - int index = startIndex; - if (direction != 0) - { - for (int i = 0; i < Items.Count; i++) - { - index += direction; - - if (index >= Items.Count) - { - index = 0; - } - else if (index < 0) - { - index = Items.Count - 1; - } - - var tabItem = ContainerFromIndex(index) as TabViewItem; - if (tabItem != null && tabItem.IsEnabled && tabItem.Visibility == Visibility.Visible) - { - break; - } - } - } - - return index; - } - - private void ItemsSource_PropertyChanged(DependencyObject sender, DependencyProperty dp) - { - // Use reflection to store a 'Remove' method of any possible collection in ItemsSource - // Cache for efficiency later. - if (ItemsSource != null) - { - _removeItemsSourceMethod = ItemsSource.GetType().GetMethod("Remove"); - } - else - { - _removeItemsSourceMethod = null; - } - } - } -} diff --git a/Microsoft.Toolkit.Uwp.UI.Controls/TabView/TabView.Properties.cs b/Microsoft.Toolkit.Uwp.UI.Controls/TabView/TabView.Properties.cs deleted file mode 100644 index 5d33b33f1cc..00000000000 --- a/Microsoft.Toolkit.Uwp.UI.Controls/TabView/TabView.Properties.cs +++ /dev/null @@ -1,245 +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; -using Windows.UI.Xaml.Controls; - -namespace Microsoft.Toolkit.Uwp.UI.Controls -{ - /// - /// TabView properties. - /// - public partial class TabView - { - /// - /// Gets or sets the content to appear to the left or above the tab strip. - /// - public object TabStartHeader - { - get { return (object)GetValue(TabStartHeaderProperty); } - set { SetValue(TabStartHeaderProperty, value); } - } - - /// - /// Identifies the dependency property. - /// - /// The identifier for the dependency property. - public static readonly DependencyProperty TabStartHeaderProperty = - DependencyProperty.Register(nameof(TabStartHeader), typeof(object), typeof(TabView), new PropertyMetadata(null, OnLayoutEffectingPropertyChanged)); - - /// - /// Gets or sets the for the . - /// - public DataTemplate TabStartHeaderTemplate - { - get { return (DataTemplate)GetValue(TabStartHeaderTemplateProperty); } - set { SetValue(TabStartHeaderTemplateProperty, value); } - } - - /// - /// Identifies the dependency property. - /// - /// The identifier for the dependency property. - public static readonly DependencyProperty TabStartHeaderTemplateProperty = - DependencyProperty.Register(nameof(TabStartHeaderTemplate), typeof(DataTemplate), typeof(TabView), new PropertyMetadata(null, OnLayoutEffectingPropertyChanged)); - - /// - /// Gets or sets the content to appear next to the tab strip. - /// - public object TabActionHeader - { - get { return (object)GetValue(TabActionHeaderProperty); } - set { SetValue(TabActionHeaderProperty, value); } - } - - /// - /// Identifies the dependency property. - /// - /// The identifier for the dependency property. - public static readonly DependencyProperty TabActionHeaderProperty = - DependencyProperty.Register(nameof(TabActionHeader), typeof(object), typeof(TabView), new PropertyMetadata(null, OnLayoutEffectingPropertyChanged)); - - /// - /// Gets or sets the for the . - /// - public DataTemplate TabActionHeaderTemplate - { - get { return (DataTemplate)GetValue(TabActionHeaderTemplateProperty); } - set { SetValue(TabActionHeaderTemplateProperty, value); } - } - - /// - /// Identifies the dependency property. - /// - /// The identifier for the dependency property. - public static readonly DependencyProperty TabActionHeaderTemplateProperty = - DependencyProperty.Register(nameof(TabActionHeaderTemplate), typeof(DataTemplate), typeof(TabView), new PropertyMetadata(null, OnLayoutEffectingPropertyChanged)); - - /// - /// Gets or sets the content to appear to the right or below the tab strip. - /// - public object TabEndHeader - { - get { return (object)GetValue(TabEndHeaderProperty); } - set { SetValue(TabEndHeaderProperty, value); } - } - - /// - /// Identifies the dependency property. - /// - /// The identifier for the dependency property. - public static readonly DependencyProperty TabEndHeaderProperty = - DependencyProperty.Register(nameof(TabEndHeader), typeof(object), typeof(TabView), new PropertyMetadata(null, OnLayoutEffectingPropertyChanged)); - - /// - /// Gets or sets the for the . - /// - public DataTemplate TabEndHeaderTemplate - { - get { return (DataTemplate)GetValue(TabEndHeaderTemplateProperty); } - set { SetValue(TabEndHeaderTemplateProperty, value); } - } - - /// - /// Identifies the dependency property. - /// - /// The identifier for the dependency property. - public static readonly DependencyProperty TabEndHeaderTemplateProperty = - DependencyProperty.Register(nameof(TabEndHeaderTemplate), typeof(DataTemplate), typeof(TabView), new PropertyMetadata(null, OnLayoutEffectingPropertyChanged)); - - /// - /// Gets or sets the default for the . - /// - public DataTemplate ItemHeaderTemplate - { - get { return (DataTemplate)GetValue(ItemHeaderTemplateProperty); } - set { SetValue(ItemHeaderTemplateProperty, value); } - } - - /// - /// Identifies the dependency property. - /// - /// The identifier for the dependency property. - public static readonly DependencyProperty ItemHeaderTemplateProperty = - DependencyProperty.Register(nameof(ItemHeaderTemplate), typeof(DataTemplate), typeof(TabView), new PropertyMetadata(null, OnLayoutEffectingPropertyChanged)); - - /// - /// Gets or sets a value indicating whether by default a Tab can be closed or not if no value to is provided. - /// - public bool CanCloseTabs - { - get { return (bool)GetValue(CanCloseTabsProperty); } - set { SetValue(CanCloseTabsProperty, value); } - } - - /// - /// Identifies the dependency property. - /// - /// The identifier for the dependency property. - public static readonly DependencyProperty CanCloseTabsProperty = - DependencyProperty.Register(nameof(CanCloseTabs), typeof(bool), typeof(TabView), new PropertyMetadata(false, OnLayoutEffectingPropertyChanged)); - - /// - /// Gets or sets a value indicating whether a Close Button should be included in layout calculations. - /// - public bool IsCloseButtonOverlay - { - get { return (bool)GetValue(IsCloseButtonOverlayProperty); } - set { SetValue(IsCloseButtonOverlayProperty, value); } - } - - /// - /// Identifies the dependency property. - /// - /// The identifier for the dependency property. - public static readonly DependencyProperty IsCloseButtonOverlayProperty = - DependencyProperty.Register(nameof(IsCloseButtonOverlay), typeof(bool), typeof(TabView), new PropertyMetadata(false, OnLayoutEffectingPropertyChanged)); - - /// - /// Gets or sets a value indicating the size of the selected tab. By default this is set to Auto and the selected tab size doesn't change. - /// - public double SelectedTabWidth - { - get { return (double)GetValue(SelectedTabWidthProperty); } - set { SetValue(SelectedTabWidthProperty, value); } - } - - /// - /// Identifies the dependency property. - /// - /// The identifier for the dependency property. - public static readonly DependencyProperty SelectedTabWidthProperty = - DependencyProperty.Register(nameof(SelectedTabWidth), typeof(double), typeof(TabView), new PropertyMetadata(double.NaN, OnLayoutEffectingPropertyChanged)); - - /// - /// Gets or sets the current which determins how tab headers' width behave. - /// - public TabWidthMode TabWidthBehavior - { - get { return (TabWidthMode)GetValue(TabWidthBehaviorProperty); } - set { SetValue(TabWidthBehaviorProperty, value); } - } - - /// - /// Identifies the dependency property. - /// - /// The identifier for the dependency property. - public static readonly DependencyProperty TabWidthBehaviorProperty = - DependencyProperty.Register(nameof(TabWidthBehavior), typeof(TabWidthMode), typeof(TabView), new PropertyMetadata(TabWidthMode.Actual, OnLayoutEffectingPropertyChanged)); - - /// - /// Gets the attached property value to indicate if this grid column should be ignored when calculating header sizes. - /// - /// Grid Column. - /// Boolean indicating if this column is ignored by TabViewHeader logic. - public static bool GetIgnoreColumn(ColumnDefinition obj) - { - return (bool)obj.GetValue(IgnoreColumnProperty); - } - - /// - /// Sets the attached property value for - /// - /// Grid Column. - /// Boolean value - public static void SetIgnoreColumn(ColumnDefinition obj, bool value) - { - obj.SetValue(IgnoreColumnProperty, value); - } - - /// - /// Identifies the attached property. - /// - /// The identifier for the IgnoreColumn attached property. - public static readonly DependencyProperty IgnoreColumnProperty = - DependencyProperty.RegisterAttached("IgnoreColumn", typeof(bool), typeof(TabView), new PropertyMetadata(false)); - - /// - /// Gets the attached value indicating this column should be restricted for the headers. - /// - /// Grid Column. - /// True if this column should be constrained. - public static bool GetConstrainColumn(ColumnDefinition obj) - { - return (bool)obj.GetValue(ConstrainColumnProperty); - } - - /// - /// Sets the attached property value for the - /// - /// Grid Column. - /// Boolean value. - public static void SetConstrainColumn(ColumnDefinition obj, bool value) - { - obj.SetValue(ConstrainColumnProperty, value); - } - - /// - /// Identifies the attached property. - /// - /// The identifier for the ConstrainColumn attached property. - public static readonly DependencyProperty ConstrainColumnProperty = - DependencyProperty.RegisterAttached("ConstrainColumn", typeof(bool), typeof(TabView), new PropertyMetadata(false)); - } -} diff --git a/Microsoft.Toolkit.Uwp.UI.Controls/TabView/TabView.cs b/Microsoft.Toolkit.Uwp.UI.Controls/TabView/TabView.cs deleted file mode 100644 index 0e86ab398f5..00000000000 --- a/Microsoft.Toolkit.Uwp.UI.Controls/TabView/TabView.cs +++ /dev/null @@ -1,320 +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.ComponentModel; -using System.Linq; -using Microsoft.Toolkit.Uwp.UI.Extensions; -using Windows.ApplicationModel.DataTransfer; -using Windows.UI.Xaml; -using Windows.UI.Xaml.Controls; -using Windows.UI.Xaml.Controls.Primitives; -using Windows.UI.Xaml.Data; - -namespace Microsoft.Toolkit.Uwp.UI.Controls -{ - /// - /// TabView is a control for displaying a set of tabs and their content. - /// - [Obsolete("Please migrate to the TabView control from WinUI, this control will be removed in a future release. https://aka.ms/winui")] - [TemplatePart(Name = TabContentPresenterName, Type = typeof(ContentPresenter))] - [TemplatePart(Name = TabViewContainerName, Type = typeof(Grid))] - [TemplatePart(Name = TabsItemsPresenterName, Type = typeof(ItemsPresenter))] - [TemplatePart(Name = TabsScrollViewerName, Type = typeof(ScrollViewer))] - [TemplatePart(Name = TabsScrollBackButtonName, Type = typeof(ButtonBase))] - [TemplatePart(Name = TabsScrollForwardButtonName, Type = typeof(ButtonBase))] - public partial class TabView : ListViewBase - { - private const int ScrollAmount = 50; // TODO: Should this be based on TabWidthMode - - private const string TabContentPresenterName = "TabContentPresenter"; - private const string TabViewContainerName = "TabViewContainer"; - private const string TabsItemsPresenterName = "TabsItemsPresenter"; - private const string TabsScrollViewerName = "ScrollViewer"; - private const string TabsScrollBackButtonName = "ScrollBackButton"; - private const string TabsScrollForwardButtonName = "ScrollForwardButton"; - - private ContentPresenter _tabContentPresenter; - private Grid _tabViewContainer; - private ItemsPresenter _tabItemsPresenter; - private ScrollViewer _tabScroller; - private ButtonBase _tabScrollBackButton; - private ButtonBase _tabScrollForwardButton; - - private bool _hasLoaded; - private bool _isDragging; - - /// - /// Initializes a new instance of the class. - /// - public TabView() - { - DefaultStyleKey = typeof(TabView); - - // Container Generation Hooks - RegisterPropertyChangedCallback(ItemsSourceProperty, ItemsSource_PropertyChanged); - ItemContainerGenerator.ItemsChanged += ItemContainerGenerator_ItemsChanged; - - // Drag and Layout Hooks - DragItemsStarting += TabView_DragItemsStarting; - DragItemsCompleted += TabView_DragItemsCompleted; - SizeChanged += TabView_SizeChanged; - - // Selection Hook - SelectionChanged += TabView_SelectionChanged; - } - - /// - /// Occurs when a tab is dragged by the user outside of the . Generally, this paradigm is used to create a new-window with the torn-off tab. - /// The creation and handling of the new-window is left to the app's developer. - /// - public event EventHandler TabDraggedOutside; - - /// - /// Occurs when a tab's Close button is clicked. Set to true to prevent automatic Tab Closure. - /// - public event EventHandler TabClosing; - - /// - protected override DependencyObject GetContainerForItemOverride() => new TabViewItem(); - - /// - protected override bool IsItemItsOwnContainerOverride(object item) - { - return item is TabViewItem; - } - - /// - protected override void OnApplyTemplate() - { - base.OnApplyTemplate(); - - if (_tabItemsPresenter != null) - { - _tabItemsPresenter.SizeChanged -= TabView_SizeChanged; - } - - if (_tabScroller != null) - { - _tabScroller.Loaded -= ScrollViewer_Loaded; - } - - _tabContentPresenter = GetTemplateChild(TabContentPresenterName) as ContentPresenter; - _tabViewContainer = GetTemplateChild(TabViewContainerName) as Grid; - _tabItemsPresenter = GetTemplateChild(TabsItemsPresenterName) as ItemsPresenter; - _tabScroller = GetTemplateChild(TabsScrollViewerName) as ScrollViewer; - - if (_tabItemsPresenter != null) - { - _tabItemsPresenter.SizeChanged += TabView_SizeChanged; - } - - if (_tabScroller != null) - { - _tabScroller.Loaded += ScrollViewer_Loaded; - } - } - - private void ScrollViewer_Loaded(object sender, RoutedEventArgs e) - { - _tabScroller.Loaded -= ScrollViewer_Loaded; - - if (_tabScrollBackButton != null) - { - _tabScrollBackButton.Click -= ScrollTabBackButton_Click; - } - - if (_tabScrollForwardButton != null) - { - _tabScrollForwardButton.Click -= ScrollTabForwardButton_Click; - } - - _tabScrollBackButton = _tabScroller.FindDescendantByName(TabsScrollBackButtonName) as ButtonBase; - _tabScrollForwardButton = _tabScroller.FindDescendantByName(TabsScrollForwardButtonName) as ButtonBase; - - if (_tabScrollBackButton != null) - { - _tabScrollBackButton.Click += ScrollTabBackButton_Click; - } - - if (_tabScrollForwardButton != null) - { - _tabScrollForwardButton.Click += ScrollTabForwardButton_Click; - } - } - - private void ScrollTabBackButton_Click(object sender, RoutedEventArgs e) - { - _tabScroller.ChangeView(Math.Max(0, _tabScroller.HorizontalOffset - ScrollAmount), null, null); - } - - private void ScrollTabForwardButton_Click(object sender, RoutedEventArgs e) - { - _tabScroller.ChangeView(Math.Min(_tabScroller.ScrollableWidth, _tabScroller.HorizontalOffset + ScrollAmount), null, null); - } - - private void TabView_SelectionChanged(object sender, SelectionChangedEventArgs e) - { - if (_isDragging) - { - // Skip if we're dragging, we'll reset when we're done. - return; - } - - if (_tabContentPresenter != null) - { - if (SelectedItem == null) - { - _tabContentPresenter.Content = null; - _tabContentPresenter.ContentTemplate = null; - } - else - { - if (ContainerFromItem(SelectedItem) is TabViewItem container) - { - _tabContentPresenter.Content = container.Content; - _tabContentPresenter.ContentTemplate = container.ContentTemplate; - } - } - } - - // If our width can be effected by the selection, need to run algorithm. - if (!double.IsNaN(SelectedTabWidth)) - { - TabView_SizeChanged(sender, null); - } - } - - /// - protected override void PrepareContainerForItemOverride(DependencyObject element, object item) - { - base.PrepareContainerForItemOverride(element, item); - - var tabitem = element as TabViewItem; - - tabitem.Loaded -= TabViewItem_Loaded; - tabitem.Closing -= TabViewItem_Closing; - tabitem.Loaded += TabViewItem_Loaded; - tabitem.Closing += TabViewItem_Closing; - - if (tabitem.Header == null) - { - tabitem.Header = item; - } - - if (tabitem.HeaderTemplate == null) - { - var headertemplatebinding = new Binding() - { - Source = this, - Path = new PropertyPath(nameof(ItemHeaderTemplate)), - Mode = BindingMode.OneWay - }; - tabitem.SetBinding(TabViewItem.HeaderTemplateProperty, headertemplatebinding); - } - - if (tabitem.IsClosable != true && tabitem.ReadLocalValue(TabViewItem.IsClosableProperty) == DependencyProperty.UnsetValue) - { - var iscloseablebinding = new Binding() - { - Source = this, - Path = new PropertyPath(nameof(CanCloseTabs)), - Mode = BindingMode.OneWay, - }; - tabitem.SetBinding(TabViewItem.IsClosableProperty, iscloseablebinding); - } - } - - private void TabViewItem_Loaded(object sender, RoutedEventArgs e) - { - var tabitem = sender as TabViewItem; - - tabitem.Loaded -= TabViewItem_Loaded; - - // Only need to do this once. - if (!_hasLoaded) - { - _hasLoaded = true; - - // Need to set a tab's selection on load, otherwise ListView resets to null. - SetInitialSelection(); - - // Need to make sure ContentPresenter is set to content based on selection. - TabView_SelectionChanged(this, null); - - // Need to make sure we've registered our removal method. - ItemsSource_PropertyChanged(this, null); - - // Make sure we complete layout now. - TabView_SizeChanged(this, null); - } - } - - private void TabViewItem_Closing(object sender, TabClosingEventArgs e) - { - var item = ItemFromContainer(e.Tab); - - var args = new TabClosingEventArgs(item, e.Tab); - TabClosing?.Invoke(this, args); - - if (!args.Cancel) - { - if (ItemsSource != null) - { - _removeItemsSourceMethod?.Invoke(ItemsSource, new object[] { item }); - } - else - { - Items.Remove(item); - } - } - } - - private void TabView_DragItemsStarting(object sender, DragItemsStartingEventArgs e) - { - // Keep track of drag so we don't modify content until done. - _isDragging = true; - } - - private void TabView_DragItemsCompleted(ListViewBase sender, DragItemsCompletedEventArgs args) - { - _isDragging = false; - - // args.DropResult == None when outside of area (e.g. create new window) - if (args.DropResult == DataPackageOperation.None) - { - var item = args.Items.FirstOrDefault(); - var tab = ContainerFromItem(item) as TabViewItem; - - if (tab == null && item is FrameworkElement fe) - { - tab = fe.FindParent(); - } - - if (tab == null) - { - // We still don't have a TabViewItem, most likely is a static TabViewItem in the template being dragged and not selected. - // This is a fallback scenario for static tabs. - // Note: This can be wrong if two TabViewItems share the exact same Content (i.e. a string), this should be unlikely in any practical scenario. - for (int i = 0; i < Items.Count; i++) - { - var tabItem = ContainerFromIndex(i) as TabViewItem; - if (ReferenceEquals(tabItem.Content, item)) - { - tab = tabItem; - break; - } - } - } - - TabDraggedOutside?.Invoke(this, new TabDraggedOutsideEventArgs(item, tab)); - } - else - { - // If dragging the active tab, there's an issue with the CP blanking. - TabView_SelectionChanged(this, null); - } - } - } -} diff --git a/Microsoft.Toolkit.Uwp.UI.Controls/TabView/TabView.xaml b/Microsoft.Toolkit.Uwp.UI.Controls/TabView/TabView.xaml deleted file mode 100644 index d3310aadf06..00000000000 --- a/Microsoft.Toolkit.Uwp.UI.Controls/TabView/TabView.xaml +++ /dev/null @@ -1,736 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 0 - 0,0,8,0 - 8,0,-8,0 - 0,1,1,0 - 16 - 48 - 40 - NaN - 32 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/Microsoft.Toolkit.Uwp.UI.Controls/TabView/TabViewItem.Properties.cs b/Microsoft.Toolkit.Uwp.UI.Controls/TabView/TabViewItem.Properties.cs deleted file mode 100644 index 3ce0f3e5483..00000000000 --- a/Microsoft.Toolkit.Uwp.UI.Controls/TabView/TabViewItem.Properties.cs +++ /dev/null @@ -1,79 +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; -using Windows.UI.Xaml.Controls; - -namespace Microsoft.Toolkit.Uwp.UI.Controls -{ - /// - /// Item Container for a . - /// - public partial class TabViewItem - { - /// - /// Gets or sets the header content for the tab. - /// - public object Header - { - get { return (object)GetValue(HeaderProperty); } - set { SetValue(HeaderProperty, value); } - } - - /// - /// Identifies the dependency property. - /// - /// The identifier for the dependency property. - public static readonly DependencyProperty HeaderProperty = - DependencyProperty.Register(nameof(Header), typeof(object), typeof(TabViewItem), new PropertyMetadata(null)); - - /// - /// Gets or sets the icon to appear in the tab header. - /// - public IconElement Icon - { - get { return (IconElement)GetValue(IconProperty); } - set { SetValue(IconProperty, value); } - } - - /// - /// Identifies the dependency property. - /// - /// The identifier for the dependency property. - public static readonly DependencyProperty IconProperty = - DependencyProperty.Register(nameof(Icon), typeof(IconElement), typeof(TabViewItem), new PropertyMetadata(null)); - - /// - /// Gets or sets the template to override for the tab header. - /// - public DataTemplate HeaderTemplate - { - get { return (DataTemplate)GetValue(HeaderTemplateProperty); } - set { SetValue(HeaderTemplateProperty, value); } - } - - /// - /// Identifies the dependency property. - /// - /// The identifier for the dependency property. - public static readonly DependencyProperty HeaderTemplateProperty = - DependencyProperty.Register(nameof(HeaderTemplate), typeof(DataTemplate), typeof(TabViewItem), new PropertyMetadata(null)); - - /// - /// Gets or sets a value indicating whether the tab can be closed by the user with the close button. - /// - public bool IsClosable - { - get { return (bool)GetValue(IsClosableProperty); } - set { SetValue(IsClosableProperty, value); } - } - - /// - /// Identifies the dependency property. - /// - /// The identifier for the dependency property. - public static readonly DependencyProperty IsClosableProperty = - DependencyProperty.Register(nameof(IsClosable), typeof(bool), typeof(TabViewItem), new PropertyMetadata(null)); - } -} diff --git a/Microsoft.Toolkit.Uwp.UI.Controls/TabView/TabViewItem.cs b/Microsoft.Toolkit.Uwp.UI.Controls/TabView/TabViewItem.cs deleted file mode 100644 index 69a968fd839..00000000000 --- a/Microsoft.Toolkit.Uwp.UI.Controls/TabView/TabViewItem.cs +++ /dev/null @@ -1,116 +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 Windows.Devices.Input; -using Windows.System; -using Windows.UI.Core; -using Windows.UI.Input; -using Windows.UI.Xaml; -using Windows.UI.Xaml.Controls; -using Windows.UI.Xaml.Controls.Primitives; -using Windows.UI.Xaml.Input; - -namespace Microsoft.Toolkit.Uwp.UI.Controls -{ - /// - /// Item Container for a . - /// - [TemplatePart(Name = TabCloseButtonName, Type = typeof(ButtonBase))] - public partial class TabViewItem : ListViewItem - { - private const string TabCloseButtonName = "CloseButton"; - - private ButtonBase _tabCloseButton; - - private bool _isMiddleClick; - - /// - /// Initializes a new instance of the class. - /// - public TabViewItem() - { - DefaultStyleKey = typeof(TabViewItem); - } - - /// - /// Fired when the Tab's close button is clicked. - /// - public event EventHandler Closing; - - /// - protected override void OnApplyTemplate() - { - base.OnApplyTemplate(); - - if (_tabCloseButton != null) - { - _tabCloseButton.Click -= TabCloseButton_Click; - } - - _tabCloseButton = GetTemplateChild(TabCloseButtonName) as ButtonBase; - - if (_tabCloseButton != null) - { - _tabCloseButton.Click += TabCloseButton_Click; - } - } - - /// - protected override void OnPointerPressed(PointerRoutedEventArgs e) - { - _isMiddleClick = false; - - if (e.Pointer.PointerDeviceType == PointerDeviceType.Mouse) - { - PointerPoint pointerPoint = e.GetCurrentPoint(this); - - // Record if middle button is pressed - if (pointerPoint.Properties.IsMiddleButtonPressed) - { - _isMiddleClick = true; - } - - // Disable unwanted behaviour inherited by ListViewItem: - // Disable "Ctrl + Left click" to deselect tab - // Or variant like "Ctrl + Shift + Left click" - // Or "Ctrl + Alt + Left click" - if (pointerPoint.Properties.IsLeftButtonPressed) - { - var ctrl = Window.Current.CoreWindow.GetKeyState(VirtualKey.Control); - if (ctrl.HasFlag(CoreVirtualKeyStates.Down)) - { - // return here so the event won't be picked up by the base class - // but keep this event unhandled so it can be picked up further - return; - } - } - } - - base.OnPointerPressed(e); - } - - /// - protected override void OnPointerReleased(PointerRoutedEventArgs e) - { - base.OnPointerReleased(e); - - // Close on Middle-Click - if (_isMiddleClick) - { - TabCloseButton_Click(this, null); - } - - _isMiddleClick = false; - } - - private void TabCloseButton_Click(object sender, RoutedEventArgs e) - { - if (IsClosable) - { - Closing?.Invoke(this, new TabClosingEventArgs(Content, this)); - } - } - } -} diff --git a/Microsoft.Toolkit.Uwp.UI.Controls/TabView/TabWidthMode.cs b/Microsoft.Toolkit.Uwp.UI.Controls/TabView/TabWidthMode.cs deleted file mode 100644 index 65b1ce03844..00000000000 --- a/Microsoft.Toolkit.Uwp.UI.Controls/TabView/TabWidthMode.cs +++ /dev/null @@ -1,36 +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; - -namespace Microsoft.Toolkit.Uwp.UI.Controls -{ - /// - /// Possible modes for how to layout a Header's Width in the . - /// - public enum TabWidthMode - { - /// - /// Each tab header takes up as much space as it needs. This is similar to how WPF and Visual Studio Code behave. - /// Suggest to keep set to false. - /// is ignored. - /// In this scenario, tab width behavior is effectively turned off. This can be useful when using custom styling or a custom panel for layout of as well. - /// - Actual, - - /// - /// Each tab header will use the minimal space set by on the . - /// Suggest to set the to show more content for the selected item. - /// - Compact, - - /// - /// Each tab header will fill to fit the available space. If is set, that will be used as a Maximum Width. - /// This is similar to how Microsoft Edge behaves when used with the . - /// Suggest to set to true. - /// Suggest to set to 200 and the TabViewItemHeaderMinWidth Resource to 90. - /// - Equal, - } -} diff --git a/Microsoft.Toolkit.Uwp.UI.Controls/Themes/Generic.xaml b/Microsoft.Toolkit.Uwp.UI.Controls/Themes/Generic.xaml index 592fbb45626..9c17a3717bd 100644 --- a/Microsoft.Toolkit.Uwp.UI.Controls/Themes/Generic.xaml +++ b/Microsoft.Toolkit.Uwp.UI.Controls/Themes/Generic.xaml @@ -9,7 +9,6 @@ - @@ -27,7 +26,6 @@ - diff --git a/Microsoft.Toolkit.Uwp.UI.Controls/TileControl/TileControl.cs b/Microsoft.Toolkit.Uwp.UI.Controls/TileControl/TileControl.cs index 6799439d67a..cdc4af642c5 100644 --- a/Microsoft.Toolkit.Uwp.UI.Controls/TileControl/TileControl.cs +++ b/Microsoft.Toolkit.Uwp.UI.Controls/TileControl/TileControl.cs @@ -181,13 +181,6 @@ public class TileControl : ContentControl /// public event EventHandler ImageLoaded; - /// - /// Gets a value indicating whether the platform supports Composition. - /// - [Obsolete("This property is now obsolete and will be removed in a future version of the Toolkit.")] - public static bool IsCompositionSupported => !DesignTimeHelpers.IsRunningInLegacyDesignerMode && - ApiInformation.IsApiContractPresent("Windows.Foundation.UniversalApiContract", 3); // SDK >= 14393 - /// /// Initializes a new instance of the class. /// diff --git a/Microsoft.Toolkit.Uwp.UI.Controls/VisualStudioToolsManifest.xml b/Microsoft.Toolkit.Uwp.UI.Controls/VisualStudioToolsManifest.xml index e2bc71350ef..27fe2b2f0bc 100644 --- a/Microsoft.Toolkit.Uwp.UI.Controls/VisualStudioToolsManifest.xml +++ b/Microsoft.Toolkit.Uwp.UI.Controls/VisualStudioToolsManifest.xml @@ -13,7 +13,6 @@ - @@ -33,8 +32,6 @@ - - diff --git a/Microsoft.Toolkit/Extensions/StringExtensions.cs b/Microsoft.Toolkit/Extensions/StringExtensions.cs index 5e81855f128..a39030a11a4 100644 --- a/Microsoft.Toolkit/Extensions/StringExtensions.cs +++ b/Microsoft.Toolkit/Extensions/StringExtensions.cs @@ -86,14 +86,6 @@ public static bool IsDecimal(this string str) /// true if the string contains only letters; otherwise, false. public static bool IsCharacterString(this string str) => Regex.IsMatch(str, CharactersRegex); - /// - /// Returns a string representation of an object. - /// - /// The object to convert. - /// String representation of the object. - [Obsolete("Use value?.ToString() instead. This will be removed in the next release of the toolkit.")] - public static string ToSafeString(this object value) => value?.ToString(); - /// /// Returns a string with HTML comments, scripts, styles, and tags removed. /// From 5066dff24fe88a29ddc28696656d7e414449d186 Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Mon, 17 Aug 2020 11:22:56 +0200 Subject: [PATCH 380/595] Added MessageHandler delegate, removed closures --- .../Messaging/IMessenger.cs | 15 +++- Microsoft.Toolkit.Mvvm/Messaging/Messenger.cs | 70 +++++++++++-------- .../Messaging/MessengerExtensions.cs | 10 +-- 3 files changed, 60 insertions(+), 35 deletions(-) diff --git a/Microsoft.Toolkit.Mvvm/Messaging/IMessenger.cs b/Microsoft.Toolkit.Mvvm/Messaging/IMessenger.cs index 39e38d5af01..d1a6e1513df 100644 --- a/Microsoft.Toolkit.Mvvm/Messaging/IMessenger.cs +++ b/Microsoft.Toolkit.Mvvm/Messaging/IMessenger.cs @@ -7,6 +7,17 @@ namespace Microsoft.Toolkit.Mvvm.Messaging { + /// + /// A used to represent actions to invoke when a message is received. + /// The recipient is given as an input argument to allow message registrations to avoid creating + /// closures: if an instance method on a recipient needs to be invoked it is possible to just + /// cast the recipient to the right type and then access the local method from that instance. + /// + /// The type of message to receive. + /// The recipient that is receiving the message. + /// The message being received. + public delegate void MessageHandler(object recipient, TMessage message); + /// /// An interface for a type providing the ability to exchange messages between different objects. /// @@ -32,9 +43,9 @@ bool IsRegistered(object recipient, TToken token) /// The type of token to use to pick the messages to receive. /// The recipient that will receive the messages. /// A token used to determine the receiving channel to use. - /// The to invoke when a message is received. + /// The to invoke when a message is received. /// Thrown when trying to register the same message twice. - void Register(object recipient, TToken token, Action action) + void Register(object recipient, TToken token, MessageHandler action) where TMessage : class where TToken : IEquatable; diff --git a/Microsoft.Toolkit.Mvvm/Messaging/Messenger.cs b/Microsoft.Toolkit.Mvvm/Messaging/Messenger.cs index 4e5e780d4dc..9049cdd1593 100644 --- a/Microsoft.Toolkit.Mvvm/Messaging/Messenger.cs +++ b/Microsoft.Toolkit.Mvvm/Messaging/Messenger.cs @@ -23,11 +23,16 @@ namespace Microsoft.Toolkit.Mvvm.Messaging /// /// Then, register your a recipient for this message: /// - /// Messenger.Default.Register<LoginCompletedMessage>(this, m => + /// Messenger.Default.Register<LoginCompletedMessage>(this, (r, m) => /// { /// // Handle the message here... /// }); /// + /// The message handler here is a lambda expression taking two parameters: the recipient and the message. + /// This is done to avoid the allocations for the closures that would've been generated if the expression + /// had captured the current instance - instead it is possible to just cast the recipient to the right type + /// and access private instance members from the handler directly. This allows the message handler to be a + /// static method, which enables the C# to perform a number of additional memory optimizations. /// Finally, send a message when needed, like so: /// /// Messenger.Default.Send<LoginCompletedMessage>(); @@ -62,7 +67,7 @@ public sealed class Messenger : IMessenger // | ________(recipients registrations)___________\________/ / __/ // | / _______(channel registrations)_____\___________________/ / // | / / \ / - // DictionarySlim>> mapping = Mapping + // DictionarySlim>> mapping = Mapping // / / \ / / // ___(Type2.tToken)____/ / \______/___________________/ // /________________(Type2.tMessage)____/ / @@ -133,7 +138,7 @@ public bool IsRegistered(object recipient, TToken token) } /// - public void Register(object recipient, TToken token, Action action) + public void Register(object recipient, TToken token, MessageHandler action) where TMessage : class where TToken : IEquatable { @@ -142,12 +147,12 @@ public void Register(object recipient, TToken token, Action registration list for this recipient Mapping mapping = GetOrAddMapping(); var key = new Recipient(recipient); - ref DictionarySlim>? map = ref mapping.GetOrAddValueRef(key); + ref DictionarySlim>? map = ref mapping.GetOrAddValueRef(key); - map ??= new DictionarySlim>(); + map ??= new DictionarySlim>(); // Add the new registration entry - ref Action? handler = ref map.GetOrAddValueRef(token); + ref MessageHandler? handler = ref map.GetOrAddValueRef(token); if (!(handler is null)) { @@ -352,7 +357,7 @@ public void Unregister(object recipient, TToken token) var key = new Recipient(recipient); - if (!mapping!.TryGetValue(key, out DictionarySlim>? dictionary)) + if (!mapping!.TryGetValue(key, out DictionarySlim>? dictionary)) { return; } @@ -386,11 +391,14 @@ public void Unregister(object recipient, TToken token) } /// - public TMessage Send(TMessage message, TToken token) + public unsafe TMessage Send(TMessage message, TToken token) where TMessage : class where TToken : IEquatable { - object[] entries; + object[] handlers; + object[] recipients; + ref object handlersRef = ref Unsafe.AsRef(null); + ref object recipientsRef = ref Unsafe.AsRef(null); int i = 0; lock (this.recipientsMap) @@ -406,9 +414,12 @@ public TMessage Send(TMessage message, TToken token) // inside one of the currently existing handlers. We can use memory pooling // to reuse arrays, to minimize the average memory usage. In practice, // we usually just need to pay the small overhead of copying the items. - entries = ArrayPool.Shared.Rent(mapping!.TotalHandlersCount); + int totalHandlersCount = mapping!.TotalHandlersCount; - ref object entriesRef = ref MemoryMarshal.GetReference(entries.AsSpan()); + handlers = ArrayPool.Shared.Rent(totalHandlersCount); + recipients = ArrayPool.Shared.Rent(totalHandlersCount); + handlersRef = ref handlers[0]; + recipientsRef = ref recipients[0]; // Copy the handlers to the local collection. // Both types being enumerate expose a struct enumerator, @@ -423,6 +434,7 @@ public TMessage Send(TMessage message, TToken token) // that doesn't expose the single standard Current property. while (mappingEnumerator.MoveNext()) { + object recipient = mappingEnumerator.Key.Target; var pairsEnumerator = mappingEnumerator.Value.GetEnumerator(); while (pairsEnumerator.MoveNext()) @@ -430,14 +442,12 @@ public TMessage Send(TMessage message, TToken token) // Only select the ones with a matching token if (pairsEnumerator.Key.Equals(token)) { - unsafe - { - // We spend quite a bit of time in these two busy loops as we go through all the - // existing mappings and registrations to find the handlers we're interested in. - // We can manually offset here to skip the bounds checks in this inner loop when - // indexing the array (the size is already verified and guaranteed to be enough). - Unsafe.Add(ref entriesRef, (IntPtr)(void*)(uint)i++) = pairsEnumerator.Value; - } + // We spend quite a bit of time in these two busy loops as we go through all the + // existing mappings and registrations to find the handlers we're interested in. + // We can manually offset here to skip the bounds checks in this inner loop when + // indexing the array (the size is already verified and guaranteed to be enough). + Unsafe.Add(ref handlersRef, (IntPtr)(void*)(uint)i) = pairsEnumerator.Value; + Unsafe.Add(ref recipientsRef, (IntPtr)(void*)(uint)i++) = recipient; } } } @@ -446,20 +456,24 @@ public TMessage Send(TMessage message, TToken token) try { // Invoke all the necessary handlers on the local copy of entries - foreach (var entry in entries.AsSpan(0, i)) + for (int j = 0; j < i; j++) { // We're doing an unsafe cast to skip the type checks again. // See the comments in the UnregisterAll method for more info. - Unsafe.As>(entry)(message); + MessageHandler handler = Unsafe.As>(Unsafe.Add(ref handlersRef, (IntPtr)(void*)(uint)j)); + + handler(Unsafe.Add(ref recipientsRef, (IntPtr)(void*)(uint)j), message); } } finally { // As before, we also need to clear it first to avoid having potentially long // lasting memory leaks due to leftover references being stored in the pool. - entries.AsSpan(0, i).Clear(); + handlers.AsSpan(0, i).Clear(); + recipients.AsSpan(0, i).Clear(); - ArrayPool.Shared.Return(entries); + ArrayPool.Shared.Return(handlers); + ArrayPool.Shared.Return(recipients); } return message; @@ -534,7 +548,7 @@ private Mapping GetOrAddMapping() /// This type is defined for simplicity and as a workaround for the lack of support for using type aliases /// over open generic types in C# (using type aliases can only be used for concrete, closed types). /// - private sealed class Mapping : DictionarySlim>>, IMapping + private sealed class Mapping : DictionarySlim>>, IMapping where TMessage : class where TToken : IEquatable { @@ -586,7 +600,7 @@ private interface IMapping : IDictionarySlim /// /// The registered recipient. /// - private readonly object target; + public readonly object Target; /// /// Initializes a new instance of the struct. @@ -595,14 +609,14 @@ private interface IMapping : IDictionarySlim [MethodImpl(MethodImplOptions.AggressiveInlining)] public Recipient(object target) { - this.target = target; + Target = target; } /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public bool Equals(Recipient other) { - return ReferenceEquals(this.target, other.target); + return ReferenceEquals(Target, other.Target); } /// @@ -615,7 +629,7 @@ public override bool Equals(object? obj) [MethodImpl(MethodImplOptions.AggressiveInlining)] public override int GetHashCode() { - return RuntimeHelpers.GetHashCode(this.target); + return RuntimeHelpers.GetHashCode(this.Target); } } diff --git a/Microsoft.Toolkit.Mvvm/Messaging/MessengerExtensions.cs b/Microsoft.Toolkit.Mvvm/Messaging/MessengerExtensions.cs index a344b76a4f3..5e2473db331 100644 --- a/Microsoft.Toolkit.Mvvm/Messaging/MessengerExtensions.cs +++ b/Microsoft.Toolkit.Mvvm/Messaging/MessengerExtensions.cs @@ -174,7 +174,7 @@ static Action GetRegistrationAction(Type type, Metho public static void Register(this IMessenger messenger, IRecipient recipient) where TMessage : class { - messenger.Register(recipient, default, recipient.Receive); + messenger.Register(recipient, default, (r, m) => Unsafe.As>(r).Receive(m)); } /// @@ -191,7 +191,7 @@ public static void Register(this IMessenger messenger, IRecipi where TMessage : class where TToken : IEquatable { - messenger.Register(recipient, token, recipient.Receive); + messenger.Register(recipient, token, (r, m) => Unsafe.As>(r).Receive(m)); } /// @@ -200,13 +200,13 @@ public static void Register(this IMessenger messenger, IRecipi /// The type of message to receive. /// The instance to use to register the recipient. /// The recipient that will receive the messages. - /// The to invoke when a message is received. + /// The to invoke when a message is received. /// Thrown when trying to register the same message twice. /// This method will use the default channel to perform the requested registration. - public static void Register(this IMessenger messenger, object recipient, Action action) + public static void Register(this IMessenger messenger, object recipient, MessageHandler handler) where TMessage : class { - messenger.Register(recipient, default(Unit), action); + messenger.Register(recipient, default(Unit), handler); } /// From f886f2878fdfdfb0554d6fef6628ee06e8cf1885 Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Mon, 17 Aug 2020 11:30:22 +0200 Subject: [PATCH 381/595] Fixed some XML comments --- Microsoft.Toolkit.Mvvm/Messaging/Messenger.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Microsoft.Toolkit.Mvvm/Messaging/Messenger.cs b/Microsoft.Toolkit.Mvvm/Messaging/Messenger.cs index 9049cdd1593..01da10a831c 100644 --- a/Microsoft.Toolkit.Mvvm/Messaging/Messenger.cs +++ b/Microsoft.Toolkit.Mvvm/Messaging/Messenger.cs @@ -42,7 +42,7 @@ namespace Microsoft.Toolkit.Mvvm.Messaging /// in the current scope. This is helpful to keep the registration and handling logic separate. /// Following up from the previous example, consider a class having this method: /// - /// private void Receive(LoginCompletedMessage message) + /// private static void Receive(object recipient, LoginCompletedMessage message) /// { /// // Handle the message there /// } @@ -51,8 +51,8 @@ namespace Microsoft.Toolkit.Mvvm.Messaging /// /// Messenger.Default.Register<LoginCompletedMessage>(this, Receive); /// - /// The C# compiler will automatically convert that expression to an instance - /// compatible with the method. + /// The C# compiler will automatically convert that expression to a instance + /// compatible with the method. /// This will also work if multiple overloads of that method are available, each handling a different /// message type: the C# compiler will automatically pick the right one for the current message type. /// For info on the other available features, check the interface. From c45bb99dd30f27e2dfa2d6784d705ddfd040f182 Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Mon, 17 Aug 2020 11:32:16 +0200 Subject: [PATCH 382/595] Updated unit tests --- .../Mvvm/Test_Messenger.Request.cs | 28 +++++++++---------- .../UnitTests.Shared/Mvvm/Test_Messenger.cs | 22 +++++++-------- .../Mvvm/Test_ObservableRecipient.cs | 4 +-- 3 files changed, 27 insertions(+), 27 deletions(-) diff --git a/UnitTests/UnitTests.Shared/Mvvm/Test_Messenger.Request.cs b/UnitTests/UnitTests.Shared/Mvvm/Test_Messenger.Request.cs index 07fef8d9300..e47855a3c0f 100644 --- a/UnitTests/UnitTests.Shared/Mvvm/Test_Messenger.Request.cs +++ b/UnitTests/UnitTests.Shared/Mvvm/Test_Messenger.Request.cs @@ -20,7 +20,7 @@ public void Test_Messenger_RequestMessage_Ok() var messenger = new Messenger(); var recipient = new object(); - void Receive(NumberRequestMessage m) + void Receive(object recipient, NumberRequestMessage m) { Assert.IsFalse(m.HasReceivedResponse); @@ -54,7 +54,7 @@ public void Test_Messenger_RequestMessage_Fail_MultipleReplies() var messenger = new Messenger(); var recipient = new object(); - void Receive(NumberRequestMessage m) + void Receive(object recipient, NumberRequestMessage m) { m.Reply(42); m.Reply(42); @@ -76,7 +76,7 @@ public async Task Test_Messenger_AsyncRequestMessage_Ok_Sync() var messenger = new Messenger(); var recipient = new object(); - void Receive(AsyncNumberRequestMessage m) + void Receive(object recipient, AsyncNumberRequestMessage m) { Assert.IsFalse(m.HasReceivedResponse); @@ -106,7 +106,7 @@ async Task GetNumberAsync() return 42; } - void Receive(AsyncNumberRequestMessage m) + void Receive(object recipient, AsyncNumberRequestMessage m) { Assert.IsFalse(m.HasReceivedResponse); @@ -140,7 +140,7 @@ public async Task Test_Messenger_AsyncRequestMessage_Fail_MultipleReplies() var messenger = new Messenger(); var recipient = new object(); - void Receive(AsyncNumberRequestMessage m) + void Receive(object recipient, AsyncNumberRequestMessage m) { m.Reply(42); m.Reply(42); @@ -162,7 +162,7 @@ public void Test_Messenger_CollectionRequestMessage_Ok_NoReplies() var messenger = new Messenger(); var recipient = new object(); - void Receive(NumbersCollectionRequestMessage m) + void Receive(object recipient, NumbersCollectionRequestMessage m) { } @@ -183,9 +183,9 @@ public void Test_Messenger_CollectionRequestMessage_Ok_MultipleReplies() recipient2 = new object(), recipient3 = new object(); - void Receive1(NumbersCollectionRequestMessage m) => m.Reply(1); - void Receive2(NumbersCollectionRequestMessage m) => m.Reply(2); - void Receive3(NumbersCollectionRequestMessage m) => m.Reply(3); + void Receive1(object recipient, NumbersCollectionRequestMessage m) => m.Reply(1); + void Receive2(object recipient, NumbersCollectionRequestMessage m) => m.Reply(2); + void Receive3(object recipient, NumbersCollectionRequestMessage m) => m.Reply(3); messenger.Register(recipient1, Receive1); messenger.Register(recipient2, Receive2); @@ -212,7 +212,7 @@ public async Task Test_Messenger_AsyncCollectionRequestMessage_Ok_NoReplies() var messenger = new Messenger(); var recipient = new object(); - void Receive(AsyncNumbersCollectionRequestMessage m) + void Receive(object recipient, AsyncNumbersCollectionRequestMessage m) { } @@ -241,10 +241,10 @@ async Task GetNumberAsync() return 3; } - void Receive1(AsyncNumbersCollectionRequestMessage m) => m.Reply(1); - void Receive2(AsyncNumbersCollectionRequestMessage m) => m.Reply(Task.FromResult(2)); - void Receive3(AsyncNumbersCollectionRequestMessage m) => m.Reply(GetNumberAsync()); - void Receive4(AsyncNumbersCollectionRequestMessage m) => m.Reply(_ => GetNumberAsync()); + void Receive1(object recipient, AsyncNumbersCollectionRequestMessage m) => m.Reply(1); + void Receive2(object recipient, AsyncNumbersCollectionRequestMessage m) => m.Reply(Task.FromResult(2)); + void Receive3(object recipient, AsyncNumbersCollectionRequestMessage m) => m.Reply(GetNumberAsync()); + void Receive4(object recipient, AsyncNumbersCollectionRequestMessage m) => m.Reply(_ => GetNumberAsync()); messenger.Register(recipient1, Receive1); messenger.Register(recipient2, Receive2); diff --git a/UnitTests/UnitTests.Shared/Mvvm/Test_Messenger.cs b/UnitTests/UnitTests.Shared/Mvvm/Test_Messenger.cs index 33d6e8cd19b..96818789f5e 100644 --- a/UnitTests/UnitTests.Shared/Mvvm/Test_Messenger.cs +++ b/UnitTests/UnitTests.Shared/Mvvm/Test_Messenger.cs @@ -58,7 +58,7 @@ public void Test_Messenger_RegisterAndUnregisterRecipientWithMessageType() var messenger = new Messenger(); var recipient = new object(); - messenger.Register(recipient, m => { }); + messenger.Register(recipient, (r, m) => { }); messenger.Unregister(recipient); @@ -72,7 +72,7 @@ public void Test_Messenger_RegisterAndUnregisterRecipientWithMessageTypeAndToken var messenger = new Messenger(); var recipient = new object(); - messenger.Register(recipient, nameof(MessageA), m => { }); + messenger.Register(recipient, nameof(MessageA), (r, m) => { }); messenger.Unregister(recipient, nameof(MessageA)); @@ -86,7 +86,7 @@ public void Test_Messenger_RegisterAndUnregisterRecipientWithToken() var messenger = new Messenger(); var recipient = new object(); - messenger.Register(recipient, nameof(MessageA), m => { }); + messenger.Register(recipient, nameof(MessageA), (r, m) => { }); messenger.UnregisterAll(recipient, nameof(MessageA)); @@ -100,7 +100,7 @@ public void Test_Messenger_RegisterAndUnregisterRecipientWithRecipient() var messenger = new Messenger(); var recipient = new object(); - messenger.Register(recipient, nameof(MessageA), m => { }); + messenger.Register(recipient, nameof(MessageA), (r, m) => { }); messenger.UnregisterAll(recipient); @@ -116,7 +116,7 @@ public void Test_Messenger_IsRegistered_Register_Send_UnregisterOfTMessage_WithN Assert.IsFalse(Messenger.Default.IsRegistered(a)); string result = null; - Messenger.Default.Register(a, m => result = m.Text); + Messenger.Default.Register(a, (r, m) => result = m.Text); Assert.IsTrue(Messenger.Default.IsRegistered(a)); @@ -143,7 +143,7 @@ public void Test_Messenger_IsRegistered_Register_Send_UnregisterRecipient_WithNo Assert.IsFalse(Messenger.Default.IsRegistered(a)); string result = null; - Messenger.Default.Register(a, m => result = m.Text); + Messenger.Default.Register(a, (r, m) => result = m.Text); Assert.IsTrue(Messenger.Default.IsRegistered(a)); @@ -170,7 +170,7 @@ public void Test_Messenger_IsRegistered_Register_Send_UnregisterOfTMessage_WithT Assert.IsFalse(Messenger.Default.IsRegistered(a)); string result = null; - Messenger.Default.Register(a, nameof(MessageA), m => result = m.Text); + Messenger.Default.Register(a, nameof(MessageA), (r, m) => result = m.Text); Assert.IsTrue(Messenger.Default.IsRegistered(a, nameof(MessageA))); @@ -195,11 +195,11 @@ public void Test_Messenger_DuplicateRegistrationWithMessageType() var messenger = new Messenger(); var recipient = new object(); - messenger.Register(recipient, m => { }); + messenger.Register(recipient, (r, m) => { }); Assert.ThrowsException(() => { - messenger.Register(recipient, m => { }); + messenger.Register(recipient, (r, m) => { }); }); } @@ -210,11 +210,11 @@ public void Test_Messenger_DuplicateRegistrationWithMessageTypeAndToken() var messenger = new Messenger(); var recipient = new object(); - messenger.Register(recipient, nameof(MessageA), m => { }); + messenger.Register(recipient, nameof(MessageA), (r, m) => { }); Assert.ThrowsException(() => { - messenger.Register(recipient, nameof(MessageA), m => { }); + messenger.Register(recipient, nameof(MessageA), (r, m) => { }); }); } diff --git a/UnitTests/UnitTests.Shared/Mvvm/Test_ObservableRecipient.cs b/UnitTests/UnitTests.Shared/Mvvm/Test_ObservableRecipient.cs index d5e330e1e53..76ff145d86f 100644 --- a/UnitTests/UnitTests.Shared/Mvvm/Test_ObservableRecipient.cs +++ b/UnitTests/UnitTests.Shared/Mvvm/Test_ObservableRecipient.cs @@ -59,7 +59,7 @@ public void Test_ObservableRecipient_Broadcast() PropertyChangedMessage message = null; - messenger.Register>(messenger, m => message = m); + messenger.Register>(messenger, (r, m) => message = m); viewmodel.Data = 42; @@ -97,7 +97,7 @@ protected override void OnActivated() { IsActivatedCheck = true; - Messenger.Register(this, m => { }); + Messenger.Register(this, (r, m) => { }); } protected override void OnDeactivated() From eef4d0af8e809fda7d3a7dc5a57ca64ef90337df Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Mon, 17 Aug 2020 12:50:27 +0200 Subject: [PATCH 383/595] Fixed an index exception in Messenger.Send --- Microsoft.Toolkit.Mvvm/Messaging/Messenger.cs | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/Microsoft.Toolkit.Mvvm/Messaging/Messenger.cs b/Microsoft.Toolkit.Mvvm/Messaging/Messenger.cs index 01da10a831c..e85f1ef4bc2 100644 --- a/Microsoft.Toolkit.Mvvm/Messaging/Messenger.cs +++ b/Microsoft.Toolkit.Mvvm/Messaging/Messenger.cs @@ -6,7 +6,6 @@ using System.Buffers; using System.Collections.Generic; using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; using System.Threading; using Microsoft.Collections.Extensions; @@ -404,17 +403,19 @@ public unsafe TMessage Send(TMessage message, TToken token) lock (this.recipientsMap) { // Check whether there are any registered recipients - if (!TryGetMapping(out Mapping? mapping)) - { - return message; - } + _ = TryGetMapping(out Mapping? mapping); // We need to make a local copy of the currently registered handlers, // since users might try to unregister (or register) new handlers from // inside one of the currently existing handlers. We can use memory pooling // to reuse arrays, to minimize the average memory usage. In practice, // we usually just need to pay the small overhead of copying the items. - int totalHandlersCount = mapping!.TotalHandlersCount; + int totalHandlersCount = mapping?.TotalHandlersCount ?? 0; + + if (totalHandlersCount == 0) + { + return message; + } handlers = ArrayPool.Shared.Rent(totalHandlersCount); recipients = ArrayPool.Shared.Rent(totalHandlersCount); @@ -428,7 +429,7 @@ public unsafe TMessage Send(TMessage message, TToken token) // handlers for different tokens. We can reuse the same variable // to count the number of matching handlers to invoke later on. // This will be the array slice with valid actions in the rented buffer. - var mappingEnumerator = mapping.GetEnumerator(); + var mappingEnumerator = mapping!.GetEnumerator(); // Explicit enumerator usage here as we're using a custom one // that doesn't expose the single standard Current property. From dea8b8913f27bf56c107a24057f9deaa09ab7287 Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Mon, 17 Aug 2020 13:17:15 +0200 Subject: [PATCH 384/595] Tweaked XML docs and comments --- Microsoft.Toolkit.Mvvm/Messaging/IMessenger.cs | 4 ++-- Microsoft.Toolkit.Mvvm/Messaging/Messenger.cs | 12 ++++++------ 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/Microsoft.Toolkit.Mvvm/Messaging/IMessenger.cs b/Microsoft.Toolkit.Mvvm/Messaging/IMessenger.cs index d1a6e1513df..a3838d2b117 100644 --- a/Microsoft.Toolkit.Mvvm/Messaging/IMessenger.cs +++ b/Microsoft.Toolkit.Mvvm/Messaging/IMessenger.cs @@ -43,9 +43,9 @@ bool IsRegistered(object recipient, TToken token) /// The type of token to use to pick the messages to receive. /// The recipient that will receive the messages. /// A token used to determine the receiving channel to use. - /// The to invoke when a message is received. + /// The to invoke when a message is received. /// Thrown when trying to register the same message twice. - void Register(object recipient, TToken token, MessageHandler action) + void Register(object recipient, TToken token, MessageHandler handler) where TMessage : class where TToken : IEquatable; diff --git a/Microsoft.Toolkit.Mvvm/Messaging/Messenger.cs b/Microsoft.Toolkit.Mvvm/Messaging/Messenger.cs index e85f1ef4bc2..8ead8d4b73b 100644 --- a/Microsoft.Toolkit.Mvvm/Messaging/Messenger.cs +++ b/Microsoft.Toolkit.Mvvm/Messaging/Messenger.cs @@ -36,7 +36,7 @@ namespace Microsoft.Toolkit.Mvvm.Messaging /// /// Messenger.Default.Send<LoginCompletedMessage>(); /// - /// Additionally, the method group syntax can also be used to specify the action + /// Additionally, the method group syntax can also be used to specify the message handler /// to invoke when receiving a message, if a method with the right signature is available /// in the current scope. This is helpful to keep the registration and handling logic separate. /// Following up from the previous example, consider a class having this method: @@ -137,7 +137,7 @@ public bool IsRegistered(object recipient, TToken token) } /// - public void Register(object recipient, TToken token, MessageHandler action) + public void Register(object recipient, TToken token, MessageHandler handler) where TMessage : class where TToken : IEquatable { @@ -151,14 +151,14 @@ public void Register(object recipient, TToken token, MessageHa map ??= new DictionarySlim>(); // Add the new registration entry - ref MessageHandler? handler = ref map.GetOrAddValueRef(token); + ref MessageHandler? registeredHandler = ref map.GetOrAddValueRef(token); - if (!(handler is null)) + if (!(registeredHandler is null)) { ThrowInvalidOperationExceptionForDuplicateRegistration(); } - handler = action; + registeredHandler = handler; // Update the total counter for handlers for the current type parameters mapping.TotalHandlersCount++; @@ -428,7 +428,7 @@ public unsafe TMessage Send(TMessage message, TToken token) // The array is oversized at this point, since it also includes // handlers for different tokens. We can reuse the same variable // to count the number of matching handlers to invoke later on. - // This will be the array slice with valid actions in the rented buffer. + // This will be the array slice with valid handler in the rented buffer. var mappingEnumerator = mapping!.GetEnumerator(); // Explicit enumerator usage here as we're using a custom one From c876f6113ae881cb52f561f41cd5eaf077a4dc71 Mon Sep 17 00:00:00 2001 From: Herrick Spencer Date: Fri, 14 Aug 2020 01:22:09 -0700 Subject: [PATCH 385/595] Massive spelling fixes --- .editorconfig | 2 +- GazeInputTest/MainPage.xaml | 2 +- .../Buffers/MemoryBufferWriter{T}.cs | 2 +- .../Extensions/ReadOnlySpanExtensions.cs | 2 +- .../Helpers/BitHelper.cs | 2 +- .../Helpers/Internals/SpanHelper.Hash.cs | 2 +- .../NullableRef{T}.cs | 2 +- .../Markdown/Blocks/CodeBlock.cs | 2 +- .../Rss/BaseRssParser.cs | 2 +- .../Rss/Enums/RssType.cs | 2 +- Microsoft.Toolkit.Parsers/Rss/Rss2Parser.cs | 2 +- Microsoft.Toolkit.Parsers/Rss/RssHelper.cs | 2 +- .../Uwp/UwpSignatureManager.cs | 2 +- .../PlatformSpecific/Uwp/UwpStorageManager.cs | 2 +- .../Services/LinkedIn/LinkedInService.cs | 4 +- .../Services/Twitter/TwitterService.cs | 4 +- .../Services/Weibo/WeiboService.cs | 2 +- .../BluetoothLEHelper/GattNativeUuid.cs | 4 +- .../GazeInput.h | 16 +++---- .../GazePointer.cpp | 4 +- .../IGazeFilter.h | 2 +- .../PointerState.h | 2 +- .../BindableProgressBarValue.cs | 2 +- .../Tiles/Elements/Element_TileBinding.cs | 2 +- .../TileBindingContentPhotos.cs | 4 +- .../Tiles/TileBinding.cs | 2 +- .../Tiles/TileVisual.cs | 2 +- .../Toasts/Elements/Element_ToastBinding.cs | 2 +- Microsoft.Toolkit.Uwp.SampleApp/App.xaml.cs | 2 +- .../Pages/SampleController.xaml.cs | 2 +- .../SamplePages/BladeView/BladeCode.bind | 2 +- .../CameraPreview/CameraPreviewPage.xaml.cs | 2 +- .../DispatcherQueueHelperCode.bind | 2 +- .../SamplePages/Loading/LoadingPage.xaml.cs | 2 +- .../MasterDetailsViewCode.bind | 2 +- .../MasterDetailsViewPage.xaml.cs | 2 +- .../ScrollViewerExtensionsXaml.bind | 2 +- .../StaggeredLayout/StaggeredLayoutPage.xaml | 2 +- .../SystemInformationCode.bind | 2 +- .../SystemInformationPage.xaml.cs | 2 +- .../SamplePages/TabView/TabViewXaml.bind | 2 +- .../Twitter Service/TwitterPage.xaml.cs | 2 +- Microsoft.Toolkit.Uwp.SampleApp/Shell.xaml.cs | 2 +- Microsoft.Toolkit.Uwp.SampleApp/readme.md | 6 +-- .../Services/Facebook/FacebookService.cs | 4 +- .../ExpressionNodes/Matrix3x2Node.cs | 12 +++--- .../ExpressionNodes/Matrix4x4Node.cs | 10 ++--- .../ExpressionNodes/Vector2Node.cs | 10 ++--- .../ExpressionNodes/Vector3Node.cs | 10 ++--- .../ExpressionNodes/Vector4Node.cs | 12 +++--- .../ReferenceNodes/PointLightReferenceNode.cs | 4 +- .../ReferenceNodes/SpotLightReferenceNode.cs | 4 +- .../Extensions/AnimationExtensions.Blur.cs | 4 +- .../Extensions/AnimationExtensions.cs | 2 +- .../ReorderGridAnimation.cs | 2 +- .../SurfaceLoader.cs | 4 +- .../CollectionViews/CollectionView.cs | 8 ++-- .../EnumerableCollectionView.cs | 2 +- .../CollectionViews/ListCollectionView.cs | 2 +- .../DataGridGroupItemAutomationPeer.cs | 2 +- .../Automation/DataGridItemAutomationPeer.cs | 2 +- .../DataGrid/DataGrid.cs | 16 +++---- .../DataGrid/DataGridClipboardCellContent.cs | 4 +- .../DataGrid/DataGridColumn.cs | 8 ++-- .../DataGrid/DataGridColumns.cs | 12 +++--- .../DataGrid/DataGridRow.cs | 20 ++++----- .../DataGrid/DataGridRows.cs | 10 ++--- .../Utilities/IndexToValueTable.cs | 4 +- .../Utilities/ValidationUtil.cs | 2 +- .../Carousel/Carousel.cs | 2 +- .../Carousel/CarouselPanel.cs | 12 +++--- .../GridSplitter/GridSplitter.Events.cs | 4 +- .../GridSplitter/GripperHoverWrapper.cs | 2 +- ...initeCanvasVirtualDrawingSurface.Render.cs | 2 +- .../LayoutTransformControl.cs | 2 +- .../PropertyChangeEventSource.cs | 2 +- .../MasterDetailsView.Properties.cs | 2 +- .../Menu/Menu.Logic.cs | 2 +- .../OrbitView/OrbitView.cs | 2 +- .../OrbitView/OrbitViewDataItem.cs | 4 +- .../RadialProgressBar/RadialProgressBar.cs | 6 +-- .../TabView/TabView.Properties.cs | 2 +- .../TabView/TabViewItem.cs | 2 +- .../TextToolbar/Formats/Format.cs | 6 +-- .../TextToolbar/Formats/Formatter.cs | 2 +- .../ToolbarItems/ToolbarSeparator.cs | 2 +- .../InterspersedObservableCollection.cs | 4 +- .../TokenizingTextBox.Selection.cs | 6 +-- .../TokenizingTextBox/TokenizingTextBox.cs | 6 +-- .../TokenizingTextBoxItem.AutoSuggestBox.cs | 2 +- .../WrapPanel/WrapPanel.Data.cs | 2 +- .../WrapPanel/WrapPanel.cs | 2 +- .../Brushes/RadialGradientBrush.cs | 2 +- .../Helpers/SurfaceLoader.Instance.cs | 2 +- .../Converters/DoubleToObjectConverter.cs | 2 +- ...eworkElementExtensions.RelativeAncestor.cs | 2 +- .../Hyperlink/HyperlinkExtensions.cs | 12 +++--- ...llViewerExtensions.MiddleClickScrolling.cs | 4 +- .../ScrollViewerExtensions.Properties.cs | 2 +- .../SurfaceDialTextbox/SurfaceDialTextbox.cs | 42 +++++++++---------- .../Extensions/TextBoxMask/TextBoxMask.cs | 4 +- .../UserHandPreferenceStateTrigger.cs | 2 +- .../Deferred/DeferredCancelEventArgs.cs | 4 +- .../Helpers/CameraHelper/CameraHelper.cs | 2 +- .../Helpers/DeepLinkParser/DeepLinkParser.cs | 2 +- .../Helpers/NotifyTaskCompletion.cs | 2 +- build/build.ps1 | 6 +-- contributing.md | 4 +- 108 files changed, 229 insertions(+), 229 deletions(-) diff --git a/.editorconfig b/.editorconfig index bae686022ef..50eeccedc3f 100644 --- a/.editorconfig +++ b/.editorconfig @@ -323,5 +323,5 @@ dotnet_diagnostic.SA1634.severity = none dotnet_diagnostic.SA1652.severity = none dotnet_diagnostic.SA1629.severity = none # DocumentationTextMustEndWithAPeriod: Let's enable this rule back when we shift to WinUI3 (v8.x). If we do it now, it would mean more than 400 file changes. -dotnet_diagnostic.SA1413.severity = none # UseTrailingCommasInMultiLineInitializers: This would also mean a lot of changes at the end of all multiline intializers. It's also debatable if we want this or not. +dotnet_diagnostic.SA1413.severity = none # UseTrailingCommasInMultiLineInitializers: This would also mean a lot of changes at the end of all multiline initializers. It's also debatable if we want this or not. dotnet_diagnostic.SA1314.severity = none # TypeParameterNamesMustBeginWithT: We do have a few templates that don't start with T. We need to double check that changing this is not a breaking change. If not, we can re-enable this. \ No newline at end of file diff --git a/GazeInputTest/MainPage.xaml b/GazeInputTest/MainPage.xaml index 7e15659b033..58d8b46280d 100644 --- a/GazeInputTest/MainPage.xaml +++ b/GazeInputTest/MainPage.xaml @@ -43,7 +43,7 @@ - + diff --git a/Microsoft.Toolkit.HighPerformance/Buffers/MemoryBufferWriter{T}.cs b/Microsoft.Toolkit.HighPerformance/Buffers/MemoryBufferWriter{T}.cs index ec0c0fcc5d9..2873d698c6a 100644 --- a/Microsoft.Toolkit.HighPerformance/Buffers/MemoryBufferWriter{T}.cs +++ b/Microsoft.Toolkit.HighPerformance/Buffers/MemoryBufferWriter{T}.cs @@ -11,7 +11,7 @@ namespace Microsoft.Toolkit.HighPerformance.Buffers { /// - /// Represents an utput sink into which data can be written, backed by a instance. + /// Represents an output sink into which data can be written, backed by a instance. /// /// The type of items to write to the current instance. /// diff --git a/Microsoft.Toolkit.HighPerformance/Extensions/ReadOnlySpanExtensions.cs b/Microsoft.Toolkit.HighPerformance/Extensions/ReadOnlySpanExtensions.cs index cf2be137c38..9d8d6b9616a 100644 --- a/Microsoft.Toolkit.HighPerformance/Extensions/ReadOnlySpanExtensions.cs +++ b/Microsoft.Toolkit.HighPerformance/Extensions/ReadOnlySpanExtensions.cs @@ -99,7 +99,7 @@ public static ref readonly T DangerousGetLookupReferenceAt(this ReadOnlySpan< // The result is then negated, producing the value 0xFFFFFFFF // for valid indices, or 0 otherwise. The generated mask // is then combined with the original index. This leaves - // the index intact if it was valid, otherwise zeroes it. + // the index intact if it was valid, otherwise zeros it. // The computed offset is finally used to access the // lookup table, and it is guaranteed to never go out of // bounds unless the input span was just empty, which for a diff --git a/Microsoft.Toolkit.HighPerformance/Helpers/BitHelper.cs b/Microsoft.Toolkit.HighPerformance/Helpers/BitHelper.cs index efe4d9ba4e8..234806443d0 100644 --- a/Microsoft.Toolkit.HighPerformance/Helpers/BitHelper.cs +++ b/Microsoft.Toolkit.HighPerformance/Helpers/BitHelper.cs @@ -54,7 +54,7 @@ public static bool HasFlag(uint value, int n) /// decrement the input parameter to ensure that the range of accepted /// values fits within the available 32 bits of the lookup table in use. /// For more info on this optimization technique, see . - /// Here is how the code from the lik above would be implemented using this method: + /// Here is how the code from the link above would be implemented using this method: /// /// bool IsReservedCharacter(char c) /// { diff --git a/Microsoft.Toolkit.HighPerformance/Helpers/Internals/SpanHelper.Hash.cs b/Microsoft.Toolkit.HighPerformance/Helpers/Internals/SpanHelper.Hash.cs index 4cda8754176..9ad86f923e0 100644 --- a/Microsoft.Toolkit.HighPerformance/Helpers/Internals/SpanHelper.Hash.cs +++ b/Microsoft.Toolkit.HighPerformance/Helpers/Internals/SpanHelper.Hash.cs @@ -105,7 +105,7 @@ public static unsafe int GetDjb2LikeByteHash(ref byte r0, IntPtr length) // and the final loop to combine the partial hash values. // Note that even when we use the vectorized path we don't need to do // any preprocessing to try to get memory aligned, as that would cause - // the hashcodes to potentially be different for the same data. + // the hash codes to potentially be different for the same data. if (Vector.IsHardwareAccelerated && (byte*)length >= (byte*)(Vector.Count << 3)) { diff --git a/Microsoft.Toolkit.HighPerformance/NullableRef{T}.cs b/Microsoft.Toolkit.HighPerformance/NullableRef{T}.cs index ee4fc9c8750..df4b5ef350a 100644 --- a/Microsoft.Toolkit.HighPerformance/NullableRef{T}.cs +++ b/Microsoft.Toolkit.HighPerformance/NullableRef{T}.cs @@ -59,7 +59,7 @@ public bool HasValue get { // We know that the span will always have a length of either - // 1 or 0, se instead of using a cmp instruction and setting the + // 1 or 0, so instead of using a cmp instruction and setting the // zero flag to produce our boolean value, we can just cast // the length to byte without overflow checks (doing a cast will // also account for the byte endianness of the current system), diff --git a/Microsoft.Toolkit.Parsers/Markdown/Blocks/CodeBlock.cs b/Microsoft.Toolkit.Parsers/Markdown/Blocks/CodeBlock.cs index 007162e0596..373d14c2e70 100644 --- a/Microsoft.Toolkit.Parsers/Markdown/Blocks/CodeBlock.cs +++ b/Microsoft.Toolkit.Parsers/Markdown/Blocks/CodeBlock.cs @@ -27,7 +27,7 @@ public CodeBlock() public string Text { get; set; } /// - /// Gets or sets the Language specified in prefix, e.g. ```c# (Github Style Parsing). + /// Gets or sets the Language specified in prefix, e.g. ```c# (GitHub Style Parsing). /// This does not guarantee that the Code Block has a language, or no language, some valid code might not have been prefixed, and this will still return null. /// To ensure all Code is Highlighted (If desired), you might have to determine the language from the provided string, such as looking for key words. /// diff --git a/Microsoft.Toolkit.Parsers/Rss/BaseRssParser.cs b/Microsoft.Toolkit.Parsers/Rss/BaseRssParser.cs index d268cf5d10c..2a80eb8c5f9 100644 --- a/Microsoft.Toolkit.Parsers/Rss/BaseRssParser.cs +++ b/Microsoft.Toolkit.Parsers/Rss/BaseRssParser.cs @@ -9,7 +9,7 @@ namespace Microsoft.Toolkit.Parsers.Rss { /// - /// Base class for Rss Parser(s). + /// Base class for RSS Parser(s). /// internal abstract class BaseRssParser { diff --git a/Microsoft.Toolkit.Parsers/Rss/Enums/RssType.cs b/Microsoft.Toolkit.Parsers/Rss/Enums/RssType.cs index 99ae9570887..4cfd3ad206c 100644 --- a/Microsoft.Toolkit.Parsers/Rss/Enums/RssType.cs +++ b/Microsoft.Toolkit.Parsers/Rss/Enums/RssType.cs @@ -5,7 +5,7 @@ namespace Microsoft.Toolkit.Parsers.Rss { /// - /// Type of Rss. + /// Type of RSS. /// internal enum RssType { diff --git a/Microsoft.Toolkit.Parsers/Rss/Rss2Parser.cs b/Microsoft.Toolkit.Parsers/Rss/Rss2Parser.cs index e693f0994ac..5afbb3901f1 100644 --- a/Microsoft.Toolkit.Parsers/Rss/Rss2Parser.cs +++ b/Microsoft.Toolkit.Parsers/Rss/Rss2Parser.cs @@ -11,7 +11,7 @@ namespace Microsoft.Toolkit.Parsers.Rss { /// - /// Rss reader implementation to parse Rss content. + /// RSS reader implementation to parse RSS content. /// internal class Rss2Parser : BaseRssParser { diff --git a/Microsoft.Toolkit.Parsers/Rss/RssHelper.cs b/Microsoft.Toolkit.Parsers/Rss/RssHelper.cs index 5b4b19d5af3..16df5e81fe2 100644 --- a/Microsoft.Toolkit.Parsers/Rss/RssHelper.cs +++ b/Microsoft.Toolkit.Parsers/Rss/RssHelper.cs @@ -13,7 +13,7 @@ namespace Microsoft.Toolkit.Parsers.Rss { /// - /// Class with utilities for Rss related works. + /// Class with utilities for RSS related works. /// internal static class RssHelper { diff --git a/Microsoft.Toolkit.Services/PlatformSpecific/Uwp/UwpSignatureManager.cs b/Microsoft.Toolkit.Services/PlatformSpecific/Uwp/UwpSignatureManager.cs index 67a6974bdbc..f257995031c 100644 --- a/Microsoft.Toolkit.Services/PlatformSpecific/Uwp/UwpSignatureManager.cs +++ b/Microsoft.Toolkit.Services/PlatformSpecific/Uwp/UwpSignatureManager.cs @@ -11,7 +11,7 @@ namespace Microsoft.Toolkit.Services.PlatformSpecific.Uwp { /// - /// Uwp specific signature generator using cryptographic library + /// UWP specific signature generator using cryptographic library /// internal class UwpSignatureManager : ISignatureManager { diff --git a/Microsoft.Toolkit.Services/PlatformSpecific/Uwp/UwpStorageManager.cs b/Microsoft.Toolkit.Services/PlatformSpecific/Uwp/UwpStorageManager.cs index ef4d814f80f..9fbfc6be3bd 100644 --- a/Microsoft.Toolkit.Services/PlatformSpecific/Uwp/UwpStorageManager.cs +++ b/Microsoft.Toolkit.Services/PlatformSpecific/Uwp/UwpStorageManager.cs @@ -11,7 +11,7 @@ namespace Microsoft.Toolkit.Services.PlatformSpecific.Uwp { /// - /// Uwp specific implementation for IStorageManager using ApplicationData and LocalSettings + /// UWP specific implementation for IStorageManager using ApplicationData and LocalSettings /// internal class UwpStorageManager : IStorageManager { diff --git a/Microsoft.Toolkit.Services/Services/LinkedIn/LinkedInService.cs b/Microsoft.Toolkit.Services/Services/LinkedIn/LinkedInService.cs index 251e5fb711d..282c7edd39a 100644 --- a/Microsoft.Toolkit.Services/Services/LinkedIn/LinkedInService.cs +++ b/Microsoft.Toolkit.Services/Services/LinkedIn/LinkedInService.cs @@ -113,7 +113,7 @@ public Task LogoutAsync() #if WINRT /// - /// Initialize underlying provider with relevant token information for Uwp. + /// Initialize underlying provider with relevant token information for UWP. /// /// Token instance. /// Scope / permissions app requires user to sign up for. @@ -138,7 +138,7 @@ public bool Initialize(string clientId, string clientSecret, string callbackUri) #if NET462 /// - /// Initialize underlying provider with relevant token information for Uwp. + /// Initialize underlying provider with relevant token information for UWP. /// /// Token instance. /// Scope / permissions app requires user to sign up for. diff --git a/Microsoft.Toolkit.Services/Services/Twitter/TwitterService.cs b/Microsoft.Toolkit.Services/Services/Twitter/TwitterService.cs index 703657cf2fb..4772c1fa24f 100644 --- a/Microsoft.Toolkit.Services/Services/Twitter/TwitterService.cs +++ b/Microsoft.Toolkit.Services/Services/Twitter/TwitterService.cs @@ -151,7 +151,7 @@ public bool Initialize(TwitterOAuthTokens oAuthTokens, IAuthenticationBroker aut #if WINRT /// - /// Initialize underlying provider with relevant token information for Uwp. + /// Initialize underlying provider with relevant token information for UWP. /// /// Consumer key. /// Consumer secret. @@ -175,7 +175,7 @@ public bool Initialize(TwitterOAuthTokens oAuthTokens) #if NET462 /// - /// Initialize underlying provider with relevant token information for Uwp. + /// Initialize underlying provider with relevant token information for UWP. /// /// Consumer key. /// Consumer secret. diff --git a/Microsoft.Toolkit.Services/Services/Weibo/WeiboService.cs b/Microsoft.Toolkit.Services/Services/Weibo/WeiboService.cs index f5f8dd7ec99..ea8a33e994a 100644 --- a/Microsoft.Toolkit.Services/Services/Weibo/WeiboService.cs +++ b/Microsoft.Toolkit.Services/Services/Weibo/WeiboService.cs @@ -136,7 +136,7 @@ public bool Initialize(WeiboOAuthTokens oAuthTokens, IAuthenticationBroker authe #if WINRT /// - /// Initialize underlying provider with relevant token information for Uwp. + /// Initialize underlying provider with relevant token information for UWP. /// /// App key. /// App secret. diff --git a/Microsoft.Toolkit.Uwp.Connectivity/BluetoothLEHelper/GattNativeUuid.cs b/Microsoft.Toolkit.Uwp.Connectivity/BluetoothLEHelper/GattNativeUuid.cs index 08f247aa60e..6bc6be1d8e5 100644 --- a/Microsoft.Toolkit.Uwp.Connectivity/BluetoothLEHelper/GattNativeUuid.cs +++ b/Microsoft.Toolkit.Uwp.Connectivity/BluetoothLEHelper/GattNativeUuid.cs @@ -648,12 +648,12 @@ public enum GattNativeUuid : ushort String = 0x2A3D, /// - /// The temperature in celsius + /// The temperature in Celsius /// TemperatureInCelsius = 0x2A1F, /// - /// The temperature in fahrenheit + /// The temperature in Fahrenheit /// TemperatureInFahrenheit = 0x2A20, diff --git a/Microsoft.Toolkit.Uwp.Input.GazeInteraction/GazeInput.h b/Microsoft.Toolkit.Uwp.Input.GazeInteraction/GazeInput.h index 42ef822a8d9..2c9f630b0d4 100644 --- a/Microsoft.Toolkit.Uwp.Input.GazeInteraction/GazeInput.h +++ b/Microsoft.Toolkit.Uwp.Input.GazeInteraction/GazeInput.h @@ -23,27 +23,27 @@ public ref class GazeInput sealed public: /// - /// Identifyes the Interaction dependency property + /// Identifies the Interaction dependency property /// static property DependencyProperty^ InteractionProperty { DependencyProperty^ get(); } /// - /// Identifyes the IsCursorVisible dependency property + /// Identifies the IsCursorVisible dependency property /// static property DependencyProperty^ IsCursorVisibleProperty { DependencyProperty^ get(); } /// - /// Identifyes the CursorRadius dependency property + /// Identifies the CursorRadius dependency property /// static property DependencyProperty^ CursorRadiusProperty { DependencyProperty^ get(); } /// - /// Identifyes the GazeElement dependency property + /// Identifies the GazeElement dependency property /// static property DependencyProperty^ GazeElementProperty { DependencyProperty^ get(); } /// - /// Identifyes the FixationDuration dependency property + /// Identifies the FixationDuration dependency property /// static property DependencyProperty^ FixationDurationProperty { DependencyProperty^ get(); } @@ -73,7 +73,7 @@ public ref class GazeInput sealed static property DependencyProperty^ MaxDwellRepeatCountProperty { DependencyProperty^ get(); } /// - /// Identifyes the IsSwitchEnabled dependency property + /// Identifies the IsSwitchEnabled dependency property /// static property DependencyProperty^ IsSwitchEnabledProperty { DependencyProperty^ get(); } @@ -128,7 +128,7 @@ public ref class GazeInput sealed static TimeSpan GetFixationDuration(UIElement^ element); /// - /// Gets the duration for the control to transition from the Fixation state to the Dwell state. At this point, a StateChanged event is fired with PointerState set to Dwell. The Enter and Fixation states are typicaly achieved too rapidly for the user to have much control over. In contrast Dwell is conscious event. This is the point at which the control is invoked, e.g. a button click. The application can modify this property to control when a gaze enabled UI element gets invoked after a user starts looking at it. + /// Gets the duration for the control to transition from the Fixation state to the Dwell state. At this point, a StateChanged event is fired with PointerState set to Dwell. The Enter and Fixation states are typically achieved too rapidly for the user to have much control over. In contrast Dwell is conscious event. This is the point at which the control is invoked, e.g. a button click. The application can modify this property to control when a gaze enabled UI element gets invoked after a user starts looking at it. /// static TimeSpan GetDwellDuration(UIElement^ element); @@ -183,7 +183,7 @@ public ref class GazeInput sealed static void SetFixationDuration(UIElement^ element, TimeSpan span); /// - /// Sets the duration for the control to transition from the Fixation state to the Dwell state. At this point, a StateChanged event is fired with PointerState set to Dwell. The Enter and Fixation states are typicaly achieved too rapidly for the user to have much control over. In contrast Dwell is conscious event. This is the point at which the control is invoked, e.g. a button click. The application can modify this property to control when a gaze enabled UI element gets invoked after a user starts looking at it. + /// Sets the duration for the control to transition from the Fixation state to the Dwell state. At this point, a StateChanged event is fired with PointerState set to Dwell. The Enter and Fixation states are typically achieved too rapidly for the user to have much control over. In contrast Dwell is conscious event. This is the point at which the control is invoked, e.g. a button click. The application can modify this property to control when a gaze enabled UI element gets invoked after a user starts looking at it. /// static void SetDwellDuration(UIElement^ element, TimeSpan span); diff --git a/Microsoft.Toolkit.Uwp.Input.GazeInteraction/GazePointer.cpp b/Microsoft.Toolkit.Uwp.Input.GazeInteraction/GazePointer.cpp index 9f4840bf23c..9ae9c31754e 100644 --- a/Microsoft.Toolkit.Uwp.Input.GazeInteraction/GazePointer.cpp +++ b/Microsoft.Toolkit.Uwp.Input.GazeInteraction/GazePointer.cpp @@ -467,8 +467,8 @@ void GazePointer::ActivateGazeTargetItem(GazeTargetItem^ target) GazeTargetItem^ GazePointer::ResolveHitTarget(Point gazePoint, TimeSpan timestamp) { - // TODO: The existance of a GazeTargetItem should be used to indicate that - // the target item is invokable. The method of invokation should be stored + // TODO: The existence of a GazeTargetItem should be used to indicate that + // the target item is invokable. The method of invocation should be stored // within the GazeTargetItem when it is created and not recalculated when // subsequently needed. diff --git a/Microsoft.Toolkit.Uwp.Input.GazeInteraction/IGazeFilter.h b/Microsoft.Toolkit.Uwp.Input.GazeInteraction/IGazeFilter.h index c22d7cefe07..489bbc96558 100644 --- a/Microsoft.Toolkit.Uwp.Input.GazeInteraction/IGazeFilter.h +++ b/Microsoft.Toolkit.Uwp.Input.GazeInteraction/IGazeFilter.h @@ -38,7 +38,7 @@ private ref struct GazeFilterArgs sealed TimeSpan _timestamp; }; -// Every filter must provide an Wpdate method which transforms sample data +// Every filter must provide an Update method which transforms sample data // and returns filtered output private interface class IGazeFilter { diff --git a/Microsoft.Toolkit.Uwp.Input.GazeInteraction/PointerState.h b/Microsoft.Toolkit.Uwp.Input.GazeInteraction/PointerState.h index 2de72ba5eb1..37ad3bbe183 100644 --- a/Microsoft.Toolkit.Uwp.Input.GazeInteraction/PointerState.h +++ b/Microsoft.Toolkit.Uwp.Input.GazeInteraction/PointerState.h @@ -35,7 +35,7 @@ public enum class PointerState Fixation = 3, /// - /// User is conciously dwelling on the control with an intent to invoke, e.g. click a button + /// User is consciously dwelling on the control with an intent to invoke, e.g. click a button /// Dwell = 4, diff --git a/Microsoft.Toolkit.Uwp.Notifications/Adaptive/BindableValues/BindableProgressBarValue.cs b/Microsoft.Toolkit.Uwp.Notifications/Adaptive/BindableValues/BindableProgressBarValue.cs index 94d43c03572..81510aed5d2 100644 --- a/Microsoft.Toolkit.Uwp.Notifications/Adaptive/BindableValues/BindableProgressBarValue.cs +++ b/Microsoft.Toolkit.Uwp.Notifications/Adaptive/BindableValues/BindableProgressBarValue.cs @@ -80,7 +80,7 @@ public static implicit operator AdaptiveProgressBarValue(BindableProgressBarValu } /// - /// Creates an that has tbe raw double value. + /// Creates an that has the raw double value. /// /// The raw value public static implicit operator BindableProgressBarValue(double d) diff --git a/Microsoft.Toolkit.Uwp.Notifications/Tiles/Elements/Element_TileBinding.cs b/Microsoft.Toolkit.Uwp.Notifications/Tiles/Elements/Element_TileBinding.cs index d403cb801b5..8dfe545f273 100644 --- a/Microsoft.Toolkit.Uwp.Notifications/Tiles/Elements/Element_TileBinding.cs +++ b/Microsoft.Toolkit.Uwp.Notifications/Tiles/Elements/Element_TileBinding.cs @@ -61,7 +61,7 @@ public Element_TileBinding(TileTemplateNameV3 template) public string DisplayName { get; set; } /// - /// Gets or sets the target locale of the XML payload, specified as a BCP-47 language tags such as "en-US" or "fr-FR". The locale specified here overrides that in visual, but can be overriden by that in text. If this value is a literal string, this attribute defaults to the user's UI language. If this value is a string reference, this attribute defaults to the locale chosen by Windows Runtime in resolving the string. See Remarks for when this value isn't specified. + /// Gets or sets the target locale of the XML payload, specified as a BCP-47 language tags such as "en-US" or "fr-FR". The locale specified here overrides that in visual, but can be overridden by that in text. If this value is a literal string, this attribute defaults to the user's UI language. If this value is a string reference, this attribute defaults to the locale chosen by Windows Runtime in resolving the string. See Remarks for when this value isn't specified. /// [NotificationXmlAttribute("lang")] public string Language { get; set; } diff --git a/Microsoft.Toolkit.Uwp.Notifications/Tiles/SpecialTemplates/TileBindingContentPhotos.cs b/Microsoft.Toolkit.Uwp.Notifications/Tiles/SpecialTemplates/TileBindingContentPhotos.cs index 546bb55077e..e01704c58c3 100644 --- a/Microsoft.Toolkit.Uwp.Notifications/Tiles/SpecialTemplates/TileBindingContentPhotos.cs +++ b/Microsoft.Toolkit.Uwp.Notifications/Tiles/SpecialTemplates/TileBindingContentPhotos.cs @@ -7,12 +7,12 @@ namespace Microsoft.Toolkit.Uwp.Notifications { /// - /// Animates through a slideshow of photos. Supported on all sizes. + /// Animates through a slide show of photos. Supported on all sizes. /// public sealed class TileBindingContentPhotos : ITileBindingContent { /// - /// Gets the collection of slideshow images. Up to 12 images can be provided (Mobile will only display up to 9), which will be used for the slideshow. Adding more than 12 will throw an exception. + /// Gets the collection of slide show images. Up to 12 images can be provided (Mobile will only display up to 9), which will be used for the slide show. Adding more than 12 will throw an exception. /// public IList Images { get; private set; } = new LimitedList(12); diff --git a/Microsoft.Toolkit.Uwp.Notifications/Tiles/TileBinding.cs b/Microsoft.Toolkit.Uwp.Notifications/Tiles/TileBinding.cs index a21f496f3cc..987af6245cd 100644 --- a/Microsoft.Toolkit.Uwp.Notifications/Tiles/TileBinding.cs +++ b/Microsoft.Toolkit.Uwp.Notifications/Tiles/TileBinding.cs @@ -12,7 +12,7 @@ namespace Microsoft.Toolkit.Uwp.Notifications public sealed class TileBinding { /// - /// Gets or sets the target locale of the XML payload, specified as a BCP-47 language tags such as "en-US" or "fr-FR". The locale specified here overrides that in visual, but can be overriden by that in text. If this value is a literal string, this attribute defaults to the user's UI language. If this value is a string reference, this attribute defaults to the locale chosen by Windows Runtime in resolving the string. See Remarks for when this value isn't specified. + /// Gets or sets the target locale of the XML payload, specified as a BCP-47 language tags such as "en-US" or "fr-FR". The locale specified here overrides that in visual, but can be overridden by that in text. If this value is a literal string, this attribute defaults to the user's UI language. If this value is a string reference, this attribute defaults to the locale chosen by Windows Runtime in resolving the string. See Remarks for when this value isn't specified. /// public string Language { get; set; } diff --git a/Microsoft.Toolkit.Uwp.Notifications/Tiles/TileVisual.cs b/Microsoft.Toolkit.Uwp.Notifications/Tiles/TileVisual.cs index 5ad10c29d1b..5f82f87746b 100644 --- a/Microsoft.Toolkit.Uwp.Notifications/Tiles/TileVisual.cs +++ b/Microsoft.Toolkit.Uwp.Notifications/Tiles/TileVisual.cs @@ -111,7 +111,7 @@ private static bool TryReuseTextElementForLockDetailedText(int lineNumber, strin throw new ArgumentNullException("binding cannot be null"); } - // If a text element already has an id with the line number (only look at immediate children, since the lockscreen will ignore things under groups/subgroups) + // If a text element already has an id with the line number (only look at immediate children, since the lock screen will ignore things under groups/subgroups) Element_AdaptiveText matchingIdTextElement = binding.Children.OfType().FirstOrDefault(i => i.Id != null && i.Id.Equals(lineNumber.ToString())); if (matchingIdTextElement != null) diff --git a/Microsoft.Toolkit.Uwp.Notifications/Toasts/Elements/Element_ToastBinding.cs b/Microsoft.Toolkit.Uwp.Notifications/Toasts/Elements/Element_ToastBinding.cs index 6a2bc44d057..b4dd8d784b9 100644 --- a/Microsoft.Toolkit.Uwp.Notifications/Toasts/Elements/Element_ToastBinding.cs +++ b/Microsoft.Toolkit.Uwp.Notifications/Toasts/Elements/Element_ToastBinding.cs @@ -37,7 +37,7 @@ public Element_ToastBinding(ToastTemplateType template) public Uri BaseUri { get; set; } /// - /// Gets or sets the target locale of the XML payload, specified as a BCP-47 language tags such as "en-US" or "fr-FR". The locale specified here overrides that in visual, but can be overriden by that in text. If this value is a literal string, this attribute defaults to the user's UI language. If this value is a string reference, this attribute defaults to the locale chosen by Windows Runtime in resolving the string. See Remarks for when this value isn't specified. + /// Gets or sets the target locale of the XML payload, specified as a BCP-47 language tags such as "en-US" or "fr-FR". The locale specified here overrides that in visual, but can be overridden by that in text. If this value is a literal string, this attribute defaults to the user's UI language. If this value is a string reference, this attribute defaults to the locale chosen by Windows Runtime in resolving the string. See Remarks for when this value isn't specified. /// [NotificationXmlAttribute("lang")] public string Language { get; set; } diff --git a/Microsoft.Toolkit.Uwp.SampleApp/App.xaml.cs b/Microsoft.Toolkit.Uwp.SampleApp/App.xaml.cs index c858a9c40aa..85d00408e02 100644 --- a/Microsoft.Toolkit.Uwp.SampleApp/App.xaml.cs +++ b/Microsoft.Toolkit.Uwp.SampleApp/App.xaml.cs @@ -104,7 +104,7 @@ private async System.Threading.Tasks.Task RunAppInitialization(string launchPara { ThemeInjector.InjectThemeResources(Application.Current.Resources); - // Go fullscreen on Xbox + // Go full screen on Xbox if (AnalyticsInfo.VersionInfo.GetDeviceFormFactor() == DeviceFormFactor.Xbox) { Windows.UI.ViewManagement.ApplicationView.GetForCurrentView().SetDesiredBoundsMode(ApplicationViewBoundsMode.UseCoreWindow); diff --git a/Microsoft.Toolkit.Uwp.SampleApp/Pages/SampleController.xaml.cs b/Microsoft.Toolkit.Uwp.SampleApp/Pages/SampleController.xaml.cs index f5cb6329feb..289912e9b7d 100644 --- a/Microsoft.Toolkit.Uwp.SampleApp/Pages/SampleController.xaml.cs +++ b/Microsoft.Toolkit.Uwp.SampleApp/Pages/SampleController.xaml.cs @@ -292,7 +292,7 @@ protected override async void OnNavigatedTo(NavigationEventArgs e) } } - // Hide the Github button if there isn't a CodeUrl. + // Hide the GitHub button if there isn't a CodeUrl. if (string.IsNullOrEmpty(CurrentSample.CodeUrl)) { GithubButton.Visibility = Visibility.Collapsed; diff --git a/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/BladeView/BladeCode.bind b/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/BladeView/BladeCode.bind index 54f3ed2ae77..d88f0c83100 100644 --- a/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/BladeView/BladeCode.bind +++ b/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/BladeView/BladeCode.bind @@ -74,7 +74,7 @@ HorizontalAlignment="Center" VerticalAlignment="Center" Style="{StaticResource SubtitleTextBlockStyle}" - Text="This is a blade with custom titlebar colors." /> + Text="This is a blade with custom title bar colors." /> ( async () => { - // Task.Run() will guarantee the given piece of code be executed on a seperate thread pool. + // Task.Run() will guarantee the given piece of code be executed on a separate thread pool. // This is used to simulate the scenario of updating the UI element from a different thread. int returnedFromUIThread = await dispatcherQueue.ExecuteOnUIThreadAsync(() => { diff --git a/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/Loading/LoadingPage.xaml.cs b/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/Loading/LoadingPage.xaml.cs index e4bcc10d58e..9d17d078c8d 100644 --- a/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/Loading/LoadingPage.xaml.cs +++ b/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/Loading/LoadingPage.xaml.cs @@ -57,7 +57,7 @@ private void Load() } }); - SampleController.Current.RegisterNewCommand("Loading control with logo and bluring when requested", async (sender, args) => + SampleController.Current.RegisterNewCommand("Loading control with logo and blurring when requested", async (sender, args) => { if (loadingContentControl != null) { diff --git a/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/MasterDetailsView/MasterDetailsViewCode.bind b/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/MasterDetailsView/MasterDetailsViewCode.bind index 99497a756fd..f28e7c75ecc 100644 --- a/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/MasterDetailsView/MasterDetailsViewCode.bind +++ b/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/MasterDetailsView/MasterDetailsViewCode.bind @@ -13,6 +13,6 @@ var Emails = new List { new Email { From = "Steve Johnson", Subject = "Lunch Tomorrow", Body = "Are you available for lunch tomorrow? A client would like to discuss a project with you." }, new Email { From = "Becky Davidson", Subject = "Kids game", Body = "Don't forget the kids have their soccer game this Friday. We have to supply end of game snacks." }, - new Email { From = "OneDrive", Subject = "Check out your event recap", Body = "Your new album.\r\nYou uploaded some photos to yuor OneDrive and automatically created an album for you." }, + new Email { From = "OneDrive", Subject = "Check out your event recap", Body = "Your new album.\r\nYou uploaded some photos to your OneDrive and automatically created an album for you." }, new Email { From = "Twitter", Subject = "Follow randomPerson, APersonYouMightKnow", Body = "Here are some people we think you might like to follow:\r\n.@randomPerson\r\nAPersonYouMightKnow" }, }; \ No newline at end of file diff --git a/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/MasterDetailsView/MasterDetailsViewPage.xaml.cs b/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/MasterDetailsView/MasterDetailsViewPage.xaml.cs index bf7758af228..378c3093cf5 100644 --- a/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/MasterDetailsView/MasterDetailsViewPage.xaml.cs +++ b/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/MasterDetailsView/MasterDetailsViewPage.xaml.cs @@ -37,7 +37,7 @@ public MasterDetailsViewPage() { From = "OneDrive", Subject = "Check out your event recap", - Body = "Your new album.\r\nYou uploaded some photos to yuor OneDrive and automatically created an album for you.", + Body = "Your new album.\r\nYou uploaded some photos to your OneDrive and automatically created an album for you.", Thumbnail = new Uri("ms-appx:///SamplePages/MasterDetailsView/OneDriveLogo.png") }, new Email diff --git a/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/ScrollViewerExtensions/ScrollViewerExtensionsXaml.bind b/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/ScrollViewerExtensions/ScrollViewerExtensionsXaml.bind index 59737c4f9a2..555377bdebe 100644 --- a/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/ScrollViewerExtensions/ScrollViewerExtensionsXaml.bind +++ b/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/ScrollViewerExtensions/ScrollViewerExtensionsXaml.bind @@ -71,7 +71,7 @@ + To inspect the C# code being used, switch to the "Code" tab in this sample page.--> - + diff --git a/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/SystemInformation/SystemInformationCode.bind b/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/SystemInformation/SystemInformationCode.bind index 7fc9a563dcb..7d605ff0df7 100644 --- a/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/SystemInformation/SystemInformationCode.bind +++ b/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/SystemInformation/SystemInformationCode.bind @@ -49,7 +49,7 @@ // To get the time the launch count was reset, not including this instance public string LastResetTime => SystemInformation.Instance.LastResetTime.ToString(Culture.DateTimeFormat); - // To get the number of times the app has been launched sicne the last reset. + // To get the number of times the app has been launched since the last reset. public long LaunchCount => SystemInformation.Instance.LaunchCount; // To get the number of times the app has been launched. diff --git a/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/SystemInformation/SystemInformationPage.xaml.cs b/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/SystemInformation/SystemInformationPage.xaml.cs index f6c68407e71..2c1c4d84344 100644 --- a/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/SystemInformation/SystemInformationPage.xaml.cs +++ b/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/SystemInformation/SystemInformationPage.xaml.cs @@ -73,7 +73,7 @@ public string LastResetTime public static readonly DependencyProperty LastResetTimeProperty = DependencyProperty.Register(nameof(LastResetTime), typeof(string), typeof(SystemInformationPage), new PropertyMetadata(string.Empty)); - // To get the number of times the app has been launched sicne the last reset. + // To get the number of times the app has been launched since the last reset. public long LaunchCount { get { return (long)GetValue(LaunchCountProperty); } diff --git a/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/TabView/TabViewXaml.bind b/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/TabView/TabViewXaml.bind index e81732aa2a3..8790aa966ac 100644 --- a/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/TabView/TabViewXaml.bind +++ b/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/TabView/TabViewXaml.bind @@ -47,7 +47,7 @@ The TabView control has multiple uses. - It has a lot of versitility out of the box for different scenarios. + It has a lot of versatility out of the box for different scenarios. You can enable drag-and-drop and reorder the tabs too. diff --git a/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/Twitter Service/TwitterPage.xaml.cs b/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/Twitter Service/TwitterPage.xaml.cs index 1259e763f67..ff22659ecf7 100644 --- a/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/Twitter Service/TwitterPage.xaml.cs +++ b/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/Twitter Service/TwitterPage.xaml.cs @@ -112,7 +112,7 @@ private async void GetLocation_OnClick(object sender, RoutedEventArgs e) } catch (Exception ex) { - await new MessageDialog($"An error occured finding your location. Message: {ex.Message}").ShowAsync(); + await new MessageDialog($"An error occurred finding your location. Message: {ex.Message}").ShowAsync(); TrackingManager.TrackException(ex); } } diff --git a/Microsoft.Toolkit.Uwp.SampleApp/Shell.xaml.cs b/Microsoft.Toolkit.Uwp.SampleApp/Shell.xaml.cs index 8c78d6d18ed..d2f9ca0754b 100644 --- a/Microsoft.Toolkit.Uwp.SampleApp/Shell.xaml.cs +++ b/Microsoft.Toolkit.Uwp.SampleApp/Shell.xaml.cs @@ -66,7 +66,7 @@ public void SetAppTitle(string title) } /// - /// Attach a ScrollViewer to Parallax hosting the backround image + /// Attach a ScrollViewer to Parallax hosting the background image /// /// The ScrollViewer public void AttachScroll(ScrollViewer viewer) diff --git a/Microsoft.Toolkit.Uwp.SampleApp/readme.md b/Microsoft.Toolkit.Uwp.SampleApp/readme.md index 41f9e8bad8a..a314fad44a3 100644 --- a/Microsoft.Toolkit.Uwp.SampleApp/readme.md +++ b/Microsoft.Toolkit.Uwp.SampleApp/readme.md @@ -6,7 +6,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 contain 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. @@ -163,7 +163,7 @@ Select the category where you want your page to be listed and add the following 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 uplevel implementors know the minimum version required. +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. ```json { @@ -188,7 +188,7 @@ Use the DocumentationUrl property to add a link to the raw documentation in *sam > NOTE: When building and running the app in release mode, the branch will automatically be changed to **master** before loading. -> 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: 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: > ``` diff --git a/Microsoft.Toolkit.Uwp.Services/Services/Facebook/FacebookService.cs b/Microsoft.Toolkit.Uwp.Services/Services/Facebook/FacebookService.cs index fb3bedd36aa..8e767aad7c7 100644 --- a/Microsoft.Toolkit.Uwp.Services/Services/Facebook/FacebookService.cs +++ b/Microsoft.Toolkit.Uwp.Services/Services/Facebook/FacebookService.cs @@ -50,7 +50,7 @@ public FacebookService() /// Initialize underlying provider with relevant token information. /// /// Token instance. - /// List of required required permissions. public_profile and user_posts permissions will be used by default. + /// List of required permissions. public_profile and user_posts permissions will be used by default. /// Success or failure. public bool Initialize(FacebookOAuthTokens oAuthTokens, FacebookPermissions requiredPermissions = FacebookPermissions.PublicProfile | FacebookPermissions.UserPosts) { @@ -66,7 +66,7 @@ public bool Initialize(FacebookOAuthTokens oAuthTokens, FacebookPermissions requ /// Initialize underlying provider with relevant token information. /// /// Application ID (Provided by Facebook developer site) - /// List of required required permissions. public_profile and user_posts permissions will be used by default. + /// List of required permissions. public_profile and user_posts permissions will be used by default. /// Windows Store SID /// Success or failure. public bool Initialize(string appId, FacebookPermissions requiredPermissions = FacebookPermissions.PublicProfile | FacebookPermissions.UserPosts, string windowsStoreId = null) diff --git a/Microsoft.Toolkit.Uwp.UI.Animations/Expressions/ExpressionNodes/Matrix3x2Node.cs b/Microsoft.Toolkit.Uwp.UI.Animations/Expressions/ExpressionNodes/Matrix3x2Node.cs index ead4440f700..5c8ca867818 100644 --- a/Microsoft.Toolkit.Uwp.UI.Animations/Expressions/ExpressionNodes/Matrix3x2Node.cs +++ b/Microsoft.Toolkit.Uwp.UI.Animations/Expressions/ExpressionNodes/Matrix3x2Node.cs @@ -235,7 +235,7 @@ public ScalarNode Channel32 /// /// Create a new type by re-arranging the Matrix subchannels. /// - /// The subchanel. + /// The subchannel. /// ScalarNode public ScalarNode GetSubchannels(Subchannel s) { @@ -245,7 +245,7 @@ public ScalarNode GetSubchannels(Subchannel s) /// /// Create a new type by re-arranging the Matrix subchannels. /// - /// The first subchanel. + /// The first subchannel. /// The second subchannel. /// Vector2Node public Vector2Node GetSubchannels(Subchannel s1, Subchannel s2) @@ -256,7 +256,7 @@ public Vector2Node GetSubchannels(Subchannel s1, Subchannel s2) /// /// Create a new type by re-arranging the Matrix subchannels. /// - /// The first subchanel. + /// The first subchannel. /// The second subchannel. /// The third subchannel. /// Vector3Node @@ -268,7 +268,7 @@ public Vector3Node GetSubchannels(Subchannel s1, Subchannel s2, Subchannel s3) /// /// Create a new type by re-arranging the Matrix subchannels. /// - /// The first subchanel. + /// The first subchannel. /// The second subchannel. /// The third subchannel. /// The fourth subchannel. @@ -281,7 +281,7 @@ public Vector4Node GetSubchannels(Subchannel s1, Subchannel s2, Subchannel s3, S /// /// Create a new type by re-arranging the Matrix subchannels. /// - /// The first subchanel. + /// The first subchannel. /// The second subchannel. /// The third subchannel. /// The fourth subchannel. @@ -296,7 +296,7 @@ public Matrix3x2Node GetSubchannels(Subchannel s1, Subchannel s2, Subchannel s3, /// /// Create a new type by re-arranging the Matrix subchannels. /// - /// The first subchanel. + /// The first subchannel. /// The second subchannel. /// The third subchannel. /// The fourth subchannel. diff --git a/Microsoft.Toolkit.Uwp.UI.Animations/Expressions/ExpressionNodes/Matrix4x4Node.cs b/Microsoft.Toolkit.Uwp.UI.Animations/Expressions/ExpressionNodes/Matrix4x4Node.cs index 81f2b59bd04..7b00d426046 100644 --- a/Microsoft.Toolkit.Uwp.UI.Animations/Expressions/ExpressionNodes/Matrix4x4Node.cs +++ b/Microsoft.Toolkit.Uwp.UI.Animations/Expressions/ExpressionNodes/Matrix4x4Node.cs @@ -316,7 +316,7 @@ public ScalarNode GetSubchannels(Subchannel s) /// /// Create a new type by re-arranging the Matrix subchannels. /// - /// The first subchanel. + /// The first subchannel. /// The second subchannel. /// Vector2Node public Vector2Node GetSubchannels(Subchannel s1, Subchannel s2) @@ -327,7 +327,7 @@ public Vector2Node GetSubchannels(Subchannel s1, Subchannel s2) /// /// Create a new type by re-arranging the Matrix subchannels. /// - /// The first subchanel. + /// The first subchannel. /// The second subchannel. /// The third subchannel. /// Vector3Node @@ -339,7 +339,7 @@ public Vector3Node GetSubchannels(Subchannel s1, Subchannel s2, Subchannel s3) /// /// Create a new type by re-arranging the Matrix subchannels. /// - /// The first subchanel. + /// The first subchannel. /// The second subchannel. /// The third subchannel. /// The fourth subchannel. @@ -352,7 +352,7 @@ public Vector4Node GetSubchannels(Subchannel s1, Subchannel s2, Subchannel s3, S /// /// Create a new type by re-arranging the Matrix subchannels. /// - /// The first subchanel. + /// The first subchannel. /// The second subchannel. /// The third subchannel. /// The fourth subchannel. @@ -367,7 +367,7 @@ public Matrix3x2Node GetSubchannels(Subchannel s1, Subchannel s2, Subchannel s3, /// /// Create a new type by re-arranging the Matrix subchannels. /// - /// The first subchanel. + /// The first subchannel. /// The second subchannel. /// The third subchannel. /// The fourth subchannel. diff --git a/Microsoft.Toolkit.Uwp.UI.Animations/Expressions/ExpressionNodes/Vector2Node.cs b/Microsoft.Toolkit.Uwp.UI.Animations/Expressions/ExpressionNodes/Vector2Node.cs index 44c997f05d6..9b17ea11c77 100644 --- a/Microsoft.Toolkit.Uwp.UI.Animations/Expressions/ExpressionNodes/Vector2Node.cs +++ b/Microsoft.Toolkit.Uwp.UI.Animations/Expressions/ExpressionNodes/Vector2Node.cs @@ -210,7 +210,7 @@ public ScalarNode GetSubchannels(Subchannel s) /// /// Create a new type by re-arranging the Vector subchannels. /// - /// The first subchanel. + /// The first subchannel. /// The second subchannel. /// Vector2Node. public Vector2Node GetSubchannels(Subchannel s1, Subchannel s2) @@ -221,7 +221,7 @@ public Vector2Node GetSubchannels(Subchannel s1, Subchannel s2) /// /// Create a new type by re-arranging the Vector subchannels. /// - /// The first subchanel. + /// The first subchannel. /// The second subchannel. /// The third subchannel. /// Vector3Node @@ -233,7 +233,7 @@ public Vector3Node GetSubchannels(Subchannel s1, Subchannel s2, Subchannel s3) /// /// Create a new type by re-arranging the Vector subchannels. /// - /// The first subchanel. + /// The first subchannel. /// The second subchannel. /// The third subchannel. /// The fourth subchannel. @@ -246,7 +246,7 @@ public Vector4Node GetSubchannels(Subchannel s1, Subchannel s2, Subchannel s3, S /// /// Create a new type by re-arranging the Vector subchannels. /// - /// The first subchanel. + /// The first subchannel. /// The second subchannel. /// The third subchannel. /// The fourth subchannel. @@ -261,7 +261,7 @@ public Matrix3x2Node GetSubchannels(Subchannel s1, Subchannel s2, Subchannel s3, /// /// Create a new type by re-arranging the Vector subchannels. /// - /// The first subchanel. + /// The first subchannel. /// The second subchannel. /// The third subchannel. /// The fourth subchannel. diff --git a/Microsoft.Toolkit.Uwp.UI.Animations/Expressions/ExpressionNodes/Vector3Node.cs b/Microsoft.Toolkit.Uwp.UI.Animations/Expressions/ExpressionNodes/Vector3Node.cs index b2824c03efb..f527d4fda54 100644 --- a/Microsoft.Toolkit.Uwp.UI.Animations/Expressions/ExpressionNodes/Vector3Node.cs +++ b/Microsoft.Toolkit.Uwp.UI.Animations/Expressions/ExpressionNodes/Vector3Node.cs @@ -233,7 +233,7 @@ public ScalarNode GetSubchannels(Subchannel s) /// /// Create a new type by re-arranging the Vector subchannels. /// - /// The first subchanel. + /// The first subchannel. /// The second subchannel. /// Vector2Node public Vector2Node GetSubchannels(Subchannel s1, Subchannel s2) @@ -244,7 +244,7 @@ public Vector2Node GetSubchannels(Subchannel s1, Subchannel s2) /// /// Create a new type by re-arranging the Vector subchannels. /// - /// The first subchanel. + /// The first subchannel. /// The second subchannel. /// The third subchannel. /// Vector3Node @@ -256,7 +256,7 @@ public Vector3Node GetSubchannels(Subchannel s1, Subchannel s2, Subchannel s3) /// /// Create a new type by re-arranging the Vector subchannels. /// - /// The first subchanel. + /// The first subchannel. /// The second subchannel. /// The third subchannel. /// The fourth subchannel. @@ -269,7 +269,7 @@ public Vector4Node GetSubchannels(Subchannel s1, Subchannel s2, Subchannel s3, S /// /// Create a new type by re-arranging the Vector subchannels. /// - /// The first subchanel. + /// The first subchannel. /// The second subchannel. /// The third subchannel. /// The fourth subchannel. @@ -284,7 +284,7 @@ public Matrix3x2Node GetSubchannels(Subchannel s1, Subchannel s2, Subchannel s3, /// /// Create a new type by re-arranging the Vector subchannels. /// - /// The first subchanel. + /// The first subchannel. /// The second subchannel. /// The third subchannel. /// The fourth subchannel. diff --git a/Microsoft.Toolkit.Uwp.UI.Animations/Expressions/ExpressionNodes/Vector4Node.cs b/Microsoft.Toolkit.Uwp.UI.Animations/Expressions/ExpressionNodes/Vector4Node.cs index b14e00f042f..3aeaf76d43b 100644 --- a/Microsoft.Toolkit.Uwp.UI.Animations/Expressions/ExpressionNodes/Vector4Node.cs +++ b/Microsoft.Toolkit.Uwp.UI.Animations/Expressions/ExpressionNodes/Vector4Node.cs @@ -246,7 +246,7 @@ public Vector3Node XYZ /// /// Create a new type by re-arranging the Vector subchannels. /// - /// The subchanel. + /// The subchannel. /// ScalarNode public ScalarNode GetSubchannels(Subchannel s) { @@ -256,7 +256,7 @@ public ScalarNode GetSubchannels(Subchannel s) /// /// Create a new type by re-arranging the Vector subchannels. /// - /// The first subchanel. + /// The first subchannel. /// The second subchannel. /// Vector2Node public Vector2Node GetSubchannels(Subchannel s1, Subchannel s2) @@ -267,7 +267,7 @@ public Vector2Node GetSubchannels(Subchannel s1, Subchannel s2) /// /// Create a new type by re-arranging the Vector subchannels. /// - /// The first subchanel. + /// The first subchannel. /// The second subchannel. /// The third subchannel. /// Vector3Node @@ -279,7 +279,7 @@ public Vector3Node GetSubchannels(Subchannel s1, Subchannel s2, Subchannel s3) /// /// Create a new type by re-arranging the Vector subchannels. /// - /// The first subchanel. + /// The first subchannel. /// The second subchannel. /// The third subchannel. /// The fourth subchannel. @@ -292,7 +292,7 @@ public Vector4Node GetSubchannels(Subchannel s1, Subchannel s2, Subchannel s3, S /// /// Create a new type by re-arranging the Vector subchannels. /// - /// The first subchanel. + /// The first subchannel. /// The second subchannel. /// The third subchannel. /// The fourth subchannel. @@ -307,7 +307,7 @@ public Matrix3x2Node GetSubchannels(Subchannel s1, Subchannel s2, Subchannel s3, /// /// Create a new type by re-arranging the Vector subchannels. /// - /// The first subchanel. + /// The first subchannel. /// The second subchannel. /// The third subchannel. /// The fourth subchannel. diff --git a/Microsoft.Toolkit.Uwp.UI.Animations/Expressions/ReferenceNodes/PointLightReferenceNode.cs b/Microsoft.Toolkit.Uwp.UI.Animations/Expressions/ReferenceNodes/PointLightReferenceNode.cs index d16c7b5423e..f80cfc364cb 100644 --- a/Microsoft.Toolkit.Uwp.UI.Animations/Expressions/ReferenceNodes/PointLightReferenceNode.cs +++ b/Microsoft.Toolkit.Uwp.UI.Animations/Expressions/ReferenceNodes/PointLightReferenceNode.cs @@ -53,9 +53,9 @@ public ScalarNode LinearAttenuation } /// - /// Gets the quadratic attentuation. + /// Gets the quadratic attenuation. /// - /// The quadratic attentuation. + /// The quadratic attenuation. public ScalarNode QuadraticAttentuation { get { return ReferenceProperty("QuadraticAttentuation"); } diff --git a/Microsoft.Toolkit.Uwp.UI.Animations/Expressions/ReferenceNodes/SpotLightReferenceNode.cs b/Microsoft.Toolkit.Uwp.UI.Animations/Expressions/ReferenceNodes/SpotLightReferenceNode.cs index 1423a1e67d2..20f627a94f5 100644 --- a/Microsoft.Toolkit.Uwp.UI.Animations/Expressions/ReferenceNodes/SpotLightReferenceNode.cs +++ b/Microsoft.Toolkit.Uwp.UI.Animations/Expressions/ReferenceNodes/SpotLightReferenceNode.cs @@ -53,9 +53,9 @@ public ScalarNode LinearAttenuation } /// - /// Gets the quadratic attentuation. + /// Gets the quadratic attenuation. /// - /// The quadratic attentuation. + /// The quadratic attenuation. public ScalarNode QuadraticAttentuation { get { return ReferenceProperty("QuadraticAttentuation"); } diff --git a/Microsoft.Toolkit.Uwp.UI.Animations/Extensions/AnimationExtensions.Blur.cs b/Microsoft.Toolkit.Uwp.UI.Animations/Extensions/AnimationExtensions.Blur.cs index c368465de86..c18d3e78958 100644 --- a/Microsoft.Toolkit.Uwp.UI.Animations/Extensions/AnimationExtensions.Blur.cs +++ b/Microsoft.Toolkit.Uwp.UI.Animations/Extensions/AnimationExtensions.Blur.cs @@ -34,7 +34,7 @@ public static partial class AnimationExtensions ApiInformation.IsApiContractPresent("Windows.Foundation.UniversalApiContract", 3); // SDK >= 14393 /// - /// Animates the gaussian blur of the UIElement. + /// Animates the Gaussian blur of the UIElement. /// /// The associated object. /// The blur amount. @@ -64,7 +64,7 @@ public static AnimationSet Blur( } /// - /// Animates the gaussian blur of the UIElement. + /// Animates the Gaussian blur of the UIElement. /// /// The animation set. /// The blur amount. diff --git a/Microsoft.Toolkit.Uwp.UI.Animations/Extensions/AnimationExtensions.cs b/Microsoft.Toolkit.Uwp.UI.Animations/Extensions/AnimationExtensions.cs index 4ecb6db00a5..7692b9a3d0e 100644 --- a/Microsoft.Toolkit.Uwp.UI.Animations/Extensions/AnimationExtensions.cs +++ b/Microsoft.Toolkit.Uwp.UI.Animations/Extensions/AnimationExtensions.cs @@ -125,7 +125,7 @@ private static void EnsureEasingsCached() // and we don't want to deal with caching a disposed easing function void Add(EasingType type, EasingMode mode, Vector2 p1, Vector2 p2) { - // In order to generate a usable hashcode for our ValueTuple without collisions + // In order to generate a usable hash code for our ValueTuple without collisions // we can't use enum values for both type & mode, so we have to string one of them. #pragma warning disable SA1008 // Opening parenthesis must be spaced correctly _compositionEasingFunctions[(type.ToString(), mode)] = (p1, p2); diff --git a/Microsoft.Toolkit.Uwp.UI.Animations/ReorderGridAnimation.cs b/Microsoft.Toolkit.Uwp.UI.Animations/ReorderGridAnimation.cs index 6873c8bb0c5..b8e86ad1596 100644 --- a/Microsoft.Toolkit.Uwp.UI.Animations/ReorderGridAnimation.cs +++ b/Microsoft.Toolkit.Uwp.UI.Animations/ReorderGridAnimation.cs @@ -49,7 +49,7 @@ public static double GetDuration(DependencyObject obj) /// Sets a value for the duration, in milliseconds, the animation should take. /// /// the object to set the value on. - /// The duration in milliseonds. + /// The duration in milliseconds. public static void SetDuration(DependencyObject obj, double value) { obj.SetValue(DurationProperty, value); diff --git a/Microsoft.Toolkit.Uwp.UI.Animations/SurfaceLoader.cs b/Microsoft.Toolkit.Uwp.UI.Animations/SurfaceLoader.cs index 8de05e2055f..8610c13fb83 100644 --- a/Microsoft.Toolkit.Uwp.UI.Animations/SurfaceLoader.cs +++ b/Microsoft.Toolkit.Uwp.UI.Animations/SurfaceLoader.cs @@ -31,7 +31,7 @@ namespace Microsoft.Toolkit.Uwp.UI.Animations public class SurfaceLoader { /// - /// A flag to store the intialized state. + /// A flag to store the initialized state. /// private static bool _intialized; @@ -46,7 +46,7 @@ public class SurfaceLoader private static CanvasDevice _canvasDevice; /// - /// The composition graphic device to determinde which GPU is handling the request. + /// The composition graphic device to determine which GPU is handling the request. /// private static CompositionGraphicsDevice _compositionDevice; diff --git a/Microsoft.Toolkit.Uwp.UI.Controls.DataGrid/CollectionViews/CollectionView.cs b/Microsoft.Toolkit.Uwp.UI.Controls.DataGrid/CollectionViews/CollectionView.cs index 87fa414b086..87fe287020b 100644 --- a/Microsoft.Toolkit.Uwp.UI.Controls.DataGrid/CollectionViews/CollectionView.cs +++ b/Microsoft.Toolkit.Uwp.UI.Controls.DataGrid/CollectionViews/CollectionView.cs @@ -237,7 +237,7 @@ public virtual CultureInfo Culture /// Enter a Defer Cycle. /// Defer cycles are used to coalesce changes to the ICollectionView. /// - /// An IDisposable deferal object. + /// An IDisposable deferral object. public virtual IDisposable DeferRefresh() { #if FEATURE_IEDITABLECOLLECTIONVIEW @@ -611,7 +611,7 @@ public abstract int Count } /// - /// Gets a value indicating whether the resulting (filtered) view is emtpy. + /// Gets a value indicating whether the resulting (filtered) view is empty. /// public abstract bool IsEmpty { @@ -772,7 +772,7 @@ protected bool OKToChangeCurrent() } /// - /// Raise a CurrentChanging event that is not cancelable. + /// Raise a CurrentChanging event that is not cancel-able. /// Internally, CurrentPosition is set to -1. /// This is called by CollectionChanges (Remove and Refresh) that affect the CurrentItem. /// @@ -1258,7 +1258,7 @@ private enum CollectionViewFlags internal const string IsCurrentBeforeFirstPropertyName = "IsCurrentBeforeFirst"; internal const string IsCurrentAfterLastPropertyName = "IsCurrentAfterLast"; - // since there's nothing in the uncancelable event args that is mutable, + // since there's nothing in the uncancel-able event args that is mutable, // just create one instance to be used universally. private static readonly CurrentChangingEventArgs UncancelableCurrentChangingEventArgs = new CurrentChangingEventArgs(false); private static readonly string IEnumerableT = typeof(IEnumerable<>).Name; diff --git a/Microsoft.Toolkit.Uwp.UI.Controls.DataGrid/CollectionViews/EnumerableCollectionView.cs b/Microsoft.Toolkit.Uwp.UI.Controls.DataGrid/CollectionViews/EnumerableCollectionView.cs index a7ea3a6eaa3..8a1d9d0e1db 100644 --- a/Microsoft.Toolkit.Uwp.UI.Controls.DataGrid/CollectionViews/EnumerableCollectionView.cs +++ b/Microsoft.Toolkit.Uwp.UI.Controls.DataGrid/CollectionViews/EnumerableCollectionView.cs @@ -207,7 +207,7 @@ public override ReadOnlyObservableCollection Groups /// Enter a Defer Cycle. /// Defer cycles are used to coalesce changes to the ICollectionView. /// - /// An IDisposable deferal object. + /// An IDisposable deferral object. public override IDisposable DeferRefresh() { return _view.DeferRefresh(); diff --git a/Microsoft.Toolkit.Uwp.UI.Controls.DataGrid/CollectionViews/ListCollectionView.cs b/Microsoft.Toolkit.Uwp.UI.Controls.DataGrid/CollectionViews/ListCollectionView.cs index 9f1370b8576..1c264306da8 100644 --- a/Microsoft.Toolkit.Uwp.UI.Controls.DataGrid/CollectionViews/ListCollectionView.cs +++ b/Microsoft.Toolkit.Uwp.UI.Controls.DataGrid/CollectionViews/ListCollectionView.cs @@ -441,7 +441,7 @@ public override int Count } /// - /// Gets a value indicating whether the resulting (filtered) view is emtpy. + /// Gets a value indicating whether the resulting (filtered) view is empty. /// public override bool IsEmpty { diff --git a/Microsoft.Toolkit.Uwp.UI.Controls.DataGrid/DataGrid/Automation/DataGridGroupItemAutomationPeer.cs b/Microsoft.Toolkit.Uwp.UI.Controls.DataGrid/DataGrid/Automation/DataGridGroupItemAutomationPeer.cs index 5652d70296d..7113caf7c43 100644 --- a/Microsoft.Toolkit.Uwp.UI.Controls.DataGrid/DataGrid/Automation/DataGridGroupItemAutomationPeer.cs +++ b/Microsoft.Toolkit.Uwp.UI.Controls.DataGrid/DataGrid/Automation/DataGridGroupItemAutomationPeer.cs @@ -377,7 +377,7 @@ protected override bool IsOffscreenCore() /// /// Gets a value indicating whether the UIElement associated with this DataGridGroupItemAutomationPeer contains protected content. /// - /// Trye if the UIElement contains protected content. + /// True if the UIElement contains protected content. protected override bool IsPasswordCore() { return this.OwningRowGroupHeaderPeer != null ? this.OwningRowGroupHeaderPeer.IsPassword() : false; diff --git a/Microsoft.Toolkit.Uwp.UI.Controls.DataGrid/DataGrid/Automation/DataGridItemAutomationPeer.cs b/Microsoft.Toolkit.Uwp.UI.Controls.DataGrid/DataGrid/Automation/DataGridItemAutomationPeer.cs index e7f5764c62d..325420318bf 100644 --- a/Microsoft.Toolkit.Uwp.UI.Controls.DataGrid/DataGrid/Automation/DataGridItemAutomationPeer.cs +++ b/Microsoft.Toolkit.Uwp.UI.Controls.DataGrid/DataGrid/Automation/DataGridItemAutomationPeer.cs @@ -343,7 +343,7 @@ protected override bool IsOffscreenCore() /// /// Gets a value indicating whether the UIElement associated with this DataGridItemAutomationPeer contains protected content. /// - /// Trye if the UIElement contains protected content. + /// True if the UIElement contains protected content. protected override bool IsPasswordCore() { return this.OwningRowPeer != null ? this.OwningRowPeer.IsPassword() : false; diff --git a/Microsoft.Toolkit.Uwp.UI.Controls.DataGrid/DataGrid/DataGrid.cs b/Microsoft.Toolkit.Uwp.UI.Controls.DataGrid/DataGrid/DataGrid.cs index 0f88d1b7a05..703734b2d04 100644 --- a/Microsoft.Toolkit.Uwp.UI.Controls.DataGrid/DataGrid/DataGrid.cs +++ b/Microsoft.Toolkit.Uwp.UI.Controls.DataGrid/DataGrid/DataGrid.cs @@ -361,12 +361,12 @@ private enum ScrollBarsSeparatorVisualState public event EventHandler RowDetailsVisibilityChanged; /// - /// Occurs when the row has been successfully committed or cancelled. + /// Occurs when the row has been successfully committed or canceled. /// public event EventHandler RowEditEnded; /// - /// Occurs immediately before the row has been successfully committed or cancelled. + /// Occurs immediately before the row has been successfully committed or canceled. /// public event EventHandler RowEditEnding; @@ -3308,7 +3308,7 @@ protected override Size MeasureOverride(Size availableSize) // Update our estimates now that the DataGrid has all of the information necessary UpdateRowDetailsHeightEstimate(); - // Update frozen columns to account for columns added prior to loading or autogenerated columns + // Update frozen columns to account for columns added prior to loading or auto-generated columns if (this.FrozenColumnCountWithFiller > 0) { ProcessFrozenColumnCount(this); @@ -3388,7 +3388,7 @@ protected override void OnApplyTemplate() _columnHeadersPresenter.OwningGrid = this; - // Columns were added before before our Template was applied, add the ColumnHeaders now + // Columns were added before our Template was applied, add the ColumnHeaders now List sortedInternal = new List(this.ColumnsItemsInternal); sortedInternal.Sort(new DisplayIndexComparer()); foreach (DataGridColumn column in sortedInternal) @@ -5932,7 +5932,7 @@ private bool EndCellEdit(DataGridEditAction editAction, bool exitEditingMode, bo OnCellEditEnding(e); if (e.Cancel) { - // CellEditEnding has been cancelled + // CellEditEnding has been canceled return false; } @@ -6062,7 +6062,7 @@ private bool EndRowEdit(DataGridEditAction editAction, bool exitEditingMode, boo OnRowEditEnding(e); if (e.Cancel) { - // RowEditEnding has been cancelled + // RowEditEnding has been canceled return false; } @@ -6115,7 +6115,7 @@ private bool EndRowEdit(DataGridEditAction editAction, bool exitEditingMode, boo { // Some EditableCollectionViews (ListCollectionView in particular) do not raise CurrentChanged when CommitEdit // changes the position of the CurrentItem. Instead, they raise a PropertyChanged event for PositionChanged. - // We recognize that case here and setup the CurrentItem again if one exists but it was removed and readded + // We recognize that case here and setup the CurrentItem again if one exists but it was removed and re-added // during Commit. This is better than reacting to PositionChanged which would double the work in most cases // and likely introduce regressions. UpdateStateOnCurrentChanged(this.DataConnection.CollectionView.CurrentItem, this.DataConnection.CollectionView.CurrentPosition); @@ -8663,7 +8663,7 @@ private bool UpdateStateOnTapped(TappedRoutedEventArgs args, int columnIndex, in // Selecting a single row or multi-selecting with Ctrl. if (this.SelectionMode == DataGridSelectionMode.Single || !ctrl) { - // Unselect the currectly selected rows except the new selected row. + // Unselect the correctly selected rows except the new selected row. action = DataGridSelectionAction.SelectCurrent; } else diff --git a/Microsoft.Toolkit.Uwp.UI.Controls.DataGrid/DataGrid/DataGridClipboardCellContent.cs b/Microsoft.Toolkit.Uwp.UI.Controls.DataGrid/DataGrid/DataGridClipboardCellContent.cs index 72177c12847..b635e8f0f9d 100644 --- a/Microsoft.Toolkit.Uwp.UI.Controls.DataGrid/DataGrid/DataGridClipboardCellContent.cs +++ b/Microsoft.Toolkit.Uwp.UI.Controls.DataGrid/DataGrid/DataGridClipboardCellContent.cs @@ -63,7 +63,7 @@ public object Item /// Field-by-field comparison to avoid reflection-based ValueType.Equals. /// /// DataGridClipboardCellContent to compare. - /// True iff this and data are equal + /// True if this and data are equal public override bool Equals(object obj) { if (!(obj is DataGridClipboardCellContent)) @@ -100,7 +100,7 @@ public override int GetHashCode() /// /// The first DataGridClipboardCellContent. /// The second DataGridClipboardCellContent. - /// True iff clipboardCellContent1 and clipboardCellContent2 are NOT equal. + /// True if clipboardCellContent1 and clipboardCellContent2 are NOT equal. public static bool operator !=(DataGridClipboardCellContent clipboardCellContent1, DataGridClipboardCellContent clipboardCellContent2) { if (clipboardCellContent1._column == clipboardCellContent2._column && clipboardCellContent1._content == clipboardCellContent2._content) diff --git a/Microsoft.Toolkit.Uwp.UI.Controls.DataGrid/DataGrid/DataGridColumn.cs b/Microsoft.Toolkit.Uwp.UI.Controls.DataGrid/DataGrid/DataGridColumn.cs index fee5a1b7b13..c0c52580f78 100644 --- a/Microsoft.Toolkit.Uwp.UI.Controls.DataGrid/DataGrid/DataGridColumn.cs +++ b/Microsoft.Toolkit.Uwp.UI.Controls.DataGrid/DataGrid/DataGridColumn.cs @@ -851,7 +851,7 @@ public FrameworkElement GetCellContent(DataGridRow dataGridRow) if (dataGridRow.OwningGrid == this.OwningGrid) { - Debug.Assert(this.Index >= 0, "Expected positif Index."); + Debug.Assert(this.Index >= 0, "Expected positive Index."); Debug.Assert(this.Index < this.OwningGrid.ColumnsItemsInternal.Count, "Expected smaller Index."); DataGridCell dataGridCell = dataGridRow.Cells[this.Index]; @@ -881,7 +881,7 @@ public FrameworkElement GetCellContent(object dataItem) throw DataGridError.DataGrid.NoOwningGrid(this.GetType()); } - Debug.Assert(this.Index >= 0, "Expected positif Index."); + Debug.Assert(this.Index >= 0, "Expected positive Index."); Debug.Assert(this.Index < this.OwningGrid.ColumnsItemsInternal.Count, "Expected smaller Index."); DataGridRow dataGridRow = this.OwningGrid.GetRowFromItem(dataItem); @@ -1051,7 +1051,7 @@ internal DataGridLength CoerceWidth(DataGridLength width) } /// - /// If the DataGrid is using using layout rounding, the pixel snapping will force all widths to + /// If the DataGrid is using layout rounding, the pixel snapping will force all widths to /// whole numbers. Since the column widths aren't visual elements, they don't go through the normal /// rounding process, so we need to do it ourselves. If we don't, then we'll end up with some /// pixel gaps and/or overlaps between columns. @@ -1188,7 +1188,7 @@ internal FrameworkElement GenerateElementInternal(DataGridCell cell, object data } /// - /// Gets the value of a cell according to the the specified binding. + /// Gets the value of a cell according to the specified binding. /// /// The item associated with a cell. /// The binding to get the value of. diff --git a/Microsoft.Toolkit.Uwp.UI.Controls.DataGrid/DataGrid/DataGridColumns.cs b/Microsoft.Toolkit.Uwp.UI.Controls.DataGrid/DataGrid/DataGridColumns.cs index 97b55a71caf..bb43011e97b 100644 --- a/Microsoft.Toolkit.Uwp.UI.Controls.DataGrid/DataGrid/DataGridColumns.cs +++ b/Microsoft.Toolkit.Uwp.UI.Controls.DataGrid/DataGrid/DataGridColumns.cs @@ -474,7 +474,7 @@ internal void OnColumnMinWidthChanged(DataGridColumn column, double oldValue) } else if (column.Width.DisplayValue == oldValue && column.Width.DesiredValue < column.Width.DisplayValue) { - // If the column was previously limited by its minimum value but but can be smaller now, + // If the column was previously limited by its minimum value but can be smaller now, // attempt to resize the column to its desired width. column.Resize(column.Width.Value, column.Width.UnitType, column.Width.DesiredValue, column.Width.DesiredValue, false); } @@ -823,7 +823,7 @@ internal DataGridCellCoordinates OnRemovingColumn(DataGridColumn dataGridColumn) } /// - /// Called when a column property changes, and its cells need to adjust that that column change. + /// Called when a column property changes, and its cells need to adjust that column change. /// internal void RefreshColumnElements(DataGridColumn dataGridColumn, string propertyName) { @@ -966,7 +966,7 @@ private bool AddGeneratedColumn(DataGridAutoGeneratingColumnEventArgs e) { if (e.Column != null) { - // Set the IsAutoGenerated flag here in case the user provides a custom autogenerated column + // Set the IsAutoGenerated flag here in case the user provides a custom auto-generated column e.Column.IsAutoGenerated = true; } @@ -1121,7 +1121,7 @@ private void AutoGenerateColumnsPrivate() _autoGeneratingColumnOperationCount++; try { - // Always remove existing autogenerated columns before generating new ones + // Always remove existing auto-generated columns before generating new ones RemoveAutoGeneratedColumns(); GenerateColumnsFromProperties(); EnsureRowsPresenterVisibility(); @@ -1558,7 +1558,7 @@ private void FlushDisplayIndexChanged(bool raiseEvent) private void GenerateColumnsFromProperties() { - // Autogenerated Columns are added at the end so the user columns appear first + // Auto-generated Columns are added at the end so the user columns appear first if (this.DataConnection.DataProperties != null && this.DataConnection.DataProperties.Length > 0) { List> columnOrderPairs = new List>(); @@ -1738,7 +1738,7 @@ private void RemoveAutoGeneratedColumns() index++; } - // Remove the autogenerated columns + // Remove the auto-generated columns while (index < this.ColumnsInternal.Count && this.ColumnsInternal[index].IsAutoGenerated) { this.ColumnsInternal.RemoveAt(index); diff --git a/Microsoft.Toolkit.Uwp.UI.Controls.DataGrid/DataGrid/DataGridRow.cs b/Microsoft.Toolkit.Uwp.UI.Controls.DataGrid/DataGrid/DataGridRow.cs index 7f5d9e825e6..dab3530737e 100644 --- a/Microsoft.Toolkit.Uwp.UI.Controls.DataGrid/DataGrid/DataGridRow.cs +++ b/Microsoft.Toolkit.Uwp.UI.Controls.DataGrid/DataGrid/DataGridRow.cs @@ -394,7 +394,7 @@ internal double ActualBottomGridLineHeight { if (_bottomGridLine != null && this.OwningGrid != null && this.OwningGrid.AreRowBottomGridLinesRequired) { - // Unfortunately, _bottomGridLine has no size yet so we can't get its actualheight + // Unfortunately, _bottomGridLine has no size yet so we can't get its actual height return DataGrid.HorizontalGridLinesThickness; } @@ -453,7 +453,7 @@ internal DataGridCell FillerCell { get { - Debug.Assert(this.OwningGrid != null, "Exptected non-null owning DataGrid."); + Debug.Assert(this.OwningGrid != null, "Expected non-null owning DataGrid."); if (_fillerCell == null) { @@ -628,13 +628,13 @@ internal double TargetHeight } } - // Returns the actual template that should be sued for Details: either explicity set on this row + // Returns the actual template that should be sued for Details: either explicitly set on this row // or inherited from the DataGrid private DataTemplate ActualDetailsTemplate { get { - Debug.Assert(this.OwningGrid != null, "Exptected non-null owning DataGrid."); + Debug.Assert(this.OwningGrid != null, "Expected non-null owning DataGrid."); DataTemplate currentDetailsTemplate = this.DetailsTemplate; return currentDetailsTemplate != null ? currentDetailsTemplate : this.OwningGrid.RowDetailsTemplate; @@ -778,7 +778,7 @@ protected override Size MeasureOverride(Size availableSize) return base.MeasureOverride(availableSize); } - // Allow the DataGrid specific componets to adjust themselves based on new values + // Allow the DataGrid specific components to adjust themselves based on new values if (_headerElement != null) { _headerElement.InvalidateMeasure(); @@ -1070,11 +1070,11 @@ internal void DetachFromDataGrid(bool recycle) this.Slot = -1; } - // Make sure the row's background is set to its correct value. It could be explicity set or inherit + // Make sure the row's background is set to its correct value. It could be explicitly set or inherit // DataGrid.RowBackground or DataGrid.AlternatingRowBackground internal void EnsureBackground() { - // Inherit the DataGrid's RowBackground properties only if this row doesn't explicity have a background set + // Inherit the DataGrid's RowBackground properties only if this row doesn't explicitly have a background set if (this.RootElement != null && this.OwningGrid != null) { Debug.Assert(this.Index != -1, "Expected Index other than -1."); @@ -1111,11 +1111,11 @@ internal void EnsureBackground() } } - // Make sure the row's foreground is set to its correct value. It could be explicity set or inherit + // Make sure the row's foreground is set to its correct value. It could be explicitly set or inherit // DataGrid.RowForeground or DataGrid.AlternatingRowForeground internal void EnsureForeground() { - // Inherit the DataGrid's RowForeground properties only if this row doesn't explicity have a foreground set + // Inherit the DataGrid's RowForeground properties only if this row doesn't explicitly have a foreground set if (this.OwningGrid != null) { Debug.Assert(this.Index != -1, "Expected Index other than -1."); @@ -1246,7 +1246,7 @@ internal void SetDetailsVisibilityInternal( Visibility visibility, bool raiseNotification) { - Debug.Assert(this.OwningGrid != null, "Exptected non-null owning DataGrid."); + Debug.Assert(this.OwningGrid != null, "Expected non-null owning DataGrid."); Debug.Assert(this.Index != -1, "Expected Index other than -1."); if (_appliedDetailsVisibility != visibility) diff --git a/Microsoft.Toolkit.Uwp.UI.Controls.DataGrid/DataGrid/DataGridRows.cs b/Microsoft.Toolkit.Uwp.UI.Controls.DataGrid/DataGrid/DataGridRows.cs index 330de25883d..e71be5bb0e6 100644 --- a/Microsoft.Toolkit.Uwp.UI.Controls.DataGrid/DataGrid/DataGridRows.cs +++ b/Microsoft.Toolkit.Uwp.UI.Controls.DataGrid/DataGrid/DataGridRows.cs @@ -347,7 +347,7 @@ internal Visibility GetRowDetailsVisibility(int rowIndex, DataGridRowDetailsVisi Debug.Assert(rowIndex != -1, "Expected rowIndex other than -1."); if (_showDetailsTable.Contains(rowIndex)) { - // The user explicity set DetailsVisibility on a row so we should respect that + // The user explicitly set DetailsVisibility on a row so we should respect that return _showDetailsTable.GetValueAt(rowIndex); } else @@ -1423,7 +1423,7 @@ private int GetDetailsCountInclusive(int lowerBound, int upperBound) if (this.RowDetailsVisibilityMode == DataGridRowDetailsVisibilityMode.Visible) { - // Total rows minus ones which explicity turned details off + // Total rows minus ones which explicitly turned details off return indexCount - _showDetailsTable.GetIndexCount(lowerBound, upperBound, Visibility.Collapsed); } else if (this.RowDetailsVisibilityMode == DataGridRowDetailsVisibilityMode.Collapsed) @@ -2253,7 +2253,7 @@ private void OnInsertedElement_Phase1(int slot, UIElement element, bool isCollap private void OnInsertedElement_Phase2(int slot, bool updateVerticalScrollBarOnly, bool isCollapsed) { - Debug.Assert(slot >= 0, "Expected positve slot."); + Debug.Assert(slot >= 0, "Expected positive slot."); if (!isCollapsed) { @@ -2780,7 +2780,7 @@ private bool SlotIsDisplayed(int slot) if (slot >= this.DisplayData.FirstScrollingSlot && slot <= this.DisplayData.LastScrollingSlot) { - // Additional row takes the spot of a displayed row - it is necessarilly displayed + // Additional row takes the spot of a displayed row - it is necessarily displayed return true; } else if (this.DisplayData.FirstScrollingSlot == -1 && @@ -2974,7 +2974,7 @@ private void ScrollSlotsByHeight(double height) if (DoubleUtil.LessThan(firstRowHeight, this.NegVerticalOffset)) { // We've scrolled off more of the first row than what's possible. This can happen - // if the first row got shorter (Ex: Collpasing RowDetails) or if the user has a recycling + // if the first row got shorter (Ex: Collapsing RowDetails) or if the user has a recycling // cleanup issue. In this case, simply try to display the next row as the first row instead if (newFirstScrollingSlot < this.SlotCount - 1) { diff --git a/Microsoft.Toolkit.Uwp.UI.Controls.DataGrid/Utilities/IndexToValueTable.cs b/Microsoft.Toolkit.Uwp.UI.Controls.DataGrid/Utilities/IndexToValueTable.cs index a850e54665f..c480f908252 100644 --- a/Microsoft.Toolkit.Uwp.UI.Controls.DataGrid/Utilities/IndexToValueTable.cs +++ b/Microsoft.Toolkit.Uwp.UI.Controls.DataGrid/Utilities/IndexToValueTable.cs @@ -153,7 +153,7 @@ public bool ContainsAll(int startIndex, int endIndex) /// /// index to search for /// value expected - /// true if the given index is contained in the table with the the given value + /// true if the given index is contained in the table with the given value public bool ContainsIndexAndValue(int index, T value) { int lowerRangeIndex = this.FindRangeIndex(index); @@ -830,7 +830,7 @@ private void InsertIndexesPrivate(int startIndex, int count, int lowerRangeIndex { Debug.Assert(count > 0, "Expected a strictly positive count parameter."); - // Same as AddRange after we fix the indicies affected by the insertion + // Same as AddRange after we fix the indices affected by the insertion int startRangeIndex = (lowerRangeIndex >= 0) ? lowerRangeIndex : 0; for (int i = startRangeIndex; i < _list.Count; i++) { diff --git a/Microsoft.Toolkit.Uwp.UI.Controls.DataGrid/Utilities/ValidationUtil.cs b/Microsoft.Toolkit.Uwp.UI.Controls.DataGrid/Utilities/ValidationUtil.cs index a2dfbc93ca5..47506ab7bbd 100644 --- a/Microsoft.Toolkit.Uwp.UI.Controls.DataGrid/Utilities/ValidationUtil.cs +++ b/Microsoft.Toolkit.Uwp.UI.Controls.DataGrid/Utilities/ValidationUtil.cs @@ -253,7 +253,7 @@ public static bool IsCriticalException(Exception exception) /// /// Gets a list of active bindings on the specified FrameworkElement. Bindings are gathered - /// according to the same conditions BindingGroup uses to find bindings of descendent elements + /// according to the same conditions BindingGroup uses to find bindings of descendant elements /// within the visual tree. /// /// Root FrameworkElement to search under diff --git a/Microsoft.Toolkit.Uwp.UI.Controls/Carousel/Carousel.cs b/Microsoft.Toolkit.Uwp.UI.Controls/Carousel/Carousel.cs index 88a948539b0..615ec02db67 100644 --- a/Microsoft.Toolkit.Uwp.UI.Controls/Carousel/Carousel.cs +++ b/Microsoft.Toolkit.Uwp.UI.Controls/Carousel/Carousel.cs @@ -120,7 +120,7 @@ public int ItemMargin /// /// Gets or sets a value indicating whether the items rendered transformations should be opposite compared to the selected item /// If false, all the items (except the selected item) will have the exact same transformations - /// If true, all the items where index > selected index will have an opposite tranformation (Rotation X Y and Z will be multiply by -1) + /// If true, all the items where index > selected index will have an opposite transformation (Rotation X Y and Z will be multiply by -1) /// public bool InvertPositive { diff --git a/Microsoft.Toolkit.Uwp.UI.Controls/Carousel/CarouselPanel.cs b/Microsoft.Toolkit.Uwp.UI.Controls/Carousel/CarouselPanel.cs index b5b9dff9138..378fe7d4a5e 100644 --- a/Microsoft.Toolkit.Uwp.UI.Controls/Carousel/CarouselPanel.cs +++ b/Microsoft.Toolkit.Uwp.UI.Controls/Carousel/CarouselPanel.cs @@ -66,7 +66,7 @@ public Carousel Carousel } /// - /// Tapp an item + /// Tap an item /// private void OnTapped(object sender, TappedRoutedEventArgs e) { @@ -208,7 +208,7 @@ internal void UpdatePosition() // Apply projection ApplyProjection(item, props, storyboard); - // Zindex and Opacity + // Z index and Opacity var deltaFromSelectedIndex = Math.Abs(Carousel.SelectedIndex - i); int zindex = (Carousel.Items.Count * 100) - deltaFromSelectedIndex; Canvas.SetZIndex(item, zindex); @@ -320,7 +320,7 @@ protected override Size ArrangeOverride(Size finalSize) // apply the projection to the current object ApplyProjection(container, proj); - // calculate zindex and opacity + // calculate z index and opacity int zindex = (Children.Count * 100) - deltaFromSelectedIndex; Canvas.SetZIndex(container, zindex); } @@ -404,7 +404,7 @@ private Proj GetProjectionFromManipulation(UIElement element, double delta) var rotationY = Carousel.ItemRotationY; var rotationZ = Carousel.ItemRotationZ; - // if the relativeposition is inside the bounds so calculate the proportionals + // if the relative position is inside the bounds so calculate the proportionals if (relativePosition <= maxBounds) { depth = relativePosition * depth / maxBounds; @@ -432,10 +432,10 @@ private Proj GetProjectionFromSelectedIndex(int childIndex) // margin var margin = Carousel.ItemMargin; - // we want the middle image to be indice 0, to be sure the centered image is with no rotation + // we want the middle image to be index 0, to be sure the centered image is with no rotation var relativeIndex = childIndex - Carousel.SelectedIndex; - // size beetween each element + // size between each element var widthOrHeight = Carousel.Orientation == Orientation.Horizontal ? desiredWidth : desiredHeight; // calculate the position with the margin applied diff --git a/Microsoft.Toolkit.Uwp.UI.Controls/GridSplitter/GridSplitter.Events.cs b/Microsoft.Toolkit.Uwp.UI.Controls/GridSplitter/GridSplitter.Events.cs index 3766119fc77..d64d1123afd 100644 --- a/Microsoft.Toolkit.Uwp.UI.Controls/GridSplitter/GridSplitter.Events.cs +++ b/Microsoft.Toolkit.Uwp.UI.Controls/GridSplitter/GridSplitter.Events.cs @@ -205,7 +205,7 @@ private bool VerticalMove(double verticalChange) // change sibling row height to the new height relative to current row // respect the other star row height by setting it's height to it's actual height with stars - // We need to validate current and sibling height to not cause any un expected behavior + // We need to validate current and sibling height to not cause any unexpected behavior if (!IsValidRowHeight(CurrentRow, verticalChange) || !IsValidRowHeight(SiblingRow, verticalChange * -1)) { @@ -271,7 +271,7 @@ private bool HorizontalMove(double horizontalChange) // change sibling column width to the new width relative to current column // respect the other star column width by setting it's width to it's actual width with stars - // We need to validate current and sibling width to not cause any un expected behavior + // We need to validate current and sibling width to not cause any unexpected behavior if (!IsValidColumnWidth(CurrentColumn, horizontalChange) || !IsValidColumnWidth(SiblingColumn, horizontalChange * -1)) { diff --git a/Microsoft.Toolkit.Uwp.UI.Controls/GridSplitter/GripperHoverWrapper.cs b/Microsoft.Toolkit.Uwp.UI.Controls/GridSplitter/GripperHoverWrapper.cs index cbcbda962be..8f654bf730b 100644 --- a/Microsoft.Toolkit.Uwp.UI.Controls/GridSplitter/GripperHoverWrapper.cs +++ b/Microsoft.Toolkit.Uwp.UI.Controls/GridSplitter/GripperHoverWrapper.cs @@ -75,7 +75,7 @@ private void Element_PointerExited(object sender, PointerRoutedEventArgs e) { if (_isDragging) { - // if dragging don't update the curser just update the splitter cursor with the last window cursor, + // if dragging don't update the cursor just update the splitter cursor with the last window cursor, // because the splitter is still using the arrow cursor and will revert to original case when drag completes _splitterPreviousPointer = _previousCursor; } diff --git a/Microsoft.Toolkit.Uwp.UI.Controls/InfiniteCanvas/Controls/InfiniteCanvasVirtualDrawingSurface.Render.cs b/Microsoft.Toolkit.Uwp.UI.Controls/InfiniteCanvas/Controls/InfiniteCanvasVirtualDrawingSurface.Render.cs index 18f75ee78d6..884811d6969 100644 --- a/Microsoft.Toolkit.Uwp.UI.Controls/InfiniteCanvas/Controls/InfiniteCanvasVirtualDrawingSurface.Render.cs +++ b/Microsoft.Toolkit.Uwp.UI.Controls/InfiniteCanvas/Controls/InfiniteCanvasVirtualDrawingSurface.Render.cs @@ -49,7 +49,7 @@ internal void ReDraw(Rect viewPort, float zoom) { /* CanvasComposition.CreateDrawingSession has an internal * limit on the size of the updateRectInPixels parameter, - * which we dont know, so we can get an ArgumentException + * which we don't know, so we can get an ArgumentException * if there is a lot of extreme zooming and panning * Therefore, the only solution is to silently catch the * exception and allow the app to continue diff --git a/Microsoft.Toolkit.Uwp.UI.Controls/LayoutTransformControl/LayoutTransformControl.cs b/Microsoft.Toolkit.Uwp.UI.Controls/LayoutTransformControl/LayoutTransformControl.cs index 21f8576983e..eed290eb35f 100644 --- a/Microsoft.Toolkit.Uwp.UI.Controls/LayoutTransformControl/LayoutTransformControl.cs +++ b/Microsoft.Toolkit.Uwp.UI.Controls/LayoutTransformControl/LayoutTransformControl.cs @@ -238,7 +238,7 @@ protected override Size MeasureOverride(Size availableSize) measureSize = _childActualSize; } - // Perform a mesaure on the _layoutRoot (containing Child) + // Perform a measure on the _layoutRoot (containing Child) _layoutRoot.Measure(measureSize); // Transform DesiredSize to find its width/height diff --git a/Microsoft.Toolkit.Uwp.UI.Controls/LayoutTransformControl/PropertyChangeEventSource.cs b/Microsoft.Toolkit.Uwp.UI.Controls/LayoutTransformControl/PropertyChangeEventSource.cs index 752bf5cf473..7b0abe2a868 100644 --- a/Microsoft.Toolkit.Uwp.UI.Controls/LayoutTransformControl/PropertyChangeEventSource.cs +++ b/Microsoft.Toolkit.Uwp.UI.Controls/LayoutTransformControl/PropertyChangeEventSource.cs @@ -82,7 +82,7 @@ public PropertyChangeEventSource( { _source = source; - // Bind to the property to be able to get its changes relayed as events throug the ValueChanged event. + // Bind to the property to be able to get its changes relayed as events through the ValueChanged event. var binding = new Binding { diff --git a/Microsoft.Toolkit.Uwp.UI.Controls/MasterDetailsView/MasterDetailsView.Properties.cs b/Microsoft.Toolkit.Uwp.UI.Controls/MasterDetailsView/MasterDetailsView.Properties.cs index 941967a0ac3..e7b7a22ca54 100644 --- a/Microsoft.Toolkit.Uwp.UI.Controls/MasterDetailsView/MasterDetailsView.Properties.cs +++ b/Microsoft.Toolkit.Uwp.UI.Controls/MasterDetailsView/MasterDetailsView.Properties.cs @@ -323,7 +323,7 @@ public CommandBar DetailsCommandBar } /// - /// Gets or sets the Threshold width that witll trigger the control to go into compact mode + /// Gets or sets the Threshold width that will trigger the control to go into compact mode /// public double CompactModeThresholdWidth { diff --git a/Microsoft.Toolkit.Uwp.UI.Controls/Menu/Menu.Logic.cs b/Microsoft.Toolkit.Uwp.UI.Controls/Menu/Menu.Logic.cs index 296f5d7cfb5..6d968f2aab0 100644 --- a/Microsoft.Toolkit.Uwp.UI.Controls/Menu/Menu.Logic.cs +++ b/Microsoft.Toolkit.Uwp.UI.Controls/Menu/Menu.Logic.cs @@ -24,7 +24,7 @@ public partial class Menu private const string AltValue = "ALT"; /// - /// Gets or sets the current flyout placement, internal because the child menu item needs it to respect the menu direction of submenus + /// Gets or sets the current flyout placement, internal because the child menu item needs it to respect the menu direction of sub-menus /// internal FlyoutPlacementMode? CurrentFlyoutPlacement { get; set; } diff --git a/Microsoft.Toolkit.Uwp.UI.Controls/OrbitView/OrbitView.cs b/Microsoft.Toolkit.Uwp.UI.Controls/OrbitView/OrbitView.cs index 15d504178ab..1b7c445edc0 100644 --- a/Microsoft.Toolkit.Uwp.UI.Controls/OrbitView/OrbitView.cs +++ b/Microsoft.Toolkit.Uwp.UI.Controls/OrbitView/OrbitView.cs @@ -20,7 +20,7 @@ namespace Microsoft.Toolkit.Uwp.UI.Controls { /// - /// ItemsControl that lays out items in a circle with support for odbits and anchors + /// ItemsControl that lays out items in a circle with support for orbits and anchors /// [TemplatePart(Name = "AnchorCanvas", Type = typeof(Canvas))] [TemplatePart(Name = "OrbitGrid", Type = typeof(Grid))] diff --git a/Microsoft.Toolkit.Uwp.UI.Controls/OrbitView/OrbitViewDataItem.cs b/Microsoft.Toolkit.Uwp.UI.Controls/OrbitView/OrbitViewDataItem.cs index c76fdcd5d0b..375947a0e6b 100644 --- a/Microsoft.Toolkit.Uwp.UI.Controls/OrbitView/OrbitViewDataItem.cs +++ b/Microsoft.Toolkit.Uwp.UI.Controls/OrbitView/OrbitViewDataItem.cs @@ -15,7 +15,7 @@ public class OrbitViewDataItem : DependencyObject { /// /// Gets or sets a value indicating the distance from the center. - /// Expected value betweeen 0 and 1 + /// Expected value between 0 and 1 /// public double Distance { @@ -47,7 +47,7 @@ public string Label /// /// Gets or sets a value indicating the diameter of the item. - /// Expected value betweeen 0 and 1 + /// Expected value between 0 and 1 /// public double Diameter { diff --git a/Microsoft.Toolkit.Uwp.UI.Controls/RadialProgressBar/RadialProgressBar.cs b/Microsoft.Toolkit.Uwp.UI.Controls/RadialProgressBar/RadialProgressBar.cs index b92c619aaf8..d2fc0af0f6c 100644 --- a/Microsoft.Toolkit.Uwp.UI.Controls/RadialProgressBar/RadialProgressBar.cs +++ b/Microsoft.Toolkit.Uwp.UI.Controls/RadialProgressBar/RadialProgressBar.cs @@ -11,7 +11,7 @@ namespace Microsoft.Toolkit.Uwp.UI.Controls { /// - /// An alternative impementation of a progress bar. + /// An alternative implementation of a progress bar. /// Progression is represented by a loop filling up in a clockwise fashion. /// Like the traditional progress bar, it inherits from RangeBase, so Minimum, Maximum and Value properties work the same way. /// @@ -84,7 +84,7 @@ protected override void OnApplyTemplate() } /// - /// Gets or sets the thickness of the circular ouline and segment + /// Gets or sets the thickness of the circular outline and segment /// public double Thickness { @@ -98,7 +98,7 @@ public double Thickness public static readonly DependencyProperty ThicknessProperty = DependencyProperty.Register(nameof(Thickness), typeof(double), typeof(RadialProgressBar), new PropertyMetadata(0.0, ThicknessChangedHandler)); /// - /// Gets or sets the color of the circular ouline on which the segment is drawn + /// Gets or sets the color of the circular outline on which the segment is drawn /// public Brush Outline { diff --git a/Microsoft.Toolkit.Uwp.UI.Controls/TabView/TabView.Properties.cs b/Microsoft.Toolkit.Uwp.UI.Controls/TabView/TabView.Properties.cs index 5d33b33f1cc..31cc672f20c 100644 --- a/Microsoft.Toolkit.Uwp.UI.Controls/TabView/TabView.Properties.cs +++ b/Microsoft.Toolkit.Uwp.UI.Controls/TabView/TabView.Properties.cs @@ -173,7 +173,7 @@ public double SelectedTabWidth DependencyProperty.Register(nameof(SelectedTabWidth), typeof(double), typeof(TabView), new PropertyMetadata(double.NaN, OnLayoutEffectingPropertyChanged)); /// - /// Gets or sets the current which determins how tab headers' width behave. + /// Gets or sets the current which determines how tab headers' width behave. /// public TabWidthMode TabWidthBehavior { diff --git a/Microsoft.Toolkit.Uwp.UI.Controls/TabView/TabViewItem.cs b/Microsoft.Toolkit.Uwp.UI.Controls/TabView/TabViewItem.cs index 69a968fd839..97666fea66e 100644 --- a/Microsoft.Toolkit.Uwp.UI.Controls/TabView/TabViewItem.cs +++ b/Microsoft.Toolkit.Uwp.UI.Controls/TabView/TabViewItem.cs @@ -72,7 +72,7 @@ protected override void OnPointerPressed(PointerRoutedEventArgs e) _isMiddleClick = true; } - // Disable unwanted behaviour inherited by ListViewItem: + // Disable unwanted behavior inherited by ListViewItem: // Disable "Ctrl + Left click" to deselect tab // Or variant like "Ctrl + Shift + Left click" // Or "Ctrl + Alt + Left click" diff --git a/Microsoft.Toolkit.Uwp.UI.Controls/TextToolbar/Formats/Format.cs b/Microsoft.Toolkit.Uwp.UI.Controls/TextToolbar/Formats/Format.cs index 76fa0c020e7..8d8aa5b7661 100644 --- a/Microsoft.Toolkit.Uwp.UI.Controls/TextToolbar/Formats/Format.cs +++ b/Microsoft.Toolkit.Uwp.UI.Controls/TextToolbar/Formats/Format.cs @@ -10,17 +10,17 @@ namespace Microsoft.Toolkit.Uwp.UI.Controls.TextToolbarFormats public enum Format { /// - /// Utilises the Built-In RichText Formatter + /// Utilizes the Built-In RichText Formatter /// RichText, /// - /// Utilises the Built-In Markdown Formatter + /// Utilizes the Built-In Markdown Formatter /// MarkDown, /// - /// Utilises the provided Custom Formatter using the Formatter Property + /// Utilizes the provided Custom Formatter using the Formatter Property /// Custom } diff --git a/Microsoft.Toolkit.Uwp.UI.Controls/TextToolbar/Formats/Formatter.cs b/Microsoft.Toolkit.Uwp.UI.Controls/TextToolbar/Formats/Formatter.cs index 25669a72fe1..4419bdcaeb0 100644 --- a/Microsoft.Toolkit.Uwp.UI.Controls/TextToolbar/Formats/Formatter.cs +++ b/Microsoft.Toolkit.Uwp.UI.Controls/TextToolbar/Formats/Formatter.cs @@ -42,7 +42,7 @@ protected virtual void Model_EditorChanged(object sender, EditorChangedArgs e) } /// - /// Called for Changes to Selction (Requires unhook if switching RichEditBox). + /// Called for Changes to Selection (Requires unhook if switching RichEditBox). /// /// Editor /// Args diff --git a/Microsoft.Toolkit.Uwp.UI.Controls/TextToolbar/ToolbarItems/ToolbarSeparator.cs b/Microsoft.Toolkit.Uwp.UI.Controls/TextToolbar/ToolbarItems/ToolbarSeparator.cs index b698b7a52a5..35c3a5905f1 100644 --- a/Microsoft.Toolkit.Uwp.UI.Controls/TextToolbar/ToolbarItems/ToolbarSeparator.cs +++ b/Microsoft.Toolkit.Uwp.UI.Controls/TextToolbar/ToolbarItems/ToolbarSeparator.cs @@ -7,7 +7,7 @@ namespace Microsoft.Toolkit.Uwp.UI.Controls.TextToolbarButtons { /// - /// Seperates a collection of + /// Separates a collection of /// public class ToolbarSeparator : AppBarSeparator, IToolbarItem { diff --git a/Microsoft.Toolkit.Uwp.UI.Controls/TokenizingTextBox/InterspersedObservableCollection.cs b/Microsoft.Toolkit.Uwp.UI.Controls/TokenizingTextBox/InterspersedObservableCollection.cs index fdc0122ba2a..74fc28fd5d1 100644 --- a/Microsoft.Toolkit.Uwp.UI.Controls/TokenizingTextBox/InterspersedObservableCollection.cs +++ b/Microsoft.Toolkit.Uwp.UI.Controls/TokenizingTextBox/InterspersedObservableCollection.cs @@ -148,7 +148,7 @@ private void MoveKeysForward(int pivot, int amount) /// by how many private void MoveKeysBackward(int pivot, int amount) { - // Sort in regular order to work from the earliest indicies onwards + // Sort in regular order to work from the earliest indices onwards var keys = _interspersedObjects.Keys.OrderBy(v => v).ToArray(); foreach (var key in keys) { @@ -259,7 +259,7 @@ private int ToOuterIndexAfterRemoval(int innerIndexToProject) } /// - /// Inserts an item to intersperse with the underlying collection, but not be part of the underyling collection itself. + /// Inserts an item to intersperse with the underlying collection, but not be part of the underlying collection itself. /// /// Position to insert the item at. /// Item to intersperse diff --git a/Microsoft.Toolkit.Uwp.UI.Controls/TokenizingTextBox/TokenizingTextBox.Selection.cs b/Microsoft.Toolkit.Uwp.UI.Controls/TokenizingTextBox/TokenizingTextBox.Selection.cs index c952b2defc0..278b57df77e 100644 --- a/Microsoft.Toolkit.Uwp.UI.Controls/TokenizingTextBox/TokenizingTextBox.Selection.cs +++ b/Microsoft.Toolkit.Uwp.UI.Controls/TokenizingTextBox/TokenizingTextBox.Selection.cs @@ -26,7 +26,7 @@ private enum MoveDirection /// /// Adjust the selected item and range based on keyboard input. - /// This is used to override the listview behavious for up/down arrow manipulation vs left/right for a horizontal control + /// This is used to override the listview behaviors for up/down arrow manipulation vs left/right for a horizontal control /// /// direction to move the selection /// True if the focus was moved, false otherwise @@ -106,7 +106,7 @@ private bool MoveFocusAndSelection(MoveDirection direction) SelectedIndex = index; // This looks like a bug in the underlying ListViewBase control. - // Might need to be reviewed if the base behaviour is fixed + // Might need to be reviewed if the base behavior is fixed // When two consecutive items are selected and the navigation moves between them, // the first time that happens the old focused item is not unselected if (SelectedItems.Count > 1) @@ -141,7 +141,7 @@ internal void SelectAllTokensAndText() { this.SelectAllSafe(); - // need to synchronize the select all and the focus behaviour on the text box + // need to synchronize the select all and the focus behavior on the text box // because there is no way to identify that the focus has been set from this point // to avoid instantly clearing the selection of tokens PauseTokenClearOnFocus = true; diff --git a/Microsoft.Toolkit.Uwp.UI.Controls/TokenizingTextBox/TokenizingTextBox.cs b/Microsoft.Toolkit.Uwp.UI.Controls/TokenizingTextBox/TokenizingTextBox.cs index bbcf95fb5e4..67971826868 100644 --- a/Microsoft.Toolkit.Uwp.UI.Controls/TokenizingTextBox/TokenizingTextBox.cs +++ b/Microsoft.Toolkit.Uwp.UI.Controls/TokenizingTextBox/TokenizingTextBox.cs @@ -88,7 +88,7 @@ private void ItemsSource_PropertyChanged(DependencyObject sender, DependencyProp private void TokenizingTextBox_ItemClick(object sender, ItemClickEventArgs e) { // If the user taps an item in the list, make sure to clear any text selection as required - // Note, token selection is cleared by the listview default behaviour + // Note, token selection is cleared by the listview default behavior if (!IsControlPressed) { // Set class state flag to prevent click item being immediately deselected @@ -165,7 +165,7 @@ internal async Task TokenizingTextBox_PreviewKeyDown(VirtualKey key) return MoveFocusAndSelection(MoveDirection.Next); case VirtualKey.A: - // modify the select-all behaviour to ensure the text in the edit box gets selected. + // modify the select-all behavior to ensure the text in the edit box gets selected. if (IsControlPressed) { this.SelectAllTokensAndText(); @@ -538,7 +538,7 @@ private void GuardAgainstPlaceholderTextLayoutIssue() { placeholder.Visibility = Visibility.Collapsed; - // After we ensure we've hid the control, make it visible again (this is inperceptable to the user). + // After we ensure we've hid the control, make it visible again (this is imperceptible to the user). _ = CompositionTargetHelper.ExecuteAfterCompositionRenderingAsync(() => { placeholder.Visibility = Visibility.Visible; diff --git a/Microsoft.Toolkit.Uwp.UI.Controls/TokenizingTextBox/TokenizingTextBoxItem.AutoSuggestBox.cs b/Microsoft.Toolkit.Uwp.UI.Controls/TokenizingTextBox/TokenizingTextBoxItem.AutoSuggestBox.cs index 372960905e2..190a0973f23 100644 --- a/Microsoft.Toolkit.Uwp.UI.Controls/TokenizingTextBox/TokenizingTextBoxItem.AutoSuggestBox.cs +++ b/Microsoft.Toolkit.Uwp.UI.Controls/TokenizingTextBox/TokenizingTextBoxItem.AutoSuggestBox.cs @@ -218,7 +218,7 @@ private void AutoSuggestBox_LostFocus(object sender, RoutedEventArgs e) private void AutoSuggestBox_GotFocus(object sender, RoutedEventArgs e) { - // Verify if the usual behaviour of clearing token selection is required + // Verify if the usual behavior of clearing token selection is required if (Owner.PauseTokenClearOnFocus == false && !TokenizingTextBox.IsShiftPressed) { // Clear any selected tokens diff --git a/Microsoft.Toolkit.Uwp.UI.Controls/WrapPanel/WrapPanel.Data.cs b/Microsoft.Toolkit.Uwp.UI.Controls/WrapPanel/WrapPanel.Data.cs index 749d3d5db07..ff27c846922 100644 --- a/Microsoft.Toolkit.Uwp.UI.Controls/WrapPanel/WrapPanel.Data.cs +++ b/Microsoft.Toolkit.Uwp.UI.Controls/WrapPanel/WrapPanel.Data.cs @@ -7,7 +7,7 @@ namespace Microsoft.Toolkit.Uwp.UI.Controls { /// - /// WrapPanel is a panel that position child control vertically or horizontally based on the orientation and when max width/ max height is recieved a new row(in case of horizontal) or column (in case of vertical) is created to fit new controls. + /// WrapPanel is a panel that position child control vertically or horizontally based on the orientation and when max width/ max height is received a new row(in case of horizontal) or column (in case of vertical) is created to fit new controls. /// public partial class WrapPanel { diff --git a/Microsoft.Toolkit.Uwp.UI.Controls/WrapPanel/WrapPanel.cs b/Microsoft.Toolkit.Uwp.UI.Controls/WrapPanel/WrapPanel.cs index 121569ff89d..527bc406e39 100644 --- a/Microsoft.Toolkit.Uwp.UI.Controls/WrapPanel/WrapPanel.cs +++ b/Microsoft.Toolkit.Uwp.UI.Controls/WrapPanel/WrapPanel.cs @@ -184,7 +184,7 @@ protected override Size MeasureOverride(Size availableSize) } // update value with the last line - // if the the last loop is(parentMeasure.U > currentMeasure.U + lineMeasure.U) the total isn't calculated then calculate it + // if the last loop is(parentMeasure.U > currentMeasure.U + lineMeasure.U) the total isn't calculated then calculate it // if the last loop is (parentMeasure.U > currentMeasure.U) the currentMeasure isn't added to the total so add it here // for the last condition it is zeros so adding it will make no difference // this way is faster than an if condition in every loop for checking the last item diff --git a/Microsoft.Toolkit.Uwp.UI.Media/Brushes/RadialGradientBrush.cs b/Microsoft.Toolkit.Uwp.UI.Media/Brushes/RadialGradientBrush.cs index 0e2f693e72a..308f5030f4f 100644 --- a/Microsoft.Toolkit.Uwp.UI.Media/Brushes/RadialGradientBrush.cs +++ b/Microsoft.Toolkit.Uwp.UI.Media/Brushes/RadialGradientBrush.cs @@ -48,7 +48,7 @@ public RadialGradientBrush() /// /// Initializes a new instance of the class - /// with with two colors specified for GradientStops at + /// with two colors specified for GradientStops at /// offsets 0.0 and 1.0. /// /// The Color at offset 0.0. diff --git a/Microsoft.Toolkit.Uwp.UI.Media/Helpers/SurfaceLoader.Instance.cs b/Microsoft.Toolkit.Uwp.UI.Media/Helpers/SurfaceLoader.Instance.cs index 213a714e588..23852ab0159 100644 --- a/Microsoft.Toolkit.Uwp.UI.Media/Helpers/SurfaceLoader.Instance.cs +++ b/Microsoft.Toolkit.Uwp.UI.Media/Helpers/SurfaceLoader.Instance.cs @@ -77,7 +77,7 @@ public static SurfaceLoader GetInstance(Compositor compositor) private CanvasDevice canvasDevice; /// - /// The instance to determinde which GPU is handling the request. + /// The instance to determine which GPU is handling the request. /// private CompositionGraphicsDevice compositionDevice; diff --git a/Microsoft.Toolkit.Uwp.UI/Converters/DoubleToObjectConverter.cs b/Microsoft.Toolkit.Uwp.UI/Converters/DoubleToObjectConverter.cs index 0c8e82a9029..0d7a43b9852 100644 --- a/Microsoft.Toolkit.Uwp.UI/Converters/DoubleToObjectConverter.cs +++ b/Microsoft.Toolkit.Uwp.UI/Converters/DoubleToObjectConverter.cs @@ -11,7 +11,7 @@ namespace Microsoft.Toolkit.Uwp.UI.Converters /// /// This class converts a double value into an other object. /// Can be used to convert doubles to visibility, a couple of colors, couple of images, etc. - /// If GreaterThan and LessThan are both set, the logic looks for a value inbetween the two values. + /// If GreaterThan and LessThan are both set, the logic looks for a value between the two values. /// Otherwise the logic looks for the value to be GreaterThan or LessThan the specified value. /// The ConverterParameter can be used to invert the logic. /// diff --git a/Microsoft.Toolkit.Uwp.UI/Extensions/FrameworkElement/FrameworkElementExtensions.RelativeAncestor.cs b/Microsoft.Toolkit.Uwp.UI/Extensions/FrameworkElement/FrameworkElementExtensions.RelativeAncestor.cs index 1b04197e110..8da09f58150 100644 --- a/Microsoft.Toolkit.Uwp.UI/Extensions/FrameworkElement/FrameworkElementExtensions.RelativeAncestor.cs +++ b/Microsoft.Toolkit.Uwp.UI/Extensions/FrameworkElement/FrameworkElementExtensions.RelativeAncestor.cs @@ -41,7 +41,7 @@ public static void SetAncestor(DependencyObject obj, object value) /// /// Gets the Type of Ancestor to look for from this element. /// - /// Blah blah + /// Type of Ancestor to look for from this element public static Type GetAncestorType(DependencyObject obj) { return (Type)obj.GetValue(AncestorTypeProperty); diff --git a/Microsoft.Toolkit.Uwp.UI/Extensions/Hyperlink/HyperlinkExtensions.cs b/Microsoft.Toolkit.Uwp.UI/Extensions/Hyperlink/HyperlinkExtensions.cs index e4697182b60..751c902659a 100644 --- a/Microsoft.Toolkit.Uwp.UI/Extensions/Hyperlink/HyperlinkExtensions.cs +++ b/Microsoft.Toolkit.Uwp.UI/Extensions/Hyperlink/HyperlinkExtensions.cs @@ -25,28 +25,28 @@ public static class HyperlinkExtensions public static readonly DependencyProperty CommandParameterProperty = DependencyProperty.RegisterAttached("CommandParameter", typeof(object), typeof(Hyperlink), new PropertyMetadata(null)); /// - /// Gets the instance assocaited with the specified + /// Gets the instance associated with the specified /// /// The from which to get the associated instance - /// The instance associated with the the or null + /// The instance associated with the or null public static ICommand GetCommand(Hyperlink obj) => (ICommand)obj.GetValue(CommandProperty); /// - /// Sets the instance assocaited with the specified + /// Sets the instance associated with the specified /// /// The to associated the instance to /// The instance to bind to the public static void SetCommand(Hyperlink obj, ICommand value) => obj.SetValue(CommandProperty, value); /// - /// Gets the instance assocaited with the specified + /// Gets the instance associated with the specified /// /// The from which to get the associated value - /// The value associated with the the or null + /// The value associated with the or null public static object GetCommandParameter(Hyperlink obj) => obj.GetValue(CommandParameterProperty); /// - /// Sets the assocaited with the specified + /// Sets the associated with the specified /// /// The to associated the instance to /// The to set the to diff --git a/Microsoft.Toolkit.Uwp.UI/Extensions/ScrollViewer/ScrollViewerExtensions.MiddleClickScrolling.cs b/Microsoft.Toolkit.Uwp.UI/Extensions/ScrollViewer/ScrollViewerExtensions.MiddleClickScrolling.cs index ba287769b23..9b956d7510a 100644 --- a/Microsoft.Toolkit.Uwp.UI/Extensions/ScrollViewer/ScrollViewerExtensions.MiddleClickScrolling.cs +++ b/Microsoft.Toolkit.Uwp.UI/Extensions/ScrollViewer/ScrollViewerExtensions.MiddleClickScrolling.cs @@ -208,7 +208,7 @@ private static void ScrollViewer_PointerPressed(object sender, PointerRoutedEven private static void CoreWindow_PointerMoved(CoreWindow sender, PointerEventArgs args) { - // If condution that occures before scrolling begins + // If condition that occurs before scrolling begins if (_isPressed && !_isMoved) { PointerPoint pointerPoint = args.CurrentPoint; @@ -220,7 +220,7 @@ private static void CoreWindow_PointerMoved(CoreWindow sender, PointerEventArgs var offsetX = _currentPosition.X - _startPosition.X; var offsetY = _currentPosition.Y - _startPosition.Y; - // Settign _isMoved if pointer goes out of threshold value + // Setting _isMoved if pointer goes out of threshold value if (Math.Abs(offsetX) > _threshold || Math.Abs(offsetY) > _threshold) { _isMoved = true; diff --git a/Microsoft.Toolkit.Uwp.UI/Extensions/ScrollViewer/ScrollViewerExtensions.Properties.cs b/Microsoft.Toolkit.Uwp.UI/Extensions/ScrollViewer/ScrollViewerExtensions.Properties.cs index fcb8adf1373..2d4cd219f8b 100644 --- a/Microsoft.Toolkit.Uwp.UI/Extensions/ScrollViewer/ScrollViewerExtensions.Properties.cs +++ b/Microsoft.Toolkit.Uwp.UI/Extensions/ScrollViewer/ScrollViewerExtensions.Properties.cs @@ -69,7 +69,7 @@ public static void SetHorizontalScrollBarMargin(FrameworkElement obj, Thickness } /// - /// Get . Returns `true` if middle click scrolling is enabled else retuen `false` + /// Get . Returns `true` if middle click scrolling is enabled else return `false` /// /// The to get the associated `bool` /// The `bool` associated with the diff --git a/Microsoft.Toolkit.Uwp.UI/Extensions/SurfaceDialTextbox/SurfaceDialTextbox.cs b/Microsoft.Toolkit.Uwp.UI/Extensions/SurfaceDialTextbox/SurfaceDialTextbox.cs index a3aef24fa4c..185e17a9c87 100644 --- a/Microsoft.Toolkit.Uwp.UI/Extensions/SurfaceDialTextbox/SurfaceDialTextbox.cs +++ b/Microsoft.Toolkit.Uwp.UI/Extensions/SurfaceDialTextbox/SurfaceDialTextbox.cs @@ -46,7 +46,7 @@ public class SurfaceDialTextbox DependencyProperty.RegisterAttached("MinValue", typeof(double), typeof(SurfaceDialTextbox), new PropertyMetadata(-100d)); /// - /// Sets the maxium value the TextBox can have when modifying it using a Surface Dial. Default is 100.0 + /// Sets the maximum value the TextBox can have when modifying it using a Surface Dial. Default is 100.0 /// public static readonly DependencyProperty MaxValueProperty = DependencyProperty.RegisterAttached("MaxValue", typeof(double), typeof(SurfaceDialTextbox), new PropertyMetadata(100d)); @@ -58,7 +58,7 @@ public class SurfaceDialTextbox DependencyProperty.RegisterAttached("EnableTapToNextControl", typeof(bool), typeof(SurfaceDialTextbox), new PropertyMetadata(true)); /// - /// EnableMinMax limits the value in the textbox to your spesificed Min and Max values, see the other properties. + /// EnableMinMax limits the value in the textbox to your specified Min and Max values, see the other properties. /// public static readonly DependencyProperty EnableMinMaxValueProperty = DependencyProperty.RegisterAttached("EnableMinMaxValue", typeof(bool), typeof(SurfaceDialTextbox), new PropertyMetadata(false)); @@ -66,7 +66,7 @@ public class SurfaceDialTextbox /// /// Getter of the EnableMinMax property /// - /// The Depenency Object we are dealing with, like a TextBox. + /// The Dependency Object we are dealing with, like a TextBox. /// Return value of property public static bool GetEnableMinMaxValue(DependencyObject obj) { @@ -76,7 +76,7 @@ public static bool GetEnableMinMaxValue(DependencyObject obj) /// /// Setter of the EnableMinMax property /// - /// The Depenency Object we are dealing with, like a TextBox. + /// The Dependency Object we are dealing with, like a TextBox. /// The value to set the property to. public static void SetEnableMinMaxValue(DependencyObject obj, bool value) { @@ -86,7 +86,7 @@ public static void SetEnableMinMaxValue(DependencyObject obj, bool value) /// /// Getter of the TapToNext flag. /// - /// The Depenency Object we are dealing with, like a TextBox. + /// The Dependency Object we are dealing with, like a TextBox. /// Return value of property public static bool GetEnableTapToNextControl(DependencyObject obj) { @@ -96,7 +96,7 @@ public static bool GetEnableTapToNextControl(DependencyObject obj) /// /// Setter of the TapToNext flag. /// - /// The Depenency Object we are dealing with, like a TextBox. + /// The Dependency Object we are dealing with, like a TextBox. /// The value to set the property to. public static void SetEnableTapToNextControl(DependencyObject obj, bool value) { @@ -106,7 +106,7 @@ public static void SetEnableTapToNextControl(DependencyObject obj, bool value) /// /// Getter of the MaxValue /// - /// The Depenency Object we are dealing with, like a TextBox. + /// The Dependency Object we are dealing with, like a TextBox. /// Return value of property public static double GetMaxValue(DependencyObject obj) { @@ -116,7 +116,7 @@ public static double GetMaxValue(DependencyObject obj) /// /// Setter of the MaxValue /// - /// The Depenency Object we are dealing with, like a TextBox. + /// The Dependency Object we are dealing with, like a TextBox. /// The value to set the property to. public static void SetMaxValue(DependencyObject obj, double value) { @@ -126,7 +126,7 @@ public static void SetMaxValue(DependencyObject obj, double value) /// /// Getter of the MinValue /// - /// The Depenency Object we are dealing with, like a TextBox. + /// The Dependency Object we are dealing with, like a TextBox. /// Return value of property public static double GetMinValue(DependencyObject obj) { @@ -136,7 +136,7 @@ public static double GetMinValue(DependencyObject obj) /// /// Setter of the MinValue /// - /// The Depenency Object we are dealing with, like a TextBox. + /// The Dependency Object we are dealing with, like a TextBox. /// The value to set the property to. public static void SetMinValue(DependencyObject obj, double value) { @@ -146,7 +146,7 @@ public static void SetMinValue(DependencyObject obj, double value) /// /// Setter of the StepValue. /// - /// The Depenency Object we are dealing with, like a TextBox. + /// The Dependency Object we are dealing with, like a TextBox. /// Return value of property public static double GetStepValue(DependencyObject obj) { @@ -156,7 +156,7 @@ public static double GetStepValue(DependencyObject obj) /// /// Getter of the StepValue /// - /// The Depenency Object we are dealing with, like a TextBox. + /// The Dependency Object we are dealing with, like a TextBox. /// The value to set the property to. public static void SetStepValue(DependencyObject obj, double value) { @@ -166,7 +166,7 @@ public static void SetStepValue(DependencyObject obj, double value) /// /// Getter of the Icon /// - /// The Depenency Object we are dealing with, like a TextBox. + /// The Dependency Object we are dealing with, like a TextBox. /// Return value of property public static RadialControllerMenuKnownIcon GetIcon(DependencyObject obj) { @@ -176,7 +176,7 @@ public static RadialControllerMenuKnownIcon GetIcon(DependencyObject obj) /// /// Setter of the Icon /// - /// The Depenency Object we are dealing with, like a TextBox. + /// The Dependency Object we are dealing with, like a TextBox. /// The value to set the property to. public static void SetIcon(DependencyObject obj, RadialControllerMenuKnownIcon value) { @@ -186,7 +186,7 @@ public static void SetIcon(DependencyObject obj, RadialControllerMenuKnownIcon v /// /// Setter of the Haptic Feedback property /// - /// The Depenency Object we are dealing with, like a TextBox. + /// The Dependency Object we are dealing with, like a TextBox. /// Return value of property public static bool GetEnableHapticFeedback(DependencyObject obj) { @@ -196,7 +196,7 @@ public static bool GetEnableHapticFeedback(DependencyObject obj) /// /// Getter of the Haptic Feedback property /// - /// The Depenency Object we are dealing with, like a TextBox. + /// The Dependency Object we are dealing with, like a TextBox. /// The value to set the property to. public static void SetEnableHapticFeedback(DependencyObject obj, bool value) { @@ -206,7 +206,7 @@ public static void SetEnableHapticFeedback(DependencyObject obj, bool value) /// /// Getter of the Force Menu Item flag /// - /// The Depenency Object we are dealing with, like a TextBox. + /// The Dependency Object we are dealing with, like a TextBox. /// Return value of property public static bool GetForceMenuItem(DependencyObject obj) { @@ -216,7 +216,7 @@ public static bool GetForceMenuItem(DependencyObject obj) /// /// Setter of the Force Menu Item flag /// - /// The Depenency Object we are dealing with, like a TextBox. + /// The Dependency Object we are dealing with, like a TextBox. /// The value to set the property to. public static void SetForceMenuItem(DependencyObject obj, bool value) { @@ -255,13 +255,13 @@ public static bool IsSupported private static RadialControllerMenuItem _stepTextMenuItem; /// - /// The textbox itself needed to refernece the current TextBox that is being modified + /// The textbox itself needed to reference the current TextBox that is being modified /// private static TextBox _textBox; /// /// Gets or sets the controller for the Surface Dial. The RadialController can be set from your app logic in case you use Surface Dial in other custom cases than on a TextBox. - /// This helper class will do everything for you, but if you want to control the Menu Items and/or wish to use the same Surface Dial insta + /// This helper class will do everything for you, but if you want to control the Menu Items and/or wish to use the same Surface Dial instance /// This is the property for the static controller so you can access it if needed. /// public static RadialController Controller @@ -322,7 +322,7 @@ private static void Controller_RotationChanged(RadialController sender, RadialCo /// /// Sets up the events needed for the current TextBox so it can trigger on GotFocus and LostFocus /// - /// The Depenency Object we are dealing with, like a TextBox. + /// The Dependency Object we are dealing with, like a TextBox. /// The arguments of the changed event. private static void StepValueChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { diff --git a/Microsoft.Toolkit.Uwp.UI/Extensions/TextBoxMask/TextBoxMask.cs b/Microsoft.Toolkit.Uwp.UI/Extensions/TextBoxMask/TextBoxMask.cs index aa06c2fd5da..7a27722de68 100644 --- a/Microsoft.Toolkit.Uwp.UI/Extensions/TextBoxMask/TextBoxMask.cs +++ b/Microsoft.Toolkit.Uwp.UI/Extensions/TextBoxMask/TextBoxMask.cs @@ -45,7 +45,7 @@ private static void Textbox_Loaded(object sender, RoutedEventArgs e) { var textbox = (TextBox)sender; - // incase no value is provided us it as normal textbox + // in case no value is provided us it as normal textbox var mask = textbox.GetValue(MaskProperty) as string; if (string.IsNullOrWhiteSpace(mask)) { @@ -340,7 +340,7 @@ private static void Textbox_TextChanging(TextBox textbox, TextBoxTextChangingEve if (!isDeleteOrBackspace) { - // Case change happended due to user input + // Case change happened due to user input var selectedChar = textbox.SelectionStart > 0 ? textbox.Text[textbox.SelectionStart - 1] : placeHolder; diff --git a/Microsoft.Toolkit.Uwp.UI/Triggers/UserHandPreferenceStateTrigger.cs b/Microsoft.Toolkit.Uwp.UI/Triggers/UserHandPreferenceStateTrigger.cs index b15c22c7d7b..91fd46ca4c5 100644 --- a/Microsoft.Toolkit.Uwp.UI/Triggers/UserHandPreferenceStateTrigger.cs +++ b/Microsoft.Toolkit.Uwp.UI/Triggers/UserHandPreferenceStateTrigger.cs @@ -8,7 +8,7 @@ namespace Microsoft.Toolkit.Uwp.UI.Triggers { /// - /// Trigger for switching UI based on whether the user favours their left or right hand. + /// Trigger for switching UI based on whether the user favors their left or right hand. /// public class UserHandPreferenceStateTrigger : StateTriggerBase { diff --git a/Microsoft.Toolkit.Uwp/Deferred/DeferredCancelEventArgs.cs b/Microsoft.Toolkit.Uwp/Deferred/DeferredCancelEventArgs.cs index d5036335c2a..39fc89aeca2 100644 --- a/Microsoft.Toolkit.Uwp/Deferred/DeferredCancelEventArgs.cs +++ b/Microsoft.Toolkit.Uwp/Deferred/DeferredCancelEventArgs.cs @@ -5,12 +5,12 @@ namespace Microsoft.Toolkit.Uwp.Deferred { /// - /// which can also be Cancelled. + /// which can also be Canceled. /// public class DeferredCancelEventArgs : DeferredEventArgs { /// - /// Gets or sets a value indicating whether the event should be cancelled. + /// Gets or sets a value indicating whether the event should be canceled. /// public bool Cancel { get; set; } } diff --git a/Microsoft.Toolkit.Uwp/Helpers/CameraHelper/CameraHelper.cs b/Microsoft.Toolkit.Uwp/Helpers/CameraHelper/CameraHelper.cs index 4043fe1728c..142000987ec 100644 --- a/Microsoft.Toolkit.Uwp/Helpers/CameraHelper/CameraHelper.cs +++ b/Microsoft.Toolkit.Uwp/Helpers/CameraHelper/CameraHelper.cs @@ -222,7 +222,7 @@ private async Task InitializeMediaCaptureAsync() return CameraHelperResult.NoFrameSourceAvailable; } - // Get only formats of a certain framerate and compatible subtype for previewing, order them by resolution + // Get only formats of a certain frame-rate and compatible subtype for previewing, order them by resolution FrameFormatsAvailable = PreviewFrameSource.SupportedFormats.Where(format => format.FrameRate.Numerator / format.FrameRate.Denominator >= 15 // fps && (string.Compare(format.Subtype, MediaEncodingSubtypes.Nv12, true) == 0 diff --git a/Microsoft.Toolkit.Uwp/Helpers/DeepLinkParser/DeepLinkParser.cs b/Microsoft.Toolkit.Uwp/Helpers/DeepLinkParser/DeepLinkParser.cs index 5c858aeac00..1c7dbc00b5d 100644 --- a/Microsoft.Toolkit.Uwp/Helpers/DeepLinkParser/DeepLinkParser.cs +++ b/Microsoft.Toolkit.Uwp/Helpers/DeepLinkParser/DeepLinkParser.cs @@ -178,7 +178,7 @@ protected virtual void SetRoot(Uri validatedUri) int queryStartPosition = origString.IndexOf('?'); if (queryStartPosition == -1) { - // No querystring on the URI + // No query string on the URI Root = origString; } else diff --git a/Microsoft.Toolkit/Helpers/NotifyTaskCompletion.cs b/Microsoft.Toolkit/Helpers/NotifyTaskCompletion.cs index 0f9fb1f0610..4a158d80e05 100644 --- a/Microsoft.Toolkit/Helpers/NotifyTaskCompletion.cs +++ b/Microsoft.Toolkit/Helpers/NotifyTaskCompletion.cs @@ -109,7 +109,7 @@ public TResult Result public bool IsSuccessfullyCompleted => Task.Status == TaskStatus.RanToCompletion; /// - /// Gets a value indicating whether the task was cancelled. + /// Gets a value indicating whether the task was canceled. /// public bool IsCanceled => Task.IsCanceled; diff --git a/build/build.ps1 b/build/build.ps1 index 7a24017f8cc..deed10851c4 100644 --- a/build/build.ps1 +++ b/build/build.ps1 @@ -169,7 +169,7 @@ if(-Not $SkipToolPackageRestore.IsPresent) { $NuGetOutput = Invoke-Expression "&`"$NUGET_EXE`" install -ExcludeVersion -OutputDirectory `"$TOOLS_DIR`"" if ($LASTEXITCODE -ne 0) { - Throw "An error occured while restoring NuGet tools." + Throw "An error occurred while restoring NuGet tools." } else { @@ -207,7 +207,7 @@ if(-Not $SkipToolPackageRestore.IsPresent) { $NuGetOutput = Invoke-Expression "&`"$NUGET_EXE`" install -ExcludeVersion -OutputDirectory `"$MODULES_DIR`"" if ($LASTEXITCODE -ne 0) { - Throw "An error occured while restoring NuGet modules." + Throw "An error occurred while restoring NuGet modules." } else { @@ -224,7 +224,7 @@ $Script = "$path/build.cake" Write-Host "Bootstrapping Cake..." Invoke-Expression "& `"$CAKE_EXE`" `"$Script`" --bootstrap" if ($LASTEXITCODE -ne 0) { - throw "An error occured while bootstrapping Cake." + throw "An error occurred while bootstrapping Cake." } Write-Host "Running build script..." diff --git a/contributing.md b/contributing.md index 4156b2effc5..b18bcaab6c9 100644 --- a/contributing.md +++ b/contributing.md @@ -47,7 +47,7 @@ Once merged, you can get a pre-release package of the toolkit by adding this ([N ### Adding Documentation -Documentation is **required** when adding, removing, or updating a control or an API. To update the documentation, you must submit a seperate Pull Request in the [documentation repository](https://github.com/MicrosoftDocs/WindowsCommunityToolkitDocs) (use the *master* branch). **Both Pull Requests (code and docs) must be approved by the core team before either one is merged.** +Documentation is **required** when adding, removing, or updating a control or an API. To update the documentation, you must submit a separate Pull Request in the [documentation repository](https://github.com/MicrosoftDocs/WindowsCommunityToolkitDocs) (use the *master* branch). **Both Pull Requests (code and docs) must be approved by the core team before either one is merged.** Make sure to update both Pull Requests with a link to each other. @@ -82,7 +82,7 @@ This is to help as part of our effort to build an accessible toolkit (starting w * DO declare static dependency properties at the top of their file. * DO NOT seal controls. * DO use extension methods over static methods where possible. -* DO NOT return true or false to give sucess status. Throw exceptions if there was a failure. +* DO NOT return true or false to give success status. Throw exceptions if there was a failure. * DO use verbs like GET. * DO NOT use verbs that are not already used like fetch. From e6733d56e542e27ada7e8a3ac21a691b2a54a999 Mon Sep 17 00:00:00 2001 From: Herrick Spencer Date: Fri, 14 Aug 2020 01:22:38 -0700 Subject: [PATCH 386/595] Return param to 'current' per PR comment --- .../Adaptive/BaseTextHelper.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Microsoft.Toolkit.Uwp.Notifications/Adaptive/BaseTextHelper.cs b/Microsoft.Toolkit.Uwp.Notifications/Adaptive/BaseTextHelper.cs index 8cf643129b1..0cbb3f154b9 100644 --- a/Microsoft.Toolkit.Uwp.Notifications/Adaptive/BaseTextHelper.cs +++ b/Microsoft.Toolkit.Uwp.Notifications/Adaptive/BaseTextHelper.cs @@ -8,12 +8,12 @@ namespace Microsoft.Toolkit.Uwp.Notifications { internal class BaseTextHelper { - internal static Element_AdaptiveText CreateBaseElement(IBaseText baseText) + internal static Element_AdaptiveText CreateBaseElement(IBaseText current) { return new Element_AdaptiveText() { - Text = baseText.Text, - Lang = baseText.Language + Text = current.Text, + Lang = current.Language }; } } From 6c89c5ba0230ae12636f9735d9e9a2a772e545a7 Mon Sep 17 00:00:00 2001 From: Herrick Spencer Date: Mon, 17 Aug 2020 10:39:07 -0700 Subject: [PATCH 387/595] Some capitalizations needed --- .../BluetoothLEHelper/GattNativeUuid.cs | 2 +- .../Properties/AssemblyInfo.cs | 2 +- .../Properties/AssemblyInfo.cs | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Microsoft.Toolkit.Uwp.Connectivity/BluetoothLEHelper/GattNativeUuid.cs b/Microsoft.Toolkit.Uwp.Connectivity/BluetoothLEHelper/GattNativeUuid.cs index 6bc6be1d8e5..c651696d7bc 100644 --- a/Microsoft.Toolkit.Uwp.Connectivity/BluetoothLEHelper/GattNativeUuid.cs +++ b/Microsoft.Toolkit.Uwp.Connectivity/BluetoothLEHelper/GattNativeUuid.cs @@ -633,7 +633,7 @@ public enum GattNativeUuid : ushort PositionQuality = 0x2A69, /// - /// The scientific temperature in celsius + /// The scientific temperature in Celsius /// ScientificTemperatureInCelsius = 0x2A3C, diff --git a/Microsoft.Toolkit.Uwp.UI.Controls.DataGrid.Design/Properties/AssemblyInfo.cs b/Microsoft.Toolkit.Uwp.UI.Controls.DataGrid.Design/Properties/AssemblyInfo.cs index 43fa7909370..1a907af922c 100644 --- a/Microsoft.Toolkit.Uwp.UI.Controls.DataGrid.Design/Properties/AssemblyInfo.cs +++ b/Microsoft.Toolkit.Uwp.UI.Controls.DataGrid.Design/Properties/AssemblyInfo.cs @@ -27,7 +27,7 @@ //In order to begin building localizable applications, set //CultureYouAreCodingWith in your .csproj file -//inside a . For example, if you are using US english +//inside a . For example, if you are using US English //in your source files, set the to en-US. Then uncomment //the NeutralResourceLanguage attribute below. Update the "en-US" in //the line below to match the UICulture setting in the project file. diff --git a/Microsoft.Toolkit.Uwp.UI.Controls.Design/Properties/AssemblyInfo.cs b/Microsoft.Toolkit.Uwp.UI.Controls.Design/Properties/AssemblyInfo.cs index b1b1d18d1a0..c4763b5af16 100644 --- a/Microsoft.Toolkit.Uwp.UI.Controls.Design/Properties/AssemblyInfo.cs +++ b/Microsoft.Toolkit.Uwp.UI.Controls.Design/Properties/AssemblyInfo.cs @@ -27,7 +27,7 @@ //In order to begin building localizable applications, set //CultureYouAreCodingWith in your .csproj file -//inside a . For example, if you are using US english +//inside a . For example, if you are using US English //in your source files, set the to en-US. Then uncomment //the NeutralResourceLanguage attribute below. Update the "en-US" in //the line below to match the UICulture setting in the project file. From 7921fa74dac63109fa11f0ca862f03347607c095 Mon Sep 17 00:00:00 2001 From: Alexandre Zollinger Chohfi Date: Mon, 17 Aug 2020 11:55:03 -0700 Subject: [PATCH 388/595] Removed deprecated FacebookService, and the Microsoft.Toolkit.Uwp.Services project. --- .../Microsoft.Toolkit.Uwp.SampleApp.csproj | 2 - .../Facebook Service/FacebookCode.bind | 23 - .../Facebook Service/FacebookLogo.png | Bin 1301 -> 0 bytes .../SamplePages/Facebook Service/icon.png | Bin 871 -> 0 bytes .../SamplePages/samples.json | 10 - .../Microsoft.Toolkit.Uwp.Services.csproj | 19 - ...ft.Toolkit.Uwp.Services.csproj.DotSettings | 4 - .../Microsoft.Toolkit.Uwp.Services.rd.xml | 5 - .../Services/Facebook/FacebookAlbum.cs | 42 -- .../Services/Facebook/FacebookDataConfig.cs | 32 -- .../Services/Facebook/FacebookDataHost.cs | 18 - .../Services/Facebook/FacebookOAuthTokens.cs | 22 - .../Services/Facebook/FacebookPermissions.cs | 120 ---- .../Services/Facebook/FacebookPhoto.cs | 55 -- .../Services/Facebook/FacebookPicture.cs | 32 -- .../Services/Facebook/FacebookPictureData.cs | 17 - .../Facebook/FacebookPlatformImageSource.cs | 32 -- .../Services/Facebook/FacebookPost.cs | 44 -- .../Facebook/FacebookRequestSource.cs | 121 ---- .../Services/Facebook/FacebookService.cs | 515 ------------------ Windows Community Toolkit.sln | 34 +- readme.md | 3 +- 22 files changed, 4 insertions(+), 1146 deletions(-) delete mode 100644 Microsoft.Toolkit.Uwp.SampleApp/SamplePages/Facebook Service/FacebookCode.bind delete mode 100644 Microsoft.Toolkit.Uwp.SampleApp/SamplePages/Facebook Service/FacebookLogo.png delete mode 100644 Microsoft.Toolkit.Uwp.SampleApp/SamplePages/Facebook Service/icon.png delete mode 100644 Microsoft.Toolkit.Uwp.Services/Microsoft.Toolkit.Uwp.Services.csproj delete mode 100644 Microsoft.Toolkit.Uwp.Services/Microsoft.Toolkit.Uwp.Services.csproj.DotSettings delete mode 100644 Microsoft.Toolkit.Uwp.Services/Properties/Microsoft.Toolkit.Uwp.Services.rd.xml delete mode 100644 Microsoft.Toolkit.Uwp.Services/Services/Facebook/FacebookAlbum.cs delete mode 100644 Microsoft.Toolkit.Uwp.Services/Services/Facebook/FacebookDataConfig.cs delete mode 100644 Microsoft.Toolkit.Uwp.Services/Services/Facebook/FacebookDataHost.cs delete mode 100644 Microsoft.Toolkit.Uwp.Services/Services/Facebook/FacebookOAuthTokens.cs delete mode 100644 Microsoft.Toolkit.Uwp.Services/Services/Facebook/FacebookPermissions.cs delete mode 100644 Microsoft.Toolkit.Uwp.Services/Services/Facebook/FacebookPhoto.cs delete mode 100644 Microsoft.Toolkit.Uwp.Services/Services/Facebook/FacebookPicture.cs delete mode 100644 Microsoft.Toolkit.Uwp.Services/Services/Facebook/FacebookPictureData.cs delete mode 100644 Microsoft.Toolkit.Uwp.Services/Services/Facebook/FacebookPlatformImageSource.cs delete mode 100644 Microsoft.Toolkit.Uwp.Services/Services/Facebook/FacebookPost.cs delete mode 100644 Microsoft.Toolkit.Uwp.Services/Services/Facebook/FacebookRequestSource.cs delete mode 100644 Microsoft.Toolkit.Uwp.Services/Services/Facebook/FacebookService.cs diff --git a/Microsoft.Toolkit.Uwp.SampleApp/Microsoft.Toolkit.Uwp.SampleApp.csproj b/Microsoft.Toolkit.Uwp.SampleApp/Microsoft.Toolkit.Uwp.SampleApp.csproj index 4291bcd25b3..18981c05b81 100644 --- a/Microsoft.Toolkit.Uwp.SampleApp/Microsoft.Toolkit.Uwp.SampleApp.csproj +++ b/Microsoft.Toolkit.Uwp.SampleApp/Microsoft.Toolkit.Uwp.SampleApp.csproj @@ -300,7 +300,6 @@ - @@ -365,7 +364,6 @@ - diff --git a/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/Facebook Service/FacebookCode.bind b/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/Facebook Service/FacebookCode.bind deleted file mode 100644 index 43f08e37c4a..00000000000 --- a/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/Facebook Service/FacebookCode.bind +++ /dev/null @@ -1,23 +0,0 @@ -// Initialize service -FacebookService.Instance.Initialize(AppIDText.Text); - -// Login to Facebook -if (!await FacebookService.Instance.LoginAsync()) -{ - return; -} - -// Get user's feed -ListView.ItemsSource = await FacebookService.Instance.RequestAsync(FacebookDataConfig.MyFeed, 50); - -// Get current user profile picture -ProfileImage.DataContext = await FacebookService.Instance.GetUserPictureInfoAsync(); - -// Post a message on your wall using Facebook Dialog -await FacebookService.Instance.PostToFeedWithDialogAsync(TitleText.Text, DescriptionText.Text, UrlText.Text); - -// Get current user's photo albums -await FacebookService.Instance.GetUserAlbumsAsync(); - -// Get current user's photos by album Id -await FacebookService.Instance.GetUserPhotosByAlbumIdAsync(addedItem.Id); \ No newline at end of file diff --git a/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/Facebook Service/FacebookLogo.png b/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/Facebook Service/FacebookLogo.png deleted file mode 100644 index 0222a8325a3ec3c2c1716a27702c80845aaf048e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1301 zcmeAS@N?(olHy`uVBq!ia0y~yU{nCI4{)#n$t4^1Z39v)#ZI0f96(URkAJ?Y_KV}wo_RpHwt)bezFtBAtYxjY(3VEvk8L#IpNzE-W6ma~( z-=esM%QNPR(j5`b!vP(uLrR3Wj&nCpU)Qmg>&$ZNif482=KQW&%XY4R=l#uP(i_Mh;t(6%QZZc zvEQ|SSy|p%U+H6)7O&}gdVBAz{W~I#KRdV7eW7BRgKpuU7yo9>ym#-`kC4#ZTLxjq zmv6uRaq;iVz>E%ttw*k$Tl-RwEp5uVt#4UYsNUN1`RD#XtLxi@(E{gKbH|!h_vY>{ zP5l`5w=Q?(zdbu+)c5~;*=X9%?{oE8@A>f1tH0mpto(kjuJl5(kdniiE}-hI1uxy^ zS{cXN>&H~TxwU@Z-2d<2E4P2+y1*f*AhrF<%WE$;bANATm2+#zxDvEHdj8x$4?B0u zttb+n)i&2|w}Ral--e7c`upR`ZyxR95a_imb*qVxWLzW|E}p;hOVZIhEz1PM|L?f^ z{npJ|>sQU?+pj)3@`L%fn%VvfPa=3MK?=owb6_vA4r2rLJ*JPcV6ODF6EW z9nU^AYJPlZ$K;~?Yv=#X)z@D$&uH1|f+aoJba=#1v%351_qn^vf61F$-`(}``lsaU zK7KvdZxo-myZU}Z8HYga&v(bY`D^FC@~Zju^t*q|=dDo%ZYE@!y;m z+~xN@?d`a8Tvb9q*}u6Mta^R>5@6_4)S*pXkv+HsXSqwI9``RUY9>tmpoXQJy@7LS(rUqmpxjSJzAJOS(rUpm_1sTJzAKVsL9IG ziApG#(+ zOlF`=XP{7OqEKt1QEQ`9Y@<|eq*ZRES8%3UbEsQ$s9klbUUsQtdaPu7t!RF)X@9S4 zfv{|Xv2TR3Z-ujSh_!%|yMdIvf|b34mA!?RzKWc|jh@4jqQ#l0$(pIjoU6;8t<0jZ z&ZDu;q_NMWvd^Wn&#AW2thm#yxzo15*t^Bs!pq;o%-_Y$;Kk11%F^S^)a1_A<Du7y+TiTqh1LF?)2>L_3ZEU?(z2T@%Hlc`1JMp_4fMr z`1|?#{QCO*`uqI*{Qdm={rvs@{r&#_{{H{}|93uRS^xk57<5ujQve4E6&o8MZE(WF z%goT#<>lq&<@NUV_ph#GHvj+urAb6VRCr!u7*1dmjDk_X#K9vfMnfRN&CbYx1+eh} z*sYJAgrXn}!$LG*0bFQ20*Z>g_g=9V6h*Nsc99~?|1ZYGn9DhmxZuvWnK!eW#k2jC z?RhFq*XdOZd7TFARxG&WE`UouW zp9w6mzyb>_u)qQfEU>@=3*5j1`wY022d*|?7Z2<*V26hT_Bbkxs=W5YU*lzFzyg>! xxcCJ~1%kq;Ku7>5;OAs#WEe(Z6pVsV001B)u}=Van{faD002ovPDHLkV1n4bx8VQ) diff --git a/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/samples.json b/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/samples.json index 1856644c3df..9e8e27cba22 100644 --- a/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/samples.json +++ b/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/samples.json @@ -621,16 +621,6 @@ "Name": "Services", "Icon": "Icons/Services.png", "Samples": [ - { - "Name": "Facebook Service", - "About": "The Facebook Service allows you to retrieve or publish data to Facebook graph.", - "CodeUrl": "https://github.com/windows-toolkit/WindowsCommunityToolkit/tree/master/Microsoft.Toolkit.Uwp.Services/Services/Facebook", - "CodeFile": "FacebookCode.bind", - "Icon": "/SamplePages/Facebook Service/FacebookLogo.png", - "BadgeUpdateVersionRequired": "DEPRECATED", - "DeprecatedWarning": "The underlying library, winsdkfb, which the FacebookService relies on is not currently maintained.", - "DocumentationUrl": "https://raw.githubusercontent.com/MicrosoftDocs/WindowsCommunityToolkitDocs/master/docs/services/Facebook.md" - }, { "Name": "Microsoft Graph Service", "About": "These providers help you easily authenticate and connect to the Microsoft Graph.", diff --git a/Microsoft.Toolkit.Uwp.Services/Microsoft.Toolkit.Uwp.Services.csproj b/Microsoft.Toolkit.Uwp.Services/Microsoft.Toolkit.Uwp.Services.csproj deleted file mode 100644 index b8407c5f573..00000000000 --- a/Microsoft.Toolkit.Uwp.Services/Microsoft.Toolkit.Uwp.Services.csproj +++ /dev/null @@ -1,19 +0,0 @@ - - - - uap10.0.16299 - Windows Community Toolkit Services - This library enables access to Facebook. It is part of the Windows Community Toolkit. - UWP Toolkit Windows OAuth Facebook - - - - - - - - - - - - diff --git a/Microsoft.Toolkit.Uwp.Services/Microsoft.Toolkit.Uwp.Services.csproj.DotSettings b/Microsoft.Toolkit.Uwp.Services/Microsoft.Toolkit.Uwp.Services.csproj.DotSettings deleted file mode 100644 index 32e2a35dca4..00000000000 --- a/Microsoft.Toolkit.Uwp.Services/Microsoft.Toolkit.Uwp.Services.csproj.DotSettings +++ /dev/null @@ -1,4 +0,0 @@ - - True - True - True \ No newline at end of file diff --git a/Microsoft.Toolkit.Uwp.Services/Properties/Microsoft.Toolkit.Uwp.Services.rd.xml b/Microsoft.Toolkit.Uwp.Services/Properties/Microsoft.Toolkit.Uwp.Services.rd.xml deleted file mode 100644 index 154280e96c9..00000000000 --- a/Microsoft.Toolkit.Uwp.Services/Properties/Microsoft.Toolkit.Uwp.Services.rd.xml +++ /dev/null @@ -1,5 +0,0 @@ - - - - - diff --git a/Microsoft.Toolkit.Uwp.Services/Services/Facebook/FacebookAlbum.cs b/Microsoft.Toolkit.Uwp.Services/Services/Facebook/FacebookAlbum.cs deleted file mode 100644 index 2c61e42396f..00000000000 --- a/Microsoft.Toolkit.Uwp.Services/Services/Facebook/FacebookAlbum.cs +++ /dev/null @@ -1,42 +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. - -namespace Microsoft.Toolkit.Uwp.Services.Facebook -{ - /// - /// Strongly types Facebook Album object. Partial for extending properties. - /// - public partial class FacebookAlbum - { - /// - /// Gets a string description of the strongly typed properties in this model. - /// - public static string Fields => "id, name, description, cover_photo, picture"; - - /// - /// Gets or sets id property. - /// - public string Id { get; set; } - - /// - /// Gets or sets name property. - /// - public string Name { get; set; } - - /// - /// Gets or sets description property. - /// - public string Description { get; set; } - - /// - /// Gets or sets cover_photo property. - /// - public FacebookPhoto Cover_Photo { get; set; } - - /// - /// Gets or sets picture property. - /// - public FacebookPictureData Picture { get; set; } - } -} diff --git a/Microsoft.Toolkit.Uwp.Services/Services/Facebook/FacebookDataConfig.cs b/Microsoft.Toolkit.Uwp.Services/Services/Facebook/FacebookDataConfig.cs deleted file mode 100644 index 06dbd8a9ec6..00000000000 --- a/Microsoft.Toolkit.Uwp.Services/Services/Facebook/FacebookDataConfig.cs +++ /dev/null @@ -1,32 +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. - -namespace Microsoft.Toolkit.Uwp.Services.Facebook -{ - /// - /// Configuration object for specifying richer query information. - /// - public class FacebookDataConfig - { - /// - /// Gets a predefined config to get user feed. The feed of posts (including status updates) and links published by this person, or by others on this person's profile - /// - public static FacebookDataConfig MyFeed => new FacebookDataConfig { Query = "/me/feed" }; - - /// - /// Gets a predefined config to show only the posts that were published by this person - /// - public static FacebookDataConfig MyPosts => new FacebookDataConfig { Query = "/me/posts" }; - - /// - /// Gets a predefined config to show only the posts that this person was tagged in - /// - public static FacebookDataConfig MyTagged => new FacebookDataConfig { Query = "/me/tagged" }; - - /// - /// Gets or sets the query string for filtering service results. - /// - public string Query { get; set; } - } -} diff --git a/Microsoft.Toolkit.Uwp.Services/Services/Facebook/FacebookDataHost.cs b/Microsoft.Toolkit.Uwp.Services/Services/Facebook/FacebookDataHost.cs deleted file mode 100644 index e2a292487e2..00000000000 --- a/Microsoft.Toolkit.Uwp.Services/Services/Facebook/FacebookDataHost.cs +++ /dev/null @@ -1,18 +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. - -namespace Microsoft.Toolkit.Uwp.Services.Facebook -{ - /// - /// Class used to store JSON data response from Facebook - /// - /// Type of the inner data - internal class FacebookDataHost - { - /// - /// Gets or sets internal data. - /// - public T Data { get; set; } - } -} diff --git a/Microsoft.Toolkit.Uwp.Services/Services/Facebook/FacebookOAuthTokens.cs b/Microsoft.Toolkit.Uwp.Services/Services/Facebook/FacebookOAuthTokens.cs deleted file mode 100644 index b3db6536110..00000000000 --- a/Microsoft.Toolkit.Uwp.Services/Services/Facebook/FacebookOAuthTokens.cs +++ /dev/null @@ -1,22 +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. - -namespace Microsoft.Toolkit.Uwp.Services.Facebook -{ - /// - /// Facebook OAuth tokens. - /// - public class FacebookOAuthTokens - { - /// - /// Gets or sets facebook AppId. - /// - public string AppId { get; set; } - - /// - /// Gets or sets Windows Store ID. - /// - public string WindowsStoreId { get; set; } - } -} diff --git a/Microsoft.Toolkit.Uwp.Services/Services/Facebook/FacebookPermissions.cs b/Microsoft.Toolkit.Uwp.Services/Services/Facebook/FacebookPermissions.cs deleted file mode 100644 index 907a4135fa1..00000000000 --- a/Microsoft.Toolkit.Uwp.Services/Services/Facebook/FacebookPermissions.cs +++ /dev/null @@ -1,120 +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; - -namespace Microsoft.Toolkit.Uwp.Services.Facebook -{ - /// - /// List of user related data permissions - /// - [Flags] - public enum FacebookPermissions - { - /// - /// Public profile - /// - PublicProfile = 1, - - /// - /// Email - /// - Email = 2, - - /// - /// Publish actions - /// - PublishActions = 4, - - /// - /// About me - /// - UserAboutMe = 8, - - /// - /// Birthday - /// - UserBirthday = 16, - - /// - /// Education history - /// - UserEducationHistory = 32, - - /// - /// Friends - /// - UserFriends = 64, - - /// - /// Games activity - /// - UserGamesActivity = 128, - - /// - /// Hometown - /// - UserHometown = 256, - - /// - /// Likes - /// - UserLikes = 512, - - /// - /// Location - /// - UserLocation = 1024, - - /// - /// Photos - /// - UserPhotos = 2048, - - /// - /// Posts - /// - UserPosts = 4096, - - /// - /// Relationship details - /// - UserRelationshipDetails = 8192, - - /// - /// Relationships - /// - UserRelationships = 16384, - - /// - /// Religion and politics - /// - UserReligionPolitics = 32768, - - /// - /// Status - /// - UserStatus = 65536, - - /// - /// Tagged places - /// - UserTaggedPlaces = 131072, - - /// - /// Videos - /// - UserVideos = 262144, - - /// - /// Website - /// - UserWebsite = 524288, - - /// - /// WorkHistory - /// - UserWorkHistory = 1048576 - } -} diff --git a/Microsoft.Toolkit.Uwp.Services/Services/Facebook/FacebookPhoto.cs b/Microsoft.Toolkit.Uwp.Services/Services/Facebook/FacebookPhoto.cs deleted file mode 100644 index 0d1a0c9870d..00000000000 --- a/Microsoft.Toolkit.Uwp.Services/Services/Facebook/FacebookPhoto.cs +++ /dev/null @@ -1,55 +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; - -namespace Microsoft.Toolkit.Uwp.Services.Facebook -{ - /// - /// Strongly types Facebook Photo object. Partial for extending properties. - /// - public partial class FacebookPhoto - { - /// - /// Gets a string description of the strongly typed properties in this model. - /// - public static string Fields => "id, album, link, created_time, name, images, picture"; - - /// - /// Gets or sets id property. - /// - public string Id { get; set; } - - /// - /// Gets or sets album property. - /// - public FacebookAlbum Album { get; set; } - - /// - /// Gets or sets a link to the entity instance. - /// - public string Link { get; set; } - - /// - /// Gets or sets time the entity instance was created. - /// - public DateTime Created_Time { get; set; } - - /// - /// Gets or sets name property. - /// - public string Name { get; set; } - - /// - /// Gets or sets images property. - /// - public List Images { get; set; } - - /// - /// Gets or sets picture property. - /// - public string Picture { get; set; } - } -} diff --git a/Microsoft.Toolkit.Uwp.Services/Services/Facebook/FacebookPicture.cs b/Microsoft.Toolkit.Uwp.Services/Services/Facebook/FacebookPicture.cs deleted file mode 100644 index ccd38a0f874..00000000000 --- a/Microsoft.Toolkit.Uwp.Services/Services/Facebook/FacebookPicture.cs +++ /dev/null @@ -1,32 +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. - -namespace Microsoft.Toolkit.Uwp.Services.Facebook -{ - /// - /// Strongly typed object for presenting picture data returned from service provider. - /// - public class FacebookPicture - { - /// - /// Gets or sets a value indicating whether the picture is a silhouette or not. - /// - public bool Is_Silhouette { get; set; } - - /// - /// Gets or sets an url to the picture. - /// - public string Url { get; set; } - - /// - /// Gets or sets the ID of the picture. - /// - public string Id { get; set; } - - /// - /// Gets or sets the url of the page with the picture. - /// - public string Link { get; set; } - } -} diff --git a/Microsoft.Toolkit.Uwp.Services/Services/Facebook/FacebookPictureData.cs b/Microsoft.Toolkit.Uwp.Services/Services/Facebook/FacebookPictureData.cs deleted file mode 100644 index fb5398acfa1..00000000000 --- a/Microsoft.Toolkit.Uwp.Services/Services/Facebook/FacebookPictureData.cs +++ /dev/null @@ -1,17 +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. - -namespace Microsoft.Toolkit.Uwp.Services.Facebook -{ - /// - /// Strongly types Facebook Picture object. Partial for extending properties. - /// - public partial class FacebookPictureData - { - /// - /// Gets or sets data property. - /// - public FacebookPicture Data { get; set; } - } -} diff --git a/Microsoft.Toolkit.Uwp.Services/Services/Facebook/FacebookPlatformImageSource.cs b/Microsoft.Toolkit.Uwp.Services/Services/Facebook/FacebookPlatformImageSource.cs deleted file mode 100644 index 61e8077edec..00000000000 --- a/Microsoft.Toolkit.Uwp.Services/Services/Facebook/FacebookPlatformImageSource.cs +++ /dev/null @@ -1,32 +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. - -namespace Microsoft.Toolkit.Uwp.Services.Facebook -{ - /// - /// Strongly types Facebook PlatformImageSource object. Partial for extending properties. - /// - public partial class FacebookPlatformImageSource - { - /// - /// Gets a string description of the strongly typed properties in this model. - /// - public static string Fields => "height, source, width"; - - /// - /// Gets or sets height property. - /// - public string Height { get; set; } - - /// - /// Gets or sets source property. - /// - public string Source { get; set; } - - /// - /// Gets or sets width property. - /// - public string Width { get; set; } - } -} diff --git a/Microsoft.Toolkit.Uwp.Services/Services/Facebook/FacebookPost.cs b/Microsoft.Toolkit.Uwp.Services/Services/Facebook/FacebookPost.cs deleted file mode 100644 index a452db85a94..00000000000 --- a/Microsoft.Toolkit.Uwp.Services/Services/Facebook/FacebookPost.cs +++ /dev/null @@ -1,44 +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; - -namespace Microsoft.Toolkit.Uwp.Services.Facebook -{ - /// - /// Strongly typed object for presenting post data returned from service provider. - /// - public class FacebookPost - { - /// - /// Gets a string description of the strongly typed properties in this model. - /// - public static string Fields => "id, message, created_time, link, full_picture"; - - /// - /// Gets or sets id property. - /// - public string Id { get; set; } - - /// - /// Gets or sets message or post text. - /// - public string Message { get; set; } - - /// - /// Gets or sets time the entity instance was created. - /// - public DateTime Created_Time { get; set; } - - /// - /// Gets or sets a link to the entity instance. - /// - public string Link { get; set; } - - /// - /// Gets or sets a link to the accompanying image. - /// - public string Full_Picture { get; set; } - } -} diff --git a/Microsoft.Toolkit.Uwp.Services/Services/Facebook/FacebookRequestSource.cs b/Microsoft.Toolkit.Uwp.Services/Services/Facebook/FacebookRequestSource.cs deleted file mode 100644 index a53679b9a4a..00000000000 --- a/Microsoft.Toolkit.Uwp.Services/Services/Facebook/FacebookRequestSource.cs +++ /dev/null @@ -1,121 +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.Threading; -using System.Threading.Tasks; -using Newtonsoft.Json; -using Windows.Foundation.Collections; -using winsdkfb; -using winsdkfb.Graph; - -namespace Microsoft.Toolkit.Uwp.Services.Facebook -{ - /// - /// Type to handle paged requests to Facebook Graph. - /// - /// Strong type to return. - public class FacebookRequestSource : Collections.IIncrementalSource - { - private bool _isFirstCall = true; - - private FBPaginatedArray _paginatedArray; - - private FacebookDataConfig _config; - - private string _fields; - - private PropertySet _propertySet; - - private FBJsonClassFactory _factory; - - private string _limit; - - private int _maxPages; - - /// - /// Initializes a new instance of the class. - /// - public FacebookRequestSource() - { - } - - /// - /// Initializes a new instance of the class. - /// - /// Config containing query information. - /// Comma-separated list of properties expected in the JSON response. Accompanying properties must be found on the strong-typed T. - /// A string representation of the number of records for page - i.e. pageSize. - /// Upper limit of pages to return. - public FacebookRequestSource(FacebookDataConfig config, string fields, string limit, int maxPages) - { - _config = config; - _fields = fields; - _limit = limit; - _maxPages = maxPages; - - _propertySet = new PropertySet { { "fields", _fields }, { "limit", _limit } }; - - _factory = new FBJsonClassFactory(s => JsonConvert.DeserializeObject(s, typeof(T))); - - // FBPaginatedArray does not allow us to set page size per request so we must go with first supplied - see https://github.com/Microsoft/winsdkfb/issues/221 - _paginatedArray = new FBPaginatedArray(_config.Query, _propertySet, _factory); - } - - /// - /// Returns strong typed page of data. - /// - /// Page number. - /// Size of page. - /// Cancellation token. - /// Strong typed page of data. - public async Task> GetPagedItemsAsync(int pageIndex, int pageSize, CancellationToken cancellationToken = default(CancellationToken)) - { - if (_isFirstCall) - { - var result = await _paginatedArray.FirstAsync(); - - return ProcessResult(result); - } - else - { - if (cancellationToken.IsCancellationRequested) - { - return null; - } - - if (_paginatedArray.HasNext && (pageIndex < _maxPages)) - { - var result = await _paginatedArray.NextAsync(); - return ProcessResult(result); - } - else - { - return null; - } - } - } - - private IEnumerable ProcessResult(FBResult result) - { - List items = new List(); - - if (result.Succeeded) - { - IReadOnlyList processedResults = (IReadOnlyList)result.Object; - - foreach (T processedResult in processedResults) - { - items.Add(processedResult); - } - - _isFirstCall = false; - return items; - } - - throw new Exception(result.ErrorInfo?.Message); - } - } -} diff --git a/Microsoft.Toolkit.Uwp.Services/Services/Facebook/FacebookService.cs b/Microsoft.Toolkit.Uwp.Services/Services/Facebook/FacebookService.cs deleted file mode 100644 index fb3bedd36aa..00000000000 --- a/Microsoft.Toolkit.Uwp.Services/Services/Facebook/FacebookService.cs +++ /dev/null @@ -1,515 +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.Diagnostics; -using System.Text; -using System.Threading.Tasks; -using Newtonsoft.Json; -using Windows.Foundation; -using Windows.Foundation.Collections; -using Windows.Foundation.Metadata; -using Windows.Security.Authentication.Web; -using Windows.Storage.Streams; -using winsdkfb; -using winsdkfb.Graph; - -namespace Microsoft.Toolkit.Uwp.Services.Facebook -{ - /// - /// Class for connecting to Facebook. - /// - [Obsolete("The underlying library, winsdkfb, which the FacebookService relies on is not currently maintained.")] - public class FacebookService - { - /// - /// Field for tracking initialization status. - /// - private bool isInitialized; - - /// - /// List of permissions required by the app. - /// - private FBPermissions permissions; - - /// - /// Gets a Windows Store ID associated with the current app - /// - public string WindowsStoreId => WebAuthenticationBroker.GetCurrentApplicationCallbackUri().ToString(); - - /// - /// Initializes a new instance of the class. - /// - public FacebookService() - { - } - - /// - /// Initialize underlying provider with relevant token information. - /// - /// Token instance. - /// List of required required permissions. public_profile and user_posts permissions will be used by default. - /// Success or failure. - public bool Initialize(FacebookOAuthTokens oAuthTokens, FacebookPermissions requiredPermissions = FacebookPermissions.PublicProfile | FacebookPermissions.UserPosts) - { - if (oAuthTokens == null) - { - throw new ArgumentNullException(nameof(oAuthTokens)); - } - - return Initialize(oAuthTokens.AppId, requiredPermissions, oAuthTokens.WindowsStoreId); - } - - /// - /// Initialize underlying provider with relevant token information. - /// - /// Application ID (Provided by Facebook developer site) - /// List of required required permissions. public_profile and user_posts permissions will be used by default. - /// Windows Store SID - /// Success or failure. - public bool Initialize(string appId, FacebookPermissions requiredPermissions = FacebookPermissions.PublicProfile | FacebookPermissions.UserPosts, string windowsStoreId = null) - { - if (string.IsNullOrEmpty(appId)) - { - throw new ArgumentNullException(nameof(appId)); - } - - if (string.IsNullOrEmpty(windowsStoreId)) - { - windowsStoreId = WindowsStoreId; - } - - isInitialized = true; - - Provider.FBAppId = appId; - Provider.WinAppId = windowsStoreId; - - // Permissions - var permissionList = new List(); - - foreach (FacebookPermissions value in Enum.GetValues(typeof(FacebookPermissions))) - { - if ((requiredPermissions & value) != 0) - { - var name = value.ToString(); - var finalName = new StringBuilder(); - - foreach (var c in name) - { - if (char.IsUpper(c)) - { - if (finalName.Length > 0) - { - finalName.Append('_'); - } - - finalName.Append(char.ToLower(c)); - } - else - { - finalName.Append(c); - } - } - - permissionList.Add(finalName.ToString()); - } - } - - permissions = new FBPermissions(permissionList); - - return true; - } - - /// - /// Private singleton field. - /// - private static FacebookService instance; - - /// - /// Gets public singleton property. - /// - public static FacebookService Instance => instance ?? (instance = new FacebookService()); - - /// - /// Gets a reference to an instance of the underlying data provider. - /// - public FBSession Provider - { - get - { - if (!isInitialized) - { - throw new InvalidOperationException("Provider not initialized."); - } - - return FBSession.ActiveSession; - } - } - - /// - /// Gets the current logged user name. - /// - public string LoggedUser => !Provider.LoggedIn ? null : FBSession.ActiveSession.User.Name; - - /// - /// Login with set of required requiredPermissions. - /// - /// Success or failure. - public async Task LoginAsync() - { - if (Provider != null) - { - var result = await Provider.LoginAsync(permissions, SessionLoginBehavior.WebView); - - if (result.Succeeded) - { - return true; - } - - if (result.ErrorInfo != null) - { - Debug.WriteLine(string.Format("Error logging in: {0}", result.ErrorInfo.Message)); - } - - return false; - } - - Debug.WriteLine("Error logging in - no Active session found"); - return false; - } - - /// - /// Log out of the underlying service instance. - /// - /// Task to support await of async call. - public Task LogoutAsync() - { - return Provider.LogoutAsync().AsTask(); - } - - /// - /// Request list data from service provider based upon a given config / query. - /// - /// FacebookDataConfig instance. - /// Upper limit of records to return. - /// Strongly typed list of data returned from the service. - public Task> RequestAsync(FacebookDataConfig config, int maxRecords = 20) - { - return RequestAsync(config, maxRecords, FacebookPost.Fields); - } - - /// - /// Request list data from service provider based upon a given config / query. - /// - /// Strong type of model. - /// FacebookDataConfig instance. - /// Upper limit of records to return. - /// A comma separated string of required fields, which will have strongly typed representation in the model passed in. - /// Strongly typed list of data returned from the service. - public async Task> RequestAsync(FacebookDataConfig config, int maxRecords = 20, string fields = "id,message,from,created_time,link,full_picture") - { - if (Provider.LoggedIn) - { - var requestSource = new FacebookRequestSource(config, fields, maxRecords.ToString(), 1); - - var list = await requestSource.GetPagedItemsAsync(0, maxRecords); - - return new List(list); - } - - var isLoggedIn = await LoginAsync(); - if (isLoggedIn) - { - return await RequestAsync(config, maxRecords, fields); - } - - return null; - } - - /// - /// Request list data from service provider based upon a given config / query. - /// - /// FacebookDataConfig instance. - /// Upper limit of records to return. - /// Upper limit of pages to return. - /// Strongly typed list of data returned from the service. - public Task, FacebookPost>> RequestAsync(FacebookDataConfig config, int pageSize, int maxPages) - { - return RequestAsync(config, pageSize, maxPages, FacebookPost.Fields); - } - - /// - /// Request generic list data from service provider based upon a given config / query. - /// - /// Strong type of model. - /// FacebookDataConfig instance. - /// Upper limit of records to return. - /// Upper limit of pages to return. - /// A comma separated string of required fields, which will have strongly typed representation in the model passed in. - /// Strongly typed list of data returned from the service. - public async Task, T>> RequestAsync(FacebookDataConfig config, int pageSize, int maxPages, string fields = "id,message,from,created_time,link,full_picture") - { - if (Provider.LoggedIn) - { - var requestSource = new FacebookRequestSource(config, fields, pageSize.ToString(), maxPages); - - return new IncrementalLoadingCollection, T>(requestSource); - } - - var isLoggedIn = await LoginAsync(); - if (isLoggedIn) - { - return await RequestAsync(config, pageSize, maxPages, fields); - } - - return null; - } - - /// - /// Returns the object associated with the logged user - /// - /// A object - public async Task GetUserPictureInfoAsync() - { - if (Provider.LoggedIn) - { - var factory = new FBJsonClassFactory(JsonConvert.DeserializeObject>); - - PropertySet propertySet = new PropertySet { { "redirect", "0" } }; - var singleValue = new FBSingleValue("/me/picture", propertySet, factory); - - var result = await singleValue.GetAsync(); - - if (result.Succeeded) - { - return ((FacebookDataHost)result.Object).Data; - } - - throw new Exception(result.ErrorInfo?.Message); - } - - var isLoggedIn = await LoginAsync(); - if (isLoggedIn) - { - return await GetUserPictureInfoAsync(); - } - - return null; - } - - /// - /// Retrieves list of user photo albums. - /// - /// Upper limit of records to return. - /// Custom list of Album fields to retrieve. - /// List of User Photo Albums. - public async Task> GetUserAlbumsAsync(int maxRecords = 20, string fields = null) - { - fields = fields ?? FacebookAlbum.Fields; - var config = new FacebookDataConfig { Query = "/me/albums" }; - - return await RequestAsync(config, maxRecords, fields); - } - - /// - /// Retrieves list of user photo albums. - /// - /// Number of records to retrieve per page. - /// Upper limit of pages to return. - /// Custom list of Album fields to retrieve. - /// List of User Photo Albums. - public async Task, FacebookAlbum>> GetUserAlbumsAsync(int pageSize, int maxPages, string fields = null) - { - fields = fields ?? FacebookAlbum.Fields; - var config = new FacebookDataConfig { Query = "/me/albums" }; - - return await RequestAsync(config, pageSize, maxPages, fields); - } - - /// - /// Retrieves list of user photos by album id. - /// - /// Albums Id for photos. - /// Upper limit of records to return - /// Custom list of Photo fields to retrieve. - /// List of User Photos. - public async Task> GetUserPhotosByAlbumIdAsync(string albumId, int maxRecords = 20, string fields = null) - { - fields = fields ?? FacebookPhoto.Fields; - var config = new FacebookDataConfig { Query = $"/{albumId}/photos" }; - - return await RequestAsync(config, maxRecords, fields); - } - - /// - /// Retrieves list of user photos by album id. - /// - /// Albums Id for photos. - /// Number of records to retrieve per page. - /// Upper limit of pages to return. - /// Custom list of Photo fields to retrieve. - /// List of User Photos. - public async Task, FacebookPhoto>> GetUserPhotosByAlbumIdAsync(string albumId, int pageSize, int maxPages, string fields = null) - { - fields = fields ?? FacebookPhoto.Fields; - var config = new FacebookDataConfig { Query = $"/{albumId}/photos" }; - - return await RequestAsync(config, pageSize, maxPages, fields); - } - - /// - /// Retrieves a photo by id. - /// - /// Photo Id for the photo. - /// A single photo. - public async Task GetPhotoByPhotoIdAsync(string photoId) - { - if (Provider.LoggedIn) - { - var factory = new FBJsonClassFactory(JsonConvert.DeserializeObject); - - PropertySet propertySet = new PropertySet { { "fields", "images" } }; - var singleValue = new FBSingleValue($"/{photoId}", propertySet, factory); - - var result = await singleValue.GetAsync(); - - if (result.Succeeded) - { - return (FacebookPhoto)result.Object; - } - - throw new Exception(result.ErrorInfo?.Message); - } - - var isLoggedIn = await LoginAsync(); - if (isLoggedIn) - { - return await GetPhotoByPhotoIdAsync(photoId); - } - - return null; - } - - /// - /// Enables direct posting data to the timeline. - /// - /// Link contained as part of the post. Cannot be null. - /// Task to support await of async call. - [Deprecated("The underlying publish_action permission is no longer supported by Facebook. Please see https://developers.facebook.com/blog/post/2018/04/24/new-facebook-platform-product-changes-policy-updates/ for details.", DeprecationType.Deprecate, 4)] - public async Task PostToFeedAsync(string link) - { - if (Provider.LoggedIn) - { - var parameters = new PropertySet { { "link", link } }; - - string path = FBSession.ActiveSession.User.Id + "/feed"; - var factory = new FBJsonClassFactory(JsonConvert.DeserializeObject); - - var singleValue = new FBSingleValue(path, parameters, factory); - var result = await singleValue.PostAsync(); - if (result.Succeeded) - { - var postResponse = result.Object as FacebookPost; - if (postResponse != null) - { - return true; - } - } - - Debug.WriteLine(string.Format("Could not post. {0}", result.ErrorInfo?.ErrorUserMessage)); - return false; - } - - var isLoggedIn = await LoginAsync(); - if (isLoggedIn) - { - return await PostToFeedAsync(link); - } - - return false; - } - - /// - /// Enables posting data to the timeline using Facebook dialog. - /// - /// Link contained as part of the post. Cannot be null. - /// Task to support await of async call. - public async Task PostToFeedWithDialogAsync(string link) - { - if (Provider.LoggedIn) - { - var parameters = new PropertySet { { "link", link } }; - - var result = await Provider.ShowFeedDialogAsync(parameters); - - if (result.Succeeded) - { - return true; - } - - Debug.WriteLine(string.Format("Could not post. {0}", result.ErrorInfo?.ErrorUserMessage)); - return false; - } - - var isLoggedIn = await LoginAsync(); - if (isLoggedIn) - { - return await PostToFeedWithDialogAsync(link); - } - - return false; - } - - /// - /// Enables posting a picture to the timeline - /// - /// Title of the post. - /// Picture name. - /// Picture stream to upload. - /// Return ID of the picture - [Deprecated("The underlying publish_action permission is no longer supported by Facebook. Please see https://developers.facebook.com/blog/post/2018/04/24/new-facebook-platform-product-changes-policy-updates/ for details.", DeprecationType.Deprecate, 4)] - public async Task PostPictureToFeedAsync(string title, string pictureName, IRandomAccessStreamWithContentType pictureStream) - { - if (pictureStream == null) - { - return null; - } - - if (Provider.LoggedIn) - { - var facebookPictureStream = new FBMediaStream(pictureName, pictureStream); - var parameters = new PropertySet - { - { "source", facebookPictureStream }, - { "name", title } - }; - - string path = FBSession.ActiveSession.User.Id + "/photos"; - var factory = new FBJsonClassFactory(JsonConvert.DeserializeObject); - - var singleValue = new FBSingleValue(path, parameters, factory); - var result = await singleValue.PostAsync(); - if (result.Succeeded) - { - var photoResponse = result.Object as FacebookPicture; - if (photoResponse != null) - { - return photoResponse.Id; - } - } - - return null; - } - - var isLoggedIn = await LoginAsync(); - if (isLoggedIn) - { - return await PostPictureToFeedAsync(title, pictureName, pictureStream); - } - - return null; - } - } -} diff --git a/Windows Community Toolkit.sln b/Windows Community Toolkit.sln index d8e5f4ccbee..b9f6ee4728f 100644 --- a/Windows Community Toolkit.sln +++ b/Windows Community Toolkit.sln @@ -19,8 +19,6 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "UI", "UI", "{F1AFFFA7-28FE- EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Tests", "Tests", "{B30036C4-D514-4E5B-A323-587A061772CE}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Toolkit.Uwp.Services", "Microsoft.Toolkit.Uwp.Services\Microsoft.Toolkit.Uwp.Services.csproj", "{7189A42D-6F1A-4FA3-8E00-E2C14FDF167A}" -EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Toolkit.Uwp.UI.Controls", "Microsoft.Toolkit.Uwp.UI.Controls\Microsoft.Toolkit.Uwp.UI.Controls.csproj", "{E9FAABFB-D726-42C1-83C1-CB46A29FEA81}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Toolkit.Uwp.UI.Controls.DataGrid", "Microsoft.Toolkit.Uwp.UI.Controls.DataGrid\Microsoft.Toolkit.Uwp.UI.Controls.DataGrid.csproj", "{DAEB9CEC-C817-33B2-74B2-BC379380DB72}" @@ -90,7 +88,7 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GazeInputTest", "GazeInputT EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Toolkit.Uwp.UI.Media", "Microsoft.Toolkit.Uwp.UI.Media\Microsoft.Toolkit.Uwp.UI.Media.csproj", "{75F9EE44-3EFA-47BC-AEDD-351B9834A0AF}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.Toolkit.Mvvm", "Microsoft.Toolkit.Mvvm\Microsoft.Toolkit.Mvvm.csproj", "{D82AE6E1-E612-434E-ACB2-363EE48738D3}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Toolkit.Mvvm", "Microsoft.Toolkit.Mvvm\Microsoft.Toolkit.Mvvm.csproj", "{D82AE6E1-E612-434E-ACB2-363EE48738D3}" EndProject Project("{D954291E-2A0B-460D-934E-DC6B0785DB48}") = "UnitTests.Shared", "UnitTests\UnitTests.Shared\UnitTests.Shared.shproj", "{4E9466D1-D5AA-46AC-801B-C8FDAB79F0D4}" EndProject @@ -128,12 +126,11 @@ EndProject Global GlobalSection(SharedMSBuildProjectFiles) = preSolution UnitTests\UnitTests.Shared\UnitTests.Shared.projitems*{4e9466d1-d5aa-46ac-801b-c8fdab79f0d4}*SharedItemsImports = 13 - UnitTests\UnitTests.Shared\UnitTests.Shared.projitems*{7878cd00-85e8-4d02-9757-8a43db4c6510}*SharedItemsImports = 5 - UnitTests\UnitTests.Notifications.Shared\UnitTests.Notifications.Shared.projitems*{982cc826-aacd-4855-9075-430bb6ce40a9}*SharedItemsImports = 13 - UnitTests\UnitTests.Shared\UnitTests.Shared.projitems*{a139968e-ad78-4e8c-93b8-9a5523bcac89}*SharedItemsImports = 4 UnitTests\UnitTests.HighPerformance.Shared\UnitTests.HighPerformance.Shared.projitems*{5524523e-db0f-41f7-a0d4-43128422a342}*SharedItemsImports = 4 + UnitTests\UnitTests.Shared\UnitTests.Shared.projitems*{7878cd00-85e8-4d02-9757-8a43db4c6510}*SharedItemsImports = 5 UnitTests\UnitTests.Notifications.Shared\UnitTests.Notifications.Shared.projitems*{982cc826-aacd-4855-9075-430bb6ce40a9}*SharedItemsImports = 13 UnitTests\UnitTests.HighPerformance.Shared\UnitTests.HighPerformance.Shared.projitems*{9b3a94a6-0d29-4523-880b-6938e2efeef7}*SharedItemsImports = 13 + UnitTests\UnitTests.Shared\UnitTests.Shared.projitems*{a139968e-ad78-4e8c-93b8-9a5523bcac89}*SharedItemsImports = 4 UnitTests\UnitTests.Notifications.Shared\UnitTests.Notifications.Shared.projitems*{bab1caf4-c400-4a7f-a987-c576de63cffd}*SharedItemsImports = 4 UnitTests\UnitTests.HighPerformance.Shared\UnitTests.HighPerformance.Shared.projitems*{d9bdbc68-3d0a-47fc-9c88-0bf769101644}*SharedItemsImports = 5 UnitTests\UnitTests.Notifications.Shared\UnitTests.Notifications.Shared.projitems*{efa96b3c-857e-4659-b942-6bef7719f4ca}*SharedItemsImports = 4 @@ -297,31 +294,6 @@ Global {B24A296C-B3EB-4E06-A64E-74AC2D1ACC91}.Release|x64.Build.0 = Release|Any CPU {B24A296C-B3EB-4E06-A64E-74AC2D1ACC91}.Release|x86.ActiveCfg = Release|Any CPU {B24A296C-B3EB-4E06-A64E-74AC2D1ACC91}.Release|x86.Build.0 = Release|Any CPU - {7189A42D-6F1A-4FA3-8E00-E2C14FDF167A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {7189A42D-6F1A-4FA3-8E00-E2C14FDF167A}.Debug|Any CPU.Build.0 = Debug|Any CPU - {7189A42D-6F1A-4FA3-8E00-E2C14FDF167A}.Debug|ARM.ActiveCfg = Debug|Any CPU - {7189A42D-6F1A-4FA3-8E00-E2C14FDF167A}.Debug|ARM.Build.0 = Debug|Any CPU - {7189A42D-6F1A-4FA3-8E00-E2C14FDF167A}.Debug|ARM64.ActiveCfg = Debug|Any CPU - {7189A42D-6F1A-4FA3-8E00-E2C14FDF167A}.Debug|ARM64.Build.0 = Debug|Any CPU - {7189A42D-6F1A-4FA3-8E00-E2C14FDF167A}.Debug|x64.ActiveCfg = Debug|Any CPU - {7189A42D-6F1A-4FA3-8E00-E2C14FDF167A}.Debug|x64.Build.0 = Debug|Any CPU - {7189A42D-6F1A-4FA3-8E00-E2C14FDF167A}.Debug|x86.ActiveCfg = Debug|Any CPU - {7189A42D-6F1A-4FA3-8E00-E2C14FDF167A}.Debug|x86.Build.0 = Debug|Any CPU - {7189A42D-6F1A-4FA3-8E00-E2C14FDF167A}.Native|Any CPU.ActiveCfg = Release|Any CPU - {7189A42D-6F1A-4FA3-8E00-E2C14FDF167A}.Native|ARM.ActiveCfg = Release|Any CPU - {7189A42D-6F1A-4FA3-8E00-E2C14FDF167A}.Native|ARM64.ActiveCfg = Release|Any CPU - {7189A42D-6F1A-4FA3-8E00-E2C14FDF167A}.Native|x64.ActiveCfg = Release|Any CPU - {7189A42D-6F1A-4FA3-8E00-E2C14FDF167A}.Native|x86.ActiveCfg = Release|Any CPU - {7189A42D-6F1A-4FA3-8E00-E2C14FDF167A}.Release|Any CPU.ActiveCfg = Release|Any CPU - {7189A42D-6F1A-4FA3-8E00-E2C14FDF167A}.Release|Any CPU.Build.0 = Release|Any CPU - {7189A42D-6F1A-4FA3-8E00-E2C14FDF167A}.Release|ARM.ActiveCfg = Release|Any CPU - {7189A42D-6F1A-4FA3-8E00-E2C14FDF167A}.Release|ARM.Build.0 = Release|Any CPU - {7189A42D-6F1A-4FA3-8E00-E2C14FDF167A}.Release|ARM64.ActiveCfg = Release|Any CPU - {7189A42D-6F1A-4FA3-8E00-E2C14FDF167A}.Release|ARM64.Build.0 = Release|Any CPU - {7189A42D-6F1A-4FA3-8E00-E2C14FDF167A}.Release|x64.ActiveCfg = Release|Any CPU - {7189A42D-6F1A-4FA3-8E00-E2C14FDF167A}.Release|x64.Build.0 = Release|Any CPU - {7189A42D-6F1A-4FA3-8E00-E2C14FDF167A}.Release|x86.ActiveCfg = Release|Any CPU - {7189A42D-6F1A-4FA3-8E00-E2C14FDF167A}.Release|x86.Build.0 = Release|Any CPU {E9FAABFB-D726-42C1-83C1-CB46A29FEA81}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {E9FAABFB-D726-42C1-83C1-CB46A29FEA81}.Debug|Any CPU.Build.0 = Debug|Any CPU {E9FAABFB-D726-42C1-83C1-CB46A29FEA81}.Debug|ARM.ActiveCfg = Debug|Any CPU diff --git a/readme.md b/readme.md index 0db48418d7e..aef7bc42f2b 100644 --- a/readme.md +++ b/readme.md @@ -36,11 +36,10 @@ Once you do a search, you should see a list similar to the one below (versions m | Microsoft.Toolkit | .NET Standard NuGet package containing common code | | Microsoft.Toolkit.HighPerformance | .NET Standard and .NET Core NuGet package with performance oriented helpers, extensions, etc. | | Microsoft.Toolkit.Parsers | .NET Standard NuGet package containing cross-platform parsers, such as Markdown and RSS | -| Microsoft.Toolkit.Services | .NET Standard NuGet package containing cross-platform services | +| Microsoft.Toolkit.Services | .NET Standard NuGet package containing cross-platform services helpers, such as LinkedIn, Microsoft Graph, Twitter and more | | Microsoft.Toolkit.Uwp | Main NuGet package includes code only helpers such as Colors conversion tool, Storage file handling, a Stream helper class, etc. | | Microsoft.Toolkit.Uwp.Notifications | Notifications Package - Generate tile, toast, and badge notifications for Windows 10 via code. Includes intellisense support to avoid having to use the XML syntax | | Microsoft.Toolkit.Uwp.Notifications.Javascript | Notification Packages for JavaScript | -| Microsoft.Toolkit.Uwp.Services | Services Package - This NuGet package includes the service helpers for Facebook, LinkedIn, Microsoft Graph, Twitter and more | | Microsoft.Toolkit.Uwp.UI | UI Packages - XAML converters, Visual tree extensions, and other extensions and helpers for your XAML UI | | Microsoft.Toolkit.Uwp.UI.Animations | Animations and Composition behaviors such as Blur, Fade, Rotate, etc. | | Microsoft.Toolkit.Uwp.UI.Controls | XAML Controls such as RadialGauge, RangeSelector, etc. | From 3fc7dc7b4c18d7ee1ae7a740e00a004dc97492db Mon Sep 17 00:00:00 2001 From: Alexandre Zollinger Chohfi Date: Mon, 17 Aug 2020 11:55:34 -0700 Subject: [PATCH 389/595] Removed deprecated NotifyTaskCompletion and Singleton classes. --- .../Helpers/NotifyTaskCompletion.cs | 141 ------------------ Microsoft.Toolkit/Helpers/Singleton.cs | 43 ------ 2 files changed, 184 deletions(-) delete mode 100644 Microsoft.Toolkit/Helpers/NotifyTaskCompletion.cs delete mode 100644 Microsoft.Toolkit/Helpers/Singleton.cs diff --git a/Microsoft.Toolkit/Helpers/NotifyTaskCompletion.cs b/Microsoft.Toolkit/Helpers/NotifyTaskCompletion.cs deleted file mode 100644 index 0f9fb1f0610..00000000000 --- a/Microsoft.Toolkit/Helpers/NotifyTaskCompletion.cs +++ /dev/null @@ -1,141 +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.ComponentModel; -using System.Threading.Tasks; - -namespace Microsoft.Toolkit.Helpers -{ - /// - /// Helper class to wrap around a Task to provide more information usable for UI data binding scenarios. As discussed in MSDN Magazine: https://msdn.microsoft.com/magazine/dn605875. - /// - /// Type of result returned by task. - [Obsolete("This helper will be removed in a future release, use the ObservableObject base class from Microsoft.Toolkit.Mvvm and the SetAndNotifyOnCompletion method")] - public sealed class NotifyTaskCompletion : INotifyPropertyChanged - { - /// - /// Initializes a new instance of the class. - /// - /// Task to wait on. - public NotifyTaskCompletion(Task task) - { - Task = task; - if (!task.IsCompleted) - { - TaskCompletion = WatchTaskAsync(task); - } - } - - private async Task WatchTaskAsync(Task task) - { - try - { - await task; - } - catch - { - } - - if (PropertyChanged == null) - { - return; - } - - PropertyChanged.Invoke(this, new PropertyChangedEventArgs(nameof(Status))); - PropertyChanged.Invoke(this, new PropertyChangedEventArgs(nameof(IsCompleted))); - PropertyChanged.Invoke(this, new PropertyChangedEventArgs(nameof(IsNotCompleted))); - - if (task.IsCanceled) - { - PropertyChanged.Invoke(this, new PropertyChangedEventArgs(nameof(IsCanceled))); - } - else if (task.IsFaulted) - { - PropertyChanged.Invoke(this, new PropertyChangedEventArgs(nameof(IsFaulted))); - PropertyChanged.Invoke(this, new PropertyChangedEventArgs(nameof(Exception))); - PropertyChanged.Invoke(this, new PropertyChangedEventArgs(nameof(InnerException))); - PropertyChanged.Invoke(this, new PropertyChangedEventArgs(nameof(ErrorMessage))); - } - else - { - PropertyChanged.Invoke(this, new PropertyChangedEventArgs(nameof(IsSuccessfullyCompleted))); - PropertyChanged.Invoke(this, new PropertyChangedEventArgs(nameof(Result))); - } - } - - /// - /// Gets the task that is being waited on. - /// - public Task Task { get; private set; } - - /// - /// Gets the task wrapper task. - /// - public Task TaskCompletion { get; private set; } - - /// - /// Gets the result of the given task. - /// - public TResult Result - { - get - { - return (Task.Status == TaskStatus.RanToCompletion) ? - Task.Result : - default(TResult); - } - } - - /// - /// Gets the status of the task. - /// - public TaskStatus Status => Task.Status; - - /// - /// Gets a value indicating whether the task is completed. - /// - public bool IsCompleted => Task.IsCompleted; - - /// - /// Gets a value indicating whether the task is not completed. - /// - public bool IsNotCompleted => !Task.IsCompleted; - - /// - /// Gets a value indicating whether the task was successfully completed. - /// - public bool IsSuccessfullyCompleted => Task.Status == TaskStatus.RanToCompletion; - - /// - /// Gets a value indicating whether the task was cancelled. - /// - public bool IsCanceled => Task.IsCanceled; - - /// - /// Gets a value indicating whether there was an error with the task. - /// - public bool IsFaulted => Task.IsFaulted; - - /// - /// Gets the exception which occurred on the task (if one occurred). - /// - public AggregateException Exception => Task.Exception; - - /// - /// Gets the inner exception of the task. - /// - public Exception InnerException => Exception?.InnerException; - - /// - /// Gets the error message of the task. - /// - public string ErrorMessage => InnerException?.Message ?? Exception.Message; - - /// - /// PropertyChanged event. - /// - public event PropertyChangedEventHandler PropertyChanged; - } -} diff --git a/Microsoft.Toolkit/Helpers/Singleton.cs b/Microsoft.Toolkit/Helpers/Singleton.cs deleted file mode 100644 index 63bd7044005..00000000000 --- a/Microsoft.Toolkit/Helpers/Singleton.cs +++ /dev/null @@ -1,43 +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.Concurrent; - -namespace Microsoft.Toolkit.Helpers -{ - /// - /// Obsolete see https://github.com/windows-toolkit/WindowsCommunityToolkit/issues/3134. - /// - /// The type to be used for creating the Singleton instance. - /// - /// Instead of this helper, migrate your code to this pattern instead: - /// - /// // Setup Singleton - /// public class MyClass - /// { - /// public static MyClass Instance { get; } = new MyClass(); - /// } - /// - /// - [Obsolete("This helper will be removed in a future release, see example tag for code replacement. https://github.com/windows-toolkit/WindowsCommunityToolkit/issues/3134")] - public static class Singleton - where T : new() - { - // Use ConcurrentDictionary for thread safety. - private static readonly ConcurrentDictionary _instances = new ConcurrentDictionary(); - - /// - /// Gets the instance of the Singleton class. - /// - public static T Instance - { - get - { - // Safely creates the first instance or retrieves the existing instance across threads. - return _instances.GetOrAdd(typeof(T), (t) => new T()); - } - } - } -} From 02790ab33f862981bf641375d9924d64d4c78686 Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Mon, 17 Aug 2020 23:33:55 +0200 Subject: [PATCH 390/595] Added IMessenger.Cleanup API (to help with extensibility) --- Microsoft.Toolkit.Mvvm/Messaging/IMessenger.cs | 8 ++++++++ Microsoft.Toolkit.Mvvm/Messaging/Messenger.cs | 10 ++++++++++ 2 files changed, 18 insertions(+) diff --git a/Microsoft.Toolkit.Mvvm/Messaging/IMessenger.cs b/Microsoft.Toolkit.Mvvm/Messaging/IMessenger.cs index a3838d2b117..e046d056c07 100644 --- a/Microsoft.Toolkit.Mvvm/Messaging/IMessenger.cs +++ b/Microsoft.Toolkit.Mvvm/Messaging/IMessenger.cs @@ -94,6 +94,14 @@ TMessage Send(TMessage message, TToken token) where TMessage : class where TToken : IEquatable; + /// + /// Performs a cleanup on the current messenger. + /// Invoking this method does not unregister any of the currently registered + /// recipient, and it can be used to perform cleanup operations such as + /// trimming the internal data structures of a messenger implementation. + /// + void Cleanup(); + /// /// Resets the instance and unregisters all the existing recipients. /// diff --git a/Microsoft.Toolkit.Mvvm/Messaging/Messenger.cs b/Microsoft.Toolkit.Mvvm/Messaging/Messenger.cs index 8ead8d4b73b..f352fa364b2 100644 --- a/Microsoft.Toolkit.Mvvm/Messaging/Messenger.cs +++ b/Microsoft.Toolkit.Mvvm/Messaging/Messenger.cs @@ -480,6 +480,16 @@ public unsafe TMessage Send(TMessage message, TToken token) return message; } + /// + void IMessenger.Cleanup() + { + // The current implementation doesn't require any kind of cleanup operation, as + // all the internal data structures are already kept in sync whenever a recipient + // is added or removed. This method is implemented through an explicit interface + // implementation so that developers using this type directly will not see it in + // the API surface (as it wouldn't be useful anyway, since it's a no-op here). + } + /// public void Reset() { From 323cfd7b6364d74882fecb6ee25e2e988bde8dbe Mon Sep 17 00:00:00 2001 From: Alexandre Zollinger Chohfi Date: Mon, 17 Aug 2020 18:20:46 -0700 Subject: [PATCH 391/595] Created a new Microsoft.Toolkit.Uwp.UI.Controls.Markdown package, moving the MarkdownTextBlock from the Controls package and decoupling the TextToolbar from the MarkDownFormatter. --- .../Microsoft.Toolkit.Uwp.SampleApp.csproj | 4 + .../Models/Sample.cs | 12 + .../EnumValuesExtensionPage.xaml.cs | 4 +- .../InAppNotificationPage.xaml.cs | 4 +- .../MarkdownParser/MarkdownParserPage.xaml | 1 - .../TextToolbar/SampleFormatter.cs | 7 +- .../SamplePages/TextToolbar/TextToolbar.bind | 19 +- .../TextToolbar/TextToolbarCode.bind | 3 +- .../TextToolbar/TextToolbarPage.xaml | 23 +- .../TextToolbar/TextToolbarPage.xaml.cs | 30 +-- ...soft.Toolkit.Uwp.UI.Controls.Design.csproj | 1 - .../TextToolbarMetadata.cs | 1 - .../Common/MetadataRegistrationBase.cs | 252 ++++++++++++++++++ .../Common/PlatformTypes.cs | 50 ++++ .../Common/ToolboxCategoryPaths.cs | 23 ++ .../MarkdownTextBlockMetadata.cs | 0 .../MetadataRegistration.cs | 46 ++++ ...kit.Uwp.UI.Controls.Markdown.Design.csproj | 126 +++++++++ .../Properties/AssemblyInfo.cs | 35 +++ .../Properties/Resources.Designer.cs | 180 +++++++++++++ .../Properties/Resources.resx | 159 +++++++++++ .../Properties/Settings.Designer.cs | 26 ++ .../Properties/Settings.settings | 7 + .../About.txt | 0 .../CodeBlockResolvingEventArgs.cs | 0 .../ImageResolvingEventArgs.cs | 0 .../MarkdownTextBlock/LinkClickedEventArgs.cs | 0 .../MarkdownRenderedEventArgs.cs | 0 .../MarkdownTextBlock.Dimensions.cs | 0 .../MarkdownTextBlock.Events.cs | 0 .../MarkdownTextBlock.Methods.cs | 0 .../MarkdownTextBlock.Properties.cs | 0 .../MarkdownTextBlock/MarkdownTextBlock.cs | 0 .../MarkdownTextBlock/MarkdownTextBlock.xaml | 0 ...ft.Toolkit.Uwp.UI.Controls.Markdown.csproj | 49 ++++ .../Render/ICodeBlockResolver.cs | 0 .../Render/IImageResolver.cs | 0 .../Render/ILinkRegister.cs | 0 .../Render/InlineRenderContext.cs | 0 .../Render/MarkdownRenderer.Blocks.cs | 0 .../Render/MarkdownRenderer.Dimensions.cs | 0 .../Render/MarkdownRenderer.Inlines.cs | 0 .../Render/MarkdownRenderer.Properties.cs | 0 .../Render/MarkdownRenderer.cs | 0 .../Render/MarkdownTable.cs | 0 .../Render/RenderContext.cs | 0 .../Render/RenderContextIncorrectException.cs | 0 .../UIElementCollectionRenderContext.cs | 0 .../Formats/MarkDown/MarkDownButtonActions.cs | 0 .../Formats/MarkDown/MarkDownFormatter.cs | 24 +- .../Themes/Generic.xaml | 6 + .../VisualStudioToolsManifest.xml | 7 + .../Microsoft.Toolkit.Uwp.UI.Controls.csproj | 6 +- .../TextToolbar/Formats/Format.cs | 27 -- .../TextToolbar/Formats/Formatter.cs | 36 ++- .../Formats/RichText/RichTextFormatter.cs | 12 +- .../TextToolbar/TextToolbar.Events.cs | 24 +- .../TextToolbar/TextToolbar.Methods.cs | 19 -- .../TextToolbar/TextToolbar.Properties.cs | 18 +- .../TextToolbar/TextToolbar.cs | 7 +- ....cs => ToolbarFormatterActiveConverter.cs} | 20 +- .../Themes/Generic.xaml | 1 - .../VisualStudioToolsManifest.xml | 1 - .../XamlIslandsTest_TextToolbar.cs | 3 +- Windows Community Toolkit.sln | 74 ++++- 65 files changed, 1157 insertions(+), 190 deletions(-) create mode 100644 Microsoft.Toolkit.Uwp.UI.Controls.Markdown.Design/Common/MetadataRegistrationBase.cs create mode 100644 Microsoft.Toolkit.Uwp.UI.Controls.Markdown.Design/Common/PlatformTypes.cs create mode 100644 Microsoft.Toolkit.Uwp.UI.Controls.Markdown.Design/Common/ToolboxCategoryPaths.cs rename {Microsoft.Toolkit.Uwp.UI.Controls.Design => Microsoft.Toolkit.Uwp.UI.Controls.Markdown.Design}/MarkdownTextBlockMetadata.cs (100%) create mode 100644 Microsoft.Toolkit.Uwp.UI.Controls.Markdown.Design/MetadataRegistration.cs create mode 100644 Microsoft.Toolkit.Uwp.UI.Controls.Markdown.Design/Microsoft.Toolkit.Uwp.UI.Controls.Markdown.Design.csproj create mode 100644 Microsoft.Toolkit.Uwp.UI.Controls.Markdown.Design/Properties/AssemblyInfo.cs create mode 100644 Microsoft.Toolkit.Uwp.UI.Controls.Markdown.Design/Properties/Resources.Designer.cs create mode 100644 Microsoft.Toolkit.Uwp.UI.Controls.Markdown.Design/Properties/Resources.resx create mode 100644 Microsoft.Toolkit.Uwp.UI.Controls.Markdown.Design/Properties/Settings.Designer.cs create mode 100644 Microsoft.Toolkit.Uwp.UI.Controls.Markdown.Design/Properties/Settings.settings rename {Microsoft.Toolkit.Uwp.UI.Controls/MarkdownTextBlock => Microsoft.Toolkit.Uwp.UI.Controls.Markdown}/About.txt (100%) rename {Microsoft.Toolkit.Uwp.UI.Controls => Microsoft.Toolkit.Uwp.UI.Controls.Markdown}/MarkdownTextBlock/CodeBlockResolvingEventArgs.cs (100%) rename {Microsoft.Toolkit.Uwp.UI.Controls => Microsoft.Toolkit.Uwp.UI.Controls.Markdown}/MarkdownTextBlock/ImageResolvingEventArgs.cs (100%) rename {Microsoft.Toolkit.Uwp.UI.Controls => Microsoft.Toolkit.Uwp.UI.Controls.Markdown}/MarkdownTextBlock/LinkClickedEventArgs.cs (100%) rename {Microsoft.Toolkit.Uwp.UI.Controls => Microsoft.Toolkit.Uwp.UI.Controls.Markdown}/MarkdownTextBlock/MarkdownRenderedEventArgs.cs (100%) rename {Microsoft.Toolkit.Uwp.UI.Controls => Microsoft.Toolkit.Uwp.UI.Controls.Markdown}/MarkdownTextBlock/MarkdownTextBlock.Dimensions.cs (100%) rename {Microsoft.Toolkit.Uwp.UI.Controls => Microsoft.Toolkit.Uwp.UI.Controls.Markdown}/MarkdownTextBlock/MarkdownTextBlock.Events.cs (100%) rename {Microsoft.Toolkit.Uwp.UI.Controls => Microsoft.Toolkit.Uwp.UI.Controls.Markdown}/MarkdownTextBlock/MarkdownTextBlock.Methods.cs (100%) rename {Microsoft.Toolkit.Uwp.UI.Controls => Microsoft.Toolkit.Uwp.UI.Controls.Markdown}/MarkdownTextBlock/MarkdownTextBlock.Properties.cs (100%) rename {Microsoft.Toolkit.Uwp.UI.Controls => Microsoft.Toolkit.Uwp.UI.Controls.Markdown}/MarkdownTextBlock/MarkdownTextBlock.cs (100%) rename {Microsoft.Toolkit.Uwp.UI.Controls => Microsoft.Toolkit.Uwp.UI.Controls.Markdown}/MarkdownTextBlock/MarkdownTextBlock.xaml (100%) create mode 100644 Microsoft.Toolkit.Uwp.UI.Controls.Markdown/Microsoft.Toolkit.Uwp.UI.Controls.Markdown.csproj rename {Microsoft.Toolkit.Uwp.UI.Controls/MarkdownTextBlock => Microsoft.Toolkit.Uwp.UI.Controls.Markdown}/Render/ICodeBlockResolver.cs (100%) rename {Microsoft.Toolkit.Uwp.UI.Controls/MarkdownTextBlock => Microsoft.Toolkit.Uwp.UI.Controls.Markdown}/Render/IImageResolver.cs (100%) rename {Microsoft.Toolkit.Uwp.UI.Controls/MarkdownTextBlock => Microsoft.Toolkit.Uwp.UI.Controls.Markdown}/Render/ILinkRegister.cs (100%) rename {Microsoft.Toolkit.Uwp.UI.Controls/MarkdownTextBlock => Microsoft.Toolkit.Uwp.UI.Controls.Markdown}/Render/InlineRenderContext.cs (100%) rename {Microsoft.Toolkit.Uwp.UI.Controls/MarkdownTextBlock => Microsoft.Toolkit.Uwp.UI.Controls.Markdown}/Render/MarkdownRenderer.Blocks.cs (100%) rename {Microsoft.Toolkit.Uwp.UI.Controls/MarkdownTextBlock => Microsoft.Toolkit.Uwp.UI.Controls.Markdown}/Render/MarkdownRenderer.Dimensions.cs (100%) rename {Microsoft.Toolkit.Uwp.UI.Controls/MarkdownTextBlock => Microsoft.Toolkit.Uwp.UI.Controls.Markdown}/Render/MarkdownRenderer.Inlines.cs (100%) rename {Microsoft.Toolkit.Uwp.UI.Controls/MarkdownTextBlock => Microsoft.Toolkit.Uwp.UI.Controls.Markdown}/Render/MarkdownRenderer.Properties.cs (100%) rename {Microsoft.Toolkit.Uwp.UI.Controls/MarkdownTextBlock => Microsoft.Toolkit.Uwp.UI.Controls.Markdown}/Render/MarkdownRenderer.cs (100%) rename {Microsoft.Toolkit.Uwp.UI.Controls/MarkdownTextBlock => Microsoft.Toolkit.Uwp.UI.Controls.Markdown}/Render/MarkdownTable.cs (100%) rename {Microsoft.Toolkit.Uwp.UI.Controls/MarkdownTextBlock => Microsoft.Toolkit.Uwp.UI.Controls.Markdown}/Render/RenderContext.cs (100%) rename {Microsoft.Toolkit.Uwp.UI.Controls/MarkdownTextBlock => Microsoft.Toolkit.Uwp.UI.Controls.Markdown}/Render/RenderContextIncorrectException.cs (100%) rename {Microsoft.Toolkit.Uwp.UI.Controls/MarkdownTextBlock => Microsoft.Toolkit.Uwp.UI.Controls.Markdown}/Render/UIElementCollectionRenderContext.cs (100%) rename {Microsoft.Toolkit.Uwp.UI.Controls => Microsoft.Toolkit.Uwp.UI.Controls.Markdown}/TextToolbar/Formats/MarkDown/MarkDownButtonActions.cs (100%) rename {Microsoft.Toolkit.Uwp.UI.Controls => Microsoft.Toolkit.Uwp.UI.Controls.Markdown}/TextToolbar/Formats/MarkDown/MarkDownFormatter.cs (96%) create mode 100644 Microsoft.Toolkit.Uwp.UI.Controls.Markdown/Themes/Generic.xaml create mode 100644 Microsoft.Toolkit.Uwp.UI.Controls.Markdown/VisualStudioToolsManifest.xml delete mode 100644 Microsoft.Toolkit.Uwp.UI.Controls/TextToolbar/Formats/Format.cs rename Microsoft.Toolkit.Uwp.UI.Controls/TextToolbar/{ToolbarFormatActiveConverter.cs => ToolbarFormatterActiveConverter.cs} (60%) diff --git a/Microsoft.Toolkit.Uwp.SampleApp/Microsoft.Toolkit.Uwp.SampleApp.csproj b/Microsoft.Toolkit.Uwp.SampleApp/Microsoft.Toolkit.Uwp.SampleApp.csproj index 6426c03d171..0a0eced3339 100644 --- a/Microsoft.Toolkit.Uwp.SampleApp/Microsoft.Toolkit.Uwp.SampleApp.csproj +++ b/Microsoft.Toolkit.Uwp.SampleApp/Microsoft.Toolkit.Uwp.SampleApp.csproj @@ -1541,6 +1541,10 @@ {cb444381-18ba-4a51-bb32-3a498bcc1e99} Microsoft.Toolkit.Uwp.UI.Controls.Layout + + {6fedf199-b052-49dd-8f3e-2a9224998e0f} + Microsoft.Toolkit.Uwp.UI.Controls.Markdown + {e9faabfb-d726-42c1-83c1-cb46a29fea81} Microsoft.Toolkit.Uwp.UI.Controls diff --git a/Microsoft.Toolkit.Uwp.SampleApp/Models/Sample.cs b/Microsoft.Toolkit.Uwp.SampleApp/Models/Sample.cs index 9e21f19f9ba..e89c1a27cfe 100644 --- a/Microsoft.Toolkit.Uwp.SampleApp/Models/Sample.cs +++ b/Microsoft.Toolkit.Uwp.SampleApp/Models/Sample.cs @@ -727,6 +727,18 @@ private static Type LookForTypeByName(string typeName) } } + // Search in Microsoft.Toolkit.Uwp.UI.Controls.Markdown + var markdownTextBlockType = typeof(MarkdownTextBlock); + assembly = markdownTextBlockType.GetTypeInfo().Assembly; + + foreach (var typeInfo in assembly.ExportedTypes) + { + if (typeInfo.Name == typeName) + { + return typeInfo; + } + } + return null; } diff --git a/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/EnumValuesExtension/EnumValuesExtensionPage.xaml.cs b/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/EnumValuesExtension/EnumValuesExtensionPage.xaml.cs index c3313a47588..e85542823fc 100644 --- a/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/EnumValuesExtension/EnumValuesExtensionPage.xaml.cs +++ b/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/EnumValuesExtension/EnumValuesExtensionPage.xaml.cs @@ -3,11 +3,11 @@ // See the LICENSE file in the project root for more information. using System; -using Windows.UI; +using Microsoft.Toolkit.Uwp.SampleApp.Enums; using Microsoft.Toolkit.Uwp.UI.Extensions; +using Windows.UI; using Windows.UI.Xaml; using Windows.UI.Xaml.Data; -using Microsoft.Toolkit.Uwp.SampleApp.Enums; namespace Microsoft.Toolkit.Uwp.SampleApp.SamplePages { diff --git a/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/InAppNotification/InAppNotificationPage.xaml.cs b/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/InAppNotification/InAppNotificationPage.xaml.cs index b9e543948a7..49a219e3d5d 100644 --- a/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/InAppNotification/InAppNotificationPage.xaml.cs +++ b/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/InAppNotification/InAppNotificationPage.xaml.cs @@ -2,11 +2,11 @@ // 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.Uwp.UI.Controls; -using Microsoft.Toolkit.Uwp.UI.Extensions; using System; using System.Collections.Generic; using System.Windows.Input; +using Microsoft.Toolkit.Uwp.UI.Controls; +using Microsoft.Toolkit.Uwp.UI.Extensions; using Windows.UI.Xaml; using Windows.UI.Xaml.Controls; diff --git a/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/MarkdownParser/MarkdownParserPage.xaml b/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/MarkdownParser/MarkdownParserPage.xaml index b495f34e9fc..657d20de2d7 100644 --- a/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/MarkdownParser/MarkdownParserPage.xaml +++ b/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/MarkdownParser/MarkdownParserPage.xaml @@ -2,7 +2,6 @@ x:Class="Microsoft.Toolkit.Uwp.SampleApp.SamplePages.MarkdownParserPage" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" - xmlns:local="using:Microsoft.Toolkit.Uwp.SampleApp.SamplePages.MarkdownParser" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" mc:Ignorable="d"> diff --git a/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/TextToolbar/SampleFormatter.cs b/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/TextToolbar/SampleFormatter.cs index e6097a8e9e0..a98ff473a34 100644 --- a/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/TextToolbar/SampleFormatter.cs +++ b/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/TextToolbar/SampleFormatter.cs @@ -11,9 +11,10 @@ namespace Microsoft.Toolkit.Uwp.SampleApp.SamplePages.TextToolbarSamples { public class SampleFormatter : Formatter { - public SampleFormatter(TextToolbar model) - : base(model) + public override void SetModel(TextToolbar model) { + base.SetModel(model); + CommonButtons = new CommonButtons(model); } @@ -31,6 +32,6 @@ public override ButtonMap DefaultButtons } } - private CommonButtons CommonButtons { get; } + private CommonButtons CommonButtons { get; set; } } } \ No newline at end of file diff --git a/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/TextToolbar/TextToolbar.bind b/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/TextToolbar/TextToolbar.bind index 2a47ec69320..5be1d7361ef 100644 --- a/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/TextToolbar/TextToolbar.bind +++ b/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/TextToolbar/TextToolbar.bind @@ -3,14 +3,17 @@ xmlns:common="using:Microsoft.Toolkit.Uwp.SampleApp.Common" xmlns:controls="using:Microsoft.Toolkit.Uwp.UI.Controls" xmlns:converters="using:Microsoft.Toolkit.Uwp.UI.Converters" + xmlns:markDown="using:Microsoft.Toolkit.Uwp.UI.Controls.TextToolbarFormats.MarkDown" + xmlns:richText="using:Microsoft.Toolkit.Uwp.UI.Controls.TextToolbarFormats.RichText" + xmlns:textToolbarSamples="using:Microsoft.Toolkit.Uwp.SampleApp.SamplePages.TextToolbarSamples" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:contract7Present="http://schemas.microsoft.com/winfx/2006/xaml/presentation?IsApiContractPresent(Windows.Foundation.UniversalApiContract,7)" mc:Ignorable="d"> - + + Background="#4C4F4F4F"> + + + + + + + @@ -46,7 +55,7 @@ + Visibility="{Binding Formatter, ElementName=Toolbar, Mode=OneWay, Converter={StaticResource IsMarkdownFormatter}}"> diff --git a/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/TextToolbar/TextToolbarCode.bind b/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/TextToolbar/TextToolbarCode.bind index ca3ffadccf9..3b455448330 100644 --- a/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/TextToolbar/TextToolbarCode.bind +++ b/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/TextToolbar/TextToolbarCode.bind @@ -18,8 +18,7 @@ private void EditZone_TextChanged(object sender, Windows.UI.Xaml.RoutedEventArgs private void UseCustomFormatter() { - var formatter = new SampleFormatter(Toolbar); - Toolbar.Format = UI.Controls.TextToolbarFormats.Format.Custom; + var formatter = new SampleFormatter(); Toolbar.Formatter = formatter; } diff --git a/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/TextToolbar/TextToolbarPage.xaml b/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/TextToolbar/TextToolbarPage.xaml index e832614adbe..8bf46959e0a 100644 --- a/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/TextToolbar/TextToolbarPage.xaml +++ b/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/TextToolbar/TextToolbarPage.xaml @@ -3,17 +3,34 @@ xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:controls="using:Microsoft.Toolkit.Uwp.UI.Controls" xmlns:converters="using:Microsoft.Toolkit.Uwp.UI.Converters" + xmlns:markDown="using:Microsoft.Toolkit.Uwp.UI.Controls.TextToolbarFormats.MarkDown" + xmlns:richText="using:Microsoft.Toolkit.Uwp.UI.Controls.TextToolbarFormats.RichText" + xmlns:textToolbarSamples="using:Microsoft.Toolkit.Uwp.SampleApp.SamplePages.TextToolbarSamples" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" mc:Ignorable="d"> - + - + + + + + + + + + + + + + + + diff --git a/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/TextToolbar/TextToolbarPage.xaml.cs b/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/TextToolbar/TextToolbarPage.xaml.cs index 5ff226e6c7b..c2b25226d5f 100644 --- a/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/TextToolbar/TextToolbarPage.xaml.cs +++ b/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/TextToolbar/TextToolbarPage.xaml.cs @@ -3,13 +3,9 @@ // See the LICENSE file in the project root for more information. using System; -using System.Collections.Generic; -using System.Threading.Tasks; -using Microsoft.Toolkit.Uwp.SampleApp.Models; using Microsoft.Toolkit.Uwp.SampleApp.SamplePages.TextToolbarSamples; using Microsoft.Toolkit.Uwp.UI.Controls; using Microsoft.Toolkit.Uwp.UI.Controls.TextToolbarButtons; -using Microsoft.Toolkit.Uwp.UI.Controls.TextToolbarFormats; using Microsoft.Toolkit.Uwp.UI.Controls.TextToolbarFormats.MarkDown; using Microsoft.Toolkit.Uwp.UI.Extensions; using Windows.System; @@ -43,11 +39,6 @@ public void OnXamlRendered(FrameworkElement control) _previewer = previewer; _previewer.LinkClicked += Previewer_LinkClicked; } - - if (ToolbarFormat != null && (Format)ToolbarFormat.Value == Format.Custom) - { - UseCustomFormatter(); - } } private void Load() @@ -99,13 +90,12 @@ private void ResetLayout() private void UseCustomFormatter() { - if (_toolbar == null || ToolbarFormat == null) + if (_toolbar == null) { return; } - var formatter = new SampleFormatter(_toolbar); - ToolbarFormat.Value = Format.Custom; + var formatter = new SampleFormatter(); _toolbar.Formatter = formatter; } @@ -179,21 +169,5 @@ private void EditZone_TextChanged(object sender, RoutedEventArgs e) } private int DemoCounter { get; set; } = 0; - - private ValueHolder ToolbarFormat - { - get - { - if (DataContext is Sample sample) - { - if (sample.PropertyDescriptor.Expando is IDictionary properties && properties.TryGetValue("Format", out var format)) - { - return format as ValueHolder; - } - } - - return null; - } - } } } \ No newline at end of file diff --git a/Microsoft.Toolkit.Uwp.UI.Controls.Design/Microsoft.Toolkit.Uwp.UI.Controls.Design.csproj b/Microsoft.Toolkit.Uwp.UI.Controls.Design/Microsoft.Toolkit.Uwp.UI.Controls.Design.csproj index b611399dd13..bd0548cfa0e 100644 --- a/Microsoft.Toolkit.Uwp.UI.Controls.Design/Microsoft.Toolkit.Uwp.UI.Controls.Design.csproj +++ b/Microsoft.Toolkit.Uwp.UI.Controls.Design/Microsoft.Toolkit.Uwp.UI.Controls.Design.csproj @@ -98,7 +98,6 @@ - diff --git a/Microsoft.Toolkit.Uwp.UI.Controls.Design/TextToolbarMetadata.cs b/Microsoft.Toolkit.Uwp.UI.Controls.Design/TextToolbarMetadata.cs index aebed58eba6..47b52f1dd14 100644 --- a/Microsoft.Toolkit.Uwp.UI.Controls.Design/TextToolbarMetadata.cs +++ b/Microsoft.Toolkit.Uwp.UI.Controls.Design/TextToolbarMetadata.cs @@ -19,7 +19,6 @@ public TextToolbarMetadata() b => { b.AddCustomAttributes(nameof(TextToolbar.Editor), new CategoryAttribute(Properties.Resources.CategoryCommon)); - b.AddCustomAttributes(nameof(TextToolbar.Format), new CategoryAttribute(Properties.Resources.CategoryCommon)); b.AddCustomAttributes(nameof(TextToolbar.Formatter), new CategoryAttribute(Properties.Resources.CategoryCommon)); b.AddCustomAttributes(nameof(TextToolbar.DefaultButtons), new CategoryAttribute(Properties.Resources.CategoryCommon)); b.AddCustomAttributes(nameof(TextToolbar.CustomButtons), new CategoryAttribute(Properties.Resources.CategoryCommon)); diff --git a/Microsoft.Toolkit.Uwp.UI.Controls.Markdown.Design/Common/MetadataRegistrationBase.cs b/Microsoft.Toolkit.Uwp.UI.Controls.Markdown.Design/Common/MetadataRegistrationBase.cs new file mode 100644 index 00000000000..dc6300e1a36 --- /dev/null +++ b/Microsoft.Toolkit.Uwp.UI.Controls.Markdown.Design/Common/MetadataRegistrationBase.cs @@ -0,0 +1,252 @@ +// 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.Windows.Design.Metadata; +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; +using System.IO; +using System.Linq; +using System.Reflection; +using System.Xml.Linq; + +namespace Microsoft.Toolkit.Uwp.Design.Common +{ + public class MetadataRegistrationBase + { + private AttributeTable masterMetadataTable; + + internal MetadataRegistrationBase() { } + + /// + /// Build design time metadata attribute table. + /// + /// Custom attribute table. + protected virtual AttributeTable BuildAttributeTable() + { + AttributeTableBuilder builder = new AttributeTableBuilder(); + + AddDescriptions(builder); + AddAttributes(builder); + AddTables(builder, this); + masterMetadataTable = builder.CreateTable(); + return masterMetadataTable; + } + + /// + /// Find all AttributeTableBuilder subclasses in the assembly + /// and add their attributes to the assembly attribute table. + /// + /// The assembly attribute table builder. + [SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Justification = "Design time dll should not fail!")] + private void AddTables(AttributeTableBuilder builder, object parent) + { + Debug.Assert(builder != null, "AddTables is called with null parameter!"); + + Assembly asm = parent.GetType().Assembly; + foreach (Type t in asm.GetTypes()) + { + if (t.IsSubclassOf(typeof(AttributeTableBuilder))) + { + try + { + AttributeTableBuilder atb = (AttributeTableBuilder)Activator.CreateInstance(t); + builder.AddTable(atb.CreateTable()); + } + catch (Exception) + { + //error loading design assembly + } + } + } + } + + /// + /// Gets or sets the case sensitive resource name of the embedded XML file. + /// + protected string XmlResourceName { get; set; } + + /// + /// Gets or sets the FullName of the corresponding run time assembly. + /// + protected string AssemblyFullName { get; set; } + + /// + /// Create description attribute from run time assembly xml file. + /// + /// The assembly attribute table builder. + [SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Justification = "Design time dll should not fail.")] + private void AddDescriptions(AttributeTableBuilder builder) + { + Debug.Assert(builder != null, "AddDescriptions is called with null parameter!"); + + if (string.IsNullOrEmpty(XmlResourceName) || + string.IsNullOrEmpty(AssemblyFullName)) + { + return; + } + XDocument xdoc = null; + try + { + xdoc = XDocument.Load(new StreamReader( + Assembly.GetExecutingAssembly().GetManifestResourceStream(XmlResourceName))); + } + catch { return; } + if (xdoc == null) + { + return; + } + + foreach (XElement member in xdoc.Descendants("member")) + { + try + { + string name = (string)member.Attribute("name"); + if (name == null) + continue; + bool isType = name.StartsWith("T:", StringComparison.OrdinalIgnoreCase); + if (isType || + name.StartsWith("P:", StringComparison.OrdinalIgnoreCase)) + { + int lastDot = name.Length; + string typeName; + if (isType) + { + typeName = name.Substring(2); // skip leading "T:" + } + else + { + lastDot = name.LastIndexOf('.'); + typeName = name.Substring(2, lastDot - 2); + } + typeName += AssemblyFullName; + + Type t = Type.GetType(typeName); + if (t != null && t.IsPublic && t.IsClass && + t.IsSubclassOf(Types.PlatformTypes.DependencyObjectType)) + { + string desc = ParseDescription(member); + if (desc == null) + continue; + + desc = desc.Trim(); + desc = string.Join(" ", desc.Split(new char[] { ' ', '\t', '\n', '\r' }, StringSplitOptions.RemoveEmptyEntries)); + if (isType) + { + bool isBrowsable = true; + try + { + isBrowsable = IsBrowsable(t); + } + catch { isBrowsable = false; } + if (isBrowsable) + builder.AddCallback(t, b => b.AddCustomAttributes(new DescriptionAttribute(desc))); + else //Hide from intellisense + { + builder.AddCallback(t, b => b.AddCustomAttributes( + new BrowsableAttribute(false), + new Microsoft.Windows.Design.ToolboxBrowsableAttribute(false), + new ToolboxItemAttribute(false))); + } + } + else + { + string propName = name.Substring(lastDot + 1); + PropertyInfo pi = t.GetProperty(propName, BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly); + if (pi != null) + { + bool isBrowsable = true; + try + { + isBrowsable = IsBrowsable(pi); + } + catch { isBrowsable = false; } + if(isBrowsable) + builder.AddCallback(t, b => b.AddCustomAttributes(propName, new DescriptionAttribute(desc))); + else //Hide from intellisense + builder.AddCallback(t, b => b.AddCustomAttributes(new BrowsableAttribute(false))); + } + } + } + } + } + catch (Exception) + { + } + } + } + private static bool IsBrowsable(Type t) + { + var attrs = t.GetCustomAttributes(Types.PlatformTypes.EditorBrowsableAttributeType, false); + foreach (var attr in attrs) + { + return Types.PlatformTypes.IsBrowsable(attr); + } + return true; + } + + private static bool IsBrowsable(System.Reflection.PropertyInfo pi) + { + var attrs = pi.GetCustomAttributes(Types.PlatformTypes.EditorBrowsableAttributeType, false); + foreach (var attr in attrs) + { + return Types.PlatformTypes.IsBrowsable(attr); + } + return true; + } + + /// + /// Create description string from xml doc summary tag. + /// + /// A single node of the xml doc. + /// Description string. + private static string ParseDescription(XElement member) + { + string desc = null; + XElement memberDesc = member.Descendants("summary").FirstOrDefault(); + + if (memberDesc != null) + { + IEnumerable nodes = memberDesc.DescendantNodes(); + + if (nodes != null) + { + foreach (XNode node in nodes) + { + if (node.NodeType == System.Xml.XmlNodeType.Text) + { + desc += node.ToString(); + } + else + { + string s = node.ToString(); + int i = s.LastIndexOf('.'); + int j = s.LastIndexOf('"'); + + if ((i != -1 || j != -1) && j - i - 1 > 0) + { + try + { + desc += s.Substring(i + 1, j - i - 1); + } + catch { } + } + } + } + } + } + return desc; + } + + /// + /// Provide a place to add custom attributes without creating a AttributeTableBuilder subclass. + /// + /// The assembly attribute table builder. + protected virtual void AddAttributes(AttributeTableBuilder builder) + { + } + } +} \ No newline at end of file diff --git a/Microsoft.Toolkit.Uwp.UI.Controls.Markdown.Design/Common/PlatformTypes.cs b/Microsoft.Toolkit.Uwp.UI.Controls.Markdown.Design/Common/PlatformTypes.cs new file mode 100644 index 00000000000..b8ce3304a08 --- /dev/null +++ b/Microsoft.Toolkit.Uwp.UI.Controls.Markdown.Design/Common/PlatformTypes.cs @@ -0,0 +1,50 @@ +// 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 Microsoft.Windows.Design.Metadata; +using Windows.UI.Xaml; + +namespace Microsoft.Toolkit.Uwp.Design.Types +{ + internal class PlatformTypes + { + public static readonly Type DependencyObjectType = typeof(DependencyObject); + public static readonly Type UIElementType = typeof(UIElement); + public static readonly Type FrameworkElementType = typeof(FrameworkElement); + public static readonly Type EditorBrowsableAttributeType = typeof(System.ComponentModel.EditorBrowsableAttribute); + + /// + /// Used by MetadataRegistrationBase to get the browsable state + /// + /// This parameter must be of type 'System.ComponentModel.EditorBrowsableAttribute' + /// + public static bool IsBrowsable(object editorBrowsableAttribute) + { + if (editorBrowsableAttribute is System.ComponentModel.EditorBrowsableAttribute) + return (editorBrowsableAttribute as System.ComponentModel.EditorBrowsableAttribute).State != + System.ComponentModel.EditorBrowsableState.Never; + return true; + } + + public static class Control + { + public static readonly TypeIdentifier TypeId = new TypeIdentifier("http://schemas.microsoft.com/winfx/2006/xaml/presentation", "Control"); + public static readonly PropertyIdentifier BackgroundProperty = new PropertyIdentifier(TypeId, "Background"); + public static readonly PropertyIdentifier BorderBrushProperty = new PropertyIdentifier(TypeId, "BorderBrush"); + public static readonly PropertyIdentifier BorderThicknessProperty = new PropertyIdentifier(TypeId, "BorderThickness"); + } + + public static class FrameworkElement + { + public static readonly TypeIdentifier TypeId = new TypeIdentifier("http://schemas.microsoft.com/winfx/2006/xaml/presentation", "FrameworkElement"); + public static readonly PropertyIdentifier MarginProperty = new PropertyIdentifier(TypeId, "Margin"); + public static readonly PropertyIdentifier HorizontalAlignmentProperty = new PropertyIdentifier(TypeId, "HorizontalAlignment"); + public static readonly PropertyIdentifier VerticalAlignmentProperty = new PropertyIdentifier(TypeId, "VerticalAlignment"); + public static readonly PropertyIdentifier HeightProperty = new PropertyIdentifier(TypeId, "Height"); + public static readonly PropertyIdentifier WidthProperty = new PropertyIdentifier(TypeId, "Width"); + } + + } +} diff --git a/Microsoft.Toolkit.Uwp.UI.Controls.Markdown.Design/Common/ToolboxCategoryPaths.cs b/Microsoft.Toolkit.Uwp.UI.Controls.Markdown.Design/Common/ToolboxCategoryPaths.cs new file mode 100644 index 00000000000..acdacdd4c55 --- /dev/null +++ b/Microsoft.Toolkit.Uwp.UI.Controls.Markdown.Design/Common/ToolboxCategoryPaths.cs @@ -0,0 +1,23 @@ +// 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.Diagnostics.CodeAnalysis; +using System.Linq.Expressions; + +[assembly: SuppressMessage("General", "SWC1001:XmlDocumentationCommentShouldBeSpelledCorrectly", MessageId = "Theming", Justification = "Correct spelling")] + +namespace Microsoft.Toolkit.Uwp.UI.Controls.Design.Common +{ + /// + /// Names for ToolboxCategoryAttribute. + /// + internal static class ToolboxCategoryPaths + { + /// + /// Basic Controls category. + /// + public const string Toolkit = "Windows Community Toolkit"; + } +} diff --git a/Microsoft.Toolkit.Uwp.UI.Controls.Design/MarkdownTextBlockMetadata.cs b/Microsoft.Toolkit.Uwp.UI.Controls.Markdown.Design/MarkdownTextBlockMetadata.cs similarity index 100% rename from Microsoft.Toolkit.Uwp.UI.Controls.Design/MarkdownTextBlockMetadata.cs rename to Microsoft.Toolkit.Uwp.UI.Controls.Markdown.Design/MarkdownTextBlockMetadata.cs diff --git a/Microsoft.Toolkit.Uwp.UI.Controls.Markdown.Design/MetadataRegistration.cs b/Microsoft.Toolkit.Uwp.UI.Controls.Markdown.Design/MetadataRegistration.cs new file mode 100644 index 00000000000..5c0b0b2df70 --- /dev/null +++ b/Microsoft.Toolkit.Uwp.UI.Controls.Markdown.Design/MetadataRegistration.cs @@ -0,0 +1,46 @@ +// 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.Reflection; +using Microsoft.Windows.Design.Metadata; +using Microsoft.Toolkit.Uwp.Design.Common; + +[assembly: ProvideMetadata(typeof(Microsoft.Toolkit.Uwp.UI.Controls.Design.MetadataRegistration))] + +namespace Microsoft.Toolkit.Uwp.UI.Controls.Design +{ + public class MetadataRegistration : MetadataRegistrationBase, IProvideAttributeTable + { + public MetadataRegistration() : base() + { + // Note: + // The default constructor sets value of AssemblyFullName and + // XmlResourceName used by MetadataRegistrationBase.AddDescriptions(). + // The convention here is that the in .design.csproj + // (or Default namespace in Project -> Properties -> Application tab) + // must be the same as runtime assembly's main namespace (t.Namespace) + // plus .Design. + Type t = typeof(Microsoft.Toolkit.Uwp.UI.Controls.MarkdownTextBlock); + AssemblyName an = t.Assembly.GetName(); + AssemblyFullName = ", " + an.FullName; + XmlResourceName = t.Namespace + ".Design." + an.Name + ".xml"; + } + + #region IProvideAttributeTable Members + + /// + /// Gets the AttributeTable for design time metadata. + /// + public AttributeTable AttributeTable + { + get + { + return BuildAttributeTable(); + } + } + + #endregion + } +} \ No newline at end of file diff --git a/Microsoft.Toolkit.Uwp.UI.Controls.Markdown.Design/Microsoft.Toolkit.Uwp.UI.Controls.Markdown.Design.csproj b/Microsoft.Toolkit.Uwp.UI.Controls.Markdown.Design/Microsoft.Toolkit.Uwp.UI.Controls.Markdown.Design.csproj new file mode 100644 index 00000000000..a0fee831253 --- /dev/null +++ b/Microsoft.Toolkit.Uwp.UI.Controls.Markdown.Design/Microsoft.Toolkit.Uwp.UI.Controls.Markdown.Design.csproj @@ -0,0 +1,126 @@ + + + + + Debug + x86 + {67FE47A0-CA93-4680-B770-A0A48C1DBC40} + Library + Properties + Microsoft.Toolkit.Uwp.UI.Controls.Markdown.Design + Microsoft.Toolkit.Uwp.UI.Controls.Markdown.Design + v4.7.2 + 512 + + $(NoWarn);0618 + $(AssetTargetFallback);uap10.0.16299 + + + 8.1 + + + true + ..\Microsoft.Toolkit.Uwp.UI.Controls.Markdown\bin\Debug\uap10.0.16299\Design\ + full + false + DEBUG;TRACE + x86 + + + ..\Microsoft.Toolkit.Uwp.UI.Controls.Markdown\bin\Release\uap10.0.16299\Design\ + pdbonly + x86 + true + TRACE + + + false + + + + + + + + False + False + + + False + False + + + + + + + + + + False + $(MSBuildProgramFiles32)\Windows Kits\10\UnionMetadata\10.0.17763.0\Windows.winmd + + + + + + {e9faabfb-d726-42c1-83c1-cb46a29fea81} + Microsoft.Toolkit.Uwp.UI.Controls.Markdown + False + true + + + + + + + $(ProgramFiles)\Windows Kits\10\References\10.0.17763.0\Windows.Foundation.FoundationContract\3.0.0.0\Windows.Foundation.FoundationContract.winmd + WindowsRuntime + False + + + $(ProgramFiles)\Windows Kits\10\References\10.0.17763.0\Windows.Foundation.UniversalApiContract\7.0.0.0\Windows.Foundation.UniversalApiContract.winmd + WindowsRuntime + False + + + + + + + + + Code + + + True + True + Resources.resx + + + True + Settings.settings + True + + + + ResXFileCodeGenerator + Resources.Designer.cs + + + SettingsSingleFileGenerator + Settings.Designer.cs + + + + + + Microsoft.Toolkit.Uwp.UI.Controls.Markdown.xml + Designer + + + + + + + \ No newline at end of file diff --git a/Microsoft.Toolkit.Uwp.UI.Controls.Markdown.Design/Properties/AssemblyInfo.cs b/Microsoft.Toolkit.Uwp.UI.Controls.Markdown.Design/Properties/AssemblyInfo.cs new file mode 100644 index 00000000000..06b96fdf78d --- /dev/null +++ b/Microsoft.Toolkit.Uwp.UI.Controls.Markdown.Design/Properties/AssemblyInfo.cs @@ -0,0 +1,35 @@ +// 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.Reflection; +using System.Resources; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Windows; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("Windows Community Toolkit Controls (Design)")] +[assembly: AssemblyDescription("Design time support for Windows Community Toolkit Controls")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("Microsoft")] +[assembly: AssemblyProduct("Windows Community Toolkit")] +[assembly: AssemblyCopyright("Copyright © Microsoft 2020")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +//In order to begin building localizable applications, set +//CultureYouAreCodingWith in your .csproj file +//inside a . For example, if you are using US english +//in your source files, set the to en-US. Then uncomment +//the NeutralResourceLanguage attribute below. Update the "en-US" in +//the line below to match the UICulture setting in the project file. + +//[assembly: NeutralResourcesLanguage("en-US", UltimateResourceFallbackLocation.Satellite)] diff --git a/Microsoft.Toolkit.Uwp.UI.Controls.Markdown.Design/Properties/Resources.Designer.cs b/Microsoft.Toolkit.Uwp.UI.Controls.Markdown.Design/Properties/Resources.Designer.cs new file mode 100644 index 00000000000..e160035a377 --- /dev/null +++ b/Microsoft.Toolkit.Uwp.UI.Controls.Markdown.Design/Properties/Resources.Designer.cs @@ -0,0 +1,180 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.42000 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace Microsoft.Toolkit.Uwp.UI.Controls.Design.Properties { + using System; + + + /// + /// A strongly-typed resource class, for looking up localized strings, etc. + /// + // This class was auto-generated by the StronglyTypedResourceBuilder + // class via a tool like ResGen or Visual Studio. + // To add or remove a member, edit your .ResX file then rerun ResGen + // with the /str option, or rebuild your VS project. + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "16.0.0.0")] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + internal class Resources { + + private static global::System.Resources.ResourceManager resourceMan; + + private static global::System.Globalization.CultureInfo resourceCulture; + + [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + internal Resources() { + } + + /// + /// Returns the cached ResourceManager instance used by this class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Resources.ResourceManager ResourceManager { + get { + if (object.ReferenceEquals(resourceMan, null)) { + global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Microsoft.Toolkit.Uwp.UI.Controls.Design.Properties.Resources", typeof(Resources).Assembly); + resourceMan = temp; + } + return resourceMan; + } + } + + /// + /// Overrides the current thread's CurrentUICulture property for all + /// resource lookups using this strongly typed resource class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Globalization.CultureInfo Culture { + get { + return resourceCulture; + } + set { + resourceCulture = value; + } + } + + /// + /// Looks up a localized string similar to Appearance. + /// + internal static string CategoryAppearance { + get { + return ResourceManager.GetString("CategoryAppearance", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Brush. + /// + internal static string CategoryBrush { + get { + return ResourceManager.GetString("CategoryBrush", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Common. + /// + internal static string CategoryCommon { + get { + return ResourceManager.GetString("CategoryCommon", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Drop Shadow. + /// + internal static string CategoryDropShadow { + get { + return ResourceManager.GetString("CategoryDropShadow", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Interactions. + /// + internal static string CategoryInteractions { + get { + return ResourceManager.GetString("CategoryInteractions", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Layout. + /// + internal static string CategoryLayout { + get { + return ResourceManager.GetString("CategoryLayout", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Markdown Style - Code. + /// + internal static string CategoryMarkdownCodeStyle { + get { + return ResourceManager.GetString("CategoryMarkdownCodeStyle", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Markdown Style - Header {0}. + /// + internal static string CategoryMarkdownHeaderStyle { + get { + return ResourceManager.GetString("CategoryMarkdownHeaderStyle", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Markdown Style - List. + /// + internal static string CategoryMarkdownListStyle { + get { + return ResourceManager.GetString("CategoryMarkdownListStyle", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Markdown Style - Quote. + /// + internal static string CategoryMarkdownQuoteStyle { + get { + return ResourceManager.GetString("CategoryMarkdownQuoteStyle", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Markdown Style. + /// + internal static string CategoryMarkdownStyle { + get { + return ResourceManager.GetString("CategoryMarkdownStyle", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Markdown Style - Table. + /// + internal static string CategoryMarkdownTableStyle { + get { + return ResourceManager.GetString("CategoryMarkdownTableStyle", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Text. + /// + internal static string CategoryText { + get { + return ResourceManager.GetString("CategoryText", resourceCulture); + } + } + } +} diff --git a/Microsoft.Toolkit.Uwp.UI.Controls.Markdown.Design/Properties/Resources.resx b/Microsoft.Toolkit.Uwp.UI.Controls.Markdown.Design/Properties/Resources.resx new file mode 100644 index 00000000000..e8903ef43b7 --- /dev/null +++ b/Microsoft.Toolkit.Uwp.UI.Controls.Markdown.Design/Properties/Resources.resx @@ -0,0 +1,159 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Appearance + + + Brush + + + Common + + + Drop Shadow + + + Interactions + + + Layout + + + Markdown Style - Code + + + Markdown Style - Header {0} + + + Markdown Style - List + + + Markdown Style - Quote + + + Markdown Style + + + Markdown Style - Table + + + Text + + \ No newline at end of file diff --git a/Microsoft.Toolkit.Uwp.UI.Controls.Markdown.Design/Properties/Settings.Designer.cs b/Microsoft.Toolkit.Uwp.UI.Controls.Markdown.Design/Properties/Settings.Designer.cs new file mode 100644 index 00000000000..662fe0ad430 --- /dev/null +++ b/Microsoft.Toolkit.Uwp.UI.Controls.Markdown.Design/Properties/Settings.Designer.cs @@ -0,0 +1,26 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.42000 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace Microsoft.Toolkit.Uwp.UI.Controls.Design.Properties { + + + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "16.3.0.0")] + internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase { + + private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings()))); + + public static Settings Default { + get { + return defaultInstance; + } + } + } +} diff --git a/Microsoft.Toolkit.Uwp.UI.Controls.Markdown.Design/Properties/Settings.settings b/Microsoft.Toolkit.Uwp.UI.Controls.Markdown.Design/Properties/Settings.settings new file mode 100644 index 00000000000..033d7a5e9e2 --- /dev/null +++ b/Microsoft.Toolkit.Uwp.UI.Controls.Markdown.Design/Properties/Settings.settings @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/Microsoft.Toolkit.Uwp.UI.Controls/MarkdownTextBlock/About.txt b/Microsoft.Toolkit.Uwp.UI.Controls.Markdown/About.txt similarity index 100% rename from Microsoft.Toolkit.Uwp.UI.Controls/MarkdownTextBlock/About.txt rename to Microsoft.Toolkit.Uwp.UI.Controls.Markdown/About.txt diff --git a/Microsoft.Toolkit.Uwp.UI.Controls/MarkdownTextBlock/CodeBlockResolvingEventArgs.cs b/Microsoft.Toolkit.Uwp.UI.Controls.Markdown/MarkdownTextBlock/CodeBlockResolvingEventArgs.cs similarity index 100% rename from Microsoft.Toolkit.Uwp.UI.Controls/MarkdownTextBlock/CodeBlockResolvingEventArgs.cs rename to Microsoft.Toolkit.Uwp.UI.Controls.Markdown/MarkdownTextBlock/CodeBlockResolvingEventArgs.cs diff --git a/Microsoft.Toolkit.Uwp.UI.Controls/MarkdownTextBlock/ImageResolvingEventArgs.cs b/Microsoft.Toolkit.Uwp.UI.Controls.Markdown/MarkdownTextBlock/ImageResolvingEventArgs.cs similarity index 100% rename from Microsoft.Toolkit.Uwp.UI.Controls/MarkdownTextBlock/ImageResolvingEventArgs.cs rename to Microsoft.Toolkit.Uwp.UI.Controls.Markdown/MarkdownTextBlock/ImageResolvingEventArgs.cs diff --git a/Microsoft.Toolkit.Uwp.UI.Controls/MarkdownTextBlock/LinkClickedEventArgs.cs b/Microsoft.Toolkit.Uwp.UI.Controls.Markdown/MarkdownTextBlock/LinkClickedEventArgs.cs similarity index 100% rename from Microsoft.Toolkit.Uwp.UI.Controls/MarkdownTextBlock/LinkClickedEventArgs.cs rename to Microsoft.Toolkit.Uwp.UI.Controls.Markdown/MarkdownTextBlock/LinkClickedEventArgs.cs diff --git a/Microsoft.Toolkit.Uwp.UI.Controls/MarkdownTextBlock/MarkdownRenderedEventArgs.cs b/Microsoft.Toolkit.Uwp.UI.Controls.Markdown/MarkdownTextBlock/MarkdownRenderedEventArgs.cs similarity index 100% rename from Microsoft.Toolkit.Uwp.UI.Controls/MarkdownTextBlock/MarkdownRenderedEventArgs.cs rename to Microsoft.Toolkit.Uwp.UI.Controls.Markdown/MarkdownTextBlock/MarkdownRenderedEventArgs.cs diff --git a/Microsoft.Toolkit.Uwp.UI.Controls/MarkdownTextBlock/MarkdownTextBlock.Dimensions.cs b/Microsoft.Toolkit.Uwp.UI.Controls.Markdown/MarkdownTextBlock/MarkdownTextBlock.Dimensions.cs similarity index 100% rename from Microsoft.Toolkit.Uwp.UI.Controls/MarkdownTextBlock/MarkdownTextBlock.Dimensions.cs rename to Microsoft.Toolkit.Uwp.UI.Controls.Markdown/MarkdownTextBlock/MarkdownTextBlock.Dimensions.cs diff --git a/Microsoft.Toolkit.Uwp.UI.Controls/MarkdownTextBlock/MarkdownTextBlock.Events.cs b/Microsoft.Toolkit.Uwp.UI.Controls.Markdown/MarkdownTextBlock/MarkdownTextBlock.Events.cs similarity index 100% rename from Microsoft.Toolkit.Uwp.UI.Controls/MarkdownTextBlock/MarkdownTextBlock.Events.cs rename to Microsoft.Toolkit.Uwp.UI.Controls.Markdown/MarkdownTextBlock/MarkdownTextBlock.Events.cs diff --git a/Microsoft.Toolkit.Uwp.UI.Controls/MarkdownTextBlock/MarkdownTextBlock.Methods.cs b/Microsoft.Toolkit.Uwp.UI.Controls.Markdown/MarkdownTextBlock/MarkdownTextBlock.Methods.cs similarity index 100% rename from Microsoft.Toolkit.Uwp.UI.Controls/MarkdownTextBlock/MarkdownTextBlock.Methods.cs rename to Microsoft.Toolkit.Uwp.UI.Controls.Markdown/MarkdownTextBlock/MarkdownTextBlock.Methods.cs diff --git a/Microsoft.Toolkit.Uwp.UI.Controls/MarkdownTextBlock/MarkdownTextBlock.Properties.cs b/Microsoft.Toolkit.Uwp.UI.Controls.Markdown/MarkdownTextBlock/MarkdownTextBlock.Properties.cs similarity index 100% rename from Microsoft.Toolkit.Uwp.UI.Controls/MarkdownTextBlock/MarkdownTextBlock.Properties.cs rename to Microsoft.Toolkit.Uwp.UI.Controls.Markdown/MarkdownTextBlock/MarkdownTextBlock.Properties.cs diff --git a/Microsoft.Toolkit.Uwp.UI.Controls/MarkdownTextBlock/MarkdownTextBlock.cs b/Microsoft.Toolkit.Uwp.UI.Controls.Markdown/MarkdownTextBlock/MarkdownTextBlock.cs similarity index 100% rename from Microsoft.Toolkit.Uwp.UI.Controls/MarkdownTextBlock/MarkdownTextBlock.cs rename to Microsoft.Toolkit.Uwp.UI.Controls.Markdown/MarkdownTextBlock/MarkdownTextBlock.cs diff --git a/Microsoft.Toolkit.Uwp.UI.Controls/MarkdownTextBlock/MarkdownTextBlock.xaml b/Microsoft.Toolkit.Uwp.UI.Controls.Markdown/MarkdownTextBlock/MarkdownTextBlock.xaml similarity index 100% rename from Microsoft.Toolkit.Uwp.UI.Controls/MarkdownTextBlock/MarkdownTextBlock.xaml rename to Microsoft.Toolkit.Uwp.UI.Controls.Markdown/MarkdownTextBlock/MarkdownTextBlock.xaml diff --git a/Microsoft.Toolkit.Uwp.UI.Controls.Markdown/Microsoft.Toolkit.Uwp.UI.Controls.Markdown.csproj b/Microsoft.Toolkit.Uwp.UI.Controls.Markdown/Microsoft.Toolkit.Uwp.UI.Controls.Markdown.csproj new file mode 100644 index 00000000000..9fe9742d1a9 --- /dev/null +++ b/Microsoft.Toolkit.Uwp.UI.Controls.Markdown/Microsoft.Toolkit.Uwp.UI.Controls.Markdown.csproj @@ -0,0 +1,49 @@ + + + uap10.0.16299 + Windows Community Toolkit Controls Markdown + + This library provides a XAML MarkdownTextBlock control, an efficient and extensible control that can parse and render markdown. It is part of the Windows Community Toolkit. + + UWP Toolkit Windows Controls XAML Markdown MarkdownTextBlock + Microsoft.Toolkit.Uwp.UI.Controls + + false + + + + + + + + + + + + + + + + + True + True + Resources.resx + + + + + + ResXFileCodeGenerator + Resources.Designer.cs + + + + + + + + + + + + diff --git a/Microsoft.Toolkit.Uwp.UI.Controls/MarkdownTextBlock/Render/ICodeBlockResolver.cs b/Microsoft.Toolkit.Uwp.UI.Controls.Markdown/Render/ICodeBlockResolver.cs similarity index 100% rename from Microsoft.Toolkit.Uwp.UI.Controls/MarkdownTextBlock/Render/ICodeBlockResolver.cs rename to Microsoft.Toolkit.Uwp.UI.Controls.Markdown/Render/ICodeBlockResolver.cs diff --git a/Microsoft.Toolkit.Uwp.UI.Controls/MarkdownTextBlock/Render/IImageResolver.cs b/Microsoft.Toolkit.Uwp.UI.Controls.Markdown/Render/IImageResolver.cs similarity index 100% rename from Microsoft.Toolkit.Uwp.UI.Controls/MarkdownTextBlock/Render/IImageResolver.cs rename to Microsoft.Toolkit.Uwp.UI.Controls.Markdown/Render/IImageResolver.cs diff --git a/Microsoft.Toolkit.Uwp.UI.Controls/MarkdownTextBlock/Render/ILinkRegister.cs b/Microsoft.Toolkit.Uwp.UI.Controls.Markdown/Render/ILinkRegister.cs similarity index 100% rename from Microsoft.Toolkit.Uwp.UI.Controls/MarkdownTextBlock/Render/ILinkRegister.cs rename to Microsoft.Toolkit.Uwp.UI.Controls.Markdown/Render/ILinkRegister.cs diff --git a/Microsoft.Toolkit.Uwp.UI.Controls/MarkdownTextBlock/Render/InlineRenderContext.cs b/Microsoft.Toolkit.Uwp.UI.Controls.Markdown/Render/InlineRenderContext.cs similarity index 100% rename from Microsoft.Toolkit.Uwp.UI.Controls/MarkdownTextBlock/Render/InlineRenderContext.cs rename to Microsoft.Toolkit.Uwp.UI.Controls.Markdown/Render/InlineRenderContext.cs diff --git a/Microsoft.Toolkit.Uwp.UI.Controls/MarkdownTextBlock/Render/MarkdownRenderer.Blocks.cs b/Microsoft.Toolkit.Uwp.UI.Controls.Markdown/Render/MarkdownRenderer.Blocks.cs similarity index 100% rename from Microsoft.Toolkit.Uwp.UI.Controls/MarkdownTextBlock/Render/MarkdownRenderer.Blocks.cs rename to Microsoft.Toolkit.Uwp.UI.Controls.Markdown/Render/MarkdownRenderer.Blocks.cs diff --git a/Microsoft.Toolkit.Uwp.UI.Controls/MarkdownTextBlock/Render/MarkdownRenderer.Dimensions.cs b/Microsoft.Toolkit.Uwp.UI.Controls.Markdown/Render/MarkdownRenderer.Dimensions.cs similarity index 100% rename from Microsoft.Toolkit.Uwp.UI.Controls/MarkdownTextBlock/Render/MarkdownRenderer.Dimensions.cs rename to Microsoft.Toolkit.Uwp.UI.Controls.Markdown/Render/MarkdownRenderer.Dimensions.cs diff --git a/Microsoft.Toolkit.Uwp.UI.Controls/MarkdownTextBlock/Render/MarkdownRenderer.Inlines.cs b/Microsoft.Toolkit.Uwp.UI.Controls.Markdown/Render/MarkdownRenderer.Inlines.cs similarity index 100% rename from Microsoft.Toolkit.Uwp.UI.Controls/MarkdownTextBlock/Render/MarkdownRenderer.Inlines.cs rename to Microsoft.Toolkit.Uwp.UI.Controls.Markdown/Render/MarkdownRenderer.Inlines.cs diff --git a/Microsoft.Toolkit.Uwp.UI.Controls/MarkdownTextBlock/Render/MarkdownRenderer.Properties.cs b/Microsoft.Toolkit.Uwp.UI.Controls.Markdown/Render/MarkdownRenderer.Properties.cs similarity index 100% rename from Microsoft.Toolkit.Uwp.UI.Controls/MarkdownTextBlock/Render/MarkdownRenderer.Properties.cs rename to Microsoft.Toolkit.Uwp.UI.Controls.Markdown/Render/MarkdownRenderer.Properties.cs diff --git a/Microsoft.Toolkit.Uwp.UI.Controls/MarkdownTextBlock/Render/MarkdownRenderer.cs b/Microsoft.Toolkit.Uwp.UI.Controls.Markdown/Render/MarkdownRenderer.cs similarity index 100% rename from Microsoft.Toolkit.Uwp.UI.Controls/MarkdownTextBlock/Render/MarkdownRenderer.cs rename to Microsoft.Toolkit.Uwp.UI.Controls.Markdown/Render/MarkdownRenderer.cs diff --git a/Microsoft.Toolkit.Uwp.UI.Controls/MarkdownTextBlock/Render/MarkdownTable.cs b/Microsoft.Toolkit.Uwp.UI.Controls.Markdown/Render/MarkdownTable.cs similarity index 100% rename from Microsoft.Toolkit.Uwp.UI.Controls/MarkdownTextBlock/Render/MarkdownTable.cs rename to Microsoft.Toolkit.Uwp.UI.Controls.Markdown/Render/MarkdownTable.cs diff --git a/Microsoft.Toolkit.Uwp.UI.Controls/MarkdownTextBlock/Render/RenderContext.cs b/Microsoft.Toolkit.Uwp.UI.Controls.Markdown/Render/RenderContext.cs similarity index 100% rename from Microsoft.Toolkit.Uwp.UI.Controls/MarkdownTextBlock/Render/RenderContext.cs rename to Microsoft.Toolkit.Uwp.UI.Controls.Markdown/Render/RenderContext.cs diff --git a/Microsoft.Toolkit.Uwp.UI.Controls/MarkdownTextBlock/Render/RenderContextIncorrectException.cs b/Microsoft.Toolkit.Uwp.UI.Controls.Markdown/Render/RenderContextIncorrectException.cs similarity index 100% rename from Microsoft.Toolkit.Uwp.UI.Controls/MarkdownTextBlock/Render/RenderContextIncorrectException.cs rename to Microsoft.Toolkit.Uwp.UI.Controls.Markdown/Render/RenderContextIncorrectException.cs diff --git a/Microsoft.Toolkit.Uwp.UI.Controls/MarkdownTextBlock/Render/UIElementCollectionRenderContext.cs b/Microsoft.Toolkit.Uwp.UI.Controls.Markdown/Render/UIElementCollectionRenderContext.cs similarity index 100% rename from Microsoft.Toolkit.Uwp.UI.Controls/MarkdownTextBlock/Render/UIElementCollectionRenderContext.cs rename to Microsoft.Toolkit.Uwp.UI.Controls.Markdown/Render/UIElementCollectionRenderContext.cs diff --git a/Microsoft.Toolkit.Uwp.UI.Controls/TextToolbar/Formats/MarkDown/MarkDownButtonActions.cs b/Microsoft.Toolkit.Uwp.UI.Controls.Markdown/TextToolbar/Formats/MarkDown/MarkDownButtonActions.cs similarity index 100% rename from Microsoft.Toolkit.Uwp.UI.Controls/TextToolbar/Formats/MarkDown/MarkDownButtonActions.cs rename to Microsoft.Toolkit.Uwp.UI.Controls.Markdown/TextToolbar/Formats/MarkDown/MarkDownButtonActions.cs diff --git a/Microsoft.Toolkit.Uwp.UI.Controls/TextToolbar/Formats/MarkDown/MarkDownFormatter.cs b/Microsoft.Toolkit.Uwp.UI.Controls.Markdown/TextToolbar/Formats/MarkDown/MarkDownFormatter.cs similarity index 96% rename from Microsoft.Toolkit.Uwp.UI.Controls/TextToolbar/Formats/MarkDown/MarkDownFormatter.cs rename to Microsoft.Toolkit.Uwp.UI.Controls.Markdown/TextToolbar/Formats/MarkDown/MarkDownFormatter.cs index a68de9da77a..75a714e1424 100644 --- a/Microsoft.Toolkit.Uwp.UI.Controls/TextToolbar/Formats/MarkDown/MarkDownFormatter.cs +++ b/Microsoft.Toolkit.Uwp.UI.Controls.Markdown/TextToolbar/Formats/MarkDown/MarkDownFormatter.cs @@ -20,13 +20,15 @@ namespace Microsoft.Toolkit.Uwp.UI.Controls.TextToolbarFormats.MarkDown /// public class MarkDownFormatter : Formatter { - /// - /// Initializes a new instance of the class. - /// - /// where formatter will be used - public MarkDownFormatter(TextToolbar model) - : base(model) + internal const string QuoteElement = "Quote"; + internal const string HeadersElement = "Headers"; + internal const string CodeElement = "Code"; + + /// + public override void SetModel(TextToolbar model) { + base.SetModel(model); + CommonButtons = new CommonButtons(model); ButtonActions = new MarkDownButtonActions(this); } @@ -40,7 +42,7 @@ public void StyleHeader(ToolbarButton button) var list = new ListBox { Margin = new Thickness(0), Padding = new Thickness(0) }; headerFlyout = new Flyout { Content = list }; - if (ControlHelpers.IsXamlRootAvailable && button.XamlRoot != null) + if (Windows.Foundation.Metadata.ApiInformation.IsPropertyPresent("Windows.UI.Xaml.UIElement", "XamlRoot") && button.XamlRoot != null) { headerFlyout.XamlRoot = button.XamlRoot; } @@ -438,7 +440,7 @@ internal string OrderedListIterate() return ListLineIterator + ". "; } - private CommonButtons CommonButtons { get; } + private CommonButtons CommonButtons { get; set; } /// public override string Text @@ -459,7 +461,7 @@ public override ButtonMap DefaultButtons OrderedListButton = OrderedListButton ?? CommonButtons.OrderedList; QuoteButton = new ToolbarButton { - Name = TextToolbar.QuoteElement, + Name = QuoteElement, ToolTip = StringExtensions.GetLocalized("TextToolbarStrings_QuoteLabel", "Microsoft.Toolkit.Uwp.UI.Controls/Resources"), Icon = new SymbolIcon { Symbol = Symbol.Message }, Activation = FormatQuote @@ -475,14 +477,14 @@ public override ButtonMap DefaultButtons new ToolbarButton { - Name = TextToolbar.HeadersElement, + Name = HeadersElement, Icon = new SymbolIcon { Symbol = Symbol.FontSize }, ToolTip = StringExtensions.GetLocalized("TextToolbarStrings_HeaderLabel", "Microsoft.Toolkit.Uwp.UI.Controls/Resources"), Activation = StyleHeader }, new ToolbarButton { - Name = TextToolbar.CodeElement, + Name = CodeElement, ToolTip = StringExtensions.GetLocalized("TextToolbarStrings_CodeLabel", "Microsoft.Toolkit.Uwp.UI.Controls/Resources"), Icon = new FontIcon { Glyph = "{}", FontFamily = new FontFamily("Segoe UI"), Margin = new Thickness(0, -5, 0, 0) }, Activation = FormatCode diff --git a/Microsoft.Toolkit.Uwp.UI.Controls.Markdown/Themes/Generic.xaml b/Microsoft.Toolkit.Uwp.UI.Controls.Markdown/Themes/Generic.xaml new file mode 100644 index 00000000000..4ab8a3f39be --- /dev/null +++ b/Microsoft.Toolkit.Uwp.UI.Controls.Markdown/Themes/Generic.xaml @@ -0,0 +1,6 @@ + + + + + \ No newline at end of file diff --git a/Microsoft.Toolkit.Uwp.UI.Controls.Markdown/VisualStudioToolsManifest.xml b/Microsoft.Toolkit.Uwp.UI.Controls.Markdown/VisualStudioToolsManifest.xml new file mode 100644 index 00000000000..ce34feb76a6 --- /dev/null +++ b/Microsoft.Toolkit.Uwp.UI.Controls.Markdown/VisualStudioToolsManifest.xml @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/Microsoft.Toolkit.Uwp.UI.Controls/Microsoft.Toolkit.Uwp.UI.Controls.csproj b/Microsoft.Toolkit.Uwp.UI.Controls/Microsoft.Toolkit.Uwp.UI.Controls.csproj index 6174415e0f3..c0d674762f4 100644 --- a/Microsoft.Toolkit.Uwp.UI.Controls/Microsoft.Toolkit.Uwp.UI.Controls.csproj +++ b/Microsoft.Toolkit.Uwp.UI.Controls/Microsoft.Toolkit.Uwp.UI.Controls.csproj @@ -24,7 +24,6 @@ - InfiniteCanvas: Supports Infinite Scrolling, Ink, Text, Format Text, Zoom in/out, Redo, Undo, Export & Import. - LayoutTransformControl: Support for transformations as if applied by LayoutTransform. - Loading: Helps to show content with animation to the user while the app is doing some calculation. - - MarkdownTextBlock: An efficient and extensible control that can parse and render markdown. - MasterDetailsView: Implements the Master/Details design pattern. - OrbitView: Positions items in a circle around a center element and supports orbits and anchors. - RadialGauge: Displays a value within a range, using a needle on a circular face. @@ -46,11 +45,12 @@ - + + + - diff --git a/Microsoft.Toolkit.Uwp.UI.Controls/TextToolbar/Formats/Format.cs b/Microsoft.Toolkit.Uwp.UI.Controls/TextToolbar/Formats/Format.cs deleted file mode 100644 index 76fa0c020e7..00000000000 --- a/Microsoft.Toolkit.Uwp.UI.Controls/TextToolbar/Formats/Format.cs +++ /dev/null @@ -1,27 +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. - -namespace Microsoft.Toolkit.Uwp.UI.Controls.TextToolbarFormats -{ - /// - /// Identifies the Format to be used by - /// - public enum Format - { - /// - /// Utilises the Built-In RichText Formatter - /// - RichText, - - /// - /// Utilises the Built-In Markdown Formatter - /// - MarkDown, - - /// - /// Utilises the provided Custom Formatter using the Formatter Property - /// - Custom - } -} \ No newline at end of file diff --git a/Microsoft.Toolkit.Uwp.UI.Controls/TextToolbar/Formats/Formatter.cs b/Microsoft.Toolkit.Uwp.UI.Controls/TextToolbar/Formats/Formatter.cs index 25669a72fe1..0a96ee41542 100644 --- a/Microsoft.Toolkit.Uwp.UI.Controls/TextToolbar/Formats/Formatter.cs +++ b/Microsoft.Toolkit.Uwp.UI.Controls/TextToolbar/Formats/Formatter.cs @@ -13,16 +13,6 @@ namespace Microsoft.Toolkit.Uwp.UI.Controls.TextToolbarFormats /// public abstract class Formatter { - /// - /// Initializes a new instance of the class. - /// - /// The where Formatter is used - public Formatter(TextToolbar model) - { - Model = model; - Model.EditorChanged += Model_EditorChanged; - } - /// /// Called when text editor has changed /// @@ -155,7 +145,31 @@ public virtual void OnSelectionChanged() /// /// Gets the source Toolbar /// - public TextToolbar Model { get; } + public TextToolbar Model { get; private set; } + + /// + /// This method is called to unset event handlers that might have been registers by + /// + /// The old the Formatter was associated with + public virtual void UnsetModel(TextToolbar model) + { + model.EditorChanged -= Model_EditorChanged; + } + + /// + /// Sets the where the Formatter is used + /// + /// The where Formatter is used + public virtual void SetModel(TextToolbar model) + { + if (Model != null) + { + Model.EditorChanged -= Model_EditorChanged; + } + + Model = model; + Model.EditorChanged += Model_EditorChanged; + } /// /// Gets or sets a map of the Actions taken when a button is pressed. Required for Common Button actions (Unless you override both Activation and ShiftActivation) diff --git a/Microsoft.Toolkit.Uwp.UI.Controls/TextToolbar/Formats/RichText/RichTextFormatter.cs b/Microsoft.Toolkit.Uwp.UI.Controls/TextToolbar/Formats/RichText/RichTextFormatter.cs index 3523ef61193..6e792a24fd4 100644 --- a/Microsoft.Toolkit.Uwp.UI.Controls/TextToolbar/Formats/RichText/RichTextFormatter.cs +++ b/Microsoft.Toolkit.Uwp.UI.Controls/TextToolbar/Formats/RichText/RichTextFormatter.cs @@ -16,13 +16,11 @@ namespace Microsoft.Toolkit.Uwp.UI.Controls.TextToolbarFormats.RichText /// public class RichTextFormatter : Formatter { - /// - /// Initializes a new instance of the class. - /// - /// The - public RichTextFormatter(TextToolbar model) - : base(model) + /// + public override void SetModel(TextToolbar model) { + base.SetModel(model); + CommonButtons = new CommonButtons(model); ButtonActions = new RichTextButtonActions(this); } @@ -88,7 +86,7 @@ public override void OnSelectionChanged() base.OnSelectionChanged(); } - private CommonButtons CommonButtons { get; } + private CommonButtons CommonButtons { get; set; } /// public override string Text diff --git a/Microsoft.Toolkit.Uwp.UI.Controls/TextToolbar/TextToolbar.Events.cs b/Microsoft.Toolkit.Uwp.UI.Controls/TextToolbar/TextToolbar.Events.cs index 986346539ec..e74e5345cfb 100644 --- a/Microsoft.Toolkit.Uwp.UI.Controls/TextToolbar/TextToolbar.Events.cs +++ b/Microsoft.Toolkit.Uwp.UI.Controls/TextToolbar/TextToolbar.Events.cs @@ -6,6 +6,7 @@ using System.Collections.Specialized; using System.Linq; using Microsoft.Toolkit.Uwp.UI.Controls.TextToolbarButtons; +using Microsoft.Toolkit.Uwp.UI.Controls.TextToolbarFormats; using Windows.System; using Windows.UI.Core; using Windows.UI.Xaml; @@ -45,7 +46,8 @@ private static void OnEditorChanged(DependencyObject obj, DependencyPropertyChan if (newEditor != null) { newEditor.AddHandler(KeyDownEvent, bar.KeyEventHandler, handledEventsToo: true); - bar.CreateFormatter(); + bar.Formatter.SetModel(bar); + bar.DefaultButtons = bar.Formatter.DefaultButtons; } var editorArgs = new EditorChangedArgs @@ -58,20 +60,6 @@ private static void OnEditorChanged(DependencyObject obj, DependencyPropertyChan } } - /// - /// Creates a new formatter, if it is a built-in formatter. - /// - /// TextToolbar - /// Property Changed Args - private static void OnFormatTypeChanged(DependencyObject obj, DependencyPropertyChangedEventArgs args) - { - var bar = obj as TextToolbar; - if (bar != null) - { - bar.CreateFormatter(); - } - } - /// /// Rebuilds the Toolbar if the formatter changes during operation /// @@ -82,6 +70,12 @@ private static void OnFormatterChanged(DependencyObject obj, DependencyPropertyC var bar = obj as TextToolbar; if (bar != null && bar.Formatter != null) { + if (args.OldValue is Formatter formatter) + { + formatter.UnsetModel(bar); + } + + bar.Formatter.SetModel(bar); bar.DefaultButtons = bar.Formatter.DefaultButtons; bar.BuildBar(); } diff --git a/Microsoft.Toolkit.Uwp.UI.Controls/TextToolbar/TextToolbar.Methods.cs b/Microsoft.Toolkit.Uwp.UI.Controls/TextToolbar/TextToolbar.Methods.cs index 6c716756467..7d30afc9e3f 100644 --- a/Microsoft.Toolkit.Uwp.UI.Controls/TextToolbar/TextToolbar.Methods.cs +++ b/Microsoft.Toolkit.Uwp.UI.Controls/TextToolbar/TextToolbar.Methods.cs @@ -4,8 +4,6 @@ using System.Linq; using Microsoft.Toolkit.Uwp.UI.Controls.TextToolbarButtons; -using Microsoft.Toolkit.Uwp.UI.Controls.TextToolbarFormats.MarkDown; -using Microsoft.Toolkit.Uwp.UI.Controls.TextToolbarFormats.RichText; using Windows.System; using Windows.UI.Core; using Windows.UI.Xaml; @@ -35,23 +33,6 @@ public ToolbarButton GetDefaultButton(ButtonType button) return null; } - /// - /// Creates one of the Default formatters. - /// - private void CreateFormatter() - { - switch (Format) - { - case TextToolbarFormats.Format.MarkDown: - Formatter = new MarkDownFormatter(this); - break; - - case TextToolbarFormats.Format.RichText: - Formatter = new RichTextFormatter(this); - break; - } - } - /// /// Attaches all of the Default Buttons, Removing any that are to be removed, and inserting Custom buttons. /// diff --git a/Microsoft.Toolkit.Uwp.UI.Controls/TextToolbar/TextToolbar.Properties.cs b/Microsoft.Toolkit.Uwp.UI.Controls/TextToolbar/TextToolbar.Properties.cs index c7f7148d0c1..a13b026de84 100644 --- a/Microsoft.Toolkit.Uwp.UI.Controls/TextToolbar/TextToolbar.Properties.cs +++ b/Microsoft.Toolkit.Uwp.UI.Controls/TextToolbar/TextToolbar.Properties.cs @@ -4,6 +4,7 @@ using Microsoft.Toolkit.Uwp.UI.Controls.TextToolbarButtons; using Microsoft.Toolkit.Uwp.UI.Controls.TextToolbarFormats; +using Microsoft.Toolkit.Uwp.UI.Controls.TextToolbarFormats.RichText; using Windows.System; using Windows.UI.Xaml; using Windows.UI.Xaml.Controls; @@ -21,17 +22,11 @@ public partial class TextToolbar public static readonly DependencyProperty EditorProperty = DependencyProperty.Register(nameof(Editor), typeof(RichEditBox), typeof(TextToolbar), new PropertyMetadata(null, OnEditorChanged)); - /// - /// Identifies the dependency property. - /// - public static readonly DependencyProperty FormatProperty = - DependencyProperty.Register(nameof(Format), typeof(Format), typeof(TextToolbar), new PropertyMetadata(Format.RichText, OnFormatTypeChanged)); - /// /// Identifies the dependency property. /// public static readonly DependencyProperty FormatterProperty = - DependencyProperty.Register(nameof(Formatter), typeof(Formatter), typeof(TextToolbar), new PropertyMetadata(null, OnFormatterChanged)); + DependencyProperty.Register(nameof(Formatter), typeof(Formatter), typeof(TextToolbar), new PropertyMetadata(new RichTextFormatter(), OnFormatterChanged)); /// /// Identifies the dependency property. @@ -66,15 +61,6 @@ public RichEditBox Editor set { SetValue(EditorProperty, value); } } - /// - /// Gets or sets which formatter to use, and which buttons to provide. - /// - public Format Format - { - get { return (Format)GetValue(FormatProperty); } - set { SetValue(FormatProperty, value); } - } - /// /// Gets or sets the formatter instance which is used to format the text, using the buttons and shortcuts. /// diff --git a/Microsoft.Toolkit.Uwp.UI.Controls/TextToolbar/TextToolbar.cs b/Microsoft.Toolkit.Uwp.UI.Controls/TextToolbar/TextToolbar.cs index 55a850484b9..2cb3e50f530 100644 --- a/Microsoft.Toolkit.Uwp.UI.Controls/TextToolbar/TextToolbar.cs +++ b/Microsoft.Toolkit.Uwp.UI.Controls/TextToolbar/TextToolbar.cs @@ -2,8 +2,8 @@ // 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 Microsoft.Toolkit.Uwp.UI.Controls.TextToolbarButtons; -using Microsoft.Toolkit.Uwp.UI.Controls.TextToolbarButtons.Common; using Windows.UI.Xaml; using Windows.UI.Xaml.Controls; using Windows.UI.Xaml.Input; @@ -20,12 +20,9 @@ public partial class TextToolbar : Control internal const string BoldElement = "Bold"; internal const string ItalicsElement = "Italics"; internal const string StrikethoughElement = "Strikethrough"; - internal const string CodeElement = "Code"; - internal const string QuoteElement = "Quote"; internal const string LinkElement = "Link"; internal const string ListElement = "List"; internal const string OrderedElement = "OrderedList"; - internal const string HeadersElement = "Headers"; /// /// Initializes a new instance of the class. @@ -48,7 +45,7 @@ protected override void OnApplyTemplate() { if (Formatter == null) { - CreateFormatter(); + throw new InvalidOperationException("No formatter specificed."); } else { diff --git a/Microsoft.Toolkit.Uwp.UI.Controls/TextToolbar/ToolbarFormatActiveConverter.cs b/Microsoft.Toolkit.Uwp.UI.Controls/TextToolbar/ToolbarFormatterActiveConverter.cs similarity index 60% rename from Microsoft.Toolkit.Uwp.UI.Controls/TextToolbar/ToolbarFormatActiveConverter.cs rename to Microsoft.Toolkit.Uwp.UI.Controls/TextToolbar/ToolbarFormatterActiveConverter.cs index 672c8a8c1f4..0729753334b 100644 --- a/Microsoft.Toolkit.Uwp.UI.Controls/TextToolbar/ToolbarFormatActiveConverter.cs +++ b/Microsoft.Toolkit.Uwp.UI.Controls/TextToolbar/ToolbarFormatterActiveConverter.cs @@ -1,4 +1,4 @@ -// 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. @@ -11,15 +11,15 @@ namespace Microsoft.Toolkit.Uwp.UI.Converters /// /// Compares if Formats are equal and returns bool /// - public class ToolbarFormatActiveConverter : IValueConverter + public class ToolbarFormatterActiveConverter : IValueConverter { /// public object Convert(object value, Type targetType, object parameter, string language) { - if (value is Format) + if (value is Formatter formatter) { - CurrentFormat = (Format)value; - return CurrentFormat == Format; + CurrentFormatter = formatter.GetType(); + return CurrentFormatter.ToString() == FormatterType; } else { @@ -30,19 +30,19 @@ public object Convert(object value, Type targetType, object parameter, string la /// public object ConvertBack(object value, Type targetType, object parameter, string language) { - if (CurrentFormat != null) + if (CurrentFormatter != null) { - return CurrentFormat; + return CurrentFormatter; } return value; } /// - /// Gets or sets the to compare + /// Gets or sets the 's to compare /// - public Format Format { get; set; } + public string FormatterType { get; set; } - private Format? CurrentFormat { get; set; } + private Type CurrentFormatter { get; set; } } } \ No newline at end of file diff --git a/Microsoft.Toolkit.Uwp.UI.Controls/Themes/Generic.xaml b/Microsoft.Toolkit.Uwp.UI.Controls/Themes/Generic.xaml index 592fbb45626..e7a558a3fa3 100644 --- a/Microsoft.Toolkit.Uwp.UI.Controls/Themes/Generic.xaml +++ b/Microsoft.Toolkit.Uwp.UI.Controls/Themes/Generic.xaml @@ -17,7 +17,6 @@ - diff --git a/Microsoft.Toolkit.Uwp.UI.Controls/VisualStudioToolsManifest.xml b/Microsoft.Toolkit.Uwp.UI.Controls/VisualStudioToolsManifest.xml index e2bc71350ef..639e6c47c05 100644 --- a/Microsoft.Toolkit.Uwp.UI.Controls/VisualStudioToolsManifest.xml +++ b/Microsoft.Toolkit.Uwp.UI.Controls/VisualStudioToolsManifest.xml @@ -20,7 +20,6 @@ - diff --git a/UnitTests/UnitTests.XamlIslands.UWPApp/XamlIslandsTest_TextToolbar.cs b/UnitTests/UnitTests.XamlIslands.UWPApp/XamlIslandsTest_TextToolbar.cs index 3d0a1a09bb8..e46d2546942 100644 --- a/UnitTests/UnitTests.XamlIslands.UWPApp/XamlIslandsTest_TextToolbar.cs +++ b/UnitTests/UnitTests.XamlIslands.UWPApp/XamlIslandsTest_TextToolbar.cs @@ -34,8 +34,7 @@ await App.Dispatcher.ExecuteOnUIThreadAsync(() => _textToolbar = new TextToolbar { Editor = richEditBox, - IsEnabled = true, - Format = Microsoft.Toolkit.Uwp.UI.Controls.TextToolbarFormats.Format.RichText + IsEnabled = true }; var grid = new Grid diff --git a/Windows Community Toolkit.sln b/Windows Community Toolkit.sln index d8e5f4ccbee..9b5efc43b8b 100644 --- a/Windows Community Toolkit.sln +++ b/Windows Community Toolkit.sln @@ -90,7 +90,7 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GazeInputTest", "GazeInputT EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Toolkit.Uwp.UI.Media", "Microsoft.Toolkit.Uwp.UI.Media\Microsoft.Toolkit.Uwp.UI.Media.csproj", "{75F9EE44-3EFA-47BC-AEDD-351B9834A0AF}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.Toolkit.Mvvm", "Microsoft.Toolkit.Mvvm\Microsoft.Toolkit.Mvvm.csproj", "{D82AE6E1-E612-434E-ACB2-363EE48738D3}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Toolkit.Mvvm", "Microsoft.Toolkit.Mvvm\Microsoft.Toolkit.Mvvm.csproj", "{D82AE6E1-E612-434E-ACB2-363EE48738D3}" EndProject Project("{D954291E-2A0B-460D-934E-DC6B0785DB48}") = "UnitTests.Shared", "UnitTests\UnitTests.Shared\UnitTests.Shared.shproj", "{4E9466D1-D5AA-46AC-801B-C8FDAB79F0D4}" EndProject @@ -125,15 +125,18 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Docs", "Docs", "{88C6FFBE-3 ThirdPartyNotices.txt = ThirdPartyNotices.txt EndProjectSection EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Toolkit.Uwp.UI.Controls.Markdown", "Microsoft.Toolkit.Uwp.UI.Controls.Markdown\Microsoft.Toolkit.Uwp.UI.Controls.Markdown.csproj", "{6FEDF199-B052-49DD-8F3E-2A9224998E0F}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.Toolkit.Uwp.UI.Controls.Markdown.Design", "Microsoft.Toolkit.Uwp.UI.Controls.Markdown.Design\Microsoft.Toolkit.Uwp.UI.Controls.Markdown.Design.csproj", "{67FE47A0-CA93-4680-B770-A0A48C1DBC40}" +EndProject Global GlobalSection(SharedMSBuildProjectFiles) = preSolution UnitTests\UnitTests.Shared\UnitTests.Shared.projitems*{4e9466d1-d5aa-46ac-801b-c8fdab79f0d4}*SharedItemsImports = 13 - UnitTests\UnitTests.Shared\UnitTests.Shared.projitems*{7878cd00-85e8-4d02-9757-8a43db4c6510}*SharedItemsImports = 5 - UnitTests\UnitTests.Notifications.Shared\UnitTests.Notifications.Shared.projitems*{982cc826-aacd-4855-9075-430bb6ce40a9}*SharedItemsImports = 13 - UnitTests\UnitTests.Shared\UnitTests.Shared.projitems*{a139968e-ad78-4e8c-93b8-9a5523bcac89}*SharedItemsImports = 4 UnitTests\UnitTests.HighPerformance.Shared\UnitTests.HighPerformance.Shared.projitems*{5524523e-db0f-41f7-a0d4-43128422a342}*SharedItemsImports = 4 + UnitTests\UnitTests.Shared\UnitTests.Shared.projitems*{7878cd00-85e8-4d02-9757-8a43db4c6510}*SharedItemsImports = 5 UnitTests\UnitTests.Notifications.Shared\UnitTests.Notifications.Shared.projitems*{982cc826-aacd-4855-9075-430bb6ce40a9}*SharedItemsImports = 13 UnitTests\UnitTests.HighPerformance.Shared\UnitTests.HighPerformance.Shared.projitems*{9b3a94a6-0d29-4523-880b-6938e2efeef7}*SharedItemsImports = 13 + UnitTests\UnitTests.Shared\UnitTests.Shared.projitems*{a139968e-ad78-4e8c-93b8-9a5523bcac89}*SharedItemsImports = 4 UnitTests\UnitTests.Notifications.Shared\UnitTests.Notifications.Shared.projitems*{bab1caf4-c400-4a7f-a987-c576de63cffd}*SharedItemsImports = 4 UnitTests\UnitTests.HighPerformance.Shared\UnitTests.HighPerformance.Shared.projitems*{d9bdbc68-3d0a-47fc-9c88-0bf769101644}*SharedItemsImports = 5 UnitTests\UnitTests.Notifications.Shared\UnitTests.Notifications.Shared.projitems*{efa96b3c-857e-4659-b942-6bef7719f4ca}*SharedItemsImports = 4 @@ -656,6 +659,7 @@ Global {7AEFC959-ED7C-4D96-9E92-72609B40FBE0}.Release|ARM.ActiveCfg = Release|x86 {7AEFC959-ED7C-4D96-9E92-72609B40FBE0}.Release|ARM.Build.0 = Release|x86 {7AEFC959-ED7C-4D96-9E92-72609B40FBE0}.Release|ARM64.ActiveCfg = Release|x86 + {7AEFC959-ED7C-4D96-9E92-72609B40FBE0}.Release|ARM64.Build.0 = Release|x86 {7AEFC959-ED7C-4D96-9E92-72609B40FBE0}.Release|x64.ActiveCfg = Release|x86 {7AEFC959-ED7C-4D96-9E92-72609B40FBE0}.Release|x64.Build.0 = Release|x86 {7AEFC959-ED7C-4D96-9E92-72609B40FBE0}.Release|x86.ActiveCfg = Release|x86 @@ -679,6 +683,7 @@ Global {6BD0BA4A-DE6D-3E87-8F83-63518C31ECD1}.Release|ARM.ActiveCfg = Release|x86 {6BD0BA4A-DE6D-3E87-8F83-63518C31ECD1}.Release|ARM.Build.0 = Release|x86 {6BD0BA4A-DE6D-3E87-8F83-63518C31ECD1}.Release|ARM64.ActiveCfg = Release|x86 + {6BD0BA4A-DE6D-3E87-8F83-63518C31ECD1}.Release|ARM64.Build.0 = Release|x86 {6BD0BA4A-DE6D-3E87-8F83-63518C31ECD1}.Release|x64.ActiveCfg = Release|x86 {6BD0BA4A-DE6D-3E87-8F83-63518C31ECD1}.Release|x64.Build.0 = Release|x86 {6BD0BA4A-DE6D-3E87-8F83-63518C31ECD1}.Release|x86.ActiveCfg = Release|x86 @@ -853,15 +858,10 @@ Global {D82AE6E1-E612-434E-ACB2-363EE48738D3}.Debug|x86.ActiveCfg = Debug|Any CPU {D82AE6E1-E612-434E-ACB2-363EE48738D3}.Debug|x86.Build.0 = Debug|Any CPU {D82AE6E1-E612-434E-ACB2-363EE48738D3}.Native|Any CPU.ActiveCfg = Debug|Any CPU - {D82AE6E1-E612-434E-ACB2-363EE48738D3}.Native|Any CPU.Build.0 = Debug|Any CPU {D82AE6E1-E612-434E-ACB2-363EE48738D3}.Native|ARM.ActiveCfg = Debug|Any CPU - {D82AE6E1-E612-434E-ACB2-363EE48738D3}.Native|ARM.Build.0 = Debug|Any CPU {D82AE6E1-E612-434E-ACB2-363EE48738D3}.Native|ARM64.ActiveCfg = Debug|Any CPU - {D82AE6E1-E612-434E-ACB2-363EE48738D3}.Native|ARM64.Build.0 = Debug|Any CPU {D82AE6E1-E612-434E-ACB2-363EE48738D3}.Native|x64.ActiveCfg = Debug|Any CPU - {D82AE6E1-E612-434E-ACB2-363EE48738D3}.Native|x64.Build.0 = Debug|Any CPU {D82AE6E1-E612-434E-ACB2-363EE48738D3}.Native|x86.ActiveCfg = Debug|Any CPU - {D82AE6E1-E612-434E-ACB2-363EE48738D3}.Native|x86.Build.0 = Debug|Any CPU {D82AE6E1-E612-434E-ACB2-363EE48738D3}.Release|Any CPU.ActiveCfg = Release|Any CPU {D82AE6E1-E612-434E-ACB2-363EE48738D3}.Release|Any CPU.Build.0 = Release|Any CPU {D82AE6E1-E612-434E-ACB2-363EE48738D3}.Release|ARM.ActiveCfg = Release|Any CPU @@ -908,15 +908,10 @@ Global {CB444381-18BA-4A51-BB32-3A498BCC1E99}.Debug|x86.ActiveCfg = Debug|Any CPU {CB444381-18BA-4A51-BB32-3A498BCC1E99}.Debug|x86.Build.0 = Debug|Any CPU {CB444381-18BA-4A51-BB32-3A498BCC1E99}.Native|Any CPU.ActiveCfg = Debug|Any CPU - {CB444381-18BA-4A51-BB32-3A498BCC1E99}.Native|Any CPU.Build.0 = Debug|Any CPU {CB444381-18BA-4A51-BB32-3A498BCC1E99}.Native|ARM.ActiveCfg = Debug|Any CPU - {CB444381-18BA-4A51-BB32-3A498BCC1E99}.Native|ARM.Build.0 = Debug|Any CPU {CB444381-18BA-4A51-BB32-3A498BCC1E99}.Native|ARM64.ActiveCfg = Debug|Any CPU - {CB444381-18BA-4A51-BB32-3A498BCC1E99}.Native|ARM64.Build.0 = Debug|Any CPU {CB444381-18BA-4A51-BB32-3A498BCC1E99}.Native|x64.ActiveCfg = Debug|Any CPU - {CB444381-18BA-4A51-BB32-3A498BCC1E99}.Native|x64.Build.0 = Debug|Any CPU {CB444381-18BA-4A51-BB32-3A498BCC1E99}.Native|x86.ActiveCfg = Debug|Any CPU - {CB444381-18BA-4A51-BB32-3A498BCC1E99}.Native|x86.Build.0 = Debug|Any CPU {CB444381-18BA-4A51-BB32-3A498BCC1E99}.Release|Any CPU.ActiveCfg = Release|Any CPU {CB444381-18BA-4A51-BB32-3A498BCC1E99}.Release|Any CPU.Build.0 = Release|Any CPU {CB444381-18BA-4A51-BB32-3A498BCC1E99}.Release|ARM.ActiveCfg = Release|Any CPU @@ -1085,6 +1080,55 @@ Global {804D0681-52F6-4E61-864A-699F0AB44B20}.Release|x86.ActiveCfg = Release|x86 {804D0681-52F6-4E61-864A-699F0AB44B20}.Release|x86.Build.0 = Release|x86 {804D0681-52F6-4E61-864A-699F0AB44B20}.Release|x86.Deploy.0 = Release|x86 + {6FEDF199-B052-49DD-8F3E-2A9224998E0F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {6FEDF199-B052-49DD-8F3E-2A9224998E0F}.Debug|Any CPU.Build.0 = Debug|Any CPU + {6FEDF199-B052-49DD-8F3E-2A9224998E0F}.Debug|ARM.ActiveCfg = Debug|Any CPU + {6FEDF199-B052-49DD-8F3E-2A9224998E0F}.Debug|ARM.Build.0 = Debug|Any CPU + {6FEDF199-B052-49DD-8F3E-2A9224998E0F}.Debug|ARM64.ActiveCfg = Debug|Any CPU + {6FEDF199-B052-49DD-8F3E-2A9224998E0F}.Debug|ARM64.Build.0 = Debug|Any CPU + {6FEDF199-B052-49DD-8F3E-2A9224998E0F}.Debug|x64.ActiveCfg = Debug|Any CPU + {6FEDF199-B052-49DD-8F3E-2A9224998E0F}.Debug|x64.Build.0 = Debug|Any CPU + {6FEDF199-B052-49DD-8F3E-2A9224998E0F}.Debug|x86.ActiveCfg = Debug|Any CPU + {6FEDF199-B052-49DD-8F3E-2A9224998E0F}.Debug|x86.Build.0 = Debug|Any CPU + {6FEDF199-B052-49DD-8F3E-2A9224998E0F}.Native|Any CPU.ActiveCfg = Debug|Any CPU + {6FEDF199-B052-49DD-8F3E-2A9224998E0F}.Native|ARM.ActiveCfg = Debug|Any CPU + {6FEDF199-B052-49DD-8F3E-2A9224998E0F}.Native|ARM64.ActiveCfg = Debug|Any CPU + {6FEDF199-B052-49DD-8F3E-2A9224998E0F}.Native|x64.ActiveCfg = Debug|Any CPU + {6FEDF199-B052-49DD-8F3E-2A9224998E0F}.Native|x86.ActiveCfg = Debug|Any CPU + {6FEDF199-B052-49DD-8F3E-2A9224998E0F}.Release|Any CPU.ActiveCfg = Release|Any CPU + {6FEDF199-B052-49DD-8F3E-2A9224998E0F}.Release|Any CPU.Build.0 = Release|Any CPU + {6FEDF199-B052-49DD-8F3E-2A9224998E0F}.Release|ARM.ActiveCfg = Release|Any CPU + {6FEDF199-B052-49DD-8F3E-2A9224998E0F}.Release|ARM.Build.0 = Release|Any CPU + {6FEDF199-B052-49DD-8F3E-2A9224998E0F}.Release|ARM64.ActiveCfg = Release|Any CPU + {6FEDF199-B052-49DD-8F3E-2A9224998E0F}.Release|ARM64.Build.0 = Release|Any CPU + {6FEDF199-B052-49DD-8F3E-2A9224998E0F}.Release|x64.ActiveCfg = Release|Any CPU + {6FEDF199-B052-49DD-8F3E-2A9224998E0F}.Release|x64.Build.0 = Release|Any CPU + {6FEDF199-B052-49DD-8F3E-2A9224998E0F}.Release|x86.ActiveCfg = Release|Any CPU + {6FEDF199-B052-49DD-8F3E-2A9224998E0F}.Release|x86.Build.0 = Release|Any CPU + {67FE47A0-CA93-4680-B770-A0A48C1DBC40}.Debug|Any CPU.ActiveCfg = Debug|x86 + {67FE47A0-CA93-4680-B770-A0A48C1DBC40}.Debug|Any CPU.Build.0 = Debug|x86 + {67FE47A0-CA93-4680-B770-A0A48C1DBC40}.Debug|ARM.ActiveCfg = Debug|x86 + {67FE47A0-CA93-4680-B770-A0A48C1DBC40}.Debug|ARM.Build.0 = Debug|x86 + {67FE47A0-CA93-4680-B770-A0A48C1DBC40}.Debug|ARM64.ActiveCfg = Debug|x86 + {67FE47A0-CA93-4680-B770-A0A48C1DBC40}.Debug|x64.ActiveCfg = Debug|x86 + {67FE47A0-CA93-4680-B770-A0A48C1DBC40}.Debug|x64.Build.0 = Debug|x86 + {67FE47A0-CA93-4680-B770-A0A48C1DBC40}.Debug|x86.ActiveCfg = Debug|x86 + {67FE47A0-CA93-4680-B770-A0A48C1DBC40}.Debug|x86.Build.0 = Debug|x86 + {67FE47A0-CA93-4680-B770-A0A48C1DBC40}.Native|Any CPU.ActiveCfg = Release|x86 + {67FE47A0-CA93-4680-B770-A0A48C1DBC40}.Native|ARM.ActiveCfg = Release|x86 + {67FE47A0-CA93-4680-B770-A0A48C1DBC40}.Native|ARM64.ActiveCfg = Release|x86 + {67FE47A0-CA93-4680-B770-A0A48C1DBC40}.Native|x64.ActiveCfg = Release|x86 + {67FE47A0-CA93-4680-B770-A0A48C1DBC40}.Native|x86.ActiveCfg = Release|x86 + {67FE47A0-CA93-4680-B770-A0A48C1DBC40}.Release|Any CPU.ActiveCfg = Release|x86 + {67FE47A0-CA93-4680-B770-A0A48C1DBC40}.Release|Any CPU.Build.0 = Release|x86 + {67FE47A0-CA93-4680-B770-A0A48C1DBC40}.Release|ARM.ActiveCfg = Release|x86 + {67FE47A0-CA93-4680-B770-A0A48C1DBC40}.Release|ARM.Build.0 = Release|x86 + {67FE47A0-CA93-4680-B770-A0A48C1DBC40}.Release|ARM64.ActiveCfg = Release|x86 + {67FE47A0-CA93-4680-B770-A0A48C1DBC40}.Release|ARM64.Build.0 = Release|x86 + {67FE47A0-CA93-4680-B770-A0A48C1DBC40}.Release|x64.ActiveCfg = Release|x86 + {67FE47A0-CA93-4680-B770-A0A48C1DBC40}.Release|x64.Build.0 = Release|x86 + {67FE47A0-CA93-4680-B770-A0A48C1DBC40}.Release|x86.ActiveCfg = Release|x86 + {67FE47A0-CA93-4680-B770-A0A48C1DBC40}.Release|x86.Build.0 = Release|x86 EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -1120,6 +1164,8 @@ Global {C79029AF-2E9B-4466-BAC4-1A41B281EAE6} = {B30036C4-D514-4E5B-A323-587A061772CE} {804D0681-52F6-4E61-864A-699F0AB44B20} = {C79029AF-2E9B-4466-BAC4-1A41B281EAE6} {88C6FFBE-322D-4CEA-842B-B2CB281D357D} = {CFA75BE0-5A44-45DE-8114-426A605B062B} + {6FEDF199-B052-49DD-8F3E-2A9224998E0F} = {F1AFFFA7-28FE-4770-BA48-10D76F3E59BC} + {67FE47A0-CA93-4680-B770-A0A48C1DBC40} = {F1AFFFA7-28FE-4770-BA48-10D76F3E59BC} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {5403B0C4-F244-4F73-A35C-FE664D0F4345} From f429a78ad89f078e9d5dfc4b1dab90209312853f Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Tue, 18 Aug 2020 13:19:12 +0200 Subject: [PATCH 392/595] Removed unnecessary using directive --- Microsoft.Toolkit.Mvvm/ComponentModel/ObservableValidator.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/Microsoft.Toolkit.Mvvm/ComponentModel/ObservableValidator.cs b/Microsoft.Toolkit.Mvvm/ComponentModel/ObservableValidator.cs index e7da951eb73..5bf505fc6dc 100644 --- a/Microsoft.Toolkit.Mvvm/ComponentModel/ObservableValidator.cs +++ b/Microsoft.Toolkit.Mvvm/ComponentModel/ObservableValidator.cs @@ -7,7 +7,6 @@ using System.Collections.Generic; using System.ComponentModel; using System.ComponentModel.DataAnnotations; -using System.Diagnostics.CodeAnalysis; using System.Runtime.CompilerServices; namespace Microsoft.Toolkit.Mvvm.ComponentModel From be11d53bb25d4951a71568e92edfff26bd4f298c Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Tue, 18 Aug 2020 13:24:09 +0200 Subject: [PATCH 393/595] Removed incorrect MethodImpl.NoInlining attribute Quick-fix PR to remove an unnecessary `[MethodImpl(MethodImplOptions.NoInlining)]` attribute from a throw helper method. That attribute caused the JIT not to see the throw in the body, resulting in the caller branch assuming the forward path being taken (the faulty one). This produced worse codegen, as the non-faulting path was always forced to have an extra jump ahead. --- .../Collections/ObservableGroupedCollectionExtensions.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/Microsoft.Toolkit/Collections/ObservableGroupedCollectionExtensions.cs b/Microsoft.Toolkit/Collections/ObservableGroupedCollectionExtensions.cs index 69e17bbc085..b52d778f560 100644 --- a/Microsoft.Toolkit/Collections/ObservableGroupedCollectionExtensions.cs +++ b/Microsoft.Toolkit/Collections/ObservableGroupedCollectionExtensions.cs @@ -444,7 +444,6 @@ private static void RemoveItemAtWithLinq( /// /// Throws a new when a key is not found. /// - [MethodImpl(MethodImplOptions.NoInlining)] private static void ThrowArgumentExceptionForKeyNotFound() { throw new InvalidOperationException("The requested key was not present in the collection"); From f71cd1ae2aa1c34872057e6a35b7257eecc6ff08 Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Tue, 18 Aug 2020 13:40:15 +0200 Subject: [PATCH 394/595] Improved codegen, moved throw code to external method Also added a more detailed exception message for users --- .../ReadOnlyObservableGroupedCollection.cs | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/Microsoft.Toolkit/Collections/ReadOnlyObservableGroupedCollection.cs b/Microsoft.Toolkit/Collections/ReadOnlyObservableGroupedCollection.cs index 9ff14698586..661edda8ca2 100644 --- a/Microsoft.Toolkit/Collections/ReadOnlyObservableGroupedCollection.cs +++ b/Microsoft.Toolkit/Collections/ReadOnlyObservableGroupedCollection.cs @@ -52,8 +52,16 @@ private void OnSourceCollectionChanged(object sender, NotifyCollectionChangedEve // reporting the changes one by one. We consider only this case for now. if (e.OldItems?.Count > 1 || e.NewItems?.Count > 1) { - Debug.Fail("OldItems and NewItems should contain at most 1 item"); - throw new NotSupportedException(); + static void ThrowNotSupportedException() + { + throw new NotSupportedException( + "ReadOnlyObservableGroupedCollection doesn't support operations on multiple items at once.\n" + + "If this exception was thrown, it likely means support for batched item updates has been added to the " + + "underlying ObservableCollection type, and this implementation doesn't support that feature yet.\n" + + "Please consider opening an issue in https://github.com/windows-toolkit/WindowsCommunityToolkit to report this."); + } + + ThrowNotSupportedException(); } var newItem = e.NewItems?.Cast>()?.FirstOrDefault(); From bac2461199d171c581f56220edf24b69a19a32a8 Mon Sep 17 00:00:00 2001 From: Alexandre Zollinger Chohfi Date: Tue, 18 Aug 2020 10:38:32 -0700 Subject: [PATCH 395/595] Fixed indentation o design projects. --- .../Common/MetadataRegistrationBase.cs | 6 +- .../Common/MetadataRegistrationBase.cs | 408 +++++++++--------- .../Common/PlatformTypes.cs | 71 ++- .../Common/MetadataRegistrationBase.cs | 408 +++++++++--------- .../Common/PlatformTypes.cs | 71 ++- 5 files changed, 481 insertions(+), 483 deletions(-) diff --git a/Microsoft.Toolkit.Uwp.UI.Controls.DataGrid.Design/Common/MetadataRegistrationBase.cs b/Microsoft.Toolkit.Uwp.UI.Controls.DataGrid.Design/Common/MetadataRegistrationBase.cs index 3572c5209a7..733b19ca7ce 100644 --- a/Microsoft.Toolkit.Uwp.UI.Controls.DataGrid.Design/Common/MetadataRegistrationBase.cs +++ b/Microsoft.Toolkit.Uwp.UI.Controls.DataGrid.Design/Common/MetadataRegistrationBase.cs @@ -105,7 +105,7 @@ private void AddDescriptions(AttributeTableBuilder builder) try { string name = (string)member.Attribute("name"); - if (name == null) + if (name == null) continue; bool isType = name.StartsWith("T:", StringComparison.OrdinalIgnoreCase); if (isType || @@ -123,7 +123,7 @@ private void AddDescriptions(AttributeTableBuilder builder) typeName = name.Substring(2, lastDot - 2); } typeName += AssemblyFullName; - + Type t = Type.GetType(typeName); if (t != null && t.IsPublic && t.IsClass && t.IsSubclassOf(Types.PlatformTypes.DependencyObjectType)) @@ -164,7 +164,7 @@ private void AddDescriptions(AttributeTableBuilder builder) isBrowsable = IsBrowsable(pi); } catch { isBrowsable = false; } - if(isBrowsable) + if (isBrowsable) builder.AddCallback(t, b => b.AddCustomAttributes(propName, new DescriptionAttribute(desc))); else //Hide from intellisense builder.AddCallback(t, b => b.AddCustomAttributes(new BrowsableAttribute(false))); diff --git a/Microsoft.Toolkit.Uwp.UI.Controls.Design/Common/MetadataRegistrationBase.cs b/Microsoft.Toolkit.Uwp.UI.Controls.Design/Common/MetadataRegistrationBase.cs index dc6300e1a36..ffcec2e7184 100644 --- a/Microsoft.Toolkit.Uwp.UI.Controls.Design/Common/MetadataRegistrationBase.cs +++ b/Microsoft.Toolkit.Uwp.UI.Controls.Design/Common/MetadataRegistrationBase.cs @@ -15,8 +15,8 @@ namespace Microsoft.Toolkit.Uwp.Design.Common { - public class MetadataRegistrationBase - { + public class MetadataRegistrationBase + { private AttributeTable masterMetadataTable; internal MetadataRegistrationBase() { } @@ -26,227 +26,227 @@ internal MetadataRegistrationBase() { } /// /// Custom attribute table. protected virtual AttributeTable BuildAttributeTable() - { - AttributeTableBuilder builder = new AttributeTableBuilder(); + { + AttributeTableBuilder builder = new AttributeTableBuilder(); AddDescriptions(builder); AddAttributes(builder); AddTables(builder, this); masterMetadataTable = builder.CreateTable(); - return masterMetadataTable; - } + return masterMetadataTable; + } - /// - /// Find all AttributeTableBuilder subclasses in the assembly - /// and add their attributes to the assembly attribute table. - /// - /// The assembly attribute table builder. - [SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Justification = "Design time dll should not fail!")] - private void AddTables(AttributeTableBuilder builder, object parent) - { - Debug.Assert(builder != null, "AddTables is called with null parameter!"); - - Assembly asm = parent.GetType().Assembly; - foreach (Type t in asm.GetTypes()) - { - if (t.IsSubclassOf(typeof(AttributeTableBuilder))) - { - try - { + /// + /// Find all AttributeTableBuilder subclasses in the assembly + /// and add their attributes to the assembly attribute table. + /// + /// The assembly attribute table builder. + [SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Justification = "Design time dll should not fail!")] + private void AddTables(AttributeTableBuilder builder, object parent) + { + Debug.Assert(builder != null, "AddTables is called with null parameter!"); + + Assembly asm = parent.GetType().Assembly; + foreach (Type t in asm.GetTypes()) + { + if (t.IsSubclassOf(typeof(AttributeTableBuilder))) + { + try + { AttributeTableBuilder atb = (AttributeTableBuilder)Activator.CreateInstance(t); builder.AddTable(atb.CreateTable()); - } - catch (Exception) - { + } + catch (Exception) + { //error loading design assembly - } - } - } - } + } + } + } + } - /// - /// Gets or sets the case sensitive resource name of the embedded XML file. - /// - protected string XmlResourceName { get; set; } + /// + /// Gets or sets the case sensitive resource name of the embedded XML file. + /// + protected string XmlResourceName { get; set; } - /// - /// Gets or sets the FullName of the corresponding run time assembly. - /// - protected string AssemblyFullName { get; set; } + /// + /// Gets or sets the FullName of the corresponding run time assembly. + /// + protected string AssemblyFullName { get; set; } - /// - /// Create description attribute from run time assembly xml file. - /// - /// The assembly attribute table builder. - [SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Justification = "Design time dll should not fail.")] - private void AddDescriptions(AttributeTableBuilder builder) - { - Debug.Assert(builder != null, "AddDescriptions is called with null parameter!"); - - if (string.IsNullOrEmpty(XmlResourceName) || - string.IsNullOrEmpty(AssemblyFullName)) - { - return; - } - XDocument xdoc = null; - try - { - xdoc = XDocument.Load(new StreamReader( - Assembly.GetExecutingAssembly().GetManifestResourceStream(XmlResourceName))); - } - catch { return; } - if (xdoc == null) - { - return; - } - - foreach (XElement member in xdoc.Descendants("member")) - { - try - { - string name = (string)member.Attribute("name"); - if (name == null) - continue; - bool isType = name.StartsWith("T:", StringComparison.OrdinalIgnoreCase); - if (isType || - name.StartsWith("P:", StringComparison.OrdinalIgnoreCase)) - { - int lastDot = name.Length; - string typeName; - if (isType) - { - typeName = name.Substring(2); // skip leading "T:" - } - else - { - lastDot = name.LastIndexOf('.'); - typeName = name.Substring(2, lastDot - 2); - } - typeName += AssemblyFullName; - - Type t = Type.GetType(typeName); - if (t != null && t.IsPublic && t.IsClass && + /// + /// Create description attribute from run time assembly xml file. + /// + /// The assembly attribute table builder. + [SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Justification = "Design time dll should not fail.")] + private void AddDescriptions(AttributeTableBuilder builder) + { + Debug.Assert(builder != null, "AddDescriptions is called with null parameter!"); + + if (string.IsNullOrEmpty(XmlResourceName) || + string.IsNullOrEmpty(AssemblyFullName)) + { + return; + } + XDocument xdoc = null; + try + { + xdoc = XDocument.Load(new StreamReader( + Assembly.GetExecutingAssembly().GetManifestResourceStream(XmlResourceName))); + } + catch { return; } + if (xdoc == null) + { + return; + } + + foreach (XElement member in xdoc.Descendants("member")) + { + try + { + string name = (string)member.Attribute("name"); + if (name == null) + continue; + bool isType = name.StartsWith("T:", StringComparison.OrdinalIgnoreCase); + if (isType || + name.StartsWith("P:", StringComparison.OrdinalIgnoreCase)) + { + int lastDot = name.Length; + string typeName; + if (isType) + { + typeName = name.Substring(2); // skip leading "T:" + } + else + { + lastDot = name.LastIndexOf('.'); + typeName = name.Substring(2, lastDot - 2); + } + typeName += AssemblyFullName; + + Type t = Type.GetType(typeName); + if (t != null && t.IsPublic && t.IsClass && t.IsSubclassOf(Types.PlatformTypes.DependencyObjectType)) - { - string desc = ParseDescription(member); + { + string desc = ParseDescription(member); if (desc == null) continue; - desc = desc.Trim(); - desc = string.Join(" ", desc.Split(new char[] { ' ', '\t', '\n', '\r' }, StringSplitOptions.RemoveEmptyEntries)); - if (isType) - { - bool isBrowsable = true; - try - { - isBrowsable = IsBrowsable(t); - } - catch { isBrowsable = false; } - if (isBrowsable) - builder.AddCallback(t, b => b.AddCustomAttributes(new DescriptionAttribute(desc))); - else //Hide from intellisense - { - builder.AddCallback(t, b => b.AddCustomAttributes( - new BrowsableAttribute(false), - new Microsoft.Windows.Design.ToolboxBrowsableAttribute(false), - new ToolboxItemAttribute(false))); - } - } - else - { - string propName = name.Substring(lastDot + 1); - PropertyInfo pi = t.GetProperty(propName, BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly); - if (pi != null) - { - bool isBrowsable = true; - try - { - isBrowsable = IsBrowsable(pi); - } - catch { isBrowsable = false; } - if(isBrowsable) - builder.AddCallback(t, b => b.AddCustomAttributes(propName, new DescriptionAttribute(desc))); - else //Hide from intellisense - builder.AddCallback(t, b => b.AddCustomAttributes(new BrowsableAttribute(false))); - } - } - } - } - } - catch (Exception) - { - } - } - } - private static bool IsBrowsable(Type t) - { - var attrs = t.GetCustomAttributes(Types.PlatformTypes.EditorBrowsableAttributeType, false); - foreach (var attr in attrs) - { + desc = desc.Trim(); + desc = string.Join(" ", desc.Split(new char[] { ' ', '\t', '\n', '\r' }, StringSplitOptions.RemoveEmptyEntries)); + if (isType) + { + bool isBrowsable = true; + try + { + isBrowsable = IsBrowsable(t); + } + catch { isBrowsable = false; } + if (isBrowsable) + builder.AddCallback(t, b => b.AddCustomAttributes(new DescriptionAttribute(desc))); + else //Hide from intellisense + { + builder.AddCallback(t, b => b.AddCustomAttributes( + new BrowsableAttribute(false), + new Microsoft.Windows.Design.ToolboxBrowsableAttribute(false), + new ToolboxItemAttribute(false))); + } + } + else + { + string propName = name.Substring(lastDot + 1); + PropertyInfo pi = t.GetProperty(propName, BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly); + if (pi != null) + { + bool isBrowsable = true; + try + { + isBrowsable = IsBrowsable(pi); + } + catch { isBrowsable = false; } + if (isBrowsable) + builder.AddCallback(t, b => b.AddCustomAttributes(propName, new DescriptionAttribute(desc))); + else //Hide from intellisense + builder.AddCallback(t, b => b.AddCustomAttributes(new BrowsableAttribute(false))); + } + } + } + } + } + catch (Exception) + { + } + } + } + private static bool IsBrowsable(Type t) + { + var attrs = t.GetCustomAttributes(Types.PlatformTypes.EditorBrowsableAttributeType, false); + foreach (var attr in attrs) + { return Types.PlatformTypes.IsBrowsable(attr); - } - return true; - } + } + return true; + } - private static bool IsBrowsable(System.Reflection.PropertyInfo pi) - { + private static bool IsBrowsable(System.Reflection.PropertyInfo pi) + { var attrs = pi.GetCustomAttributes(Types.PlatformTypes.EditorBrowsableAttributeType, false); - foreach (var attr in attrs) - { + foreach (var attr in attrs) + { return Types.PlatformTypes.IsBrowsable(attr); - } - return true; - } + } + return true; + } - /// - /// Create description string from xml doc summary tag. - /// - /// A single node of the xml doc. - /// Description string. - private static string ParseDescription(XElement member) - { - string desc = null; - XElement memberDesc = member.Descendants("summary").FirstOrDefault(); - - if (memberDesc != null) - { - IEnumerable nodes = memberDesc.DescendantNodes(); - - if (nodes != null) - { - foreach (XNode node in nodes) - { - if (node.NodeType == System.Xml.XmlNodeType.Text) - { - desc += node.ToString(); - } - else - { - string s = node.ToString(); - int i = s.LastIndexOf('.'); - int j = s.LastIndexOf('"'); - - if ((i != -1 || j != -1) && j - i - 1 > 0) - { - try - { - desc += s.Substring(i + 1, j - i - 1); - } - catch { } - } - } - } - } - } - return desc; - } - - /// - /// Provide a place to add custom attributes without creating a AttributeTableBuilder subclass. - /// - /// The assembly attribute table builder. - protected virtual void AddAttributes(AttributeTableBuilder builder) - { - } - } + /// + /// Create description string from xml doc summary tag. + /// + /// A single node of the xml doc. + /// Description string. + private static string ParseDescription(XElement member) + { + string desc = null; + XElement memberDesc = member.Descendants("summary").FirstOrDefault(); + + if (memberDesc != null) + { + IEnumerable nodes = memberDesc.DescendantNodes(); + + if (nodes != null) + { + foreach (XNode node in nodes) + { + if (node.NodeType == System.Xml.XmlNodeType.Text) + { + desc += node.ToString(); + } + else + { + string s = node.ToString(); + int i = s.LastIndexOf('.'); + int j = s.LastIndexOf('"'); + + if ((i != -1 || j != -1) && j - i - 1 > 0) + { + try + { + desc += s.Substring(i + 1, j - i - 1); + } + catch { } + } + } + } + } + } + return desc; + } + + /// + /// Provide a place to add custom attributes without creating a AttributeTableBuilder subclass. + /// + /// The assembly attribute table builder. + protected virtual void AddAttributes(AttributeTableBuilder builder) + { + } + } } \ No newline at end of file diff --git a/Microsoft.Toolkit.Uwp.UI.Controls.Design/Common/PlatformTypes.cs b/Microsoft.Toolkit.Uwp.UI.Controls.Design/Common/PlatformTypes.cs index b8ce3304a08..30196482c4c 100644 --- a/Microsoft.Toolkit.Uwp.UI.Controls.Design/Common/PlatformTypes.cs +++ b/Microsoft.Toolkit.Uwp.UI.Controls.Design/Common/PlatformTypes.cs @@ -8,43 +8,42 @@ namespace Microsoft.Toolkit.Uwp.Design.Types { - internal class PlatformTypes - { - public static readonly Type DependencyObjectType = typeof(DependencyObject); - public static readonly Type UIElementType = typeof(UIElement); - public static readonly Type FrameworkElementType = typeof(FrameworkElement); - public static readonly Type EditorBrowsableAttributeType = typeof(System.ComponentModel.EditorBrowsableAttribute); + internal class PlatformTypes + { + public static readonly Type DependencyObjectType = typeof(DependencyObject); + public static readonly Type UIElementType = typeof(UIElement); + public static readonly Type FrameworkElementType = typeof(FrameworkElement); + public static readonly Type EditorBrowsableAttributeType = typeof(System.ComponentModel.EditorBrowsableAttribute); - /// - /// Used by MetadataRegistrationBase to get the browsable state - /// - /// This parameter must be of type 'System.ComponentModel.EditorBrowsableAttribute' - /// - public static bool IsBrowsable(object editorBrowsableAttribute) - { - if (editorBrowsableAttribute is System.ComponentModel.EditorBrowsableAttribute) - return (editorBrowsableAttribute as System.ComponentModel.EditorBrowsableAttribute).State != - System.ComponentModel.EditorBrowsableState.Never; - return true; - } + /// + /// Used by MetadataRegistrationBase to get the browsable state + /// + /// This parameter must be of type 'System.ComponentModel.EditorBrowsableAttribute' + /// + public static bool IsBrowsable(object editorBrowsableAttribute) + { + if (editorBrowsableAttribute is System.ComponentModel.EditorBrowsableAttribute) + return (editorBrowsableAttribute as System.ComponentModel.EditorBrowsableAttribute).State != + System.ComponentModel.EditorBrowsableState.Never; + return true; + } - public static class Control - { - public static readonly TypeIdentifier TypeId = new TypeIdentifier("http://schemas.microsoft.com/winfx/2006/xaml/presentation", "Control"); - public static readonly PropertyIdentifier BackgroundProperty = new PropertyIdentifier(TypeId, "Background"); - public static readonly PropertyIdentifier BorderBrushProperty = new PropertyIdentifier(TypeId, "BorderBrush"); - public static readonly PropertyIdentifier BorderThicknessProperty = new PropertyIdentifier(TypeId, "BorderThickness"); - } + public static class Control + { + public static readonly TypeIdentifier TypeId = new TypeIdentifier("http://schemas.microsoft.com/winfx/2006/xaml/presentation", "Control"); + public static readonly PropertyIdentifier BackgroundProperty = new PropertyIdentifier(TypeId, "Background"); + public static readonly PropertyIdentifier BorderBrushProperty = new PropertyIdentifier(TypeId, "BorderBrush"); + public static readonly PropertyIdentifier BorderThicknessProperty = new PropertyIdentifier(TypeId, "BorderThickness"); + } - public static class FrameworkElement - { - public static readonly TypeIdentifier TypeId = new TypeIdentifier("http://schemas.microsoft.com/winfx/2006/xaml/presentation", "FrameworkElement"); - public static readonly PropertyIdentifier MarginProperty = new PropertyIdentifier(TypeId, "Margin"); - public static readonly PropertyIdentifier HorizontalAlignmentProperty = new PropertyIdentifier(TypeId, "HorizontalAlignment"); - public static readonly PropertyIdentifier VerticalAlignmentProperty = new PropertyIdentifier(TypeId, "VerticalAlignment"); - public static readonly PropertyIdentifier HeightProperty = new PropertyIdentifier(TypeId, "Height"); - public static readonly PropertyIdentifier WidthProperty = new PropertyIdentifier(TypeId, "Width"); - } - - } + public static class FrameworkElement + { + public static readonly TypeIdentifier TypeId = new TypeIdentifier("http://schemas.microsoft.com/winfx/2006/xaml/presentation", "FrameworkElement"); + public static readonly PropertyIdentifier MarginProperty = new PropertyIdentifier(TypeId, "Margin"); + public static readonly PropertyIdentifier HorizontalAlignmentProperty = new PropertyIdentifier(TypeId, "HorizontalAlignment"); + public static readonly PropertyIdentifier VerticalAlignmentProperty = new PropertyIdentifier(TypeId, "VerticalAlignment"); + public static readonly PropertyIdentifier HeightProperty = new PropertyIdentifier(TypeId, "Height"); + public static readonly PropertyIdentifier WidthProperty = new PropertyIdentifier(TypeId, "Width"); + } + } } diff --git a/Microsoft.Toolkit.Uwp.UI.Controls.Markdown.Design/Common/MetadataRegistrationBase.cs b/Microsoft.Toolkit.Uwp.UI.Controls.Markdown.Design/Common/MetadataRegistrationBase.cs index dc6300e1a36..ffcec2e7184 100644 --- a/Microsoft.Toolkit.Uwp.UI.Controls.Markdown.Design/Common/MetadataRegistrationBase.cs +++ b/Microsoft.Toolkit.Uwp.UI.Controls.Markdown.Design/Common/MetadataRegistrationBase.cs @@ -15,8 +15,8 @@ namespace Microsoft.Toolkit.Uwp.Design.Common { - public class MetadataRegistrationBase - { + public class MetadataRegistrationBase + { private AttributeTable masterMetadataTable; internal MetadataRegistrationBase() { } @@ -26,227 +26,227 @@ internal MetadataRegistrationBase() { } /// /// Custom attribute table. protected virtual AttributeTable BuildAttributeTable() - { - AttributeTableBuilder builder = new AttributeTableBuilder(); + { + AttributeTableBuilder builder = new AttributeTableBuilder(); AddDescriptions(builder); AddAttributes(builder); AddTables(builder, this); masterMetadataTable = builder.CreateTable(); - return masterMetadataTable; - } + return masterMetadataTable; + } - /// - /// Find all AttributeTableBuilder subclasses in the assembly - /// and add their attributes to the assembly attribute table. - /// - /// The assembly attribute table builder. - [SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Justification = "Design time dll should not fail!")] - private void AddTables(AttributeTableBuilder builder, object parent) - { - Debug.Assert(builder != null, "AddTables is called with null parameter!"); - - Assembly asm = parent.GetType().Assembly; - foreach (Type t in asm.GetTypes()) - { - if (t.IsSubclassOf(typeof(AttributeTableBuilder))) - { - try - { + /// + /// Find all AttributeTableBuilder subclasses in the assembly + /// and add their attributes to the assembly attribute table. + /// + /// The assembly attribute table builder. + [SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Justification = "Design time dll should not fail!")] + private void AddTables(AttributeTableBuilder builder, object parent) + { + Debug.Assert(builder != null, "AddTables is called with null parameter!"); + + Assembly asm = parent.GetType().Assembly; + foreach (Type t in asm.GetTypes()) + { + if (t.IsSubclassOf(typeof(AttributeTableBuilder))) + { + try + { AttributeTableBuilder atb = (AttributeTableBuilder)Activator.CreateInstance(t); builder.AddTable(atb.CreateTable()); - } - catch (Exception) - { + } + catch (Exception) + { //error loading design assembly - } - } - } - } + } + } + } + } - /// - /// Gets or sets the case sensitive resource name of the embedded XML file. - /// - protected string XmlResourceName { get; set; } + /// + /// Gets or sets the case sensitive resource name of the embedded XML file. + /// + protected string XmlResourceName { get; set; } - /// - /// Gets or sets the FullName of the corresponding run time assembly. - /// - protected string AssemblyFullName { get; set; } + /// + /// Gets or sets the FullName of the corresponding run time assembly. + /// + protected string AssemblyFullName { get; set; } - /// - /// Create description attribute from run time assembly xml file. - /// - /// The assembly attribute table builder. - [SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Justification = "Design time dll should not fail.")] - private void AddDescriptions(AttributeTableBuilder builder) - { - Debug.Assert(builder != null, "AddDescriptions is called with null parameter!"); - - if (string.IsNullOrEmpty(XmlResourceName) || - string.IsNullOrEmpty(AssemblyFullName)) - { - return; - } - XDocument xdoc = null; - try - { - xdoc = XDocument.Load(new StreamReader( - Assembly.GetExecutingAssembly().GetManifestResourceStream(XmlResourceName))); - } - catch { return; } - if (xdoc == null) - { - return; - } - - foreach (XElement member in xdoc.Descendants("member")) - { - try - { - string name = (string)member.Attribute("name"); - if (name == null) - continue; - bool isType = name.StartsWith("T:", StringComparison.OrdinalIgnoreCase); - if (isType || - name.StartsWith("P:", StringComparison.OrdinalIgnoreCase)) - { - int lastDot = name.Length; - string typeName; - if (isType) - { - typeName = name.Substring(2); // skip leading "T:" - } - else - { - lastDot = name.LastIndexOf('.'); - typeName = name.Substring(2, lastDot - 2); - } - typeName += AssemblyFullName; - - Type t = Type.GetType(typeName); - if (t != null && t.IsPublic && t.IsClass && + /// + /// Create description attribute from run time assembly xml file. + /// + /// The assembly attribute table builder. + [SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Justification = "Design time dll should not fail.")] + private void AddDescriptions(AttributeTableBuilder builder) + { + Debug.Assert(builder != null, "AddDescriptions is called with null parameter!"); + + if (string.IsNullOrEmpty(XmlResourceName) || + string.IsNullOrEmpty(AssemblyFullName)) + { + return; + } + XDocument xdoc = null; + try + { + xdoc = XDocument.Load(new StreamReader( + Assembly.GetExecutingAssembly().GetManifestResourceStream(XmlResourceName))); + } + catch { return; } + if (xdoc == null) + { + return; + } + + foreach (XElement member in xdoc.Descendants("member")) + { + try + { + string name = (string)member.Attribute("name"); + if (name == null) + continue; + bool isType = name.StartsWith("T:", StringComparison.OrdinalIgnoreCase); + if (isType || + name.StartsWith("P:", StringComparison.OrdinalIgnoreCase)) + { + int lastDot = name.Length; + string typeName; + if (isType) + { + typeName = name.Substring(2); // skip leading "T:" + } + else + { + lastDot = name.LastIndexOf('.'); + typeName = name.Substring(2, lastDot - 2); + } + typeName += AssemblyFullName; + + Type t = Type.GetType(typeName); + if (t != null && t.IsPublic && t.IsClass && t.IsSubclassOf(Types.PlatformTypes.DependencyObjectType)) - { - string desc = ParseDescription(member); + { + string desc = ParseDescription(member); if (desc == null) continue; - desc = desc.Trim(); - desc = string.Join(" ", desc.Split(new char[] { ' ', '\t', '\n', '\r' }, StringSplitOptions.RemoveEmptyEntries)); - if (isType) - { - bool isBrowsable = true; - try - { - isBrowsable = IsBrowsable(t); - } - catch { isBrowsable = false; } - if (isBrowsable) - builder.AddCallback(t, b => b.AddCustomAttributes(new DescriptionAttribute(desc))); - else //Hide from intellisense - { - builder.AddCallback(t, b => b.AddCustomAttributes( - new BrowsableAttribute(false), - new Microsoft.Windows.Design.ToolboxBrowsableAttribute(false), - new ToolboxItemAttribute(false))); - } - } - else - { - string propName = name.Substring(lastDot + 1); - PropertyInfo pi = t.GetProperty(propName, BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly); - if (pi != null) - { - bool isBrowsable = true; - try - { - isBrowsable = IsBrowsable(pi); - } - catch { isBrowsable = false; } - if(isBrowsable) - builder.AddCallback(t, b => b.AddCustomAttributes(propName, new DescriptionAttribute(desc))); - else //Hide from intellisense - builder.AddCallback(t, b => b.AddCustomAttributes(new BrowsableAttribute(false))); - } - } - } - } - } - catch (Exception) - { - } - } - } - private static bool IsBrowsable(Type t) - { - var attrs = t.GetCustomAttributes(Types.PlatformTypes.EditorBrowsableAttributeType, false); - foreach (var attr in attrs) - { + desc = desc.Trim(); + desc = string.Join(" ", desc.Split(new char[] { ' ', '\t', '\n', '\r' }, StringSplitOptions.RemoveEmptyEntries)); + if (isType) + { + bool isBrowsable = true; + try + { + isBrowsable = IsBrowsable(t); + } + catch { isBrowsable = false; } + if (isBrowsable) + builder.AddCallback(t, b => b.AddCustomAttributes(new DescriptionAttribute(desc))); + else //Hide from intellisense + { + builder.AddCallback(t, b => b.AddCustomAttributes( + new BrowsableAttribute(false), + new Microsoft.Windows.Design.ToolboxBrowsableAttribute(false), + new ToolboxItemAttribute(false))); + } + } + else + { + string propName = name.Substring(lastDot + 1); + PropertyInfo pi = t.GetProperty(propName, BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly); + if (pi != null) + { + bool isBrowsable = true; + try + { + isBrowsable = IsBrowsable(pi); + } + catch { isBrowsable = false; } + if (isBrowsable) + builder.AddCallback(t, b => b.AddCustomAttributes(propName, new DescriptionAttribute(desc))); + else //Hide from intellisense + builder.AddCallback(t, b => b.AddCustomAttributes(new BrowsableAttribute(false))); + } + } + } + } + } + catch (Exception) + { + } + } + } + private static bool IsBrowsable(Type t) + { + var attrs = t.GetCustomAttributes(Types.PlatformTypes.EditorBrowsableAttributeType, false); + foreach (var attr in attrs) + { return Types.PlatformTypes.IsBrowsable(attr); - } - return true; - } + } + return true; + } - private static bool IsBrowsable(System.Reflection.PropertyInfo pi) - { + private static bool IsBrowsable(System.Reflection.PropertyInfo pi) + { var attrs = pi.GetCustomAttributes(Types.PlatformTypes.EditorBrowsableAttributeType, false); - foreach (var attr in attrs) - { + foreach (var attr in attrs) + { return Types.PlatformTypes.IsBrowsable(attr); - } - return true; - } + } + return true; + } - /// - /// Create description string from xml doc summary tag. - /// - /// A single node of the xml doc. - /// Description string. - private static string ParseDescription(XElement member) - { - string desc = null; - XElement memberDesc = member.Descendants("summary").FirstOrDefault(); - - if (memberDesc != null) - { - IEnumerable nodes = memberDesc.DescendantNodes(); - - if (nodes != null) - { - foreach (XNode node in nodes) - { - if (node.NodeType == System.Xml.XmlNodeType.Text) - { - desc += node.ToString(); - } - else - { - string s = node.ToString(); - int i = s.LastIndexOf('.'); - int j = s.LastIndexOf('"'); - - if ((i != -1 || j != -1) && j - i - 1 > 0) - { - try - { - desc += s.Substring(i + 1, j - i - 1); - } - catch { } - } - } - } - } - } - return desc; - } - - /// - /// Provide a place to add custom attributes without creating a AttributeTableBuilder subclass. - /// - /// The assembly attribute table builder. - protected virtual void AddAttributes(AttributeTableBuilder builder) - { - } - } + /// + /// Create description string from xml doc summary tag. + /// + /// A single node of the xml doc. + /// Description string. + private static string ParseDescription(XElement member) + { + string desc = null; + XElement memberDesc = member.Descendants("summary").FirstOrDefault(); + + if (memberDesc != null) + { + IEnumerable nodes = memberDesc.DescendantNodes(); + + if (nodes != null) + { + foreach (XNode node in nodes) + { + if (node.NodeType == System.Xml.XmlNodeType.Text) + { + desc += node.ToString(); + } + else + { + string s = node.ToString(); + int i = s.LastIndexOf('.'); + int j = s.LastIndexOf('"'); + + if ((i != -1 || j != -1) && j - i - 1 > 0) + { + try + { + desc += s.Substring(i + 1, j - i - 1); + } + catch { } + } + } + } + } + } + return desc; + } + + /// + /// Provide a place to add custom attributes without creating a AttributeTableBuilder subclass. + /// + /// The assembly attribute table builder. + protected virtual void AddAttributes(AttributeTableBuilder builder) + { + } + } } \ No newline at end of file diff --git a/Microsoft.Toolkit.Uwp.UI.Controls.Markdown.Design/Common/PlatformTypes.cs b/Microsoft.Toolkit.Uwp.UI.Controls.Markdown.Design/Common/PlatformTypes.cs index b8ce3304a08..30196482c4c 100644 --- a/Microsoft.Toolkit.Uwp.UI.Controls.Markdown.Design/Common/PlatformTypes.cs +++ b/Microsoft.Toolkit.Uwp.UI.Controls.Markdown.Design/Common/PlatformTypes.cs @@ -8,43 +8,42 @@ namespace Microsoft.Toolkit.Uwp.Design.Types { - internal class PlatformTypes - { - public static readonly Type DependencyObjectType = typeof(DependencyObject); - public static readonly Type UIElementType = typeof(UIElement); - public static readonly Type FrameworkElementType = typeof(FrameworkElement); - public static readonly Type EditorBrowsableAttributeType = typeof(System.ComponentModel.EditorBrowsableAttribute); + internal class PlatformTypes + { + public static readonly Type DependencyObjectType = typeof(DependencyObject); + public static readonly Type UIElementType = typeof(UIElement); + public static readonly Type FrameworkElementType = typeof(FrameworkElement); + public static readonly Type EditorBrowsableAttributeType = typeof(System.ComponentModel.EditorBrowsableAttribute); - /// - /// Used by MetadataRegistrationBase to get the browsable state - /// - /// This parameter must be of type 'System.ComponentModel.EditorBrowsableAttribute' - /// - public static bool IsBrowsable(object editorBrowsableAttribute) - { - if (editorBrowsableAttribute is System.ComponentModel.EditorBrowsableAttribute) - return (editorBrowsableAttribute as System.ComponentModel.EditorBrowsableAttribute).State != - System.ComponentModel.EditorBrowsableState.Never; - return true; - } + /// + /// Used by MetadataRegistrationBase to get the browsable state + /// + /// This parameter must be of type 'System.ComponentModel.EditorBrowsableAttribute' + /// + public static bool IsBrowsable(object editorBrowsableAttribute) + { + if (editorBrowsableAttribute is System.ComponentModel.EditorBrowsableAttribute) + return (editorBrowsableAttribute as System.ComponentModel.EditorBrowsableAttribute).State != + System.ComponentModel.EditorBrowsableState.Never; + return true; + } - public static class Control - { - public static readonly TypeIdentifier TypeId = new TypeIdentifier("http://schemas.microsoft.com/winfx/2006/xaml/presentation", "Control"); - public static readonly PropertyIdentifier BackgroundProperty = new PropertyIdentifier(TypeId, "Background"); - public static readonly PropertyIdentifier BorderBrushProperty = new PropertyIdentifier(TypeId, "BorderBrush"); - public static readonly PropertyIdentifier BorderThicknessProperty = new PropertyIdentifier(TypeId, "BorderThickness"); - } + public static class Control + { + public static readonly TypeIdentifier TypeId = new TypeIdentifier("http://schemas.microsoft.com/winfx/2006/xaml/presentation", "Control"); + public static readonly PropertyIdentifier BackgroundProperty = new PropertyIdentifier(TypeId, "Background"); + public static readonly PropertyIdentifier BorderBrushProperty = new PropertyIdentifier(TypeId, "BorderBrush"); + public static readonly PropertyIdentifier BorderThicknessProperty = new PropertyIdentifier(TypeId, "BorderThickness"); + } - public static class FrameworkElement - { - public static readonly TypeIdentifier TypeId = new TypeIdentifier("http://schemas.microsoft.com/winfx/2006/xaml/presentation", "FrameworkElement"); - public static readonly PropertyIdentifier MarginProperty = new PropertyIdentifier(TypeId, "Margin"); - public static readonly PropertyIdentifier HorizontalAlignmentProperty = new PropertyIdentifier(TypeId, "HorizontalAlignment"); - public static readonly PropertyIdentifier VerticalAlignmentProperty = new PropertyIdentifier(TypeId, "VerticalAlignment"); - public static readonly PropertyIdentifier HeightProperty = new PropertyIdentifier(TypeId, "Height"); - public static readonly PropertyIdentifier WidthProperty = new PropertyIdentifier(TypeId, "Width"); - } - - } + public static class FrameworkElement + { + public static readonly TypeIdentifier TypeId = new TypeIdentifier("http://schemas.microsoft.com/winfx/2006/xaml/presentation", "FrameworkElement"); + public static readonly PropertyIdentifier MarginProperty = new PropertyIdentifier(TypeId, "Margin"); + public static readonly PropertyIdentifier HorizontalAlignmentProperty = new PropertyIdentifier(TypeId, "HorizontalAlignment"); + public static readonly PropertyIdentifier VerticalAlignmentProperty = new PropertyIdentifier(TypeId, "VerticalAlignment"); + public static readonly PropertyIdentifier HeightProperty = new PropertyIdentifier(TypeId, "Height"); + public static readonly PropertyIdentifier WidthProperty = new PropertyIdentifier(TypeId, "Width"); + } + } } From e8981027facbbdf4540f1925b599e2243cdc5454 Mon Sep 17 00:00:00 2001 From: Alexandre Zollinger Chohfi Date: Tue, 18 Aug 2020 11:15:28 -0700 Subject: [PATCH 396/595] Remove extra blank line Co-authored-by: Sergio Pedri --- Microsoft.Toolkit.Services/Microsoft.Toolkit.Services.csproj | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Microsoft.Toolkit.Services/Microsoft.Toolkit.Services.csproj b/Microsoft.Toolkit.Services/Microsoft.Toolkit.Services.csproj index eda0b41f4ee..cbc40a855c6 100644 --- a/Microsoft.Toolkit.Services/Microsoft.Toolkit.Services.csproj +++ b/Microsoft.Toolkit.Services/Microsoft.Toolkit.Services.csproj @@ -10,7 +10,6 @@ - Facebook: Album, DataConfig, DataHost, OAuthTokens, Permissions, Photo, Picture, PictureData, PlatformImageSource, Post, RequestSource, Service. UWP Community Toolkit Windows Microsoft Graph OneDrive Twitter Translator LinkedIn service login OAuth - 8.0 CS8002;CS0618 false @@ -54,4 +53,4 @@ - \ No newline at end of file + From e0f1cb3092a7e28bc8d69c6d8bb516ab3aefa41a Mon Sep 17 00:00:00 2001 From: Alexandre Zollinger Chohfi Date: Tue, 18 Aug 2020 15:22:16 -0700 Subject: [PATCH 397/595] Update Microsoft.Toolkit/Collections/ReadOnlyObservableGroupedCollection.cs --- .../Collections/ReadOnlyObservableGroupedCollection.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Microsoft.Toolkit/Collections/ReadOnlyObservableGroupedCollection.cs b/Microsoft.Toolkit/Collections/ReadOnlyObservableGroupedCollection.cs index 661edda8ca2..42d012356f7 100644 --- a/Microsoft.Toolkit/Collections/ReadOnlyObservableGroupedCollection.cs +++ b/Microsoft.Toolkit/Collections/ReadOnlyObservableGroupedCollection.cs @@ -58,7 +58,7 @@ static void ThrowNotSupportedException() "ReadOnlyObservableGroupedCollection doesn't support operations on multiple items at once.\n" + "If this exception was thrown, it likely means support for batched item updates has been added to the " + "underlying ObservableCollection type, and this implementation doesn't support that feature yet.\n" + - "Please consider opening an issue in https://github.com/windows-toolkit/WindowsCommunityToolkit to report this."); + "Please consider opening an issue in https://aka.ms/windowstoolkit to report this."); } ThrowNotSupportedException(); From 834531c3821143ffce78e9f286989f3d66fdf0c8 Mon Sep 17 00:00:00 2001 From: Alexandre Zollinger Chohfi Date: Tue, 18 Aug 2020 16:27:29 -0700 Subject: [PATCH 398/595] Added 2 extra commands for each Formatter of TextToolbar. --- .../TextToolbar/TextToolbarPage.xaml.cs | 33 +++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/TextToolbar/TextToolbarPage.xaml.cs b/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/TextToolbar/TextToolbarPage.xaml.cs index c2b25226d5f..8e26d0fd635 100644 --- a/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/TextToolbar/TextToolbarPage.xaml.cs +++ b/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/TextToolbar/TextToolbarPage.xaml.cs @@ -7,6 +7,7 @@ using Microsoft.Toolkit.Uwp.UI.Controls; using Microsoft.Toolkit.Uwp.UI.Controls.TextToolbarButtons; using Microsoft.Toolkit.Uwp.UI.Controls.TextToolbarFormats.MarkDown; +using Microsoft.Toolkit.Uwp.UI.Controls.TextToolbarFormats.RichText; using Microsoft.Toolkit.Uwp.UI.Extensions; using Windows.System; using Windows.UI.Xaml; @@ -57,6 +58,16 @@ private void Load() AddCustomButton(); }); + SampleController.Current.RegisterNewCommand("Use RichText Formatter", (sender, args) => + { + UseRichTextFormatter(); + }); + + SampleController.Current.RegisterNewCommand("Use MarkDown Formatter", (sender, args) => + { + UseMarkDownFormatter(); + }); + SampleController.Current.RegisterNewCommand("Use Custom Formatter", (sender, args) => { UseCustomFormatter(); @@ -88,6 +99,28 @@ private void ResetLayout() } } + private void UseRichTextFormatter() + { + if (_toolbar == null) + { + return; + } + + var formatter = new RichTextFormatter(); + _toolbar.Formatter = formatter; + } + + private void UseMarkDownFormatter() + { + if (_toolbar == null) + { + return; + } + + var formatter = new MarkDownFormatter(); + _toolbar.Formatter = formatter; + } + private void UseCustomFormatter() { if (_toolbar == null) From 5b7d04b70d7c15bd4f8b4b05365a674aaef58f10 Mon Sep 17 00:00:00 2001 From: Alexandre Zollinger Chohfi Date: Tue, 18 Aug 2020 16:33:24 -0700 Subject: [PATCH 399/595] Remove Toolkit Platform Analyzers (fixes #3396). --- .../App.config | 6 - .../DifferencesGen.csproj | 65 ---- .../Program.cs | 256 ------------- .../Analyzer.cs | 357 ------------------ .../Differences-6.0.0.0.gz | Bin 7609 -> 0 bytes .../Differences-7.0.0.0.gz | Bin 7949 -> 0 bytes .../HowToGuard.cs | 32 -- ...oolkit.Uwp.PlatformSpecificAnalyzer.csproj | 36 -- .../NewMember.cs | 46 --- .../Platform.cs | 69 ---- .../PlatformKind.cs | 27 -- .../PlatformSpecificAnalyzerCS.cs | 230 ----------- .../PlatformSpecificAnalyzerVB.cs | 254 ------------- .../PlatformSpecificFixerCS.cs | 168 --------- .../PlatformSpecificFixerVB.cs | 168 --------- .../tools/install.ps1 | 58 --- .../tools/uninstall.ps1 | 65 ---- .../SamplePages/samples.json | 8 - Windows Community Toolkit.sln | 57 --- 19 files changed, 1902 deletions(-) delete mode 100644 Microsoft.Toolkit.Uwp.PlatformDifferencesGen/App.config delete mode 100644 Microsoft.Toolkit.Uwp.PlatformDifferencesGen/DifferencesGen.csproj delete mode 100644 Microsoft.Toolkit.Uwp.PlatformDifferencesGen/Program.cs delete mode 100644 Microsoft.Toolkit.Uwp.PlatformSpecificAnalyzer/Analyzer.cs delete mode 100644 Microsoft.Toolkit.Uwp.PlatformSpecificAnalyzer/Differences-6.0.0.0.gz delete mode 100644 Microsoft.Toolkit.Uwp.PlatformSpecificAnalyzer/Differences-7.0.0.0.gz delete mode 100644 Microsoft.Toolkit.Uwp.PlatformSpecificAnalyzer/HowToGuard.cs delete mode 100644 Microsoft.Toolkit.Uwp.PlatformSpecificAnalyzer/Microsoft.Toolkit.Uwp.PlatformSpecificAnalyzer.csproj delete mode 100644 Microsoft.Toolkit.Uwp.PlatformSpecificAnalyzer/NewMember.cs delete mode 100644 Microsoft.Toolkit.Uwp.PlatformSpecificAnalyzer/Platform.cs delete mode 100644 Microsoft.Toolkit.Uwp.PlatformSpecificAnalyzer/PlatformKind.cs delete mode 100644 Microsoft.Toolkit.Uwp.PlatformSpecificAnalyzer/PlatformSpecificAnalyzerCS.cs delete mode 100644 Microsoft.Toolkit.Uwp.PlatformSpecificAnalyzer/PlatformSpecificAnalyzerVB.cs delete mode 100644 Microsoft.Toolkit.Uwp.PlatformSpecificAnalyzer/PlatformSpecificFixerCS.cs delete mode 100644 Microsoft.Toolkit.Uwp.PlatformSpecificAnalyzer/PlatformSpecificFixerVB.cs delete mode 100644 Microsoft.Toolkit.Uwp.PlatformSpecificAnalyzer/tools/install.ps1 delete mode 100644 Microsoft.Toolkit.Uwp.PlatformSpecificAnalyzer/tools/uninstall.ps1 diff --git a/Microsoft.Toolkit.Uwp.PlatformDifferencesGen/App.config b/Microsoft.Toolkit.Uwp.PlatformDifferencesGen/App.config deleted file mode 100644 index 731f6de6c29..00000000000 --- a/Microsoft.Toolkit.Uwp.PlatformDifferencesGen/App.config +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/Microsoft.Toolkit.Uwp.PlatformDifferencesGen/DifferencesGen.csproj b/Microsoft.Toolkit.Uwp.PlatformDifferencesGen/DifferencesGen.csproj deleted file mode 100644 index e2726cf8cc5..00000000000 --- a/Microsoft.Toolkit.Uwp.PlatformDifferencesGen/DifferencesGen.csproj +++ /dev/null @@ -1,65 +0,0 @@ - - - - - Debug - AnyCPU - {292D34E8-0F01-4FA8-951D-8232F75A88D5} - Exe - DifferencesGen - DifferencesGen - v4.6.1 - 512 - true - - - AnyCPU - true - full - false - bin\Debug\ - DEBUG;TRACE - prompt - 4 - - - AnyCPU - pdbonly - true - bin\Release\ - TRACE - prompt - 4 - bin\Release\DifferencesGen.xml - 1591 - - - - - - - - - - - - - - - - - - - - - - - - 10.0.3 - - - - - - - \ No newline at end of file diff --git a/Microsoft.Toolkit.Uwp.PlatformDifferencesGen/Program.cs b/Microsoft.Toolkit.Uwp.PlatformDifferencesGen/Program.cs deleted file mode 100644 index 1cfaef00249..00000000000 --- a/Microsoft.Toolkit.Uwp.PlatformDifferencesGen/Program.cs +++ /dev/null @@ -1,256 +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.IO; -using System.IO.Compression; -using System.Linq; -using System.Reflection; -using System.Runtime.InteropServices.WindowsRuntime; -using System.Text; - -namespace DifferencesGen -{ - public class Program - { - private static HashSet enumTypes = new HashSet(); - private static HashSet typeEvents = new HashSet(); - - public static void Main(string[] args) - { - string min = null; - string max = null; - - foreach (var arg in args) - { - if (arg.StartsWith("/min:")) - { - min = arg.Replace("/min:", string.Empty); - } - else if (arg.StartsWith("/max:")) - { - max = arg.Replace("/max:", string.Empty); - } - } - - Version.TryParse(min, out Version minVersion); - Version.TryParse(max, out Version maxVersion); - - if (minVersion == null || maxVersion == null) - { - Console.WriteLine("The differences generator needs to be run as follows:"); - Console.WriteLine("DifferencesGen /min:4.0.0.0 /max:5.0.0.0"); - - return; - } - - string folderPath = @"C:\Program Files (x86)\Windows Kits\10\References"; - - string universalApiFile = "Windows.Foundation.UniversalApiContract.winmd"; - - string universalApiDifferencesCompressedFile = "Differences-{0}.gz"; - - AppDomain.CurrentDomain.ReflectionOnlyAssemblyResolve += (sender, eventArgs) => Assembly.ReflectionOnlyLoad(eventArgs.Name); - WindowsRuntimeMetadata.ReflectionOnlyNamespaceResolve += (sender, eventArgs) => - { - string path = - WindowsRuntimeMetadata.ResolveNamespace(eventArgs.NamespaceName, Enumerable.Empty()) - .FirstOrDefault(); - if (path == null) - { - return; - } - - eventArgs.ResolvedAssemblies.Add(Assembly.ReflectionOnlyLoadFrom(path)); - }; - - DirectoryInfo directoryInfo = new DirectoryInfo(folderPath); - - FileInfo[] files = directoryInfo.GetFiles(universalApiFile, SearchOption.AllDirectories); - - List> assemblyList = new List>(); - - if (files.Length > 0) - { - foreach (var file in files) - { - var assembly = Assembly.ReflectionOnlyLoadFrom(file.FullName); - - var nameParts = assembly.FullName.Split(new string[] { ", " }, StringSplitOptions.RemoveEmptyEntries); - - var versionParts = nameParts[1].Split(new char[] { '=' }, StringSplitOptions.RemoveEmptyEntries); - - var version = Version.Parse(versionParts[1]); - - if (version >= minVersion && version <= maxVersion) - { - assemblyList.Add(new Tuple(version, assembly)); - } - } - } - - if (assemblyList.Count >= 2) - { - var orderedList = assemblyList.OrderBy(t => t.Item1).ToList(); - - for (int i = 1; i < orderedList.Count; i++) - { - var previousVersionAssembly = orderedList[i - 1].Item2; - var newerVersionAssembly = orderedList[i].Item2; - - var version = orderedList[i].Item1; - - var previousVersionTypes = ProcessAssembly(previousVersionAssembly); - var newerVersionTypes = ProcessAssembly(newerVersionAssembly); - - var addedTypes = new Dictionary>(); - - foreach (var type in newerVersionTypes) - { - if (!previousVersionTypes.ContainsKey(type.Key)) - { - addedTypes.Add(type.Key, null); - - if (enumTypes.Contains(type.Key)) - { - System.Diagnostics.Debug.WriteLine($"New enum {type.Key}"); - } - - continue; - } - - HashSet previousVersionTypeMembers = new HashSet(previousVersionTypes[type.Key]); - HashSet newerVersionTypeMembers = new HashSet(type.Value); - - newerVersionTypeMembers.ExceptWith(previousVersionTypeMembers); - - if (newerVersionTypeMembers.Count == 0) - { - continue; - } - - if (enumTypes.Contains(type.Key)) - { - System.Diagnostics.Debug.WriteLine($"Enum {type.Key} has new members: {string.Join(",", newerVersionTypeMembers)}"); - } - - foreach (var member in newerVersionTypeMembers) - { - if (typeEvents.Contains($"{type.Key}-{member}")) - { - System.Diagnostics.Debug.WriteLine($"Type {type.Key} has new event: {member}"); - } - } - - addedTypes.Add(type.Key, newerVersionTypeMembers.ToList()); - } - - StringBuilder stringBuilder = new StringBuilder(); - - using (var compressedFS = File.Create(Path.Combine(AssemblyDirectory, string.Format(universalApiDifferencesCompressedFile, version.ToString())))) - { - using (var compressionFS = new GZipStream(compressedFS, CompressionMode.Compress)) - { - using (var writer = new StreamWriter(compressionFS)) - { - foreach (var addedType in addedTypes) - { - stringBuilder.Clear(); - - stringBuilder.Append(addedType.Key); - - if (addedType.Value != null && addedType.Value.Count > 0) - { - stringBuilder.Append(':'); - stringBuilder.Append(string.Join(",", addedType.Value)); - } - - writer.WriteLine(stringBuilder.ToString()); - } - } - } - } - - stringBuilder.Length = 0; - } - } - } - - public static string AssemblyDirectory - { - get - { - string codeBase = Assembly.GetExecutingAssembly().CodeBase; - UriBuilder uri = new UriBuilder(codeBase); - string path = Uri.UnescapeDataString(uri.Path); - return Path.GetDirectoryName(path); - } - } - - private static Dictionary> ProcessAssembly(Assembly assembly) - { - var types = new Dictionary>(); - - foreach (var exportedType in assembly.ExportedTypes) - { - var members = new List(); - - if (exportedType.IsEnum) - { - if (!enumTypes.Contains(exportedType.FullName)) - { - enumTypes.Add(exportedType.FullName); - } - - foreach (var member in exportedType.GetFields()) - { - if (member.Name.Equals("value__")) - { - continue; - } - - members.Add(member.Name); - } - } - else - { - foreach (var methodInfo in exportedType.GetMethods()) - { - if (!methodInfo.IsPublic) - { - continue; - } - - if (methodInfo.Name.StartsWith("get_") || - methodInfo.Name.StartsWith("set_") || - methodInfo.Name.StartsWith("put_") || - methodInfo.Name.StartsWith("add_") || - methodInfo.Name.StartsWith("remove_")) - { - continue; - } - - members.Add($"{methodInfo.Name}#{methodInfo.GetParameters().Length}"); - } - - foreach (var propertyInfo in exportedType.GetProperties()) - { - members.Add(propertyInfo.Name); - } - - foreach (var eventInfo in exportedType.GetEvents()) - { - typeEvents.Add($"{exportedType.FullName}-{eventInfo.Name}"); - members.Add(eventInfo.Name); - } - } - - types.Add(exportedType.FullName, members); - } - - return types; - } - } -} diff --git a/Microsoft.Toolkit.Uwp.PlatformSpecificAnalyzer/Analyzer.cs b/Microsoft.Toolkit.Uwp.PlatformSpecificAnalyzer/Analyzer.cs deleted file mode 100644 index ebc19cd548b..00000000000 --- a/Microsoft.Toolkit.Uwp.PlatformSpecificAnalyzer/Analyzer.cs +++ /dev/null @@ -1,357 +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.IO; -using System.IO.Compression; -using System.Linq; -using System.Reflection; -using Microsoft.CodeAnalysis; - -namespace Microsoft.Toolkit.Uwp.PlatformSpecificAnalyzer -{ - /// - /// This class offers loads platform differences for use by Code Analyzer and Code Fixer. - /// - public static class Analyzer - { - internal enum TypePresenceIndicator - { - New, - Changes, - NotFound, - } - - private static Dictionary>> _differencesDictionary = null; - - /// - /// Embedded differences between API contract version 4 and 5. - /// - public const string N1DifferencesRes = "Differences-5.0.0.0.gz"; - - /// - /// Embedded differences between API contract version 5 and 6. - /// - public const string N0DifferencesRes = "Differences-6.0.0.0.gz"; - - /// - /// Earliest supported SDK version. - /// - public const string N2SDKVersion = "15063"; - - /// - /// Intermediate SDK version. - /// - public const string N1SDKVersion = "16299"; - - /// - /// Latest SDK version. - /// - public const string N0SDKVersion = "17134"; - - /// - /// Platform related diagnostic descriptor - /// - public static readonly DiagnosticDescriptor PlatformRule = new DiagnosticDescriptor("UWP001", "Platform-specific", "Platform-specific code detected. Consider using ApiInformation.IsTypePresent to guard against failure", "Safety", DiagnosticSeverity.Warning, true); - - /// - /// Version related diagnostic descriptor - /// - public static readonly DiagnosticDescriptor VersionRule = new DiagnosticDescriptor("UWP002", "Version-specific", "Version-specific code detected. Consider using ApiInformation.IsTypePresent / ApiInformation.IsMethodPresent / ApiInformation.IsPropertyPresent to guard against failure", "Safety", DiagnosticSeverity.Warning, true); - - private static char[] typeMemberSeparator = { ':' }; - private static char[] memberSeparator = { ',' }; - - static Analyzer() - { - _differencesDictionary = new Dictionary>>(); - _differencesDictionary.Add(N0DifferencesRes, GetApiAdditions(N0DifferencesRes)); - _differencesDictionary.Add(N1DifferencesRes, GetApiAdditions(N1DifferencesRes)); - } - - /// - /// Gets the API differences from specified resource. - /// - /// name of embedded resource - /// Dictionary with Fully qualified name of type as key and list of new members as value - public static Dictionary> GetUniversalApiAdditions(string resourceName) - { - return _differencesDictionary[resourceName]; - } - - private static Dictionary> GetApiAdditions(string resourceName) - { - Dictionary> apiAdditionsDictionary = new Dictionary>(); - - Assembly assembly = typeof(Analyzer).GetTypeInfo().Assembly; - - var resource = assembly.GetManifestResourceStream("Microsoft.Toolkit.Uwp.PlatformSpecificAnalyzer." + resourceName); - - if (resource == null) - { - System.Diagnostics.Debug.WriteLine($"Resource {resourceName} not found."); - return new Dictionary>(); - } - - System.Diagnostics.Debug.WriteLine($"Resource {resourceName} found."); - Dictionary> differencesDictionary = new Dictionary>(); - - using (GZipStream decompressionStream = new GZipStream(resource, CompressionMode.Decompress)) - { - using (StreamReader reader = new StreamReader(decompressionStream)) - { - while (!reader.EndOfStream) - { - var typeDetails = reader.ReadLine(); - - var typeMemberParts = typeDetails.Split(typeMemberSeparator, StringSplitOptions.RemoveEmptyEntries); - - if (typeMemberParts.Length == 1) - { - differencesDictionary.Add(typeMemberParts[0], null); - - continue; - } - - var membersAddedToType = typeMemberParts[1].Split(memberSeparator, StringSplitOptions.RemoveEmptyEntries); - - differencesDictionary.Add(typeMemberParts[0], new List(membersAddedToType)); - } - } - } - - if (differencesDictionary == null) - { - return apiAdditionsDictionary; - } - - foreach (var kvp in differencesDictionary) - { - var list = new List(); - if (kvp.Value != null) - { - list.AddRange(kvp.Value.Select(v => new NewMember(v))); - } - - apiAdditionsDictionary.Add(kvp.Key, list); - } - - return apiAdditionsDictionary; - } - - /// - /// This function tells which version/platform the symbol is from. - /// - /// represents a compiler - /// instance of - public static Platform GetPlatformForSymbol(ISymbol symbol) - { - if (symbol == null) - { - return new Platform(PlatformKind.Unchecked); - } - - if (symbol.ContainingNamespace != null && symbol.ContainingNamespace.ToDisplayString().StartsWith("Windows.")) - { - var assembly = symbol.ContainingAssembly.Name; - var version = symbol.ContainingAssembly.Identity.Version.Major; - - // Any call to ApiInformation.* is allowed without warning - if (symbol.ContainingType?.Name == "ApiInformation") - { - return new Platform(PlatformKind.Uwp, Analyzer.N2SDKVersion); - } - - // Don't want to give warning when analyzing code in an PCL project. - // In those two targets, every Windows type is found in Windows.winmd, so that's how we'll suppress it: - if (assembly == "Windows") - { - return new Platform(PlatformKind.Unchecked); - } - - // Some WinRT types like Windows.UI.Color get projected to come from .NET assemblies, always present: - if (assembly.StartsWith("System.Runtime.")) - { - return new Platform(PlatformKind.Uwp, Analyzer.N2SDKVersion); - } - - // Some things are emphatically part of UWP.10240 - if (assembly == "Windows.Foundation.FoundationContract" || (assembly == "Windows.Foundation.UniversalApiContract" && version == 1)) - { - return new Platform(PlatformKind.Uwp, Analyzer.N2SDKVersion); - } - - if (assembly == "Windows.Foundation.UniversalApiContract") - { - var isType = symbol.Kind == SymbolKind.NamedType; - - var typeName = isType ? symbol.ToDisplayString() : symbol.ContainingType.ToDisplayString(); - - TypePresenceIndicator presentInN0ApiDiff = CheckCollectionForType(Analyzer.GetUniversalApiAdditions(Analyzer.N0DifferencesRes), typeName, symbol); - - if (presentInN0ApiDiff == TypePresenceIndicator.New) - { - // the entire type was found in Target Version - return new Platform(PlatformKind.Uwp, Analyzer.N0SDKVersion); - } - else if (presentInN0ApiDiff == TypePresenceIndicator.Changes) - { - // the entire type was found in Target Version with matching parameter lengths - return new Platform(PlatformKind.Uwp, Analyzer.N0SDKVersion, true); - } - else - { - TypePresenceIndicator presentInN1ApiDiff = CheckCollectionForType(Analyzer.GetUniversalApiAdditions(Analyzer.N1DifferencesRes), typeName, symbol); - - if (presentInN1ApiDiff == TypePresenceIndicator.New) - { - // the entire type was found in Target Version - return new Platform(PlatformKind.Uwp, Analyzer.N1SDKVersion); - } - else if (presentInN1ApiDiff == TypePresenceIndicator.Changes) - { - // the entire type was found in Target Version with matching parameter lengths - return new Platform(PlatformKind.Uwp, Analyzer.N1SDKVersion, true); - } - else - { - // the type was in Min version - return new Platform(PlatformKind.Uwp, Analyzer.N2SDKVersion); - } - } - } - - // All other Windows.* types come from platform-specific extensions - return new Platform(PlatformKind.ExtensionSDK); - } - else - { - return new Platform(PlatformKind.Unchecked); - } - } - - /// - /// returns instance of for - /// - /// instance of - /// instance of - public static HowToGuard GetGuardForSymbol(ISymbol target) - { - var plat = Analyzer.GetPlatformForSymbol(target); - - switch (plat.Kind) - { - case PlatformKind.ExtensionSDK: - return new HowToGuard() - { - TypeToCheck = target.Kind == SymbolKind.NamedType ? target.ToDisplayString() : target.ContainingType.ToDisplayString(), - KindOfCheck = "IsTypePresent" - }; - case PlatformKind.Uwp: - if (target.Kind == SymbolKind.NamedType) - { - return new HowToGuard() - { - TypeToCheck = target.ToDisplayString(), - KindOfCheck = "IsTypePresent" - }; - } - else - { - var g = new HowToGuard - { - TypeToCheck = target.ContainingType.ToDisplayString() - }; - - var d0 = Analyzer.GetUniversalApiAdditions(Analyzer.N0DifferencesRes); - var d1 = Analyzer.GetUniversalApiAdditions(Analyzer.N1DifferencesRes); - - if (!d0.TryGetValue(g.TypeToCheck, out List newMembers)) - { - d1.TryGetValue(g.TypeToCheck, out newMembers); - } - - if (newMembers == null) - { - throw new InvalidOperationException("oops! expected this UWP version API to be in the dictionary of new things"); - } - - g.MemberToCheck = target.Name; - - if (target.Kind == SymbolKind.Field) - { - // the only fields in WinRT are enum fields - g.KindOfCheck = "IsEnumNamedValuePresent"; - } - else if (target.Kind == SymbolKind.Event) - { - g.KindOfCheck = "IsEventPresent"; - } - else if (target.Kind == SymbolKind.Property) - { - // TODO: if SDK starts introducing additional accessors on properties, we'll have to change this - g.KindOfCheck = "IsPropertyPresent"; - } - else if (target.Kind == SymbolKind.Method) - { - g.KindOfCheck = "IsMethodPresent"; - - if (target.Kind == SymbolKind.Method && plat.ByParameterCount) - { - g.ParameterCountToCheck = (target as IMethodSymbol).Parameters.Length; - } - } - - return g; - } - - default: - throw new InvalidOperationException("oops! don't know why I was asked to check something that's fine"); - } - } - - private static TypePresenceIndicator CheckCollectionForType(Dictionary> collection, string typeName, ISymbol symbol) - { - if (!collection.TryGetValue(typeName, out var newMembers)) - { - return TypePresenceIndicator.NotFound; - } - - if (newMembers == null || newMembers.Count == 0) - { - return TypePresenceIndicator.New; - } - - if (symbol.Kind == SymbolKind.NamedType) - { - return TypePresenceIndicator.NotFound; - } - - var memberName = symbol.Name; - - foreach (var newMember in newMembers) - { - if (memberName == newMember.Name && !newMember.ParameterCount.HasValue) - { - return TypePresenceIndicator.New; - } - - // this member was new in collection - if (symbol.Kind != SymbolKind.Method) - { - // TODO: Continue For... Warning!!! not translated - } - - if (memberName == newMember.Name && ((IMethodSymbol)symbol).Parameters.Length == newMember.ParameterCount) - { - return TypePresenceIndicator.Changes; - } - } - - // this member existed in a different collection - return TypePresenceIndicator.NotFound; - } - } -} diff --git a/Microsoft.Toolkit.Uwp.PlatformSpecificAnalyzer/Differences-6.0.0.0.gz b/Microsoft.Toolkit.Uwp.PlatformSpecificAnalyzer/Differences-6.0.0.0.gz deleted file mode 100644 index 90a9ae8fef7274eb886055cefa294a378db06ee0..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 7609 zcmV;q9Y*3GiwFP!000040Nq_#bK^LYexHc>74@oc)Y03!p*LBw%bNC~k?gA3eS=6? z!kQwuAnCID*KZ~c61*f*g4>9BsImwmKP0Y1W+DOpWO2xUr5BSViC91~#^XC4(&%E! z1;x}`mS$IRCidf7lE=aJnv18Dib=YUgHK=n{&M9L=EO0eyjPF+aG^4w#S77H+unjcY z5}Qg;;PyP61Jrs9%;Pj8Q3OrD5zv&!38n<{^UWBB(UPV-7Xhsdr*F_YJ7}cZ%d|0O zj}*|30?&Dr9RO*v^^oA({xxeN)C;|**)J|$SiHF~!|oNwZ#f2`IiF!%T(bxU*m#kP z1cuf3oP|9=XcXlU5tAf_^o+h`V~uuCcGhrsnio>y2hKTQ^)k;g9{*q|TeAqT#xs`U zSPUyvnA@7V5(NL7$4Q>y%YXg%H4pOiQII%&reX$@NmgnY%J@<24TM4D3&D0Ut?~V* zucKN)FlQ?{8t18HnISteqWR*4_Z33I&Ka$ODcx+?WYm;nZ*W;&0b`S?4{ z)O&cHP?51d^Ai%JwCF9B%Ade12p8Q=|A_Up@1;hs}FLa~sp zd!v!U9WOqA`P`~X=${WD9|-8mX*~g@$P+5e_dH|ITG~{rbqbnRH)l|L7Nz~L9x1BU zI51HAkL02~-O}LY6tFuEDi6IBoP=u_ZaAcOsDZ#hSOi?H4WCDISpBYcRBS-A-cLd8 z#eD6ljgc{+bSUw3uIDL_YVvGY(by@9`>uvOwB#P?;eH^;y0#xmkE%7)9kd!UsvgqH z6PR!r3WxzDKS>rqJ+6e{LrNN-Uex$1&Ue`I&>o;%CJ}|DVDZdtAtAK8j>>s%?3#Wp@x;9DG8ue}V<;yA;&tO+$m(#FEj^A{S zpDf$1_;dD)2&!ZwXvO8L2Dz=(!jc2)tH@RXEH&}PBaE;+5UgPG5j2_PAe1VUY_?0f z1%-u+Vf5oe04tbPFrUodYaWHr?@Cmev5nOgpFXz`Q6hq^dAwz70ftb8>XbQu9IjIT zN3code@o2x6thALy=C!BGwYHC+pCae1xYiDMPFSD8*E!DE*LHL<6fzES4!NZ<4KkY zww6YNUUadj)1b$X9QA2fLZG7-M5GiAQJM6mktEFqOy}0qY2{*H#4w;>Y%FO2)vD$- z79pB*UecusN$Wt+93iyIf-qFE zdx70voeozsv+p=8_*|6qRqOFE)ndDc1|*TDu3OZHiDxto1WN`|N$X_@K{KCpAQvO+ zJNc8UH&Babw#k)-Exk56Pag=GqTF@^sMH&+ke^C72}2s{w=L4XCyLsjIUh&Kpp$U! zPG#T<9eXE|zOfUzRc{7#+Jdt<>&!3TK0L3ez~^%Fw7R-$1-|C+9ENcfqr#{k8tT~j zSO;3MAE;Jo-Ei0nb~cMEnn?`{)}-BAkSx4+PK=rE;gmlW49a90lPeL z&21Or8O-W_+8fi9rZQB5wyZh*xRm>N4bU^Ieh}2fL~zgYLC~{VzJWC9*3z>0OlCXa z&Sujjm#W#hNL|jP4m8k@*^Y))EV-X;SJyb>>&+nj^L7Ff#t*StG0MWP(PyO$*?aVF z;!cU&or{TO2x_M$nhs_Q{CmXLxcx^|ga&=4GQ!4KKPS5Fb9eR{akg|G;)_NR-GapzJm!CoJak6 z(hjBm1Pg{P%mbTn_pI)a?AlzE+jI}F7(0R^o5n=2wk^9F*(FS%Z59LL187@_bbQDG zqAG*O^XtD?@fNos(PL_eq49`}9|CpaY=G`<>;Zr+L<_K0v5FVU;i4s!N>)ja`-F}k z`DPOhrJwQyIIp|_yII7L9X-e+V~4hn#Y4z)hh)Y6r0)ifwSEt9E6(CQ;QyJ+-IMo# zmJ&u9^jWu29mrBG#8=Vv3?Km1;&t~ z9IU(>Iv`|-FLLrO!%d+}ad|$Ms6AlldX>{j7~+dhzm44R{7BN5Y6)r&*sg^g5ZDB* z_D=tt4KIHOLCIk{t`m<`Qx(Yv(M+^r1GNXp{5$14nu)z~Hh!ITuff{aJ-2+NYEyrb zhm5QLr6;N3Ds5zw(7Hp^vL{QI@1&>e9z?R)zwDLm>#@>|)-%2%Zwsi+nglPagwjx* z8)zr*EYxg@sy(cM#O>LSRI?stu1Jk;`$r7ckT#E`O)|a#HBqY3VExe156~*nP+Dv` zZtQ|d30l!sh4U=n&(5xGY9iX~rlMxQkf-4OJYBj^8UANN@N3AW=TJ zYH}}G4xACj2X?a1L9UCAa7PP4ik%Fd+8a(sB1({}7z}=BpcDFQX-sK_RK7TZa3uGP z5j@k9@qkD20mCO4*b`tZqiy^YGngT&@_4R7J0L`$3;X)GWMCr`n+yz!x0@TL!f?2TGk< zJ{xK+57S!MZabwMRVSB=XWl{+gnWEj*2$$rIj_>)Van}9D0&E!J3}to+N>oYvMX&N zZWr3lwN-#V)kRtkj>(0$XgerALpP4#+Iqrd^ z8Wpu@sougOQ2ek|u^DJr5hr2GQ7&^DPzNcZi)CceW_tJkyh5ANxqm^S>g4KQQX(Up zn5P*FCgynTdBw_^V-=w73=F_DwNnA?Oa^E+(z@}qi=ku>e}G)PUy1(N26N_&4j4+-B6eTqZ(tKTDBhs z4z2+sL~Vvv!`OR{ll}yaK;)kF89Fj_kk<(fsKdGrQ?7XMLbKEy0My~`R?JeztTA-@ z_J^Nztx`Z-$KgGO<;5hDhGB7N8eUx2Qx9TeM^aHQ;RKR~YSf$3I?~vKS}n$Ih+8Rs zBr*QOfL)qh=Aet=!4+6;FsxNyR&a>WoMyno>v&*0_ z2_Co_Pf2X{F7Y%gLiNX3B}4}5G7K!ZhvK!LgoI#2j9SyN!EWG(Y=pv}sXf0V~{o=spm8h+7+>KWP_#Lz{cSQ#!WWyD%4^>rHxTACzyujOxAAx?Rwh zeHfvYj_$he1}2%(I6)qTlPD^W+N5=n*B8Rj*Voab^jLP~6{ecA2Q_dbN?Vh^%48Ke zqu-b)BAf0&hNB4I`qit5X@Z9$kcq$0SkLaUJc()r&A@ajLKgOhS?j$%pl}U_PP9j7 zVdbUZcmqZCZkB>Qn27RN1Bud$vYQQ8creD?!cjX6q_qgVWGRvVEqR{NnP9KQ9%VY7 z(FEpuE+{$?x@hIqMbaN$8c864QK7UXMVV&J038PfwJj|;iKI{9)P1I9eq+E%BSey@ z=+8Vw-<^p7(=t2F3;(N1dZYquN_iBtvxM?EQ)A2*d5TLztQ!?Na+eh}(QONu%Ot8i zRPUIAp`@M4mJtmr%?u4EK_VH?RxGGb2|81$CL9&%#z|e?A#*No@^Y6}bpi&uF2$S~ zJd*2MuUx;ExoTQOdx71Vemh)uYY^f!6k@#0W6QT7YmY-23V>PCbt4WCLvL}hH;1oi zIHT!H#uFGaWo~)D(k|htbTpaUgj9k&{?gbmsZrRhTj?{p&NsO3go6CiP7mDENlyZT zRX@XNeiCsrOsqz1-G73fI6Pi!B`M#_l7=Qg5gm|Y852sy&n>0VLmcf#Qnp6)k>|lS zA*lJ;Svt#}#B8TmB-7N!5N>je;p@&{2HgigU5-G^~TT5g zi@3)ViX^(@@6{2y99aEu#yc8U2l~cMJvv0r`**pE{$Qx~$(Fu)ON2gYf8K1XNQsCG zFi}E`%f!Pf=K@}$3#w%!;!N1pmL!&t=|gfwa6{=DcWfOr)TPMN?YSUo-^mG-F)Q_f z+;h-yXHn|!mta){R1e_-3lz}i>u=51#SX0Ot0MU-idd3T6FpX> z_y^r9H(Y}k(5N#|)1zhyOWXp%dsezKNbZ%yDAN)!!)Uc$cA8;IUPi~?KB1dQs-k$a zBg>3cVmIR|Ab2~c#ko3-Q3PCIFcy--1RC9ux0bheEN-R_QH1)8Gi>Kx`K-$X&mG#j zOZux?Gm>s=w=8y(%s^q5(G>DhpL3c0!N7DTky}tHl^seP6}h-STL=fx$Dx$)XG7^5 znOxNIk%-NSqfT3F=C8`7@cQ)=PH!g-QV>dC$N8&p@Djp|bke?eBz`UxMo-gIZp*DScCe}dKFKpUFx&|S%FDpzVKETfR) zv?&b3{0wp&6U0U9EHRo89Y#tc)B8+kYQU?ECT{iEDa@J^Es>OFCsQkh>dXP7K%QcG z$VT3I>S*{CgeL)&cG-D2s(L5ZMeE*YU?_$541ikHCwF#G9)wh7z|y0juYvp3A#6YGOFPdkD7Cv-IxG5mCcr)*(2SlWBx66~8x zu;1K(wYon#b65P3F8azPV1i1Mb6DHU(hhrU2sy8#d)$h{?xb3Nd#75u#I5)pd(~3V zi#h(UcL=RJ^1e26;(xNZSOz{9S@E+si3+IvXHN5$Z|114!h}>FB3ye4TZ@6V*7OgTv3C-(|!4eew8TH7tw zK@v5jE(z!$Vy}Xy>{t%~`$^a4e;sntxTJQBH*gop)u`Jl!r)tP8(*iu4}krGWgm|j zk(fp@vIbew#F;XBf-l6SDEo6<+~-HwWsz0$^anAKucJ2BILJ|A{AL(47m1Bw&0cC zN~~TiC^WW}fYG5aer}<}mI8}vneVUMx8gUl;dKsp5-h@Pm9}7#<9&22J4lUzRI-sy zD5>rK`f9cD!($B3Heuj-oL@(HJy*XrXv3H!3CJY%*`;$3)O{CyaV~MK2Z7A715G61 z58U+zmFwa8Ii=aiJQi|I-mf;g;e!2%ePZ&J3DulOO6~Jeq|P}|mkHyRJWaT#x&AHy zOu7vi{1{UTf(Zl!hR=b8d}y|*eT9Qr&2$j_dlK`dG6l_KJdFGJ~a$}y9TeRx{P@6f>FEA5%)pxRQU3MAD?)^d~5#Z z0Vh)uzmirB8zc%mrR}KWO+ zi$%O2-6Iy&MR+O}`ryWlxiPPPio=k0ZRLDdJ5q8Fv=UoA#m{|2>f6IBn%&c1G7m7J zbh%o9i3JHdhBL&9cNTtlFj!>ZpZbQ42iz>ENO=tG$Zx5BJU_V6SX?{m13&Z`X7N^S z@8iA4J@(Ig%2^x-tAqxb$afypbVYXruiW_`&V67oF+JhTiflU~4yC#2PhYEV zZQ0iZV)A&NqFzJ-`QeG?g@&1@wLx#jNG*BPm9gmX+G~#nf(l3j@r=i-Kb{@DozfrCp{~U9L;jj5K^4j9 z0Yi_RM?mfYCm?Ls944$MJ#`(i#CQulUtq9=G{!?H5Z*H}qtHD`q@kkPG=;76RIZOpa3tTEY^rN-?0`B$wn z^$B!4NuPH;#kogBu=QijUnLRCJXvD#V=3OqAhmAzz(}6)RleC!U$lI@q7~*kw2nD; z=RuTIJ?H?xEY5w{UQ!B?Pc0-5k9)vJRb6aj2)E|UhrmYl3l!Sevk5%fb}MV@(>N%( z@<}_kxXRa_SXm1{&R{ElCFb^G{FLi}2h5nbVz1(R>|kdP*fHK*uiD6tw)TLXYKWjm zevBq#UNYs=Qqts3jeHMSv+wCT8n5#XiqeM|Q{ArRe-}lsdF|J~b8XG?=D~b2tB41z zRHY6w<^fYi-XJ^8Y}WOJ<&pjaOSF1UItQ-?@fLA&2;{b1IAJ}n#SBAp4XuQfLVNQCZj30C{Uaa^( z2XI9Yssk)}cbEelE2hu^T6=`Pi)m3txw}BKqSRf?G5cCD*LcPc>j1YJcK#6h(qlR8qtM_GJ)VOu;g(GS5~8a1jEe&uEPbL zwVK+Fa@5{2<#d1(wfcZQQ=oypvClyqVICfMyg0vh%ju35w)ghCPAx-Z%n-;@? zw6&){<2A`a0)aS$G^CaEJ z!Kbfy*%zW%XzBaMtM?6#Y=i@d}G#C2o=pW7pevS3RnZQduKb{F5?KdBJ^2OpK z@}XCXFW!5w<2a9B@Xm;}7tvuQGYc9|?GG~gbNxukmgUmJo2)&E=d$6_=#mdn@vpt zKY6=hn{5RDGA}!J;l(1=^nMf!y4W@#$;o>V<@pbi$p6fd+j&~v)mS@I+5_d1MUiaB z5~qLJ-)4iTXt#`^THV ze7$lXB61}kp4fQy#}7D9fZC%wsO&HKVw-vRQHxrmdEcuZHlgfVuMVcQGxxzw*WBG` z4n0Sk9G#1@qdl2-!>+c7dqhWDeD78NVqtNKaAB5ceb5X7rf~0oEoJJC>$Uux^7RbY<8u^q#uvUEShjT5pI(R}LvX%@aJZLc=G)R>2k)^5G2J(_iZS;PT;VNmHJSfDWcD ztKBY;x^h46lm3|-ljFM}$(9A6NH75%{V(%ZpN0cfs3O$mXkvjJkQiIIrC}b?@R5@= bv)_%c-V&zv?r=5v```W_7Y^~4tN8!`->;iN diff --git a/Microsoft.Toolkit.Uwp.PlatformSpecificAnalyzer/Differences-7.0.0.0.gz b/Microsoft.Toolkit.Uwp.PlatformSpecificAnalyzer/Differences-7.0.0.0.gz deleted file mode 100644 index 53e84c4e98e074cd625c0c3f4642a9f46d518259..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 7949 zcmV+oAM)TIiwFP!000040PS5}bK@$~zTc|-6?5xKoik^zeCOEtaBV)kmM8Ppxx*5% z*~mf*QWBqE|8@iVvSeEln#^v7i@kC9HdBvJoONlex(WC^44o{`tKvJ!ab2w9wjtN;LTi`7grHkhco~obGhVX z$D&P<1pJ2OJ4?2YF&0+>PKMXiYE%`KrBG zRLW}#)PgSQe^M%wvk*1$_R8=Sy+I2qg1u6)$8TxJ{!(-^{uYIt1V}%aAdjcQw-X0?m4H5;YN4tS z(?Y0!f|rL2)XVXLMJv7{@?}lLd7-IUlJZyUSvWe_>4B7L2|{3 zOdfc`o=b%rP5CayE;%stja2q^%f^~1iA>5h7n3mjCaD+zI@9Tn0iO-MOp_UhDpVl) zKuGHq99_LbUlZU^zKaRlMDw5>@S}pk9wfyvbo=UWJ;Q&JCVU6nJ`nA*4{Z`l^^$GZK$ACQ3$|6>0(9S6ZId|FIxrmAj3?<~V1njQ zJ=C%_NDIMsM8GE|eZf@C+wm>A&Z!+WR6OEo%Qio>%}&y`R|iFky?D14R&3X4r20^3 zzQ1U9_Q#m&@vDpKS2??Af|rVC(iXuL5gXdFNSYS9dI=6y`|cIT_Ak*wg2&Y9G_nrM+XDZ2}UWpMhLn%P@AOm$C=tVbol(DKqXTcxT?cqxLeo_Z5$T`nbH20Aus%BfMM6gIj z%q49o&4RNid3^S$z~_rgB7B@=@zEg=<@svxjGkCPxpDDwckHalX-L?P235)9X$a-% z$*g%#K-?^3O5}DX$Xn;!3&x%~-?50AF1%ybCC;9C-;+%QpMmcsU?qZrlAv?_n?&?1 z&4Np?Tcf0N84AX(Im6)d3W_TZblW&`=E;1xW9TzLtF^(` zR8aI(3Xt=axaJqXAA8ZeP##Ux3>eLtgMC;tct2AI^voiEPTq*1E5SCK5Bn@VoCWJ|n(MWP3FK)qtr;}(CD*bBoG3ul`r9Gt)eSSVJ6W}p4KOY6lW=r%7oPO1$ z9u47};sU-MTzH&k@YO|rXV*;qx7aHCYJ7WD5UI&-?Tg%`EV!j%T<$z?LcYfN1Whqb zg#eFwKM$Z(?3sa&J@&lC$Xh*(p02#n{qkFhRgiege0c$;6|NG3XD6V*}#ER?prRavN6h-#fyOYT9HIpo|WpD?`m87#ZewnM=>&?w}g*<-{T1ANM$ z%_1^t+a%gQ;apR>63wE_`g69}2yd{{l4_noy(z89)jsBCkxRPcuOwe!RFAZA?ed-m zj9g68fbk3csi@!Clt-K<4*-as`Wt5pf>%P6JGqlG9@E4|$Tjr#cEvymmW5D?)0I=D z=;>gU+@luv?41TMMBuuS7eil5LR}?20R~QbxeqFHdN>?B6AWaj@g2_Nw&gpV#jWs_ zsLhI3dFk^!Q#Se7gUL(R$-caZsL8YCUOhM^9+mh>9Up0{TzY`>_7cVr^(K!uGZfMY zv=&fXzw0XpUz+G6M(rzx(^PBdfU@Ml1XibNEQK{IWoL0KaIixb(ewBnz}!}!%9U@+ zW6IV#k9sA}bApwkJE{aat(`~Ex-8jDg>7cmWxjkJ)zS{7&Uc9L#bV17u74Xl04K7c zJI1LG9D=EwM1eBJRg9drX_h<;&kNL1N0s$UO?uEgYN)5#DnXHMs)$2=Ii9RphI-Cq znBGPzbK#J)uv}DwqjkS8exa^{`2oYRq)BG%%$E1rf;t#P4XNM2qzk3?~gO$ti z6p+`UE+P7#;9HE;P-WJGrZB4CGx%7e&B#O;+>^xL!ZTQ-5Bw@*8wSl~$^^sKYRjY^ zCr5c+leIF&M_0hjD9$Jd$C~L-+VYnkH zzz>S2e_Zo0(2+ygzH9aRk6JM@-Br^>2jL+HB&#>B7K0UOU{;3#wG{p5 z12yl=TAT6eE8(~Sl1o$_ka+|@?eY1Nzr$BRaf-`73PPC+oR;)p0q}s%Bx5(Q!J+BU zbu#4WC&OT&9%SWd3>sB0{Be39??!!6osV-C<6UF@+c0KRuEyx&0!pK1Tl?1LJ6%H$ z1QtP}ZM_;RegRp_OkmX9MA^oHdRY3m`GtPPbok_j=L?H7rxv%5YuQP5(`Qwv zae3cJE&tYm7~=(xq3=xcO&)5y!q<_G#*Ub_25>5CoUi^%A8%Tyy1&hb>${5U{4;}~=bx)Y&&sU-yi75kNZ z<`SB(V?F*!_j(EN$7ONJIT&+MEvKUl54homg!lXh*BygEiTJ48E};|g>Br{zq*hP9 zery##I8&Y7A3vjxAC{>K4*P&lwW-fhoqN~rvGn^=q;hM+4?cMST=sIn2hF$q}r?e=HQW0B>ln`(-1E%(6a=QZUb}UgCSxE7=B&JrS zh!VnZecnhU_O3Str(2XpF(qVPvRJw?xc-Zm8n)}wMS`D8w?s802CBM#E+IwNwcqPuM*>A}TTr}gZzg#g@|bIISWB#0y>;V^?H7iK=E(yiz5l-1NM zm~u%2wX|mfrG9Hp&Zv+1sO9yvL%%~w=w2sdkMv|u*UZ19e=#cT>iK zIulWO$^(-+P(Mr|hmu=L*K4{qbuMMmAh$6vr<07VA7O{Pky=Vw!QU5k@{`$WluiGF zlGf5L_eWKCG?()Tm-dMYklg`|>|L5^NOow*J;uF|F;Yx<02UQJJ5<{pN*_`$sP{n(bSW-?r(=#jVfMIZBpR1iKD-?x;8 zy5~5WjS432H?V8x%x+9aU*-cQ2^Ao{M*DxIKr15n`2oeOT6!nQz)ivp&e~01(l`WD zwg3E}p5Xt0`YE@ipR#!|@7I9nFo8M$p=%gA&ktTyC};X%v;5i}?b)OWjqQ35gYXAv zu7UN#a{9+d6FjnwI2 zFOA^}homT+1`*(Pg3QF#EJ(Op(tqn}%xFmVfAXjv&jak`!_Xx3pj#TGAq{E=rFmP9 z(+SO?5mg~2UNB6~imXWTwloYc%=u;6PL{d!s1KeG5&IPj>4HU3c{iU5JAuRsQT7@Q z`Y3LGSY<=^?yeR}dh9?L{;jX9f(I+9F9OXGj|1cnPwlPq{fKjA`WAhlkq z<4I&)$ddWnD{uz!VaELw18&FayxDI+2qQ4h&z-F>;{;P_vrp+nH+-WB&4lt8zY?(1 z9$%>d6&l@>cRcv9yw9sHFt40aOm%cAz=Li5<+oAxOF@RahEEr+E~|Qe18{t&Yxuti z9#(sAj~vT&mfQsTHE=utH24F{pr@@)W}yBrK|5~9anvC5n5d`2&-VYwF1*Oj8^rjQ z{O47uuq$t&oXZ&_AkXt+8JGLvo6GNOYp$)owL z_+}H*p~^j&G)VB$%?5hXOa5*nX-Yzh9r+ALe}*oxoiw$NcmR!62O3+}ZHYmSwkqp| zxE(WSni`7DUQ7 zE&|J*9FMvNKIWqjYb9vt&Ue9Hxs^IA0oE{QLTo#Zg}GS8a!x7}vxI)lZk`}u+}@;F z5?tFDphv_*h)FU)fy~Ug?KBFA!O8c_kozx#3BtMd=a5_36P}M7D=oE13&C#4?i=+e z5&qT^Yme?olraBK&Mjvl^g^5NgKQ)X((+1(9WtwJy0dbD@FWcTNsvOyfZe;E79MV} z*7CeJmiSvMIfBw5#^+29TG}1ts2Fu+gQ@lFI)R>9B_Sw#emIY8BGcB}g+4Ll)Pd_6 z%eT3)A=~X>$F(B7My&L#%55&=%BxUL)xYtIgnN<*_CCUp7h0lK&!g<>mC}nW2~Q%) zA4ZQc!Q)9vbW^36DwGP31iGZUtJ5gdQ;x4|9WF)n24ybqI37y+-;a*ovc_JO-YOU1 z@V?NRc9|c&!I^S_Cwzl3lFCuBj_R?$@Z6&E+e~teDu40mfd-jB7Vl4upk9r1$aO~! zZ_r}MQeF~wKz0XGqvw$8`|&#b?C>+DOukp{EQ3d5qHpeWnA>!l87;ZSX!f!_Q`nCWEV`n!9&v+iT8~xOHfsj{nZ2PkV ztO>C41`B7*G-717j<^o2&(cc1SH;0+HO-X;S~?i}XrbQw(tq(?$D4S`9%_aUXKm-X z#7HTk@0`&|Wg(TsPiJsrrMz?vs;jQk8AR`$32CSM4nI5mDmr~4(x=S0BT?Yi#cfAE z9mA&9#qkV;NrKS94?JmMG!gVMrqMLyvN$S5=6CPn@dxI19DygMNR&&&-ZYK`5Rv_~ zn4(iZC$7}}qqzJrD{j&4t5YkFM@Thjk%plaX%-8c2NOCC>>U-P!wDULy#cgWLN^_J z9Aw-&v7);eam`(X8=4r8UY+yf@BNVG38#hG&ME6M&;UhM9zLCsTdtgGC(D7Z90&eG zhr3oPpv&>!PzE=Q*>w;G%uHd`0j?)+Y&(zuV9)qlq=Q3R6Q0{#n6qWaB)Hb?-n!)@ zCLDf!@cUVvoa@@`8xMZC7>uLn;3k`~*%(b%954O)^2f0w*;`;$?6C%7|0Vwxj=cDE z249ahz++zqaC^>qIKSSyW%=A7*LC(&OlNc1>t1p4j6<@&7CID%K+)mX7e8~Jngg#p zc%_ru-FVUy!gTFm>jj&4^11I!DtWTB?-eA1v@+k!E>6* zgzp@f1ND{e?1drK(&0HYo^C830?jKyBy|TZ4S-~Etf|A%2FF{r*@k%6rc=uy;8aKJ zI~<2_di1U3sZhDQbCrex)n{%T9{|Z^8oK^+2q3-0>7*T$c9pfJW?1vhi`n(NSdL+3oZ{_y?m-CIX*3v>on&N4z6z8Y-H(M5-qy8RRiCbV!CiTXDLYz5=^SojYVT zsrj2)2`X<&Wv!&^>J%qPHr2a)zGO3ArE`X>wJ4p=QLe2!wBt~RoryFgl9uNR%ExFP zz4Dj5BPbG_<~HH2dl9F*LttqXt(Y-03HUC*_7jiW1gDKgfJ-5wC@3P|DOYFH4c3Ub zl;KdcMX_e+wB|f8cUtr1w$I_AmE#s4TYgS!ZacyFO;X`j znJn(wve+l>3PcH@uB6aqSEtb_Q5{qYB{tlC`wfia;A@NoCFS={v6_2li4V{E+ z6Vc#~J}0AU(h^VM7+vxt;k!f1TB;_-ccny-cbKFZM~eJ_@-WSiV(597T4w%eSDr^T zz0?xW@&V@`b(`R?^6`|n_c&|^#+LIK>g#T9{}E`qiGiUJbq-o@BU(tWB{@GYUH4!=Hk#^B_zi@fa*Abke?+vdU94#VCUJ~64L z4;+a7K*XZI(xJRGcW-wMVdihI0%mnQ=BcP>kj0+?;$k!RXhq+X8TV6Mi))N+uJimd zVb9f_HCT4LHm%9ax9XhX$40sgx&Jbr@_3(%S@9ncCV#9||99H0XDj6>XM4DuKhYR5 zr1&?``Bq^)=sQxGhOFxxTAyCv>p6OU;kSwJtbKar>37TwgT98Qlm|HGYE z@0LY+XwN0xFbVRtIFKK(0APa_`xc3#?`~*vvQbojbB5$=@2+f`3T#t@z!R`C09ikx zAHqjV8l-*>$u$(73##@5sLh+cAEpvy$@PIAE9|G$v5_ZNu(ny}3=QQkhS*k` z|5*lNgFtpFV=vvNMEF~G?s&hBf$m5Iy7(vOZndx%emX+V>{oIKc1JJ{nOV1KM}?t! z8n^{L8D>!80c+ZSX=9Ts9(V9~&!!ymvJp3jP=oi4#6ZQOqUt{k>|YL3hom)(=w8V~L`(F9XGj5BEBqB4<0Q zcl=qd)X)G$RUSTRZ|3OO-S8g z^VTh)aBMfrQlo7>*HYu0%juRv`v;wy92|+n-@2ym)6_g%vHr2*`PSVfr(qzo%~aRX1~~-^I3Vy|Jy5II5fj zTHqwBFT7+QRs~dFX*-q+v%^TO)XoIi)E}yrZ^2PGQ#IiyUgewh8c9mksSsq9Z%k|y zY4H4X`kprn7({VMajh*+Qi|5sFgG8?6tW_*tQ-@4`nsTuvtF`RW5pqNR*5dmSMM>d zaF>|c7TU@hDSx-OMB=Kuid}@Ul7)ewQFH1qp{Euq=#IJWdEOTh4816frU~4&=RM9# zqi<`A@PkhdPO-6j(DlJzMZ3P!^)GV5G}LrSr5e`y1O%xIAuldPDvvpj$9U9Lxt;h9 zTgQ&Osp4(S7}r;~)r}cRNM`nrZ)jp}s09eXx!wCA%@Z1U={ntHaX#Z~!oq7JlgH>v z2re3$CyTk47dPQP-uAze^fNZZG0GF_Zkpzrps%)v#%n`nnzfm9UZrgyz}e&rsOQx? z_0y!)SE+eAGpKtt?ny*8RG?;}mg&3QZg`@9+;N#F!tdnBRUCDBnS@S-$a6T$RW5De zK!G7Gs8-8)g92#QQk6P#;?Hk$Gpm1$2tF-5_&4ZI_)peEd{4wn8ed$eVTjN7G$8@> zDDXkQ;+c?>1U|ZT+H@_u;hf{cCA{-|=mTbqY$AY}`3=~XIad3a9v!vtC%VjBf1+v# zI&oI&F)_g`F88`FJ3Wuaf&GP3-kn+qn=AQQR0-)f`x0X^DFzzT}#smnkF361_ zuh4;7%TZ^JHr?=}`%w!4*s^0S#dP0Un;#PjbqesXraqc7-$B>Xhw+X@ln9%wm-LmA z(1J6!kt}4W+0Q=w9)|{0ho*MpE}Hz}T992y1&*FTi8cZ$&7?L^Y)OpggWw-$)zpEx z3PToK99I2_p~3Ii;Zny1FVk_jG~mL+{lBf^Fm`5O7t!jBHU=sJ0mfYM!*Vf3Z z%6Xt~`hDHWMhAL^(O-guUw#|qlxHL^sMs-yTQt$(8qaq-Dx?AS?|=J$;Bl4Kx@Z9a D)>T_G diff --git a/Microsoft.Toolkit.Uwp.PlatformSpecificAnalyzer/HowToGuard.cs b/Microsoft.Toolkit.Uwp.PlatformSpecificAnalyzer/HowToGuard.cs deleted file mode 100644 index 6827975ab9e..00000000000 --- a/Microsoft.Toolkit.Uwp.PlatformSpecificAnalyzer/HowToGuard.cs +++ /dev/null @@ -1,32 +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. - -namespace Microsoft.Toolkit.Uwp.PlatformSpecificAnalyzer -{ - /// - /// The struct provides guard related info. - /// - public struct HowToGuard - { - /// - /// Type being checked - /// - public string TypeToCheck; - - /// - /// Member being checked - /// - public string MemberToCheck; - - /// - /// Whether parameter count will be used for the check - /// - public int? ParameterCountToCheck; - - /// - /// Type of check - /// - public string KindOfCheck; - } -} diff --git a/Microsoft.Toolkit.Uwp.PlatformSpecificAnalyzer/Microsoft.Toolkit.Uwp.PlatformSpecificAnalyzer.csproj b/Microsoft.Toolkit.Uwp.PlatformSpecificAnalyzer/Microsoft.Toolkit.Uwp.PlatformSpecificAnalyzer.csproj deleted file mode 100644 index f0661505dd3..00000000000 --- a/Microsoft.Toolkit.Uwp.PlatformSpecificAnalyzer/Microsoft.Toolkit.Uwp.PlatformSpecificAnalyzer.csproj +++ /dev/null @@ -1,36 +0,0 @@ - - - - netstandard2.0 - Windows Community Toolkit UWP Platform Specific Analyzer - This standard library provides code analysis and code fixers (on CS and VB) to ensure that version / platform specific code is well guarded. - UWP Toolkit Windows Platform Specific Analyzer - - false - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/Microsoft.Toolkit.Uwp.PlatformSpecificAnalyzer/NewMember.cs b/Microsoft.Toolkit.Uwp.PlatformSpecificAnalyzer/NewMember.cs deleted file mode 100644 index 9596e4109be..00000000000 --- a/Microsoft.Toolkit.Uwp.PlatformSpecificAnalyzer/NewMember.cs +++ /dev/null @@ -1,46 +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; - -namespace Microsoft.Toolkit.Uwp.PlatformSpecificAnalyzer -{ - /// - /// This class wraps a new data members - /// - public struct NewMember - { - private static char[] methodCountSeparator = { '#' }; - - /// - /// Member name - /// - public string Name; - - /// - /// Parameter count (if its a method) - /// - public int? ParameterCount; - - /// - /// Initializes a new instance of the struct. - /// - /// data containing name and optionally parameter count - public NewMember(string s) - { - string[] parts = s.Split(methodCountSeparator, StringSplitOptions.RemoveEmptyEntries); - - if (parts.Length == 2) - { - Name = parts[0]; - ParameterCount = int.Parse(parts[1]); - } - else - { - Name = s; - ParameterCount = null; - } - } - } -} diff --git a/Microsoft.Toolkit.Uwp.PlatformSpecificAnalyzer/Platform.cs b/Microsoft.Toolkit.Uwp.PlatformSpecificAnalyzer/Platform.cs deleted file mode 100644 index d6a3f876d2d..00000000000 --- a/Microsoft.Toolkit.Uwp.PlatformSpecificAnalyzer/Platform.cs +++ /dev/null @@ -1,69 +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; - -namespace Microsoft.Toolkit.Uwp.PlatformSpecificAnalyzer -{ - /// - /// Simple struct to hold platform / version / param count info for given symbol - /// - public struct Platform - { - /// - /// Platform Kind - /// - public PlatformKind Kind; - - /// - /// For UWP, this is version 15063 or 16299etc. For User, the fully qualified name of the attribute in use - /// - public string Version; - - /// - /// For UWP only - /// - public bool ByParameterCount; - - /// - /// Initializes a new instance of the struct. - /// - /// - /// version - /// boolean - public Platform(PlatformKind kind, string version = null, bool byParameterCount = false) - { - Kind = kind; - Version = version; - ByParameterCount = byParameterCount; - - switch (kind) - { - case PlatformKind.Unchecked: - if (version != null) - { - throw new ArgumentException("No version expected"); - } - - break; - - case PlatformKind.Uwp: - break; - - case PlatformKind.ExtensionSDK: - if (version != null) - { - throw new ArgumentException("Don't specify versions for extension SDKs"); - } - - break; - } - - if (byParameterCount && kind != PlatformKind.Uwp) - { - throw new ArgumentException("Only UWP can be distinguished by parameter count"); - } - } - } -} diff --git a/Microsoft.Toolkit.Uwp.PlatformSpecificAnalyzer/PlatformKind.cs b/Microsoft.Toolkit.Uwp.PlatformSpecificAnalyzer/PlatformKind.cs deleted file mode 100644 index c38de8ed495..00000000000 --- a/Microsoft.Toolkit.Uwp.PlatformSpecificAnalyzer/PlatformKind.cs +++ /dev/null @@ -1,27 +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. - -namespace Microsoft.Toolkit.Uwp.PlatformSpecificAnalyzer -{ - /// - /// Platform kind enum - /// - public enum PlatformKind - { - /// - /// .NET and Pre-UWP WinRT - /// - Unchecked, - - /// - /// Core UWP platform - /// - Uwp, - - /// - /// Desktop, Mobile, IOT, Xbox extension SDK - /// - ExtensionSDK - } -} diff --git a/Microsoft.Toolkit.Uwp.PlatformSpecificAnalyzer/PlatformSpecificAnalyzerCS.cs b/Microsoft.Toolkit.Uwp.PlatformSpecificAnalyzer/PlatformSpecificAnalyzerCS.cs deleted file mode 100644 index ea7b344a626..00000000000 --- a/Microsoft.Toolkit.Uwp.PlatformSpecificAnalyzer/PlatformSpecificAnalyzerCS.cs +++ /dev/null @@ -1,230 +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.Collections.Concurrent; -using System.Collections.Generic; -using System.Collections.Immutable; -using System.Linq; -using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.CSharp; -using Microsoft.CodeAnalysis.CSharp.Syntax; -using Microsoft.CodeAnalysis.Diagnostics; - -namespace Microsoft.Toolkit.Uwp.PlatformSpecificAnalyzer -{ - /// - /// This class is a Roslyn code analyzer that checks for types / members that should be guarded against. - /// - [DiagnosticAnalyzer(LanguageNames.CSharp)] - public class PlatformSpecificAnalyzerCS : DiagnosticAnalyzer - { - /// - /// Gets supported diagnostics - /// - public override ImmutableArray SupportedDiagnostics - { - get { return ImmutableArray.Create(Analyzer.PlatformRule, Analyzer.VersionRule); } - } - - /// - /// Gets instance of symbol from syntax node - /// - /// instance of - /// - /// - public static ISymbol GetTargetOfNode(SyntaxNode node, SemanticModel semanticModel) - { - var parentKind = node.Parent.Kind(); - - if (parentKind == SyntaxKind.InvocationExpression && node == ((InvocationExpressionSyntax)node.Parent).Expression) - { - // (...) - // points to the method after overload resolution - return semanticModel.GetSymbolInfo((InvocationExpressionSyntax)node.Parent).Symbol; - } - else if (parentKind == SyntaxKind.ObjectCreationExpression && node == ((ObjectCreationExpressionSyntax)node.Parent).Type) - { - // New - var objectCreationExpression = (ObjectCreationExpressionSyntax)node.Parent; - var target = semanticModel.GetSymbolInfo(objectCreationExpression).Symbol; - - // points to the constructor after overload resolution - return target; - } - else - { - // f(...) - // x = ... - // Action x = -- note that following code does pick the right overload - // += delegate -- the following code does recognize events - // nameof() -- I think it's nicer to report on this, even if not technically needed - // Field access? I'll disallow it for enum values, and allow it for everything else - var target = semanticModel.GetSymbolInfo(node).Symbol; - - if (target == null) - { - return null; - } - - var targetKind = target.Kind; - - if (targetKind == SymbolKind.Method || targetKind == SymbolKind.Event || targetKind == SymbolKind.Property || targetKind == SymbolKind.NamedType) - { - return target; - } - - if (targetKind == SymbolKind.Field && target.ContainingType.TypeKind == TypeKind.Enum) - { - return target; - } - - return null; - } - } - - /// - /// Initializes the analyzer, registering for code analysis. - /// - /// - public override void Initialize(AnalysisContext context) - { - ConcurrentDictionary reportsDictionary = new ConcurrentDictionary(); - - context.RegisterSyntaxNodeAction((c) => AnalyzeExpression(c, reportsDictionary), SyntaxKind.VariableDeclaration, SyntaxKind.FieldDeclaration, SyntaxKind.IdentifierName, SyntaxKind.SimpleMemberAccessExpression, SyntaxKind.QualifiedName); - } - - private static IEnumerable GetGuards(SyntaxNode node, SemanticModel semanticModel) - { - foreach (var condition in GetConditions(node)) - { - // First check for invocations of ApiInformation.IsTypePresent - foreach (var invocation in condition.DescendantNodesAndSelf(i => i is InvocationExpressionSyntax)) - { - var targetMethod = semanticModel.GetSymbolInfo(invocation).Symbol; - - if (targetMethod?.ContainingType?.Name == "ApiInformation") - { - yield return targetMethod; - } - } - - // Next check for any property/field access - var accesses1 = condition.DescendantNodesAndSelf(d => d is MemberAccessExpressionSyntax).Select(n => semanticModel.GetSymbolInfo(n).Symbol); - var accesses2 = condition.DescendantNodesAndSelf(d => d is IdentifierNameSyntax).Select(n => semanticModel.GetSymbolInfo(n).Symbol); - - foreach (var symbol in accesses1.Concat(accesses2)) - { - var symbolKind = symbol.Kind; - - if (symbolKind == SymbolKind.Field || symbolKind == SymbolKind.Property) - { - yield return symbol; - } - } - } - } - - private static IEnumerable GetConditions(SyntaxNode node) - { - var check = node.FirstAncestorOrSelf(); - - while (check != null) - { - yield return check.Condition; - check = check.Parent.FirstAncestorOrSelf(); - } - } - - private void AnalyzeExpression(SyntaxNodeAnalysisContext context, ConcurrentDictionary reports) - { - var parentKind = context.Node.Parent.Kind(); - - // will be handled at higher level - if (parentKind == SyntaxKind.SimpleMemberAccessExpression || parentKind == SyntaxKind.QualifiedName) - { - return; - } - - var target = GetTargetOfNode(context.Node, context.SemanticModel); - - if (target == null) - { - return; - } - - var platform = Analyzer.GetPlatformForSymbol(target); - - // Some quick escapes - if (platform.Kind == PlatformKind.Unchecked) - { - return; - } - - if (platform.Kind == PlatformKind.Uwp && platform.Version == Analyzer.N2SDKVersion) - { - return; - } - - // Is this expression inside a method/constructor/property that claims to be specific? - var containingBlock = context.Node.FirstAncestorOrSelf(); - - // for constructors and methods - MemberDeclarationSyntax containingMember = containingBlock?.FirstAncestorOrSelf(); - - if (containingBlock == null || containingBlock?.Parent is AccessorDeclarationSyntax) - { - containingMember = context.Node.FirstAncestorOrSelf(); - } - - // Is this invocation properly guarded? See readme.md for explanations. - if (IsProperlyGuarded(context.Node, context.SemanticModel)) - { - return; - } - - if (containingBlock != null) - { - foreach (var ret in containingBlock.DescendantNodes().OfType()) - { - if (IsProperlyGuarded(ret, context.SemanticModel)) - { - return; - } - } - } - - // We'll report only a single diagnostic per line, the first. - var loc = context.Node.GetLocation(); - if (!loc.IsInSource) - { - return; - } - - var line = loc.GetLineSpan().StartLinePosition.Line; - if (reports.TryGetValue(line, out var diagnostic) && diagnostic.Location.SourceSpan.Start <= loc.SourceSpan.Start) - { - return; - } - - diagnostic = Diagnostic.Create(platform.Kind == PlatformKind.Uwp ? Analyzer.VersionRule : Analyzer.PlatformRule, loc); - - reports[line] = diagnostic; - - context.ReportDiagnostic(diagnostic); - } - - private bool IsProperlyGuarded(SyntaxNode node, SemanticModel semanticModel) - { - foreach (var symbol in GetGuards(node, semanticModel)) - { - if (symbol.ContainingType?.Name == "ApiInformation") - { - return true; - } - } - - return false; - } - } -} diff --git a/Microsoft.Toolkit.Uwp.PlatformSpecificAnalyzer/PlatformSpecificAnalyzerVB.cs b/Microsoft.Toolkit.Uwp.PlatformSpecificAnalyzer/PlatformSpecificAnalyzerVB.cs deleted file mode 100644 index ec67c731a74..00000000000 --- a/Microsoft.Toolkit.Uwp.PlatformSpecificAnalyzer/PlatformSpecificAnalyzerVB.cs +++ /dev/null @@ -1,254 +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.Collections.Concurrent; -using System.Collections.Generic; -using System.Collections.Immutable; -using System.Linq; -using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.Diagnostics; -using Microsoft.CodeAnalysis.VisualBasic; -using Microsoft.CodeAnalysis.VisualBasic.Syntax; - -namespace Microsoft.Toolkit.Uwp.PlatformSpecificAnalyzer -{ - /// - /// This class is a Roslyn code analyzer that checks for types / members that should be guarded against. - /// - [DiagnosticAnalyzer(LanguageNames.VisualBasic)] - public class PlatformSpecificAnalyzerVB : DiagnosticAnalyzer - { - /// - /// Gets supported diagnostics - /// - public override ImmutableArray SupportedDiagnostics - { - get { return ImmutableArray.Create(Analyzer.PlatformRule, Analyzer.VersionRule); } - } - - /// - /// Gets instance of symbol from syntax node - /// - /// instance of - /// - /// - public static ISymbol GetTargetOfNode(SyntaxNode node, SemanticModel semanticModel) - { - var parentKind = node.Parent.Kind(); - - if (parentKind == SyntaxKind.InvocationExpression && node == ((InvocationExpressionSyntax)node.Parent).Expression) - { - // (...) - // points to the method after overload resolution - return semanticModel.GetSymbolInfo((InvocationExpressionSyntax)node.Parent).Symbol; - } - else if (parentKind == SyntaxKind.AddressOfExpression) - { - // AddressOf - return semanticModel.GetSymbolInfo(node).Symbol; // points to the method after overload resolution - } - else if (parentKind == SyntaxKind.ObjectCreationExpression && node == ((ObjectCreationExpressionSyntax)node.Parent).Type) - { - // New - var objectCreationExpression = (ObjectCreationExpressionSyntax)node.Parent; - var target = semanticModel.GetSymbolInfo(objectCreationExpression).Symbol; - - // points to the constructor after overload resolution - return target; - } - else if (parentKind == SyntaxKind.AddHandlerStatement && node == ((AddRemoveHandlerStatementSyntax)node.Parent).EventExpression) - { - // AddHandler , delegate - return semanticModel.GetSymbolInfo(node).Symbol; // points to the event - } - else if (parentKind == SyntaxKind.NameOfExpression) - { - // NameOf() - return null; - } - else - { - // f(Of )(...) -- no warning - // Dim x As = ... -- no warning - // property access -- warning - // field access -- only warning on enum fields - // method access without arguments -- warning - var target = semanticModel.GetSymbolInfo(node).Symbol; - - if (target == null) - { - return null; - } - - var targetKind = target.Kind; - - if (targetKind == SymbolKind.Method || targetKind == SymbolKind.Property || targetKind == SymbolKind.NamedType) - { - return target; - } - - if (targetKind == SymbolKind.Field && target.ContainingType.TypeKind == TypeKind.Enum) - { - return target; - } - - return null; - } - } - - /// - /// Initializes the analyzer, registering for code analysis. - /// - /// - public override void Initialize(AnalysisContext context) - { - ConcurrentDictionary reportsDictionary = new ConcurrentDictionary(); - - context.RegisterSyntaxNodeAction((c) => AnalyzeExpression(c, reportsDictionary), SyntaxKind.LocalDeclarationStatement, SyntaxKind.IdentifierName, SyntaxKind.SimpleMemberAccessExpression, SyntaxKind.QualifiedName); - } - - private static IEnumerable GetGuards(SyntaxNode node, SemanticModel semanticModel) - { - foreach (var condition in GetConditions(node)) - { - // First check for invocations of ApiInformation.IsTypePresent - foreach (var invocation in condition.DescendantNodesAndSelf(i => i is InvocationExpressionSyntax)) - { - var targetMethod = semanticModel.GetSymbolInfo(invocation).Symbol; - - if (targetMethod?.ContainingType?.Name == "ApiInformation") - { - yield return targetMethod; - } - } - - // Next check for any property/field access - var accesses1 = condition.DescendantNodesAndSelf(d => d is MemberAccessExpressionSyntax).Select(n => semanticModel.GetSymbolInfo(n).Symbol); - var accesses2 = condition.DescendantNodesAndSelf(d => d is IdentifierNameSyntax).Select(n => semanticModel.GetSymbolInfo(n).Symbol); - - foreach (var symbol in accesses1.Concat(accesses2)) - { - if (symbol == null) - { - continue; - } - - var symbolKind = symbol.Kind; - - if (symbolKind == SymbolKind.Field || symbolKind == SymbolKind.Property) - { - yield return symbol; - } - } - } - } - - private static IEnumerable GetConditions(SyntaxNode node) - { - var check1 = node.FirstAncestorOrSelf(); - - while (check1 != null) - { - yield return check1.IfStatement.Condition; - check1 = check1.Parent.FirstAncestorOrSelf(); - } - - var check2 = node.FirstAncestorOrSelf(); - - while (check2 != null) - { - yield return check2.Condition; - check2 = check2.Parent.FirstAncestorOrSelf(); - } - } - - private void AnalyzeExpression(SyntaxNodeAnalysisContext context, ConcurrentDictionary reports) - { - var parentKind = context.Node.Parent.Kind(); - - // will be handled at higher level - if (parentKind == SyntaxKind.SimpleMemberAccessExpression || parentKind == SyntaxKind.QualifiedName) - { - return; - } - - var target = GetTargetOfNode(context.Node, context.SemanticModel); - - if (target == null) - { - return; - } - - var platform = Analyzer.GetPlatformForSymbol(target); - - // Some quick escapes - if (platform.Kind == PlatformKind.Unchecked) - { - return; - } - - if (platform.Kind == PlatformKind.Uwp && platform.Version == Analyzer.N2SDKVersion) - { - return; - } - - // Is this expression inside a method/constructor/property that claims to be specific? - DeclarationStatementSyntax containingMember = context.Node.FirstAncestorOrSelf(); - - if (containingMember is AccessorBlockSyntax) - { - containingMember = containingMember.FirstAncestorOrSelf(); - } - - // Is this invocation properly guarded? See readme.md for explanations. - if (IsProperlyGuarded(context.Node, context.SemanticModel)) - { - return; - } - - if (containingMember != null) - { - foreach (var ret in containingMember.DescendantNodes().OfType()) - { - if (IsProperlyGuarded(ret, context.SemanticModel)) - { - return; - } - } - } - - // We'll report only a single diagnostic per line, the first. - var loc = context.Node.GetLocation(); - if (!loc.IsInSource) - { - return; - } - - var line = loc.GetLineSpan().StartLinePosition.Line; - if (reports.TryGetValue(line, out var diagnostic) && diagnostic.Location.SourceSpan.Start <= loc.SourceSpan.Start) - { - return; - } - - diagnostic = Diagnostic.Create(platform.Kind == PlatformKind.Uwp ? Analyzer.VersionRule : Analyzer.PlatformRule, loc); - - reports[line] = diagnostic; - - context.ReportDiagnostic(diagnostic); - } - - private bool IsProperlyGuarded(SyntaxNode node, SemanticModel semanticModel) - { - foreach (var symbol in GetGuards(node, semanticModel)) - { - if (symbol.ContainingType?.Name == "ApiInformation") - { - return true; - } - } - - return false; - } - } -} diff --git a/Microsoft.Toolkit.Uwp.PlatformSpecificAnalyzer/PlatformSpecificFixerCS.cs b/Microsoft.Toolkit.Uwp.PlatformSpecificAnalyzer/PlatformSpecificFixerCS.cs deleted file mode 100644 index 439d1958f19..00000000000 --- a/Microsoft.Toolkit.Uwp.PlatformSpecificAnalyzer/PlatformSpecificFixerCS.cs +++ /dev/null @@ -1,168 +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.Collections.Generic; -using System.Collections.Immutable; -using System.Composition; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; -using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.CodeActions; -using Microsoft.CodeAnalysis.CodeFixes; -using Microsoft.CodeAnalysis.CSharp; -using Microsoft.CodeAnalysis.CSharp.Syntax; -using Microsoft.CodeAnalysis.Formatting; -using Microsoft.CodeAnalysis.Simplification; -using Microsoft.CodeAnalysis.Text; - -namespace Microsoft.Toolkit.Uwp.PlatformSpecificAnalyzer -{ - /// - /// This class provides guard suggestion and can make the suggested changes. - /// - [ExportCodeFixProvider(LanguageNames.CSharp, Name = nameof(PlatformSpecificFixerCS))] - [Shared] - public class PlatformSpecificFixerCS : CodeFixProvider - { - /// - /// Gets the list of Diagnostics that can be fixed. - /// - public sealed override ImmutableArray FixableDiagnosticIds - { - get { return ImmutableArray.Create(Analyzer.PlatformRule.Id, Analyzer.VersionRule.Id); } - } - - /// - /// Gets the Fix All provider - /// - /// - public sealed override FixAllProvider GetFixAllProvider() - { - return WellKnownFixAllProviders.BatchFixer; - } - - /// - /// Registers for code fix. - /// - /// - /// awaitable - public sealed override async Task RegisterCodeFixesAsync(CodeFixContext context) - { - try - { - var root = await context.Document.GetSyntaxRootAsync(context.CancellationToken).ConfigureAwait(false); - var semanticModel = await context.Document.GetSemanticModelAsync(context.CancellationToken).ConfigureAwait(false); - - // Which node are we interested in? -- if the squiggle is over A.B().C, - // then we need the largest IdentifierName/SimpleMemberAccess/QualifiedName - // that encompasses "C" itself - var diagnostic = context.Diagnostics.First(); - var span = new TextSpan(diagnostic.Location.SourceSpan.End - 1, 1); - var node = root.FindToken(span.Start).Parent; - - SyntaxKind nodeKind = node.Kind(); - - while (nodeKind != SyntaxKind.IdentifierName && nodeKind != SyntaxKind.SimpleMemberAccessExpression && nodeKind != SyntaxKind.QualifiedName) - { - node = node.Parent; - - if (node == null) - { - return; - } - - nodeKind = node.Kind(); - } - - while (true) - { - if (node.Parent?.Kind() == SyntaxKind.SimpleMemberAccessExpression) - { - node = node.Parent; - continue; - } - - if (node.Parent?.Kind() == SyntaxKind.QualifiedName) - { - node = node.Parent; - continue; - } - - break; - } - - var target = PlatformSpecificAnalyzerCS.GetTargetOfNode(node, semanticModel); - var g = Analyzer.GetGuardForSymbol(target); - - // Introduce a guard? (only if it is a method/accessor/constructor, i.e. somewhere that allows code) - var containingBlock = node.FirstAncestorOrSelf(); - if (containingBlock != null) - { - var act1 = CodeAction.Create($"Add 'If ApiInformation.{g.KindOfCheck}'", (c) => IntroduceGuardAsync(context.Document, node, g, c), "PlatformSpecificGuard"); - context.RegisterCodeFix(act1, diagnostic); - } - } - catch - { - } - } - - private async Task IntroduceGuardAsync(Document document, SyntaxNode node, HowToGuard g, CancellationToken cancellationToken) - { - // + if (Windows.Foundation.Metadata.ApiInformation.IsTypePresent(targetContainingType)) - // { - // old-statement - // + } - try - { - var oldStatement = node.FirstAncestorOrSelf(); - var oldLeadingTrivia = oldStatement.GetLeadingTrivia(); - - var conditionReceiver = SyntaxFactory.ParseName($"Windows.Foundation.Metadata.ApiInformation.{g.KindOfCheck}").WithAdditionalAnnotations(Simplifier.Annotation); - ArgumentListSyntax conditionArgument = null; - - if (g.MemberToCheck == null) - { - var conditionString1 = SyntaxFactory.LiteralExpression(SyntaxKind.StringLiteralExpression, SyntaxFactory.Literal(g.TypeToCheck)); - conditionArgument = SyntaxFactory.ArgumentList(SyntaxFactory.SingletonSeparatedList(SyntaxFactory.Argument(conditionString1))); - } - else - { - var conditionString1 = SyntaxFactory.LiteralExpression(SyntaxKind.StringLiteralExpression, SyntaxFactory.Literal(g.TypeToCheck)); - var conditionString2 = SyntaxFactory.LiteralExpression(SyntaxKind.StringLiteralExpression, SyntaxFactory.Literal(g.MemberToCheck)); - var conditionInt3 = SyntaxFactory.LiteralExpression(SyntaxKind.NumericLiteralExpression, SyntaxFactory.Literal(g.ParameterCountToCheck ?? 0)); - - IEnumerable conditions = null; - - if (g.ParameterCountToCheck.HasValue) - { - conditions = new ArgumentSyntax[] { SyntaxFactory.Argument(conditionString1), SyntaxFactory.Argument(conditionString2), SyntaxFactory.Argument(conditionInt3) }; - } - else - { - conditions = new ArgumentSyntax[] { SyntaxFactory.Argument(conditionString1), SyntaxFactory.Argument(conditionString2) }; - } - - conditionArgument = SyntaxFactory.ArgumentList(SyntaxFactory.SeparatedList(conditions)); - } - - var condition = SyntaxFactory.InvocationExpression(conditionReceiver, conditionArgument); - - var thenStatements = SyntaxFactory.Block(oldStatement.WithoutLeadingTrivia()); - var ifStatement = SyntaxFactory.IfStatement(condition, thenStatements).WithLeadingTrivia(oldLeadingTrivia).WithAdditionalAnnotations(Formatter.Annotation); - - var oldRoot = await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false); - var newRoot = oldRoot.ReplaceNode(oldStatement, ifStatement); - - return document.WithSyntaxRoot(newRoot); - } - catch - { - } - - return document; - } - } -} diff --git a/Microsoft.Toolkit.Uwp.PlatformSpecificAnalyzer/PlatformSpecificFixerVB.cs b/Microsoft.Toolkit.Uwp.PlatformSpecificAnalyzer/PlatformSpecificFixerVB.cs deleted file mode 100644 index e7f91129fd3..00000000000 --- a/Microsoft.Toolkit.Uwp.PlatformSpecificAnalyzer/PlatformSpecificFixerVB.cs +++ /dev/null @@ -1,168 +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.Collections.Generic; -using System.Collections.Immutable; -using System.Composition; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; -using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.CodeActions; -using Microsoft.CodeAnalysis.CodeFixes; -using Microsoft.CodeAnalysis.Formatting; -using Microsoft.CodeAnalysis.Simplification; -using Microsoft.CodeAnalysis.Text; -using Microsoft.CodeAnalysis.VisualBasic; -using Microsoft.CodeAnalysis.VisualBasic.Syntax; - -namespace Microsoft.Toolkit.Uwp.PlatformSpecificAnalyzer -{ - /// - /// This class provides guard suggestion and can make the suggested changes. - /// - [ExportCodeFixProvider(LanguageNames.VisualBasic, Name = nameof(PlatformSpecificFixerCS))] - [Shared] - public class PlatformSpecificFixerVB : CodeFixProvider - { - /// - /// Gets the list of Diagnostics that can be fixed. - /// - public sealed override ImmutableArray FixableDiagnosticIds - { - get { return ImmutableArray.Create(Analyzer.PlatformRule.Id, Analyzer.VersionRule.Id); } - } - - /// - /// Gets the Fix All provider - /// - /// - public sealed override FixAllProvider GetFixAllProvider() - { - return WellKnownFixAllProviders.BatchFixer; - } - - /// - /// Registers for code fix. - /// - /// - /// awaitable - public sealed override async Task RegisterCodeFixesAsync(CodeFixContext context) - { - try - { - var root = await context.Document.GetSyntaxRootAsync(context.CancellationToken).ConfigureAwait(false); - var semanticModel = await context.Document.GetSemanticModelAsync(context.CancellationToken).ConfigureAwait(false); - - // Which node are we interested in? -- if the squiggle is over A.B().C, - // then we need the largest IdentifierName/SimpleMemberAccess/QualifiedName - // that encompasses "C" itself - var diagnostic = context.Diagnostics.First(); - var span = new TextSpan(diagnostic.Location.SourceSpan.End - 1, 1); - var node = root.FindToken(span.Start).Parent; - - SyntaxKind nodeKind = node.Kind(); - - while (nodeKind != SyntaxKind.IdentifierName && nodeKind != SyntaxKind.SimpleMemberAccessExpression && nodeKind != SyntaxKind.QualifiedName) - { - node = node.Parent; - - if (node == null) - { - return; - } - - nodeKind = node.Kind(); - } - - while (true) - { - if (node.Parent?.Kind() == SyntaxKind.SimpleMemberAccessExpression) - { - node = node.Parent; - continue; - } - - if (node.Parent?.Kind() == SyntaxKind.QualifiedName) - { - node = node.Parent; - continue; - } - - break; - } - - var target = PlatformSpecificAnalyzerVB.GetTargetOfNode(node, semanticModel); - var g = Analyzer.GetGuardForSymbol(target); - - // Introduce a guard? (only if it is a method/accessor/constructor, i.e. somewhere that allows code) - var containingBlock = node.FirstAncestorOrSelf(); - if (containingBlock != null) - { - var act1 = CodeAction.Create($"Add 'If ApiInformation.{g.KindOfCheck}'", (c) => IntroduceGuardAsync(context.Document, node, g, c), "PlatformSpecificGuard"); - context.RegisterCodeFix(act1, diagnostic); - } - } - catch - { - } - } - - private async Task IntroduceGuardAsync(Document document, SyntaxNode node, HowToGuard g, CancellationToken cancellationToken) - { - // + If Windows.Foundation.Metadata.ApiInformation.IsTypePresent(targetContainingType) Then - // old-statement - // + End If - try - { - var oldStatement = node.FirstAncestorOrSelf(); - var oldLeadingTrivia = oldStatement.GetLeadingTrivia(); - - var conditionReceiver = SyntaxFactory.ParseName($"Windows.Foundation.Metadata.ApiInformation.{g.KindOfCheck}").WithAdditionalAnnotations(Simplifier.Annotation); - ArgumentListSyntax conditionArgument = null; - - if (g.MemberToCheck == null) - { - var conditionString1 = SyntaxFactory.StringLiteralExpression(SyntaxFactory.StringLiteralToken($"\"{g.TypeToCheck}\"", g.TypeToCheck)); - conditionArgument = SyntaxFactory.ArgumentList(SyntaxFactory.SingletonSeparatedList(SyntaxFactory.SimpleArgument(conditionString1))); - } - else - { - var conditionString1 = SyntaxFactory.StringLiteralExpression(SyntaxFactory.StringLiteralToken($"\"{g.TypeToCheck}\"", g.TypeToCheck)); - var conditionString2 = SyntaxFactory.StringLiteralExpression(SyntaxFactory.StringLiteralToken($"\"{g.MemberToCheck}\"", g.MemberToCheck)); - var conditionInt3 = SyntaxFactory.LiteralExpression(SyntaxKind.NumericLiteralExpression, SyntaxFactory.Literal(g.ParameterCountToCheck ?? 0)); - - IEnumerable conditions = null; - - if (g.ParameterCountToCheck.HasValue) - { - conditions = new ArgumentSyntax[] { SyntaxFactory.SimpleArgument(conditionString1), SyntaxFactory.SimpleArgument(conditionString2), SyntaxFactory.SimpleArgument(conditionInt3) }; - } - else - { - conditions = new ArgumentSyntax[] { SyntaxFactory.SimpleArgument(conditionString1), SyntaxFactory.SimpleArgument(conditionString2) }; - } - - conditionArgument = SyntaxFactory.ArgumentList(SyntaxFactory.SeparatedList(conditions)); - } - - var condition = SyntaxFactory.InvocationExpression(conditionReceiver, conditionArgument); - - var ifStatement = SyntaxFactory.IfStatement(condition); - var thenStatements = SyntaxFactory.SingletonList(oldStatement.WithoutLeadingTrivia()); - var ifBlock = SyntaxFactory.MultiLineIfBlock(ifStatement).WithStatements(thenStatements).WithLeadingTrivia(oldLeadingTrivia).WithAdditionalAnnotations(Formatter.Annotation); - - var oldRoot = await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false); - var newRoot = oldRoot.ReplaceNode(oldStatement, ifBlock); - - return document.WithSyntaxRoot(newRoot); - } - catch - { - } - - return document; - } - } -} diff --git a/Microsoft.Toolkit.Uwp.PlatformSpecificAnalyzer/tools/install.ps1 b/Microsoft.Toolkit.Uwp.PlatformSpecificAnalyzer/tools/install.ps1 deleted file mode 100644 index c1c3d88223e..00000000000 --- a/Microsoft.Toolkit.Uwp.PlatformSpecificAnalyzer/tools/install.ps1 +++ /dev/null @@ -1,58 +0,0 @@ -param($installPath, $toolsPath, $package, $project) - -if($project.Object.SupportsPackageDependencyResolution) -{ - if($project.Object.SupportsPackageDependencyResolution()) - { - # Do not install analyzers via install.ps1, instead let the project system handle it. - return - } -} - -$analyzersPaths = Join-Path (Join-Path (Split-Path -Path $toolsPath -Parent) "analyzers") * -Resolve - -foreach($analyzersPath in $analyzersPaths) -{ - if (Test-Path $analyzersPath) - { - # Install the language agnostic analyzers. - foreach ($analyzerFilePath in Get-ChildItem -Path "$analyzersPath\*.dll" -Exclude *.resources.dll) - { - if($project.Object.AnalyzerReferences) - { - $project.Object.AnalyzerReferences.Add($analyzerFilePath.FullName) - } - } - } -} - -# $project.Type gives the language name like (C# or VB.NET) -$languageFolder = "" -if($project.Type -eq "C#") -{ - $languageFolder = "cs" -} -if($project.Type -eq "VB.NET") -{ - $languageFolder = "vb" -} -if($languageFolder -eq "") -{ - return -} - -foreach($analyzersPath in $analyzersPaths) -{ - # Install language specific analyzers. - $languageAnalyzersPath = join-path $analyzersPath $languageFolder - if (Test-Path $languageAnalyzersPath) - { - foreach ($analyzerFilePath in Get-ChildItem -Path "$languageAnalyzersPath\*.dll" -Exclude *.resources.dll) - { - if($project.Object.AnalyzerReferences) - { - $project.Object.AnalyzerReferences.Add($analyzerFilePath.FullName) - } - } - } -} \ No newline at end of file diff --git a/Microsoft.Toolkit.Uwp.PlatformSpecificAnalyzer/tools/uninstall.ps1 b/Microsoft.Toolkit.Uwp.PlatformSpecificAnalyzer/tools/uninstall.ps1 deleted file mode 100644 index 65a86237039..00000000000 --- a/Microsoft.Toolkit.Uwp.PlatformSpecificAnalyzer/tools/uninstall.ps1 +++ /dev/null @@ -1,65 +0,0 @@ -param($installPath, $toolsPath, $package, $project) - -if($project.Object.SupportsPackageDependencyResolution) -{ - if($project.Object.SupportsPackageDependencyResolution()) - { - # Do not uninstall analyzers via uninstall.ps1, instead let the project system handle it. - return - } -} - -$analyzersPaths = Join-Path (Join-Path (Split-Path -Path $toolsPath -Parent) "analyzers") * -Resolve - -foreach($analyzersPath in $analyzersPaths) -{ - # Uninstall the language agnostic analyzers. - if (Test-Path $analyzersPath) - { - foreach ($analyzerFilePath in Get-ChildItem -Path "$analyzersPath\*.dll" -Exclude *.resources.dll) - { - if($project.Object.AnalyzerReferences) - { - $project.Object.AnalyzerReferences.Remove($analyzerFilePath.FullName) - } - } - } -} - -# $project.Type gives the language name like (C# or VB.NET) -$languageFolder = "" -if($project.Type -eq "C#") -{ - $languageFolder = "cs" -} -if($project.Type -eq "VB.NET") -{ - $languageFolder = "vb" -} -if($languageFolder -eq "") -{ - return -} - -foreach($analyzersPath in $analyzersPaths) -{ - # Uninstall language specific analyzers. - $languageAnalyzersPath = join-path $analyzersPath $languageFolder - if (Test-Path $languageAnalyzersPath) - { - foreach ($analyzerFilePath in Get-ChildItem -Path "$languageAnalyzersPath\*.dll" -Exclude *.resources.dll) - { - if($project.Object.AnalyzerReferences) - { - try - { - $project.Object.AnalyzerReferences.Remove($analyzerFilePath.FullName) - } - catch - { - - } - } - } - } -} \ 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 9e8e27cba22..7b4d6b3a1c6 100644 --- a/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/samples.json +++ b/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/samples.json @@ -897,14 +897,6 @@ "Icon": "/Assets/Helpers.png", "DocumentationUrl": "https://raw.githubusercontent.com/MicrosoftDocs/WindowsCommunityToolkitDocs/master/docs/high-performance/Introduction.md" }, - { - "Name": "PlatformSpecificAnalyzer", - "Subcategory": "Developer", - "About": "Platform Specific Analyzer is a Roslyn analyzer that analyzes and suggests code fixes to ensure that any version / platform specific API are guarded by correct runtime checks", - "CodeUrl": "https://github.com/windows-toolkit/WindowsCommunityToolkit/tree/master/Microsoft.Toolkit.Uwp.PlatformSpecificAnalyzer", - "Icon": "/Assets/Helpers.png", - "DocumentationUrl": "https://raw.githubusercontent.com/MicrosoftDocs/WindowsCommunityToolkitDocs/master/docs/platform-specific/PlatformSpecificAnalyzer.md" - }, { "Name": "CompareStateTrigger", "Type": "CompareStateTriggerPage", diff --git a/Windows Community Toolkit.sln b/Windows Community Toolkit.sln index b9f6ee4728f..bde15545a69 100644 --- a/Windows Community Toolkit.sln +++ b/Windows Community Toolkit.sln @@ -76,12 +76,6 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.Toolkit.Uwp.UI.Co EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Toolkit.Parsers", "Microsoft.Toolkit.Parsers\Microsoft.Toolkit.Parsers.csproj", "{42CA4935-54BE-42EA-AC19-992378C08DE6}" EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "PlatformSpecific", "PlatformSpecific", "{096ECFD7-7035-4487-9C87-81DCE9389620}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DifferencesGen", "Microsoft.Toolkit.Uwp.PlatformDifferencesGen\DifferencesGen.csproj", "{292D34E8-0F01-4FA8-951D-8232F75A88D5}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Toolkit.Uwp.PlatformSpecificAnalyzer", "Microsoft.Toolkit.Uwp.PlatformSpecificAnalyzer\Microsoft.Toolkit.Uwp.PlatformSpecificAnalyzer.csproj", "{262BB7CE-EF42-4BF7-B90C-107E6CBB57FF}" -EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Microsoft.Toolkit.Uwp.Input.GazeInteraction", "Microsoft.Toolkit.UWP.Input.GazeInteraction\Microsoft.Toolkit.Uwp.Input.GazeInteraction.vcxproj", "{A5E98964-45B1-442D-A07A-298A3221D81E}" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GazeInputTest", "GazeInputTest\GazeInputTest.csproj", "{A122EA02-4DE7-413D-BFBF-AF7DFC668DD6}" @@ -680,55 +674,6 @@ Global {42CA4935-54BE-42EA-AC19-992378C08DE6}.Release|x64.Build.0 = Release|Any CPU {42CA4935-54BE-42EA-AC19-992378C08DE6}.Release|x86.ActiveCfg = Release|Any CPU {42CA4935-54BE-42EA-AC19-992378C08DE6}.Release|x86.Build.0 = Release|Any CPU - {292D34E8-0F01-4FA8-951D-8232F75A88D5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {292D34E8-0F01-4FA8-951D-8232F75A88D5}.Debug|Any CPU.Build.0 = Debug|Any CPU - {292D34E8-0F01-4FA8-951D-8232F75A88D5}.Debug|ARM.ActiveCfg = Debug|Any CPU - {292D34E8-0F01-4FA8-951D-8232F75A88D5}.Debug|ARM.Build.0 = Debug|Any CPU - {292D34E8-0F01-4FA8-951D-8232F75A88D5}.Debug|ARM64.ActiveCfg = Debug|Any CPU - {292D34E8-0F01-4FA8-951D-8232F75A88D5}.Debug|ARM64.Build.0 = Debug|Any CPU - {292D34E8-0F01-4FA8-951D-8232F75A88D5}.Debug|x64.ActiveCfg = Debug|Any CPU - {292D34E8-0F01-4FA8-951D-8232F75A88D5}.Debug|x64.Build.0 = Debug|Any CPU - {292D34E8-0F01-4FA8-951D-8232F75A88D5}.Debug|x86.ActiveCfg = Debug|Any CPU - {292D34E8-0F01-4FA8-951D-8232F75A88D5}.Debug|x86.Build.0 = Debug|Any CPU - {292D34E8-0F01-4FA8-951D-8232F75A88D5}.Native|Any CPU.ActiveCfg = Release|Any CPU - {292D34E8-0F01-4FA8-951D-8232F75A88D5}.Native|ARM.ActiveCfg = Release|Any CPU - {292D34E8-0F01-4FA8-951D-8232F75A88D5}.Native|ARM64.ActiveCfg = Release|Any CPU - {292D34E8-0F01-4FA8-951D-8232F75A88D5}.Native|x64.ActiveCfg = Release|Any CPU - {292D34E8-0F01-4FA8-951D-8232F75A88D5}.Native|x86.ActiveCfg = Release|Any CPU - {292D34E8-0F01-4FA8-951D-8232F75A88D5}.Release|Any CPU.ActiveCfg = Release|Any CPU - {292D34E8-0F01-4FA8-951D-8232F75A88D5}.Release|ARM.ActiveCfg = Release|Any CPU - {292D34E8-0F01-4FA8-951D-8232F75A88D5}.Release|ARM.Build.0 = Release|Any CPU - {292D34E8-0F01-4FA8-951D-8232F75A88D5}.Release|ARM64.ActiveCfg = Release|Any CPU - {292D34E8-0F01-4FA8-951D-8232F75A88D5}.Release|ARM64.Build.0 = Release|Any CPU - {292D34E8-0F01-4FA8-951D-8232F75A88D5}.Release|x64.ActiveCfg = Release|Any CPU - {292D34E8-0F01-4FA8-951D-8232F75A88D5}.Release|x64.Build.0 = Release|Any CPU - {292D34E8-0F01-4FA8-951D-8232F75A88D5}.Release|x86.ActiveCfg = Release|Any CPU - {292D34E8-0F01-4FA8-951D-8232F75A88D5}.Release|x86.Build.0 = Release|Any CPU - {262BB7CE-EF42-4BF7-B90C-107E6CBB57FF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {262BB7CE-EF42-4BF7-B90C-107E6CBB57FF}.Debug|Any CPU.Build.0 = Debug|Any CPU - {262BB7CE-EF42-4BF7-B90C-107E6CBB57FF}.Debug|ARM.ActiveCfg = Debug|Any CPU - {262BB7CE-EF42-4BF7-B90C-107E6CBB57FF}.Debug|ARM.Build.0 = Debug|Any CPU - {262BB7CE-EF42-4BF7-B90C-107E6CBB57FF}.Debug|ARM64.ActiveCfg = Debug|Any CPU - {262BB7CE-EF42-4BF7-B90C-107E6CBB57FF}.Debug|ARM64.Build.0 = Debug|Any CPU - {262BB7CE-EF42-4BF7-B90C-107E6CBB57FF}.Debug|x64.ActiveCfg = Debug|Any CPU - {262BB7CE-EF42-4BF7-B90C-107E6CBB57FF}.Debug|x64.Build.0 = Debug|Any CPU - {262BB7CE-EF42-4BF7-B90C-107E6CBB57FF}.Debug|x86.ActiveCfg = Debug|Any CPU - {262BB7CE-EF42-4BF7-B90C-107E6CBB57FF}.Debug|x86.Build.0 = Debug|Any CPU - {262BB7CE-EF42-4BF7-B90C-107E6CBB57FF}.Native|Any CPU.ActiveCfg = Release|Any CPU - {262BB7CE-EF42-4BF7-B90C-107E6CBB57FF}.Native|ARM.ActiveCfg = Release|Any CPU - {262BB7CE-EF42-4BF7-B90C-107E6CBB57FF}.Native|ARM64.ActiveCfg = Release|Any CPU - {262BB7CE-EF42-4BF7-B90C-107E6CBB57FF}.Native|x64.ActiveCfg = Release|Any CPU - {262BB7CE-EF42-4BF7-B90C-107E6CBB57FF}.Native|x86.ActiveCfg = Release|Any CPU - {262BB7CE-EF42-4BF7-B90C-107E6CBB57FF}.Release|Any CPU.ActiveCfg = Release|Any CPU - {262BB7CE-EF42-4BF7-B90C-107E6CBB57FF}.Release|Any CPU.Build.0 = Release|Any CPU - {262BB7CE-EF42-4BF7-B90C-107E6CBB57FF}.Release|ARM.ActiveCfg = Release|Any CPU - {262BB7CE-EF42-4BF7-B90C-107E6CBB57FF}.Release|ARM.Build.0 = Release|Any CPU - {262BB7CE-EF42-4BF7-B90C-107E6CBB57FF}.Release|ARM64.ActiveCfg = Release|Any CPU - {262BB7CE-EF42-4BF7-B90C-107E6CBB57FF}.Release|ARM64.Build.0 = Release|Any CPU - {262BB7CE-EF42-4BF7-B90C-107E6CBB57FF}.Release|x64.ActiveCfg = Release|Any CPU - {262BB7CE-EF42-4BF7-B90C-107E6CBB57FF}.Release|x64.Build.0 = Release|Any CPU - {262BB7CE-EF42-4BF7-B90C-107E6CBB57FF}.Release|x86.ActiveCfg = Release|Any CPU - {262BB7CE-EF42-4BF7-B90C-107E6CBB57FF}.Release|x86.Build.0 = Release|Any CPU {A5E98964-45B1-442D-A07A-298A3221D81E}.Debug|Any CPU.ActiveCfg = Debug|Win32 {A5E98964-45B1-442D-A07A-298A3221D81E}.Debug|Any CPU.Build.0 = Debug|Win32 {A5E98964-45B1-442D-A07A-298A3221D81E}.Debug|ARM.ActiveCfg = Debug|ARM @@ -1076,8 +1021,6 @@ Global {EFA96B3C-857E-4659-B942-6BEF7719F4CA} = {9333C63A-F64F-4797-82B3-017422668A5D} {7AEFC959-ED7C-4D96-9E92-72609B40FBE0} = {F1AFFFA7-28FE-4770-BA48-10D76F3E59BC} {6BD0BA4A-DE6D-3E87-8F83-63518C31ECD1} = {F1AFFFA7-28FE-4770-BA48-10D76F3E59BC} - {292D34E8-0F01-4FA8-951D-8232F75A88D5} = {096ECFD7-7035-4487-9C87-81DCE9389620} - {262BB7CE-EF42-4BF7-B90C-107E6CBB57FF} = {096ECFD7-7035-4487-9C87-81DCE9389620} {A122EA02-4DE7-413D-BFBF-AF7DFC668DD6} = {B30036C4-D514-4E5B-A323-587A061772CE} {75F9EE44-3EFA-47BC-AEDD-351B9834A0AF} = {F1AFFFA7-28FE-4770-BA48-10D76F3E59BC} {4E9466D1-D5AA-46AC-801B-C8FDAB79F0D4} = {B30036C4-D514-4E5B-A323-587A061772CE} From 1cb4df26de0ed40ff3c3ad3993daf7618ae8ff2a Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Wed, 19 Aug 2020 13:45:47 +0200 Subject: [PATCH 400/595] Speed/memory improvements to SetPropertyAndNotifyOnCompletion --- .../ComponentModel/ObservableObject.cs | 58 +++++++------------ .../Input/AsyncRelayCommand.cs | 2 +- .../Input/AsyncRelayCommand{T}.cs | 2 +- .../Mvvm/Test_ObservableObject.cs | 2 +- 4 files changed, 24 insertions(+), 40 deletions(-) diff --git a/Microsoft.Toolkit.Mvvm/ComponentModel/ObservableObject.cs b/Microsoft.Toolkit.Mvvm/ComponentModel/ObservableObject.cs index eaff311f873..4e5eb57ecaa 100644 --- a/Microsoft.Toolkit.Mvvm/ComponentModel/ObservableObject.cs +++ b/Microsoft.Toolkit.Mvvm/ComponentModel/ObservableObject.cs @@ -312,27 +312,22 @@ parentExpression.Expression is ConstantExpression instanceExpression && /// public Task MyTask /// { /// get => myTask; - /// private set => SetAndNotifyOnCompletion(ref myTask, () => myTask, value); + /// private set => SetAndNotifyOnCompletion(() => ref myTask, value); /// } /// /// /// The type of to set and monitor. - /// The field storing the property's value. - /// - /// An returning the field to update. This is needed to be - /// able to raise the to notify the completion of the input task. - /// + /// The instance to access the backing field for the property. /// The property's value after the change occurred. /// (optional) The name of the property that changed. /// if the property was changed, otherwise. /// - /// The and events are not raised if the current - /// and new value for the target property are the same. The return value being only - /// indicates that the new value being assigned to is different than the previous one, + /// The and events are not raised if the current and new + /// value for the target property are the same. The return value being only indicates that the + /// new value being assigned to field provided by is different than the previous one, /// and it does not mean the new instance passed as argument is in any particular state. /// - /// Thrown when is not valid. - protected bool SetPropertyAndNotifyOnCompletion(ref TTask? field, Expression> fieldExpression, TTask? newValue, [CallerMemberName] string? propertyName = null) + protected bool SetPropertyAndNotifyOnCompletion(FieldAccessor fieldAccessor, TTask? newValue, [CallerMemberName] string? propertyName = null) where TTask : Task { // We invoke the overload with a callback here to avoid code duplication, and simply pass an empty callback. @@ -341,21 +336,19 @@ protected bool SetPropertyAndNotifyOnCompletion(ref TTask? field, Express // instance. This will result in no further allocations after the first time this method is called for a given // generic type. We only pay the cost of the virtual call to the delegate, but this is not performance critical // code and that overhead would still be much lower than the rest of the method anyway, so that's fine. - return SetPropertyAndNotifyOnCompletion(ref field, fieldExpression, newValue, _ => { }, propertyName); + return SetPropertyAndNotifyOnCompletion(fieldAccessor, newValue, _ => { }, propertyName); } /// /// Compares the current and new values for a given field (which should be the backing /// field for a property). If the value has changed, raises the /// event, updates the field and then raises the event. - /// This method is just like , + /// This method is just like , /// with the difference being an extra parameter with a callback being invoked /// either immediately, if the new task has already completed or is , or upon completion. /// /// The type of to set and monitor. - /// The field storing the property's value. - /// - /// An returning the field to update. + /// The instance to access the backing field for the property. /// The property's value after the change occurred. /// A callback to invoke to update the property value. /// (optional) The name of the property that changed. @@ -364,10 +357,12 @@ protected bool SetPropertyAndNotifyOnCompletion(ref TTask? field, Express /// The and events are not raised /// if the current and new value for the target property are the same. /// - /// Thrown when is not valid. - protected bool SetPropertyAndNotifyOnCompletion(ref TTask? field, Expression> fieldExpression, TTask? newValue, Action callback, [CallerMemberName] string? propertyName = null) + protected bool SetPropertyAndNotifyOnCompletion(FieldAccessor fieldAccessor, TTask? newValue, Action callback, [CallerMemberName] string? propertyName = null) where TTask : Task { + // Invoke the accessor once to get a field reference for the synchronous part + ref TTask? field = ref fieldAccessor(); + if (ReferenceEquals(field, newValue)) { return false; @@ -398,16 +393,6 @@ protected bool SetPropertyAndNotifyOnCompletion(ref TTask? field, Express return true; } - // Get the target field to set. This is needed because we can't - // capture the ref field in a closure (for the async method). - if (!((fieldExpression.Body as MemberExpression)?.Member is FieldInfo fieldInfo)) - { - ThrowArgumentExceptionForInvalidFieldExpression(); - - // This is never executed, as the method above always throws - return false; - } - // We use a local async function here so that the main method can // remain synchronous and return a value that can be immediately // used by the caller. This mirrors Set(ref T, T, string). @@ -427,7 +412,7 @@ async void MonitorTask() { } - TTask? currentTask = (TTask?)fieldInfo.GetValue(this); + TTask? currentTask = fieldAccessor(); // Only notify if the property hasn't changed if (ReferenceEquals(newValue, currentTask)) @@ -444,19 +429,18 @@ async void MonitorTask() } /// - /// Throws an when a given is invalid for a property. + /// A custom that returns a reference to a backing field to a property. /// - private static void ThrowArgumentExceptionForInvalidPropertyExpression() - { - throw new ArgumentException("The given expression must be in the form () => MyModel.MyProperty"); - } + /// The type of reference to return. + /// A reference to the backing field of a property. + protected delegate ref T FieldAccessor(); /// - /// Throws an when a given is invalid for a property field. + /// Throws an when a given is invalid for a property. /// - private static void ThrowArgumentExceptionForInvalidFieldExpression() + private static void ThrowArgumentExceptionForInvalidPropertyExpression() { - throw new ArgumentException("The given expression must be in the form () => field"); + throw new ArgumentException("The given expression must be in the form () => MyModel.MyProperty"); } } } \ No newline at end of file diff --git a/Microsoft.Toolkit.Mvvm/Input/AsyncRelayCommand.cs b/Microsoft.Toolkit.Mvvm/Input/AsyncRelayCommand.cs index ee03b23a1f0..b2c74b300db 100644 --- a/Microsoft.Toolkit.Mvvm/Input/AsyncRelayCommand.cs +++ b/Microsoft.Toolkit.Mvvm/Input/AsyncRelayCommand.cs @@ -58,7 +58,7 @@ public Task? ExecutionTask get => this.executionTask; private set { - if (SetPropertyAndNotifyOnCompletion(ref this.executionTask, () => this.executionTask, value, _ => OnPropertyChanged(nameof(IsRunning)))) + if (SetPropertyAndNotifyOnCompletion(() => ref this.executionTask, value, _ => OnPropertyChanged(nameof(IsRunning)))) { OnPropertyChanged(nameof(IsRunning)); } diff --git a/Microsoft.Toolkit.Mvvm/Input/AsyncRelayCommand{T}.cs b/Microsoft.Toolkit.Mvvm/Input/AsyncRelayCommand{T}.cs index 14d2c65d4b7..3d8d733ddcd 100644 --- a/Microsoft.Toolkit.Mvvm/Input/AsyncRelayCommand{T}.cs +++ b/Microsoft.Toolkit.Mvvm/Input/AsyncRelayCommand{T}.cs @@ -58,7 +58,7 @@ public Task? ExecutionTask get => this.executionTask; private set { - if (SetPropertyAndNotifyOnCompletion(ref this.executionTask, () => this.executionTask, value, _ => OnPropertyChanged(nameof(IsRunning)))) + if (SetPropertyAndNotifyOnCompletion(() => ref this.executionTask, value, _ => OnPropertyChanged(nameof(IsRunning)))) { OnPropertyChanged(nameof(IsRunning)); } diff --git a/UnitTests/UnitTests.Shared/Mvvm/Test_ObservableObject.cs b/UnitTests/UnitTests.Shared/Mvvm/Test_ObservableObject.cs index 89e70171139..82f64fffbdd 100644 --- a/UnitTests/UnitTests.Shared/Mvvm/Test_ObservableObject.cs +++ b/UnitTests/UnitTests.Shared/Mvvm/Test_ObservableObject.cs @@ -226,7 +226,7 @@ public class SampleModelWithTask : ObservableObject public Task Data { get => data; - set => SetPropertyAndNotifyOnCompletion(ref data, () => data, value); + set => SetPropertyAndNotifyOnCompletion(() => ref data, value); } } } From f9995415d212dec7880d62d0a9f84deaabead8f9 Mon Sep 17 00:00:00 2001 From: Alexandre Zollinger Chohfi Date: Wed, 19 Aug 2020 16:28:48 -0700 Subject: [PATCH 401/595] Update Microsoft.Toolkit.Uwp.SampleApp/SamplePages/TextToolbar/TextToolbarPage.xaml.cs Co-authored-by: Michael Hawker MSFT (XAML Llama) <24302614+michael-hawker@users.noreply.github.com> --- .../SamplePages/TextToolbar/TextToolbarPage.xaml.cs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/TextToolbar/TextToolbarPage.xaml.cs b/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/TextToolbar/TextToolbarPage.xaml.cs index 8e26d0fd635..42a46a44a3b 100644 --- a/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/TextToolbar/TextToolbarPage.xaml.cs +++ b/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/TextToolbar/TextToolbarPage.xaml.cs @@ -106,8 +106,7 @@ private void UseRichTextFormatter() return; } - var formatter = new RichTextFormatter(); - _toolbar.Formatter = formatter; + _toolbar.Formatter = new RichTextFormatter(); } private void UseMarkDownFormatter() @@ -203,4 +202,4 @@ private void EditZone_TextChanged(object sender, RoutedEventArgs e) private int DemoCounter { get; set; } = 0; } -} \ No newline at end of file +} From 16d8ac080aebe97ad9e5f04f704f6f77736e080a Mon Sep 17 00:00:00 2001 From: Alexandre Zollinger Chohfi Date: Wed, 19 Aug 2020 16:29:03 -0700 Subject: [PATCH 402/595] Update Microsoft.Toolkit.Uwp.SampleApp/SamplePages/TextToolbar/TextToolbarPage.xaml.cs Co-authored-by: Michael Hawker MSFT (XAML Llama) <24302614+michael-hawker@users.noreply.github.com> --- .../SamplePages/TextToolbar/TextToolbarPage.xaml.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/TextToolbar/TextToolbarPage.xaml.cs b/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/TextToolbar/TextToolbarPage.xaml.cs index 42a46a44a3b..a299a577d2c 100644 --- a/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/TextToolbar/TextToolbarPage.xaml.cs +++ b/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/TextToolbar/TextToolbarPage.xaml.cs @@ -116,8 +116,7 @@ private void UseMarkDownFormatter() return; } - var formatter = new MarkDownFormatter(); - _toolbar.Formatter = formatter; + _toolbar.Formatter = new MarkDownFormatter(); } private void UseCustomFormatter() From 4f3bbd12db3d64171c5ed2cda86210b4c27bfb33 Mon Sep 17 00:00:00 2001 From: Alexandre Zollinger Chohfi Date: Wed, 19 Aug 2020 16:29:24 -0700 Subject: [PATCH 403/595] Update Microsoft.Toolkit.Uwp.UI.Controls/TextToolbar/TextToolbar.Events.cs Co-authored-by: Michael Hawker MSFT (XAML Llama) <24302614+michael-hawker@users.noreply.github.com> --- .../TextToolbar/TextToolbar.Events.cs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/Microsoft.Toolkit.Uwp.UI.Controls/TextToolbar/TextToolbar.Events.cs b/Microsoft.Toolkit.Uwp.UI.Controls/TextToolbar/TextToolbar.Events.cs index e74e5345cfb..30a9491d6ad 100644 --- a/Microsoft.Toolkit.Uwp.UI.Controls/TextToolbar/TextToolbar.Events.cs +++ b/Microsoft.Toolkit.Uwp.UI.Controls/TextToolbar/TextToolbar.Events.cs @@ -67,8 +67,7 @@ private static void OnEditorChanged(DependencyObject obj, DependencyPropertyChan /// Property Changed Args private static void OnFormatterChanged(DependencyObject obj, DependencyPropertyChangedEventArgs args) { - var bar = obj as TextToolbar; - if (bar != null && bar.Formatter != null) + if (obj is TextToolbar bar && bar.Formatter != null) { if (args.OldValue is Formatter formatter) { @@ -316,4 +315,4 @@ public bool ShiftKeyDown /// public event EventHandler EditorChanged; } -} \ No newline at end of file +} From acf41acfae771e697e6940277ad5421255bc305d Mon Sep 17 00:00:00 2001 From: Alexandre Zollinger Chohfi Date: Wed, 19 Aug 2020 16:32:57 -0700 Subject: [PATCH 404/595] Update Microsoft.Toolkit.Uwp.UI.Controls/TextToolbar/TextToolbar.cs Co-authored-by: Michael Hawker MSFT (XAML Llama) <24302614+michael-hawker@users.noreply.github.com> --- Microsoft.Toolkit.Uwp.UI.Controls/TextToolbar/TextToolbar.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Microsoft.Toolkit.Uwp.UI.Controls/TextToolbar/TextToolbar.cs b/Microsoft.Toolkit.Uwp.UI.Controls/TextToolbar/TextToolbar.cs index 2cb3e50f530..c8418670bbc 100644 --- a/Microsoft.Toolkit.Uwp.UI.Controls/TextToolbar/TextToolbar.cs +++ b/Microsoft.Toolkit.Uwp.UI.Controls/TextToolbar/TextToolbar.cs @@ -45,7 +45,7 @@ protected override void OnApplyTemplate() { if (Formatter == null) { - throw new InvalidOperationException("No formatter specificed."); + throw new InvalidOperationException("No formatter specified."); } else { @@ -55,4 +55,4 @@ protected override void OnApplyTemplate() base.OnApplyTemplate(); } } -} \ No newline at end of file +} From 5b2af5686d4167102cc3adda7a5eb8bdf6313357 Mon Sep 17 00:00:00 2001 From: Alexandre Zollinger Chohfi Date: Wed, 19 Aug 2020 17:03:32 -0700 Subject: [PATCH 405/595] Null check on Formatter, and using pattern matching on TextToolbar. --- .../TextToolbar/TextToolbar.Events.cs | 58 +++++++++---------- .../TextToolbar/TextToolbar.Methods.cs | 9 +-- 2 files changed, 31 insertions(+), 36 deletions(-) diff --git a/Microsoft.Toolkit.Uwp.UI.Controls/TextToolbar/TextToolbar.Events.cs b/Microsoft.Toolkit.Uwp.UI.Controls/TextToolbar/TextToolbar.Events.cs index 30a9491d6ad..b9bcb3ce1d8 100644 --- a/Microsoft.Toolkit.Uwp.UI.Controls/TextToolbar/TextToolbar.Events.cs +++ b/Microsoft.Toolkit.Uwp.UI.Controls/TextToolbar/TextToolbar.Events.cs @@ -32,8 +32,7 @@ private static void OnEditorChanged(DependencyObject obj, DependencyPropertyChan return; } - var bar = obj as TextToolbar; - if (bar != null) + if (obj is TextToolbar bar) { var oldEditor = args.OldValue as RichEditBox; var newEditor = args.NewValue as RichEditBox; @@ -46,8 +45,15 @@ private static void OnEditorChanged(DependencyObject obj, DependencyPropertyChan if (newEditor != null) { newEditor.AddHandler(KeyDownEvent, bar.KeyEventHandler, handledEventsToo: true); - bar.Formatter.SetModel(bar); - bar.DefaultButtons = bar.Formatter.DefaultButtons; + if (bar.Formatter != null) + { + bar.Formatter.SetModel(bar); + bar.DefaultButtons = bar.Formatter.DefaultButtons; + } + else + { + bar.DefaultButtons = null; + } } var editorArgs = new EditorChangedArgs @@ -67,16 +73,19 @@ private static void OnEditorChanged(DependencyObject obj, DependencyPropertyChan /// Property Changed Args private static void OnFormatterChanged(DependencyObject obj, DependencyPropertyChangedEventArgs args) { - if (obj is TextToolbar bar && bar.Formatter != null) + if (obj is TextToolbar bar) { if (args.OldValue is Formatter formatter) { formatter.UnsetModel(bar); } - bar.Formatter.SetModel(bar); - bar.DefaultButtons = bar.Formatter.DefaultButtons; - bar.BuildBar(); + if (bar.Formatter != null) + { + bar.Formatter.SetModel(bar); + bar.DefaultButtons = bar.Formatter.DefaultButtons; + bar.BuildBar(); + } } } @@ -87,14 +96,11 @@ private static void OnFormatterChanged(DependencyObject obj, DependencyPropertyC /// Property Changed Args private static void OnButtonMapChanged(DependencyObject obj, DependencyPropertyChangedEventArgs args) { - var bar = obj as TextToolbar; - if (bar != null) + if (obj is TextToolbar bar) { - var oldSource = args.OldValue as ButtonMap; - var newSource = args.NewValue as ButtonMap; var root = bar.GetTemplateChild(RootControl) as CommandBar; - if (oldSource != null) + if (args.OldValue is ButtonMap oldSource) { oldSource.CollectionChanged -= bar.OnButtonMapModified; @@ -107,7 +113,7 @@ private static void OnButtonMapChanged(DependencyObject obj, DependencyPropertyC } } - if (newSource != null) + if (args.NewValue is ButtonMap newSource) { newSource.CollectionChanged += bar.OnButtonMapModified; @@ -126,19 +132,16 @@ private static void OnButtonMapChanged(DependencyObject obj, DependencyPropertyC /// Property Changed Args private static void OnDefaultButtonModificationsChanged(DependencyObject obj, DependencyPropertyChangedEventArgs args) { - var bar = obj as TextToolbar; - if (bar != null) + if (obj is TextToolbar bar) { - var oldSource = args.OldValue as DefaultButtonModificationList; - var newSource = args.NewValue as DefaultButtonModificationList; var root = bar.GetTemplateChild(RootControl) as CommandBar; - if (oldSource != null) + if (args.OldValue is DefaultButtonModificationList oldSource) { oldSource.CollectionChanged -= bar.OnDefaultButtonModificationListChanged; } - if (newSource != null) + if (args.NewValue is DefaultButtonModificationList newSource) { newSource.CollectionChanged += bar.OnDefaultButtonModificationListChanged; @@ -158,8 +161,7 @@ private static void OnDefaultButtonModificationsChanged(DependencyObject obj, De /// Collection Changed Args private void OnButtonMapModified(object sender, NotifyCollectionChangedEventArgs e) { - var root = GetTemplateChild(RootControl) as CommandBar; - if (root != null) + if (GetTemplateChild(RootControl) is CommandBar root) { switch (e.Action) { @@ -168,8 +170,7 @@ private void OnButtonMapModified(object sender, NotifyCollectionChangedEventArgs { AddToolbarItem(item, root); - var button = item as ToolbarButton; - if (button != null) + if (item is ToolbarButton button) { button.PropertyChanged += ToolbarItemPropertyChanged; } @@ -182,8 +183,7 @@ private void OnButtonMapModified(object sender, NotifyCollectionChangedEventArgs { RemoveToolbarItem(item); - var button = item as ToolbarButton; - if (button != null) + if (item is ToolbarButton button) { button.PropertyChanged -= ToolbarItemPropertyChanged; } @@ -225,8 +225,7 @@ private void OnDefaultButtonModificationListChanged(object sender, NotifyCollect /// Property Changed Event private void ToolbarItemPropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e) { - var root = GetTemplateChild(RootControl) as CommandBar; - if (root != null) + if (GetTemplateChild(RootControl) is CommandBar root) { if (e.PropertyName == nameof(IToolbarItem.Position)) { @@ -249,8 +248,7 @@ private void Editor_KeyDown(object sender, KeyRoutedEventArgs e) LastKeyPress = e.Key; - var root = GetTemplateChild(RootControl) as CommandBar; - if (root != null) + if (GetTemplateChild(RootControl) is CommandBar root) { if (ControlKeyDown && e.Key != VirtualKey.Control) { diff --git a/Microsoft.Toolkit.Uwp.UI.Controls/TextToolbar/TextToolbar.Methods.cs b/Microsoft.Toolkit.Uwp.UI.Controls/TextToolbar/TextToolbar.Methods.cs index 7d30afc9e3f..589a6a61258 100644 --- a/Microsoft.Toolkit.Uwp.UI.Controls/TextToolbar/TextToolbar.Methods.cs +++ b/Microsoft.Toolkit.Uwp.UI.Controls/TextToolbar/TextToolbar.Methods.cs @@ -23,8 +23,7 @@ public partial class TextToolbar /// Default Toolbar Button public ToolbarButton GetDefaultButton(ButtonType button) { - var root = GetTemplateChild(RootControl) as CommandBar; - if (root != null) + if (GetTemplateChild(RootControl) is CommandBar root) { var element = root.PrimaryCommands.OfType().FirstOrDefault(item => ((FrameworkElement)item).Name == button.ToString()); return element; @@ -38,8 +37,7 @@ public ToolbarButton GetDefaultButton(ButtonType button) /// private void BuildBar() { - var root = GetTemplateChild(RootControl) as CommandBar; - if (root != null) + if (GetTemplateChild(RootControl) is CommandBar root) { root.PrimaryCommands.Clear(); @@ -90,8 +88,7 @@ private void AddToolbarItem(IToolbarItem item, CommandBar root) if (!root.PrimaryCommands.Contains(item)) { - var button = item as ToolbarButton; - if (button != null) + if (item is ToolbarButton button) { button.Model = this; } From c80e1544e959eb77d5a4607aba1d146189703db8 Mon Sep 17 00:00:00 2001 From: Alexandre Zollinger Chohfi Date: Wed, 19 Aug 2020 17:06:15 -0700 Subject: [PATCH 406/595] Applied PR feedback. --- .../SamplePages/TextToolbar/TextToolbarCode.bind | 3 +-- .../SamplePages/TextToolbar/TextToolbarPage.xaml.cs | 3 +-- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/TextToolbar/TextToolbarCode.bind b/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/TextToolbar/TextToolbarCode.bind index 3b455448330..74d8d36f80a 100644 --- a/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/TextToolbar/TextToolbarCode.bind +++ b/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/TextToolbar/TextToolbarCode.bind @@ -18,8 +18,7 @@ private void EditZone_TextChanged(object sender, Windows.UI.Xaml.RoutedEventArgs private void UseCustomFormatter() { - var formatter = new SampleFormatter(); - Toolbar.Formatter = formatter; + Toolbar.Formatter = new SampleFormatter(); } private int DemoCounter { get; set; } = 0; diff --git a/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/TextToolbar/TextToolbarPage.xaml.cs b/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/TextToolbar/TextToolbarPage.xaml.cs index a299a577d2c..1d70f2ebc67 100644 --- a/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/TextToolbar/TextToolbarPage.xaml.cs +++ b/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/TextToolbar/TextToolbarPage.xaml.cs @@ -126,8 +126,7 @@ private void UseCustomFormatter() return; } - var formatter = new SampleFormatter(); - _toolbar.Formatter = formatter; + _toolbar.Formatter = new SampleFormatter(); } private void AddCustomButton() From 32f36c84a38ede183565effcfef3331a4674e092 Mon Sep 17 00:00:00 2001 From: Alexandre Zollinger Chohfi Date: Wed, 19 Aug 2020 19:27:27 -0700 Subject: [PATCH 407/595] Replaced ToolbarFormatterActiveConverter to a generic TypeToObjectConverter. --- .../SamplePages/TextToolbar/TextToolbar.bind | 11 ++- .../TextToolbar/TextToolbarPage.xaml | 11 ++- .../ToolbarFormatterActiveConverter.cs | 48 ---------- .../Converters/TypeToObjectConverter.cs | 95 +++++++++++++++++++ .../Converters/Test_TypeToObjectConverter.cs | 57 +++++++++++ UnitTests/UnitTests.UWP/UnitTests.UWP.csproj | 1 + 6 files changed, 171 insertions(+), 52 deletions(-) delete mode 100644 Microsoft.Toolkit.Uwp.UI.Controls/TextToolbar/ToolbarFormatterActiveConverter.cs create mode 100644 Microsoft.Toolkit.Uwp.UI/Converters/TypeToObjectConverter.cs create mode 100644 UnitTests/UnitTests.UWP/Converters/Test_TypeToObjectConverter.cs diff --git a/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/TextToolbar/TextToolbar.bind b/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/TextToolbar/TextToolbar.bind index 5be1d7361ef..826d35feae1 100644 --- a/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/TextToolbar/TextToolbar.bind +++ b/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/TextToolbar/TextToolbar.bind @@ -12,8 +12,15 @@ mc:Ignorable="d"> - + + + Visible + + + Collapsed + + - + + + Visible + + + Collapsed + + diff --git a/Microsoft.Toolkit.Uwp.UI.Controls/TextToolbar/ToolbarFormatterActiveConverter.cs b/Microsoft.Toolkit.Uwp.UI.Controls/TextToolbar/ToolbarFormatterActiveConverter.cs deleted file mode 100644 index 0729753334b..00000000000 --- a/Microsoft.Toolkit.Uwp.UI.Controls/TextToolbar/ToolbarFormatterActiveConverter.cs +++ /dev/null @@ -1,48 +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 Microsoft.Toolkit.Uwp.UI.Controls.TextToolbarFormats; -using Windows.UI.Xaml.Data; - -namespace Microsoft.Toolkit.Uwp.UI.Converters -{ - /// - /// Compares if Formats are equal and returns bool - /// - public class ToolbarFormatterActiveConverter : IValueConverter - { - /// - public object Convert(object value, Type targetType, object parameter, string language) - { - if (value is Formatter formatter) - { - CurrentFormatter = formatter.GetType(); - return CurrentFormatter.ToString() == FormatterType; - } - else - { - return value; - } - } - - /// - public object ConvertBack(object value, Type targetType, object parameter, string language) - { - if (CurrentFormatter != null) - { - return CurrentFormatter; - } - - return value; - } - - /// - /// Gets or sets the 's to compare - /// - public string FormatterType { get; set; } - - private Type CurrentFormatter { get; set; } - } -} \ No newline at end of file diff --git a/Microsoft.Toolkit.Uwp.UI/Converters/TypeToObjectConverter.cs b/Microsoft.Toolkit.Uwp.UI/Converters/TypeToObjectConverter.cs new file mode 100644 index 00000000000..e7ad49ac726 --- /dev/null +++ b/Microsoft.Toolkit.Uwp.UI/Converters/TypeToObjectConverter.cs @@ -0,0 +1,95 @@ +// 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 Windows.UI.Xaml; +using Windows.UI.Xaml.Data; + +namespace Microsoft.Toolkit.Uwp.UI.Converters +{ + /// + /// This class returns an object or another, depending on whether the type of the provided value matches another provided Type. + /// + public class TypeToObjectConverter : DependencyObject, IValueConverter + { + /// + /// Identifies the property. + /// + public static readonly DependencyProperty TrueValueProperty = + DependencyProperty.Register(nameof(TrueValue), typeof(object), typeof(TypeToObjectConverter), new PropertyMetadata(null)); + + /// + /// Identifies the property. + /// + public static readonly DependencyProperty FalseValueProperty = + DependencyProperty.Register(nameof(FalseValue), typeof(object), typeof(TypeToObjectConverter), new PropertyMetadata(null)); + + /// + /// Identifies the property. + /// + public static readonly DependencyProperty TypeProperty = + DependencyProperty.Register(nameof(Type), typeof(Type), typeof(TypeToObjectConverter), new PropertyMetadata(typeof(object))); + + /// + /// Gets or sets the value to be returned when the type of the provided value matches . + /// + public object TrueValue + { + get { return GetValue(TrueValueProperty); } + set { SetValue(TrueValueProperty, value); } + } + + /// + /// Gets or sets the value to be returned when the type of the provided value does not match . + /// + public object FalseValue + { + get { return GetValue(FalseValueProperty); } + set { SetValue(FalseValueProperty, value); } + } + + /// + /// Gets or sets the Type used to compare the type of the provided value. + /// + public Type Type + { + get { return (Type)GetValue(TypeProperty); } + set { SetValue(TypeProperty, value); } + } + + /// + /// Convert the 's Type to an other object. + /// + /// The source data being passed to the target. + /// The type of the target property, as a type reference. + /// An optional parameter to be used to invert the converter logic. + /// The language of the conversion. + /// The value to be passed to the target dependency property. + public object Convert(object value, Type targetType, object parameter, string language) + { + var typeMatches = value != null && Type.Equals(value.GetType()); + + // Negate if needed + if (ConverterTools.TryParseBool(parameter)) + { + typeMatches = !typeMatches; + } + + return ConverterTools.Convert(typeMatches ? TrueValue : FalseValue, targetType); + } + + /// + /// Not implemented. + /// + /// The source data being passed to the target. + /// The type of the target property, as a type reference. + /// Optional parameter. Not used. + /// The language of the conversion. Not used. + /// The value to be passed to the target dependency property. + public object ConvertBack(object value, Type targetType, object parameter, string language) + { + throw new NotImplementedException(); + } + } +} diff --git a/UnitTests/UnitTests.UWP/Converters/Test_TypeToObjectConverter.cs b/UnitTests/UnitTests.UWP/Converters/Test_TypeToObjectConverter.cs new file mode 100644 index 00000000000..9f5b6653832 --- /dev/null +++ b/UnitTests/UnitTests.UWP/Converters/Test_TypeToObjectConverter.cs @@ -0,0 +1,57 @@ +// 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.Uwp.UI.Converters; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Microsoft.VisualStudio.TestTools.UnitTesting.AppContainer; +using Windows.UI.Xaml; + +namespace UnitTests.Converters +{ + [TestClass] + public class Test_TypeToObjectConverter + { + [TestCategory("Converters")] + [UITestMethod] + public void Test_StringConvertStringToVisibility() + { + var converter = new TypeToObjectConverter + { + TrueValue = Visibility.Visible, + FalseValue = Visibility.Collapsed, + Type = typeof(string) + }; + var result = converter.Convert("anything", typeof(Visibility), null, "en-us"); + Assert.AreEqual(Visibility.Visible, result); + } + + [TestCategory("Converters")] + [UITestMethod] + public void Test_StringDoesntConvertBoolToVisibility() + { + var converter = new TypeToObjectConverter + { + TrueValue = Visibility.Visible, + FalseValue = Visibility.Collapsed, + Type = typeof(string) + }; + var result = converter.Convert(true, typeof(Visibility), null, "en-us"); + Assert.AreEqual(Visibility.Collapsed, result); + } + + [TestCategory("Converters")] + [UITestMethod] + public void Test_StringConvertStringToVisibilityWithNegateTrue() + { + var converter = new TypeToObjectConverter + { + TrueValue = Visibility.Visible, + FalseValue = Visibility.Collapsed, + Type = typeof(string) + }; + var result = converter.Convert("anything", typeof(Visibility), "true", "en-us"); + Assert.AreEqual(Visibility.Collapsed, result); + } + } +} diff --git a/UnitTests/UnitTests.UWP/UnitTests.UWP.csproj b/UnitTests/UnitTests.UWP/UnitTests.UWP.csproj index 0b40d7f34d8..898608f956f 100644 --- a/UnitTests/UnitTests.UWP/UnitTests.UWP.csproj +++ b/UnitTests/UnitTests.UWP/UnitTests.UWP.csproj @@ -133,6 +133,7 @@ + From 62145a02546797c62715ac3f501186884f98c8d0 Mon Sep 17 00:00:00 2001 From: Alexandre Zollinger Chohfi Date: Wed, 19 Aug 2020 19:32:08 -0700 Subject: [PATCH 408/595] Revert "Removed TabView/TabViewItem, HeaderedTextBlock, LightBehavior/AnimationExtensions.Light, and some other smaller methods marked as Obsolete." This reverts commit 274492be9708ee1f7eed68a6a0b786394ff6481f. --- .../Services/LinkedIn/LinkedInDataProvider.cs | 11 + .../Services/LinkedIn/LinkedInService.cs | 9 + .../Services/Twitter/TwitterDataProvider.cs | 11 + .../Services/Twitter/TwitterService.cs | 9 + .../Services/Weibo/WeiboDataProvider.cs | 11 + .../Services/Weibo/WeiboService.cs | 9 + .../Microsoft.Toolkit.Uwp.SampleApp.csproj | 31 + .../HeaderedTextBlock/HeaderedTextBlock.png | Bin 0 -> 3308 bytes .../HeaderedTextBlockCode.bind | 16 + .../HeaderedTextBlockPage.xaml | 9 + .../HeaderedTextBlockPage.xaml.cs | 18 + .../SamplePages/Light/LightBehavior.png | Bin 0 -> 6164 bytes .../SamplePages/Light/LightBehaviorCode.bind | 8 + .../SamplePages/Light/LightBehaviorPage.xaml | 39 + .../Light/LightBehaviorPage.xaml.cs | 52 ++ .../SamplePages/Light/LightBehaviorXaml.bind | 25 + .../SamplePages/TabView/TabView.png | Bin 0 -> 1392 bytes .../SamplePages/TabView/TabViewPage.xaml | 19 + .../SamplePages/TabView/TabViewPage.xaml.cs | 73 ++ .../SamplePages/TabView/TabViewXaml.bind | 97 +++ .../SamplePages/samples.json | 37 + .../Behaviors/Light.cs | 69 ++ .../ConnectedAnimations/Connected.cs | 16 + .../Extensions/AnimationExtensions.Light.cs | 210 ++++- .../HeaderedTextBlockMetadata.cs | 49 ++ ...soft.Toolkit.Uwp.UI.Controls.Design.csproj | 3 + .../TabViewItemMetadata.cs | 43 + .../TabViewMetadata.cs | 54 ++ .../HeaderedTextBlock.Properties.cs | 172 ++++ .../HeaderedTextBlock/HeaderedTextBlock.cs | 76 ++ .../HeaderedTextBlock/HeaderedTextBlock.xaml | 60 ++ .../Microsoft.Toolkit.Uwp.UI.Controls.csproj | 1 + ...Toolkit.Uwp.UI.Controls.csproj.DotSettings | 1 + .../Strings/en-US/Resources.resw | 8 + .../TabView/TabClosingEventArgs.cs | 36 + .../TabView/TabDraggedOutsideEventArgs.cs | 35 + .../TabView/TabView.HeaderLayout.cs | 221 ++++++ .../TabView/TabView.ItemSources.cs | 115 +++ .../TabView/TabView.Properties.cs | 245 ++++++ .../TabView/TabView.cs | 320 ++++++++ .../TabView/TabView.xaml | 736 ++++++++++++++++++ .../TabView/TabViewItem.Properties.cs | 79 ++ .../TabView/TabViewItem.cs | 116 +++ .../TabView/TabWidthMode.cs | 36 + .../Themes/Generic.xaml | 2 + .../TileControl/TileControl.cs | 7 + .../VisualStudioToolsManifest.xml | 3 + .../Extensions/StringExtensions.cs | 8 + 48 files changed, 3201 insertions(+), 4 deletions(-) create mode 100644 Microsoft.Toolkit.Uwp.SampleApp/SamplePages/HeaderedTextBlock/HeaderedTextBlock.png create mode 100644 Microsoft.Toolkit.Uwp.SampleApp/SamplePages/HeaderedTextBlock/HeaderedTextBlockCode.bind create mode 100644 Microsoft.Toolkit.Uwp.SampleApp/SamplePages/HeaderedTextBlock/HeaderedTextBlockPage.xaml create mode 100644 Microsoft.Toolkit.Uwp.SampleApp/SamplePages/HeaderedTextBlock/HeaderedTextBlockPage.xaml.cs create mode 100644 Microsoft.Toolkit.Uwp.SampleApp/SamplePages/Light/LightBehavior.png create mode 100644 Microsoft.Toolkit.Uwp.SampleApp/SamplePages/Light/LightBehaviorCode.bind create mode 100644 Microsoft.Toolkit.Uwp.SampleApp/SamplePages/Light/LightBehaviorPage.xaml create mode 100644 Microsoft.Toolkit.Uwp.SampleApp/SamplePages/Light/LightBehaviorPage.xaml.cs create mode 100644 Microsoft.Toolkit.Uwp.SampleApp/SamplePages/Light/LightBehaviorXaml.bind create mode 100644 Microsoft.Toolkit.Uwp.SampleApp/SamplePages/TabView/TabView.png create mode 100644 Microsoft.Toolkit.Uwp.SampleApp/SamplePages/TabView/TabViewPage.xaml create mode 100644 Microsoft.Toolkit.Uwp.SampleApp/SamplePages/TabView/TabViewPage.xaml.cs create mode 100644 Microsoft.Toolkit.Uwp.SampleApp/SamplePages/TabView/TabViewXaml.bind create mode 100644 Microsoft.Toolkit.Uwp.UI.Animations/Behaviors/Light.cs create mode 100644 Microsoft.Toolkit.Uwp.UI.Controls.Design/HeaderedTextBlockMetadata.cs create mode 100644 Microsoft.Toolkit.Uwp.UI.Controls.Design/TabViewItemMetadata.cs create mode 100644 Microsoft.Toolkit.Uwp.UI.Controls.Design/TabViewMetadata.cs create mode 100644 Microsoft.Toolkit.Uwp.UI.Controls/HeaderedTextBlock/HeaderedTextBlock.Properties.cs create mode 100644 Microsoft.Toolkit.Uwp.UI.Controls/HeaderedTextBlock/HeaderedTextBlock.cs create mode 100644 Microsoft.Toolkit.Uwp.UI.Controls/HeaderedTextBlock/HeaderedTextBlock.xaml create mode 100644 Microsoft.Toolkit.Uwp.UI.Controls/TabView/TabClosingEventArgs.cs create mode 100644 Microsoft.Toolkit.Uwp.UI.Controls/TabView/TabDraggedOutsideEventArgs.cs create mode 100644 Microsoft.Toolkit.Uwp.UI.Controls/TabView/TabView.HeaderLayout.cs create mode 100644 Microsoft.Toolkit.Uwp.UI.Controls/TabView/TabView.ItemSources.cs create mode 100644 Microsoft.Toolkit.Uwp.UI.Controls/TabView/TabView.Properties.cs create mode 100644 Microsoft.Toolkit.Uwp.UI.Controls/TabView/TabView.cs create mode 100644 Microsoft.Toolkit.Uwp.UI.Controls/TabView/TabView.xaml create mode 100644 Microsoft.Toolkit.Uwp.UI.Controls/TabView/TabViewItem.Properties.cs create mode 100644 Microsoft.Toolkit.Uwp.UI.Controls/TabView/TabViewItem.cs create mode 100644 Microsoft.Toolkit.Uwp.UI.Controls/TabView/TabWidthMode.cs diff --git a/Microsoft.Toolkit.Services/Services/LinkedIn/LinkedInDataProvider.cs b/Microsoft.Toolkit.Services/Services/LinkedIn/LinkedInDataProvider.cs index 27c7739c333..f852688bc57 100644 --- a/Microsoft.Toolkit.Services/Services/LinkedIn/LinkedInDataProvider.cs +++ b/Microsoft.Toolkit.Services/Services/LinkedIn/LinkedInDataProvider.cs @@ -154,6 +154,17 @@ public async Task LoginAsync() return false; } + /// + /// Log user out of LinkedIn. + /// + [Obsolete("Logout is deprecated, please use LogoutAsync instead.", true)] + public void Logout() + { +#pragma warning disable CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed + LogoutAsync(); +#pragma warning restore CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed + } + /// /// Log user out of LinkedIn. /// diff --git a/Microsoft.Toolkit.Services/Services/LinkedIn/LinkedInService.cs b/Microsoft.Toolkit.Services/Services/LinkedIn/LinkedInService.cs index 7a79b8fc344..251e5fb711d 100644 --- a/Microsoft.Toolkit.Services/Services/LinkedIn/LinkedInService.cs +++ b/Microsoft.Toolkit.Services/Services/LinkedIn/LinkedInService.cs @@ -92,6 +92,15 @@ public Task ShareActivityAsync(LinkedInShareRequest share return Provider.ShareDataAsync(shareRequest); } + /// + /// Log user out of LinkedIn. + /// + [Obsolete("Logout is deprecated, please use LogoutAsync instead.", true)] + public void Logout() + { + Provider.Logout(); + } + /// /// Log user out of LinkedIn. /// diff --git a/Microsoft.Toolkit.Services/Services/Twitter/TwitterDataProvider.cs b/Microsoft.Toolkit.Services/Services/Twitter/TwitterDataProvider.cs index 41db01d748b..a6eda59f07e 100644 --- a/Microsoft.Toolkit.Services/Services/Twitter/TwitterDataProvider.cs +++ b/Microsoft.Toolkit.Services/Services/Twitter/TwitterDataProvider.cs @@ -273,6 +273,17 @@ public async Task LoginAsync() return false; } + /// + /// Log user out of Twitter. + /// + [Obsolete("Logout is deprecated, please use LogoutAsync instead.", true)] + public void Logout() + { +#pragma warning disable CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed + LogoutAsync(); +#pragma warning restore CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed + } + /// /// Log user out of Twitter. /// diff --git a/Microsoft.Toolkit.Services/Services/Twitter/TwitterService.cs b/Microsoft.Toolkit.Services/Services/Twitter/TwitterService.cs index b461458ce8d..703657cf2fb 100644 --- a/Microsoft.Toolkit.Services/Services/Twitter/TwitterService.cs +++ b/Microsoft.Toolkit.Services/Services/Twitter/TwitterService.cs @@ -400,6 +400,15 @@ public Task LoginAsync() return Provider.LoginAsync(); } + /// + /// Log user out of Twitter. + /// + [Obsolete("Logout is deprecated, please use LogoutAsync instead.", true)] + public void Logout() + { + Provider.Logout(); + } + /// /// Log user out of Twitter. /// diff --git a/Microsoft.Toolkit.Services/Services/Weibo/WeiboDataProvider.cs b/Microsoft.Toolkit.Services/Services/Weibo/WeiboDataProvider.cs index 6975a33d005..3dd6d59a8ac 100644 --- a/Microsoft.Toolkit.Services/Services/Weibo/WeiboDataProvider.cs +++ b/Microsoft.Toolkit.Services/Services/Weibo/WeiboDataProvider.cs @@ -163,6 +163,17 @@ public async Task LoginAsync() return false; } + /// + /// Log user out of Weibo. + /// + [Obsolete("Logout is deprecated, please use LogoutAsync instead.", true)] + public void Logout() + { +#pragma warning disable CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed + LogoutAsync(); +#pragma warning restore CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed + } + /// /// Log user out of Weibo. /// diff --git a/Microsoft.Toolkit.Services/Services/Weibo/WeiboService.cs b/Microsoft.Toolkit.Services/Services/Weibo/WeiboService.cs index ea2f6cea3b1..f5f8dd7ec99 100644 --- a/Microsoft.Toolkit.Services/Services/Weibo/WeiboService.cs +++ b/Microsoft.Toolkit.Services/Services/Weibo/WeiboService.cs @@ -183,6 +183,15 @@ public Task LoginAsync() return Provider.LoginAsync(); } + /// + /// Log user out of Weibo. + /// + [Obsolete("Logout is deprecated, please use LogoutAsync instead.", true)] + public void Logout() + { + Provider.Logout(); + } + /// /// Log user out of Weibo. /// diff --git a/Microsoft.Toolkit.Uwp.SampleApp/Microsoft.Toolkit.Uwp.SampleApp.csproj b/Microsoft.Toolkit.Uwp.SampleApp/Microsoft.Toolkit.Uwp.SampleApp.csproj index 18981c05b81..51c0fd428b1 100644 --- a/Microsoft.Toolkit.Uwp.SampleApp/Microsoft.Toolkit.Uwp.SampleApp.csproj +++ b/Microsoft.Toolkit.Uwp.SampleApp/Microsoft.Toolkit.Uwp.SampleApp.csproj @@ -280,6 +280,7 @@ + @@ -316,6 +317,7 @@ + @@ -324,6 +326,7 @@ + @@ -364,6 +367,8 @@ + + @@ -423,6 +428,8 @@ + + Designer @@ -504,6 +511,9 @@ + + Designer + @@ -645,6 +655,9 @@ + + TabViewPage.xaml + UniformGridPage.xaml @@ -853,6 +866,9 @@ + + LightBehaviorPage.xaml + LinkedInPage.xaml @@ -904,6 +920,9 @@ FadeBehaviorPage.xaml + + HeaderedTextBlockPage.xaml + ImageExPage.xaml @@ -1036,6 +1055,10 @@ Designer MSBuild:Compile + + MSBuild:Compile + Designer + MSBuild:Compile Designer @@ -1328,6 +1351,10 @@ MSBuild:Compile Designer + + Designer + MSBuild:Compile + Designer MSBuild:Compile @@ -1396,6 +1423,10 @@ MSBuild:Compile Designer + + Designer + MSBuild:Compile + MSBuild:Compile Designer diff --git a/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/HeaderedTextBlock/HeaderedTextBlock.png b/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/HeaderedTextBlock/HeaderedTextBlock.png new file mode 100644 index 0000000000000000000000000000000000000000..4b495ab237ab617b406082956fa4df6764313695 GIT binary patch literal 3308 zcmeH~**6=C7RE!&Lrtfps8CaTYN&%!Eipt@kydpu#nhIVrIa2af{KI|HIy1^o|V#| z#&Ag0P%+Q5LyRdWGMKM*|AV{MeYg*Iul3t|KYb71!?)MQJ%F0<^N8~R004f|yO4(f z0E_h5-p0jo)(gJ0)}0MDe?!wpTxS)+^&ATT@a3CA3?9AC+@AHtO42TM?bZ_&pUMlc zio3@h=)1=x=nuF=iKt-~BC$pCwht0Cu3TMhXMf{l43ZNQdh@pd#4?YK4<7#{Qs%v& zNJ02z+c(#+inVx`d(WiV)qW1^G5f#xfBlMVN}WBVc+aU?)91JPe1ih@s2RWdDE7b# zlSE;AJNxYW-cbeRgJqx>_4-4jy@15z@Bekg?02C>h|5THccD*?22UmI zbwaK!FtbuOkn44;^MrC+yHxb07glX%DTmXso0g#K@Er)LZ}tm=>AKK_ z_H^FFNs$+~FeR`+fi;hS+{J1mv$>dEhK3*Li%OHsMxWHc&@J9AHrLq0X!JNWUA->Q znfdlMQvc@MHI?3WDt^4UUz4w%B~1e5uL1q8hE_l~;aq%e>^`#VvFhc7%Ghwv7*r@L zhJQ{WpJT}ef>=`}iZr>sIHZ&uz`T;|sI{TiD4t&y#`F9{8n83{k`t!HGi2`iI~ruL zRg!dd;dp7c2%&21x2Nuyt`}+5ww!`E#?htvT^FoVZzQ)jB&~d974ys&)1S5pu?!S` z%`#z)41pmg*|#|6DsKk~Y$M0Zyu+<0vYk;}7A(I4)B1B}d2c^OdU+yBIe)}qoVA8d zJ}MQW$h|C61fT?tbfn~WYdf2YO7F0i^~&_n7ip7FYR+ka?6DnGr;JUB=8f-pWiNjd zrsW%?C_MTm_dDWw(Y>D!pG|{WJ>wWC!g0SyWJ^%ev)z&u|Ji8bt;{@ySKC9To=D; z@%-I41W!)12g42isvBIQTmOEI2*_kR;j-sEN-qI7-z^_|Sf&CA(lifK6|(nqcl4_q z`M7lOyu<%R)03fAr~XF{>L&e^6Jo#UnQl2_Cr~|1BtSDu0Iih$^+f&hoobE|())qO zw5Y0z_W>_~gQMA(Zxjcw4^}8S4C8Mi(yVzM6?j_BVE*npZM%o^K1NCZmYOsp|Ln1G zS;>9jkr}KXTaCFQ_q4-JqPk(|Q6${g~997JC(|!K8$_xdb*_Is>f~;#MYl*`^Cc9MbV`~SC z-@pvfdNMkD#KB*E_r)i+Pg{R{F0#G1@5wM-7(|KdNoN^S#Slkk#|hzuHEH{-YM>3x zoJq{Cx12{%3-b*4EvaiW~NYHy5BTs3#r1985Qm_qZU;sKUG7O$%Z)z9*r9we7n?X&+|VY5M3&6T_3O z@aNVtu24X5F}ffoUU(8H4z>tO8SHjFVk!P-0@xU{P291pu{^qp!H`Zn2Jc=+e<%4Y zxb>ZavuqZumUhBP@NJ94hc>{(WTJRN!EKI&0*(KaWpRBwEI|R5G@Bqlu;&m2P=p_= z;x!WQ$eYII z%;A?ogJ>E@oQUG$EkiG!<{y4W@EC3We|VOTcH;$|k?H%qlnV>W3%xFB-b5;A8iCU@ z4^A~BBCpK%8W9UYB!CtRp~~FXF?wBB3%&kI-R3PrWl55f5l#@yHoYj78cW_Fqvj30 zd|LQIfL_x5hfo9(xU&nGE|d9D$r5Td66OGyV}}Yk2L$Rb6Jo8KJ`{saB$;u{2oNWf zem!ox4@~@NsDO6EMXj#%EW3qggwG4*a{1xt2jlDuVwP`Ir-Q&>QmjkLG?T%@Vb%NB zR>#MC)@O~%eXO8WsMQgoc~yoYZr?K71i%?C5ZiTMIAh~JAWU9wSrKO#v_JV%NXBt) z?2m+5U<8v@7rF#rW7puRNiBjbfvd1Ncf&Cx%!?y)@5;XaCA5%MRl_f6MWx0 z7l1)IM*wVlH{(!}6fYr_A$=o~PM_*Yf%^ zdFvyF%(3`&=|D^Ooa4NCGr>76UjtNm-Bfb_7JS8_*ctlEF0Yk-rDgQ62p2(%?sRC{ z?wf}RSUMGV?Ygr)XP}F);%%sf&82EPtoQ&Qm4V5U3FcE3jOyiW3k?8c;Ov$IDo8Pc z()X6s#qFyNcPk3d@)d06$Ze;=5kjF5w}&b4wQGDT-?*rblDAA!Q5H_rU2r)tzwn1C(#X;J~ZVr({YkD zg6wo?IS;YBKdggVja@ppyo)+9+dMu3G!I>GjqOxfi^o0-hl(_<7hmjWt$Q?_A#1qe z+u01Zc++Enk6|X^pFA8*`JO?f1*}vjbu!#VElniGR!cel7I(2bJu(E|i&BPIR}@mE zG)qOxYmoZ3D2hzX5+=Sfjimr(E`$_}ch!O{%QSI)9>hmYT-8*GGZe^Z3(2r$~0rRsg07MSAnB+zwd#+H=^bD+VhE(_f9!uYP7w2 TO!KSG-WPzWF%(j1=oIlU(~cTl literal 0 HcmV?d00001 diff --git a/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/HeaderedTextBlock/HeaderedTextBlockCode.bind b/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/HeaderedTextBlock/HeaderedTextBlockCode.bind new file mode 100644 index 00000000000..a2db4b8dc80 --- /dev/null +++ b/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/HeaderedTextBlock/HeaderedTextBlockCode.bind @@ -0,0 +1,16 @@ + + + + + + \ No newline at end of file diff --git a/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/HeaderedTextBlock/HeaderedTextBlockPage.xaml b/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/HeaderedTextBlock/HeaderedTextBlockPage.xaml new file mode 100644 index 00000000000..a5795898a0e --- /dev/null +++ b/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/HeaderedTextBlock/HeaderedTextBlockPage.xaml @@ -0,0 +1,9 @@ + + + diff --git a/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/HeaderedTextBlock/HeaderedTextBlockPage.xaml.cs b/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/HeaderedTextBlock/HeaderedTextBlockPage.xaml.cs new file mode 100644 index 00000000000..77cdba59f3e --- /dev/null +++ b/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/HeaderedTextBlock/HeaderedTextBlockPage.xaml.cs @@ -0,0 +1,18 @@ +// 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.Uwp.SampleApp.Models; + +using Windows.UI.Xaml.Navigation; + +namespace Microsoft.Toolkit.Uwp.SampleApp.SamplePages +{ + public sealed partial class HeaderedTextBlockPage + { + public HeaderedTextBlockPage() + { + InitializeComponent(); + } + } +} \ No newline at end of file diff --git a/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/Light/LightBehavior.png b/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/Light/LightBehavior.png new file mode 100644 index 0000000000000000000000000000000000000000..b2235f1b9e4577a48af0b24d219f8485b2a71878 GIT binary patch literal 6164 zcmZ9QcQl*r8^@DaF`JayBSuRrwEEhs5hB)W#0)}PlvZh#m{qg(Y|$DaX4EXAX3=4k zB34qfLlIJ=W?R+%^8WjK{&=3}+~?f)ea@|sr>nD zBb@pSO=Xcoc^X?Jv2w9hyZp#udlnKkApBQS0A-^=-~O2Sw*!Ah07a;dI>8@tDWqsS zvO-WcZOMQ#g$?zS%|vSs`r1H8)S{*az6DI&3zEMrXUdSy;e!JA-Sl`>p*MZypSqm` z(Q&_S&|w87QrGrDP{hajHkQH3UkjWS7X^U(k)5Zt34xpJtcw#uDS%&mo3Ue$uTnXA zU(?Nv0D3OC=pP9fKiaE3`HKeo2qf$|BB=nPYi$4q_~=1jzs>C|UXn@j z7X$|N)`OI|zG7_d@a4*UP99o%#{n5Uc(p5R?%?v(UG+?{z!QaIKt zfv17X#EbyAT16NpW-PaFSUY#<1CJfJj2i{OkqEG##vyj+yUH#N$0kAM;2@vg;23WW zVog{L$B0icIIRp;CidTQV=&@62l4WEiJvlCY{}@DY#>yc%)DHG5txI!J6Bn~Y;aA0 zxfWCoCO*J-NnWhtx0wBrBy%?ROlBV#X!Dl0NzPXgI9;o_Xuv;krsl?^6JZU(D5wwLPs$++zphG(nR4~VxmV711D zyz*|dt;tAz@VYYH@Y?kQ7vi%{X=F9?sHzM1J6{DlhULA>4Y-2!j%%-kS5Sn_Aix}& z@{e!+=;IK@jR({RtadgKC{idDFuZQ|!3vsnnhqIGaaMWHcCNvADB-yA~-AD$L0ItVa)BL%4~9p7JGrJ&-;bOa<)X zIqrA%O`X_0FmjOGUp`PsdvN~9LCkp)iex%(#_pljxpUmb@3A)C=M(bWCRkRly@gSp z<=u3X@=5|U=i<2}uC4=&g_6nwJbrv!$1cn5IQc|XJ| z3&C@~7&s9X4%LNt!E7I9okf~JfO3j11+KhOih`rI0Y)PA(Lc$if##Qnqv;pG-*puf zKne}Gop-U`AvbBj@}%YbWu*_HR|{4Q7-nFb$6(@>+}XQU(N=O%6-tv=YVXm-@p@N= z4=!-S9dkWcr{zEjRo{%alykS@As3V~S@W&BLJmD*OuO=^tzyV{9%i_fq~ysEb_~pQ zZoc>bgg3p=RuMdX@V==)tYDC*^5hm6YTb(hoVZ&hTG3k7Kk|JF4GcX?yTEHv%z_;A z1(z}{bg+Kw$G7OLDz2iei+p2E#Zv|ivfhRSNLj2*^dA!6QQYeIYk6+DAj^s?yWOoV z#T`~7nbAYB?5DfN#Pldss+Euc1W4}PAAT**Qbc!kc0Ysk5m9TC4)pQR`{Z-ND3XkK zN>NJ%)YNk8pZVnDix3yQCuKi<{sex6QRY;}6f0y=JG5a&F*4CXD8g8{`$H>7v1WZw zoV_&1n4~!QzT|zGvsjM!GG)3Gfe$t<9kj9$ZB?Pi zT&*4W5-HE3(AgnV$UjP8{fDX%wYNn0s{5T6Uo%YmvJ;P)X!yHET|n{}v|NdA!{U z*`%g9@kJaqKmr$W=tfG88{JhTE7YU8vM|@&gFz8fTv zi7lIQQrGie7slk5t0;`RGVAXP69h!uN_bE`btX|Ua8*wEvzDWI}1}&=7A_*mjj>{GFv%cGJH(S z)7z+C2IsyoO?=a@TBhV){a%>Y)1!%Bbtd@_+e^~8Q*r{(eas!3cIfro^^w`B(nf2Y4zFMpK5bQ(#0nU=?s0?r}BqbeBAUx9>5hlKd zb^({;ssE(|ec+oyT<|OTjaah!ztq8Xx8C5qIKbdAKO2V12E9zQ1X{oC_uLEXC~H8A zi9O8ZC^lKQKi_YyQIUS~z9fcHjuQ|YlVG!r#1Qj=5F+|DP(J<4a!3|G<{n98++~#Px725>;;kjUy$iNQ5O0G*I zrBxI7L^eg)ChOoub*||3#x9{9Sm_qW?4_h*>@wMjRr%4ICL+^b_Z)KaZC%=)1Zpl* z@#)f;dtX#b8w(psHXRwP9At)2`f9o+p@VnULV*m?s90&4gPNWlB}oLB6-;3R8%hdH z8*cEtgg@FfS{PO4*t_K&1d5jP(#To!S&f)J38f{mOwQ@iiM}(}b?Xt>af4OiO`DO# z;)75cLzMcoiCiBkIV2OVc3Y0X1+z+0qz`#(@r?kzKJ&9#u^~_2m*enQF&Ya?fGqdd zBd_M#hx9lmdn2^M#8fcBrD98rmQ|xq9zcsO+8^vP)%Rxo#9D=()OyFwpIN^9N0u?+ zI?)s%qr-=Bn(=AJ*zMMTl@5G8s1GjoYBZi@b!IF~{>oaD))Qh4nk;zfTVKyOuuVVK zZeneD2w)Kr8X^o^Rq`|NqC$Jh(j`!PD+i)KD}ch{{kc$It@(LoVb($_u|h9X+i zGrqda*Zs~rZrZZ(mK|;7MlR*Q&)jVvJEOIS9W*`K3pJJ0Bsb0NJ>QM+4STu7L$sV1 z#s7mTY-_5&#}Q!UhP^&2zp}I1BoLFS8cY~Dp1!4UWTLNx7Brd=wrgaaDFg?8=jw zo)*@^LE_n`j{ev!FS+@F$xLmrM)AD?W|uR0!jUo*Dj?`qsG7JE4*9Wwi$%dy<8?if z3A9}mvbZ|IhaK}FM+f$w825Zgw<+IP7EYY{x?uDkj&(yxe-9Qz!alARYna-Fd zG%k)DH~{c9yXKil`Q0)!?$LBF()b>Rs#^_D^crQ1Vq?w6E>`TAiK&K>Hqv zXRoPtj-1-=x{Ucf=dO3Rc)q^peMtP}g2C;gHQ#+>4zhV}NH=cV`H>4!{84Ka7@XZI z{j(KcYQ1=%b8Wp9D-Wcn2pPRap)@+%X1fDCD_@38;DiK(zN>WC2dc_ow>_Q(f7&Jfe z$v=%{!AweTalG@YXTwDd_?7wY4A`;}c!;>|yR#~bFC_6-vco3uMuT3i)^c}V{2f+7 zBey&OD1oLe9h$Y0Y*ru-FFTIuuN16`TcLWJ;8dl2w|)-n9VmAeu|rUx-Bj+yON|Q%b3w%i{*BQH(ehUu4~ui82rjAXrwtxYzXNPM zAk}3U;0%H#M0sTPwv=CKOHWTktvr>L4!#`SU_GeQ@kHu;#O1^;2lrN1Lt1RZOHcuQ zJnq)?I-qlxq7p8cb6%x8-6Pis{8olT4L$w=S=d-kz4hwqOeA{PHBNsLAPM zh#je6!>(U2KS7Prh6(7~gRz0(k=Y*|`&-MjL1yy6YI8?|7%>b=y7me^{A~0wR_u|x zix4y7{8c6;(`Qu7u^)gMQup_n-c83eA1d zu6QqBO4fR2u3P`qUCe!yV^{ScBb|@F;qLaL+ff=Rdt|;6^iGwUGZJ=!R9I*b!(ENXdh^ddO+vY(v5NP<_xsxo!m=v zIvN=E(knW?@LvkJh%ve2y~)+bK{Uo%O2YGBMD&MNhEU5uW27Pd$sD%4xc(Kdlk+u>Ie>$r2sP?;S z7PG%&=esAl0uTyG-D=LXB(Ny@4!gC0sLrb4=Via(3c1JPX==eUkIkNIja}&N{+V@u zNwiY4-jC$Q*`mP$&V7+x+0!wqI7n7+WNk51s+HRvt`95P*VX>q(%{~i**B;-qH1D# zH0in|n%(4pO+apILJ{p3yo67ezhW8n2EM)L;Kq4w5nbV@Iqv6l_DPOVejCQQ>GvG5 zjRpMB_=RiB!sF=#&C=Up>hYTEq5)>kJz;LPQ?%0sb)kWB+0!g$;xl8?ycHdIXc%KmWkd;B!sCIbw^CfTkJ2?ZEXkAerL zy@6=k{s1hpD3(=nJ;1MJ?xm(LVPf*VEwlBm+X3*ot`0oT8CXj+$h0CdFZk1K2~4BM zKPk7v$Jy%rOu9lgwG{o*^MapvHcoH?Htt?>Sh}TEl`ewpcFe2yfFA8-vzKXkCtC0L zWSWGnJ^cz~t`5^@tiu@}W4%0cxoyP!UMggVY)g|P3g1U~AE~!KX^~Nk64ZAd zYf5uzq~WETuty#%k!nuXgiq0`c+GM?p$aWorC3G?-^3R+11)17E1U>PO0+^pdz>@{Q-ag zg7HdXg**-V#o}J0#dP9zF)pjNwOFcwdYPq=XMZW8c^4MwtJSS;w507`YYUjJR0vh2 z!^Y5zam3K@KY2}z*6B>HhmbGE%{vCAq*3X5l=BmsF(-3?$$1g*Qd5yy$jx>dByGwZ z0W6B-D)tK+p-hv2?^7m6Fs$0}ULGhyucjS#O4SA&7MS*Z?c9d|zKYS-7g)##wvr@9 za%V|(2K%Hbphi=qQMaH`_EPgN%2SmfAD_qJgpRc%D6!9%^Yh%>PqM z5?J*Z5L8*_Rdk@4<9b@R`;o36ZFW%bdO>y!#D1%fE z^Y||$JBZ(0C9Ug(-*Toy;1MM>i!mcYN{AI?} zg82YveORTg$J=X|pQ=2w)A<++QK=Yf0e#$7Vu2Ch(F#et04UgQ z89VRnALfgs)+|N@N|GJ}1(YgfmM0d9=aUa3=cvD1;PRf4-`wpzTgo!LdyJ*y0cQMM zbv{cLzZIELH$X9nX(w7Jt0taxcp9j<^x>;FBCx$gDAfzaVrItBDx zAI4a)j0p?hcOYHQ4GjEyX>0I9i^^rMk%g^XABA{whf_FC0W>6tF~cK$c; zcNKKEjOT zcF9^lKld{yTej-TEM*zuUBeB-?T)E{=7+`out=LB6N*7meva&;-6_?>1Q%(EFr5K(;&Tn^F$)0hz9c@KZVerF){0nW#B zZMV>zH~MdkwEI&$qmvJ%ltADqK5%+WhqQnN!|A3(i?xvF%20K4e)b2&5yjUe1rXR# zBc6O{l3&&8-2)Frt*_Ke>r{ct>7f;;)=BI@0sDQ3u!u{5N%r^RPP0anr3~FJ*i@Jx z>by+!mtB}=zY}C)PWEo|T;=cTtq;}&jv2=eN!5g3N+1OiSA1PhQepCo@kr;ti^F+p zj_qQvz2m^d?qH-3IMVHSZmpwa=<^<*y+#npux6i$*-%VZOE6s>)%M0$8u3ckC)!osjU4IMna2Ks=$;Y+lC22-bpC&e+DV;~|LQDIGTMFM zY$<@bT7`Iah*Y}aby}#oW0dm?meXB*Xf_mJ)3HJ>^(`7OwH{i0SC}ASxT9;qO2MbJ zms3>qztX`j=6VVFA_$z}rn&)vA%YO&$8cYP+J$ErBCZ27-DZt_f|YmX2Rd2%u~ z&QIPmPa`Z>$AXCwxxfisW$nYQW^-Hoo;!BTCS4}FxRm~rqJ3B{5v4og@eoj%XN(C-A=5OmYL5ftGX@4;7U^7t?6@2 zmR;IqR%>a3;{+ksn7lf&qyb*vb5k~I%J~_Sh4#37{H<3~11=Iu0)#(Vy0=r8ObN>e z6OYX1>plX`#jZl2h_&^bB}q_qj#NO|0F1S|aG$owO;p%$g(3t{i&a*fhm9_d1~${# azb+0$RdwfQyenj4et;>;(x?G>HTHj)(0CpI literal 0 HcmV?d00001 diff --git a/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/Light/LightBehaviorCode.bind b/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/Light/LightBehaviorCode.bind new file mode 100644 index 00000000000..4019e9a2ce7 --- /dev/null +++ b/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/Light/LightBehaviorCode.bind @@ -0,0 +1,8 @@ +// XAML UIElement + + +// C# - Light can be applied to any UIElement. In this case it is an image called ToolkitLogo. +using Microsoft.Toolkit.Uwp.UI.Animations; + +var lightAnimationSet = ToolkitLogo.Light(distance: 10, duration: 500, delay: 0, color: Colors.Red); +await lightAnimationSet.StartAsync(); \ No newline at end of file diff --git a/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/Light/LightBehaviorPage.xaml b/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/Light/LightBehaviorPage.xaml new file mode 100644 index 00000000000..8eba95bedbd --- /dev/null +++ b/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/Light/LightBehaviorPage.xaml @@ -0,0 +1,39 @@ + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/Light/LightBehaviorPage.xaml.cs b/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/Light/LightBehaviorPage.xaml.cs new file mode 100644 index 00000000000..fd59ec7d5be --- /dev/null +++ b/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/Light/LightBehaviorPage.xaml.cs @@ -0,0 +1,52 @@ +// 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.Linq; +using Microsoft.Toolkit.Uwp.UI.Animations; +using Microsoft.Toolkit.Uwp.UI.Animations.Behaviors; +using Microsoft.Toolkit.Uwp.UI.Extensions; +using Microsoft.Xaml.Interactivity; +using Windows.UI.Xaml; + +namespace Microsoft.Toolkit.Uwp.SampleApp.SamplePages +{ + /// + /// A page that shows how to use the light behavior. + /// + public sealed partial class LightBehaviorPage : IXamlRenderListener + { +#pragma warning disable CS0618 // Type or member is obsolete + private Light _lightBehavior; +#pragma warning restore CS0618 // Type or member is obsolete + + /// + /// Initializes a new instance of the class. + /// + public LightBehaviorPage() + { + this.InitializeComponent(); + + if (!AnimationExtensions.IsLightingSupported) + { + WarningText.Visibility = Visibility.Visible; + } + + SampleController.Current.RegisterNewCommand("Apply", (s, e) => + { + _lightBehavior?.StartAnimation(); + }); + } + + public void OnXamlRendered(FrameworkElement control) + { + if (control.FindChildByName("EffectElement") is FrameworkElement element) + { + var behaviors = Interaction.GetBehaviors(element); +#pragma warning disable CS0618 // Type or member is obsolete + _lightBehavior = behaviors.FirstOrDefault(item => item is Light) as Light; +#pragma warning restore CS0618 // Type or member is obsolete + } + } + } +} \ No newline at end of file diff --git a/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/Light/LightBehaviorXaml.bind b/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/Light/LightBehaviorXaml.bind new file mode 100644 index 00000000000..2ca51bf1cfc --- /dev/null +++ b/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/Light/LightBehaviorXaml.bind @@ -0,0 +1,25 @@ + + + + + + + + + + \ No newline at end of file diff --git a/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/TabView/TabView.png b/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/TabView/TabView.png new file mode 100644 index 0000000000000000000000000000000000000000..9aeebd1d673de8670b59cb522700c93f1c75b887 GIT binary patch literal 1392 zcmeAS@N?(olHy`uVBq!ia0y~yU{nCI4{)#n$t4^1Z39v)>5jgR3=A9lx&I`x0{NT; z9+AZi3_@Vc*rT+Q9VjSK;u=vBoS#-wo>-L1P>`6JSE84fTB2a0XQ*fNOZn_apgHe6 zT^vIy;@;jkn3<9;(sHr$a7C&>Me2+jB8``VnSxjjMP@u{{Ps+A*_%VZT#Kdr^5tW8 z?PLG@_VH)g2NUMU?`QbaU-$RJ?e_cje?B@kH25n!957^Jks+S?oXJ@AJU_4Ksap8< z*>{W?tq%CEyYu8#+2m?92lgE&r_PSKn^$z6vX!n@AaI&`f&Rut(%&YBXu(h{8E`O?i>b#Gwa0B+aHTQB0Y2Yh>nZ-My5VNg$EMEQ9q9}FxG6!zhBS5Tj$4m^~aMNz|xVy)78&qol`;+ E0K}H&HUIzs literal 0 HcmV?d00001 diff --git a/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/TabView/TabViewPage.xaml b/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/TabView/TabViewPage.xaml new file mode 100644 index 00000000000..32f465f2c64 --- /dev/null +++ b/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/TabView/TabViewPage.xaml @@ -0,0 +1,19 @@ + + + + + + + diff --git a/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/TabView/TabViewPage.xaml.cs b/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/TabView/TabViewPage.xaml.cs new file mode 100644 index 00000000000..74d9a3db253 --- /dev/null +++ b/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/TabView/TabViewPage.xaml.cs @@ -0,0 +1,73 @@ +// 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.ObjectModel; +using Microsoft.Toolkit.Uwp.UI.Controls; +using Microsoft.Toolkit.Uwp.UI.Extensions; +using Windows.UI.Popups; +using Windows.UI.Xaml; +using Windows.UI.Xaml.Controls; + +namespace Microsoft.Toolkit.Uwp.SampleApp.SamplePages +{ + public sealed partial class TabViewPage : Page, IXamlRenderListener + { + #pragma warning disable CS0618 // Type or member is obsolete + private TabView _tabs; + + private int _counter = 1; + + public TabViewPage() + { + this.InitializeComponent(); + } + + public void OnXamlRendered(FrameworkElement control) + { + _tabs = control.FindChildByName("Tabs") as TabView; + if (_tabs != null) + { + _tabs.TabDraggedOutside += Tabs_TabDraggedOutside; + _tabs.TabClosing += Tabs_TabClosing; + } + + var btn = control.FindDescendantByName("AddTabButtonUpper") as Button; + if (btn != null) + { + btn.Click += AddUpperTabClick; + } + } + + private void AddUpperTabClick(object sender, RoutedEventArgs e) + { + _tabs.Items.Add(new TabViewItem() + { + Header = "Untitled " + _counter, + Icon = new SymbolIcon(Symbol.Document), + Content = "This is new tab #" + _counter++ + "." + }); + } + + private void Tabs_TabClosing(object sender, TabClosingEventArgs e) + { + TabViewNotification.Show("You're closing the '" + e.Tab.Header + "' tab.", 2000); + } + + private void Tabs_TabDraggedOutside(object sender, TabDraggedOutsideEventArgs e) + { + // The sample app let's you drag items from a static TabView with TabViewItem's pre-defined. + // In the case of data bound scenarios e.Item should be your data item, and e.Tab should always be the TabViewItem. + var str = e.Item.ToString(); + + if (e.Tab != null) + { + str = e.Tab.Header.ToString(); + } + + TabViewNotification.Show("Tore Tab '" + str + "' Outside of TabView.", 2000); + } + #pragma warning restore CS0618 // Type or member is obsolete + } +} diff --git a/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/TabView/TabViewXaml.bind b/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/TabView/TabViewXaml.bind new file mode 100644 index 00000000000..e81732aa2a3 --- /dev/null +++ b/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/TabView/TabViewXaml.bind @@ -0,0 +1,97 @@ + + + + + + + + + + 40 + 48 + 200 + + + + + + + + + + + The TabView control has multiple uses. + + + It has a lot of versitility out of the box for different scenarios. + + + You can enable drag-and-drop and reorder the tabs too. + + + This tab isn't closable because its IsClosable property is set to False, even when CanCloseTabs is True. + + + + + + + + + + + + + + + + + diff --git a/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/samples.json b/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/samples.json index 7b4d6b3a1c6..f106b270277 100644 --- a/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/samples.json +++ b/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/samples.json @@ -14,6 +14,18 @@ "Icon": "/SamplePages/TextToolbar/TextToolbar.png", "DocumentationUrl": "https://raw.githubusercontent.com/MicrosoftDocs/WindowsCommunityToolkitDocs/master/docs/controls/TextToolbar.md" }, + { + "Name": "TabView", + "Type": "TabViewPage", + "Subcategory": "Layout", + "BadgeUpdateVersionRequired": "DEPRECATED", + "DeprecatedWarning": "Please migrate to the TabView control from WinUI, this control will be removed in a future release. https://aka.ms/winui", + "About": "A control for displaying multiple items in the same space and allows a user to easily switch between them.", + "CodeUrl": "https://github.com/windows-toolkit/WindowsCommunityToolkit/tree/master/Microsoft.Toolkit.Uwp.UI.Controls/TabView", + "XamlCodeFile": "TabViewXaml.bind", + "Icon": "/SamplePages/TabView/TabView.png", + "DocumentationUrl": "https://raw.githubusercontent.com/MicrosoftDocs/WindowsCommunityToolkitDocs/master/docs/controls/TabView.md" + }, { "Name": "DataGrid", "Type": "DataGridPage", @@ -74,6 +86,18 @@ "Icon": "/SamplePages/ImageEx/ImageEx.png", "DocumentationUrl": "https://raw.githubusercontent.com/MicrosoftDocs/WindowsCommunityToolkitDocs/master/docs/controls/ImageEx.md" }, + { + "Name": "HeaderedTextBlock", + "Type": "HeaderedTextBlockPage", + "Subcategory": "Layout", + "About": "The HeaderedTextBlock control is designed to provide a header for read only text. This control is useful for displaying read only forms.", + "CodeUrl": "https://github.com/windows-toolkit/WindowsCommunityToolkit/tree/master/Microsoft.Toolkit.Uwp.UI.Controls/HeaderedTextBlock", + "XamlCodeFile": "HeaderedTextBlockCode.bind", + "Icon": "/SamplePages/HeaderedTextBlock/HeaderedTextBlock.png", + "DocumentationUrl": "https://raw.githubusercontent.com/MicrosoftDocs/WindowsCommunityToolkitDocs/master/docs/controls/HeaderedTextBlock.md", + "BadgeUpdateVersionRequired": "DEPRECATED", + "DeprecatedWarning": "The HeaderedTextBlock has been replaced with the HeaderedContentControl and will be removed in a future major release." + }, { "Name": "MasterDetailsView", "Type": "MasterDetailsViewPage", @@ -508,6 +532,19 @@ "BadgeUpdateVersionRequired": "Anniversary Update required", "DocumentationUrl": "https://raw.githubusercontent.com/MicrosoftDocs/WindowsCommunityToolkitDocs/master/docs/animations/Saturation.md" }, + { + "Name": "Light", + "Type": "LightBehaviorPage", + "Subcategory": "Behavior", + "About": "The Light effect will be removed in a future major release", + "BadgeUpdateVersionRequired": "DEPRECATED", + "DeprecatedWarning": "The Light effect will be removed in a future major release", + "CodeUrl": "https://github.com/windows-toolkit/WindowsCommunityToolkit/tree/master/Microsoft.Toolkit.Uwp.UI.Animations/Behaviors", + "CodeFile": "LightBehaviorCode.bind", + "XamlCodeFile": "LightBehaviorXaml.bind", + "Icon": "/SamplePages/Light/LightBehavior.png", + "DocumentationUrl": "https://raw.githubusercontent.com/MicrosoftDocs/WindowsCommunityToolkitDocs/master/docs/animations/Light.md" + }, { "Name": "FadeHeader", "Type": "FadeHeaderBehaviorPage", diff --git a/Microsoft.Toolkit.Uwp.UI.Animations/Behaviors/Light.cs b/Microsoft.Toolkit.Uwp.UI.Animations/Behaviors/Light.cs new file mode 100644 index 00000000000..8940f3eb630 --- /dev/null +++ b/Microsoft.Toolkit.Uwp.UI.Animations/Behaviors/Light.cs @@ -0,0 +1,69 @@ +// 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 Windows.UI; +using Windows.UI.Xaml; +using Windows.UI.Xaml.Media; + +namespace Microsoft.Toolkit.Uwp.UI.Animations.Behaviors +{ + /// + /// Applies a basic point light to a UIElement. You control the intensity by setting the distance of the light. + /// + /// + /// + [Obsolete("The Light effect will be removed in a future major release. Please use XamlLight instead")] + + public class Light : CompositionBehaviorBase + { + /// + /// The Blur value of the associated object + /// + public static readonly DependencyProperty DistanceProperty = DependencyProperty.Register(nameof(Distance), typeof(double), typeof(Light), new PropertyMetadata(0d, PropertyChangedCallback)); + + /// + /// The Color of the spotlight no the associated object. + /// + public static readonly DependencyProperty ColorProperty = DependencyProperty.Register("Color", typeof(Brush), typeof(Light), new PropertyMetadata(new SolidColorBrush(Colors.White))); + + /// + /// Gets or sets the Blur. + /// + /// + /// The Blur. + /// + public double Distance + { + get { return (double)GetValue(DistanceProperty); } + set { SetValue(DistanceProperty, value); } + } + + /// + /// Gets or sets the color of the spotlight. + /// + public Brush Color + { + get { return (Brush)GetValue(ColorProperty); } + set { SetValue(ColorProperty, value); } + } + + /// + /// Starts the animation. + /// + public override void StartAnimation() + { + if (AnimationExtensions.IsLightingSupported) + { + AssociatedObject?.Light( + duration: Duration, + delay: Delay, + easingType: EasingType, + easingMode: EasingMode, + distance: (float)Distance, + color: ((SolidColorBrush)Color).Color)?.Start(); + } + } + } +} diff --git a/Microsoft.Toolkit.Uwp.UI.Animations/ConnectedAnimations/Connected.cs b/Microsoft.Toolkit.Uwp.UI.Animations/ConnectedAnimations/Connected.cs index 999ea1d40c4..a0e5afa756d 100644 --- a/Microsoft.Toolkit.Uwp.UI.Animations/ConnectedAnimations/Connected.cs +++ b/Microsoft.Toolkit.Uwp.UI.Animations/ConnectedAnimations/Connected.cs @@ -361,6 +361,22 @@ public static void UnregisterListItemForConnectedAnimation(this Page page, ListV } } + /// + /// Sets the object that will be used during next Frame navigation for + /// Connected Animation involving a list control (item must be an element of + /// ListViewBase.ItemsSource collection). + /// Useful if the parameter used during page navigation is different from the + /// data item in the list control. Also useful during back navigation if the + /// item navigating back to is different from the item that was navigated from. + /// + /// The Frame handling the navigation + /// The data item from a list control to be animated during next frame navigation + [Obsolete("Method is replaced by SetListDataItemForNextConnectedAnimation")] + public static void SetListDataItemForNextConnectedAnnimation(this Frame frame, object item) + { + GetConnectedAnimationHelper(frame)?.SetParameterForNextFrameNavigation(item); + } + /// /// Sets the object that will be used during next Frame navigation for /// Connected Animation involving a list control (item must be an element of diff --git a/Microsoft.Toolkit.Uwp.UI.Animations/Extensions/AnimationExtensions.Light.cs b/Microsoft.Toolkit.Uwp.UI.Animations/Extensions/AnimationExtensions.Light.cs index 3bc0da11259..0949dd235dd 100644 --- a/Microsoft.Toolkit.Uwp.UI.Animations/Extensions/AnimationExtensions.Light.cs +++ b/Microsoft.Toolkit.Uwp.UI.Animations/Extensions/AnimationExtensions.Light.cs @@ -3,6 +3,16 @@ // See the LICENSE file in the project root for more information. using System; +using System.Collections.Generic; +using Microsoft.Graphics.Canvas; +using Microsoft.Graphics.Canvas.Effects; +using Microsoft.Toolkit.Uwp.Helpers; +using Windows.UI; +using Windows.UI.Composition; +using Windows.UI.Composition.Effects; +using Windows.UI.Xaml; +using Windows.UI.Xaml.Hosting; +using Windows.UI.Xaml.Media.Animation; namespace Microsoft.Toolkit.Uwp.UI.Animations { @@ -12,12 +22,204 @@ namespace Microsoft.Toolkit.Uwp.UI.Animations public static partial class AnimationExtensions { /// - /// Gets a value indicating whether this instance is lighting supported. Deprecated. Will always return true. + /// Stores all the point lights along with the visuals that they are applied to. + /// This is to stop multiplication of point lights on a single visual. + /// + private static Dictionary pointLights = new Dictionary(); + + /// + /// Gets a value indicating whether this instance is lighting supported. /// /// - /// Deprecated. Will always returns true. + /// true if this instance is lighting supported; otherwise, false. /// - [Obsolete("This method is deprecated and will always return true")] - public static bool IsLightingSupported => true; + public static bool IsLightingSupported + { + get + { + bool lightingSupported = true; + + if (!Windows.Foundation.Metadata.ApiInformation.IsMethodPresent("Windows.UI.Xaml.Hosting.ElementCompositionPreview", "SetElementChildVisual")) + { + lightingSupported = false; + } + + if (!Windows.Foundation.Metadata.ApiInformation.IsTypePresent("Windows.UI.Composition.CompositionSurfaceBrush")) + { + lightingSupported = false; + } + + return lightingSupported; + } + } + + /// + /// Animates a point light and it's distance. + /// + /// The associated object. + /// The value. + /// The duration. + /// The delay. + /// The color of the spotlight. + /// The easing function + /// The easing mode + /// An animation set. + [Obsolete("The Light effect will be removed in a future major release. Please use XamlLight instead")] + public static AnimationSet Light( + this FrameworkElement associatedObject, + double distance = 0d, + double duration = 500d, + double delay = 0d, + Color? color = null, + EasingType easingType = EasingType.Default, + EasingMode easingMode = EasingMode.EaseOut) + { + if (associatedObject == null) + { + return null; + } + + var animationSet = new AnimationSet(associatedObject); + return animationSet.Light(distance, duration, delay, color, easingType, easingMode); + } + + /// + /// Animates a point light and it's distance. + /// + /// The animation set. + /// The distance of the light. + /// The duration in milliseconds. + /// The delay. (ignored if duration == 0) + /// The color of the spotlight. + /// The easing function + /// The easing mode + /// + /// + /// An Animation Set. + /// + [Obsolete("The Light effect will be removed in a future major release. Please use XamlLight instead")] + public static AnimationSet Light( + this AnimationSet animationSet, + double distance = 0d, + double duration = 500d, + double delay = 0d, + Color? color = null, + EasingType easingType = EasingType.Default, + EasingMode easingMode = EasingMode.EaseOut) + { + if (!IsLightingSupported) + { + return null; + } + + if (animationSet == null) + { + return null; + } + + var visual = animationSet.Visual; + var associatedObject = animationSet.Element as FrameworkElement; + + if (associatedObject == null) + { + return animationSet; + } + + var compositor = visual?.Compositor; + if (compositor == null) + { + return null; + } + + var task = new AnimationTask(); + task.AnimationSet = animationSet; + + task.Task = DispatcherHelper.ExecuteOnUIThreadAsync( + () => + { + const string sceneName = "PointLightScene"; + PointLight pointLight; + CompositionDrawingSurface normalMap = null; + + if (!pointLights.ContainsKey(visual)) + { + SurfaceLoader.Initialize(compositor); + normalMap = SurfaceLoader.LoadText(string.Empty, new Windows.Foundation.Size(512, 512), new Graphics.Canvas.Text.CanvasTextFormat(), Colors.Transparent, Colors.Transparent); + } + + if (pointLights.ContainsKey(visual)) + { + pointLight = pointLights[visual]; + } + else + { + pointLight = compositor.CreatePointLight(); + + var normalBrush = compositor.CreateSurfaceBrush(normalMap); + normalBrush.Stretch = CompositionStretch.Fill; + + // check to see if the visual already has a point light applied. + var spriteVisual = ElementCompositionPreview.GetElementChildVisual(associatedObject) as SpriteVisual; + var normalsBrush = spriteVisual?.Brush as CompositionEffectBrush; + + if (normalsBrush == null || normalsBrush.Comment != sceneName) + { + var lightEffect = new CompositeEffect() + { + Mode = CanvasComposite.Add, + Sources = + { + new CompositionEffectSourceParameter("ImageSource"), + new SceneLightingEffect() + { + Name = sceneName, + AmbientAmount = 0, + DiffuseAmount = 0.5f, + SpecularAmount = 0, + NormalMapSource = new CompositionEffectSourceParameter("NormalMap"), + } + } + }; + + var effectFactory = compositor.CreateEffectFactory(lightEffect); + var brush = effectFactory.CreateBrush(); + brush.SetSourceParameter("NormalMap", normalBrush); + + var sprite = compositor.CreateSpriteVisual(); + sprite.Size = visual.Size; + sprite.Brush = brush; + sprite.Comment = sceneName; + + ElementCompositionPreview.SetElementChildVisual(task.AnimationSet.Element, sprite); + + pointLight.CoordinateSpace = visual; + pointLight.Targets.Add(visual); + } + } + + pointLight.Color = color ?? Colors.White; + var delayTime = task.Delay != null ? task.Delay.Value : TimeSpan.FromMilliseconds(delay); + var durationTime = task.Duration != null ? task.Duration.Value : TimeSpan.FromMilliseconds(duration); + + if (durationTime.TotalMilliseconds <= 0) + { + task.AnimationSet.AddEffectDirectPropertyChange(pointLight, (float)distance, nameof(pointLight.Offset)); + } + else + { + var diffuseAnimation = compositor.CreateVector3KeyFrameAnimation(); + diffuseAnimation.InsertKeyFrame(1f, new System.Numerics.Vector3(visual.Size.X / 2, visual.Size.Y / 2, (float)distance), GetCompositionEasingFunction(easingType, compositor, easingMode)); + diffuseAnimation.Duration = durationTime; + diffuseAnimation.DelayTime = delayTime; + + task.AnimationSet.AddCompositionEffectAnimation(pointLight, diffuseAnimation, nameof(pointLight.Offset)); + } + + pointLights[visual] = pointLight; + }, Windows.UI.Core.CoreDispatcherPriority.Normal); + + animationSet.AddAnimationThroughTask(task); + return animationSet; + } } } diff --git a/Microsoft.Toolkit.Uwp.UI.Controls.Design/HeaderedTextBlockMetadata.cs b/Microsoft.Toolkit.Uwp.UI.Controls.Design/HeaderedTextBlockMetadata.cs new file mode 100644 index 00000000000..3cba40e11f1 --- /dev/null +++ b/Microsoft.Toolkit.Uwp.UI.Controls.Design/HeaderedTextBlockMetadata.cs @@ -0,0 +1,49 @@ +// 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.Uwp.UI.Controls.Design.Common; +using Microsoft.Windows.Design; +using Microsoft.Windows.Design.Features; +using Microsoft.Windows.Design.Metadata; +using Microsoft.Windows.Design.Model; +using Microsoft.Windows.Design.PropertyEditing; +using System.ComponentModel; + +namespace Microsoft.Toolkit.Uwp.UI.Controls.Design +{ + internal class HeaderedTextBlockDefaults : DefaultInitializer + { + public override void InitializeDefaults(ModelItem item) + { + item.Properties[nameof(HeaderedTextBlock.Header)].SetValue(string.Empty); + item.Properties[nameof(HeaderedTextBlock.Text)].SetValue(string.Empty); + } + } + internal class HeaderedTextBlockMetadata : AttributeTableBuilder + { + public HeaderedTextBlockMetadata() + : base() + { + AddCallback(typeof(Microsoft.Toolkit.Uwp.UI.Controls.HeaderedTextBlock), + b => + { + b.AddCustomAttributes(new FeatureAttribute(typeof(HeaderedTextBlockDefaults))); + b.AddCustomAttributes(nameof(HeaderedTextBlock.HeaderTemplate), + new CategoryAttribute(Properties.Resources.CategoryAppearance), + new EditorBrowsableAttribute(EditorBrowsableState.Advanced) + ); + b.AddCustomAttributes(nameof(HeaderedTextBlock.TextStyle), + new CategoryAttribute(Properties.Resources.CategoryAppearance), + new EditorBrowsableAttribute(EditorBrowsableState.Advanced) + ); + b.AddCustomAttributes(nameof(HeaderedTextBlock.Header), new CategoryAttribute(Properties.Resources.CategoryCommon)); + b.AddCustomAttributes(nameof(HeaderedTextBlock.Text), new CategoryAttribute(Properties.Resources.CategoryCommon)); + b.AddCustomAttributes(nameof(HeaderedTextBlock.Orientation), new CategoryAttribute(Properties.Resources.CategoryAppearance)); + b.AddCustomAttributes(nameof(HeaderedTextBlock.HideTextIfEmpty), new CategoryAttribute(Properties.Resources.CategoryAppearance)); + b.AddCustomAttributes(new ToolboxCategoryAttribute(ToolboxCategoryPaths.Toolkit, false)); + } + ); + } + } +} diff --git a/Microsoft.Toolkit.Uwp.UI.Controls.Design/Microsoft.Toolkit.Uwp.UI.Controls.Design.csproj b/Microsoft.Toolkit.Uwp.UI.Controls.Design/Microsoft.Toolkit.Uwp.UI.Controls.Design.csproj index b7b241a6403..b611399dd13 100644 --- a/Microsoft.Toolkit.Uwp.UI.Controls.Design/Microsoft.Toolkit.Uwp.UI.Controls.Design.csproj +++ b/Microsoft.Toolkit.Uwp.UI.Controls.Design/Microsoft.Toolkit.Uwp.UI.Controls.Design.csproj @@ -85,6 +85,7 @@ + @@ -92,6 +93,7 @@ + @@ -103,6 +105,7 @@ + Code diff --git a/Microsoft.Toolkit.Uwp.UI.Controls.Design/TabViewItemMetadata.cs b/Microsoft.Toolkit.Uwp.UI.Controls.Design/TabViewItemMetadata.cs new file mode 100644 index 00000000000..6ed6bc37bf8 --- /dev/null +++ b/Microsoft.Toolkit.Uwp.UI.Controls.Design/TabViewItemMetadata.cs @@ -0,0 +1,43 @@ +// 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.Uwp.UI.Controls.Design.Common; +using Microsoft.Windows.Design; +using Microsoft.Windows.Design.Features; +using Microsoft.Windows.Design.Metadata; +using Microsoft.Windows.Design.Model; +using Microsoft.Windows.Design.PropertyEditing; +using System.ComponentModel; + +namespace Microsoft.Toolkit.Uwp.UI.Controls.Design +{ + + internal class TabViewItemMetadata : AttributeTableBuilder + { + public TabViewItemMetadata() + : base() + { + AddCallback(typeof(TabViewItem), + b => + { + b.AddCustomAttributes(nameof(TabViewItem.Header), + new CategoryAttribute(Properties.Resources.CategoryCommon) + ); + b.AddCustomAttributes(nameof(TabViewItem.HeaderTemplate), + new CategoryAttribute(Properties.Resources.CategoryCommon), + new EditorBrowsableAttribute(EditorBrowsableState.Advanced) + ); + b.AddCustomAttributes(nameof(TabViewItem.IsClosable), + new CategoryAttribute(Properties.Resources.CategoryCommon) + ); + b.AddCustomAttributes(nameof(TabViewItem.Icon), + new CategoryAttribute(Properties.Resources.CategoryCommon) + ); + + b.AddCustomAttributes(new ToolboxCategoryAttribute(ToolboxCategoryPaths.Toolkit, false)); + } + ); + } + } +} \ No newline at end of file diff --git a/Microsoft.Toolkit.Uwp.UI.Controls.Design/TabViewMetadata.cs b/Microsoft.Toolkit.Uwp.UI.Controls.Design/TabViewMetadata.cs new file mode 100644 index 00000000000..7bfa77541db --- /dev/null +++ b/Microsoft.Toolkit.Uwp.UI.Controls.Design/TabViewMetadata.cs @@ -0,0 +1,54 @@ +// 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.Uwp.UI.Controls.Design.Common; +using Microsoft.Windows.Design; +using Microsoft.Windows.Design.Metadata; +using Microsoft.Windows.Design.PropertyEditing; +using System.ComponentModel; + +namespace Microsoft.Toolkit.Uwp.UI.Controls.Design +{ + internal class TabViewMetadata : AttributeTableBuilder + { + public TabViewMetadata() + : base() + { + AddCallback(typeof(Microsoft.Toolkit.Uwp.UI.Controls.TabView), + b => + { + // Layout + b.AddCustomAttributes(nameof(TabView.TabWidthBehavior), new CategoryAttribute(Properties.Resources.CategoryLayout)); + b.AddCustomAttributes(nameof(TabView.SelectedTabWidth), new CategoryAttribute(Properties.Resources.CategoryLayout)); + b.AddCustomAttributes(nameof(TabView.IsCloseButtonOverlay), new CategoryAttribute(Properties.Resources.CategoryLayout)); + + // Interactions + b.AddCustomAttributes(nameof(TabView.CanCloseTabs), new CategoryAttribute(Properties.Resources.CategoryCommon)); + + // Templates + b.AddCustomAttributes(nameof(TabView.ItemHeaderTemplate), + new CategoryAttribute(Properties.Resources.CategoryCommon), + new EditorBrowsableAttribute(EditorBrowsableState.Advanced) + ); + b.AddCustomAttributes(nameof(TabView.TabActionHeader), new CategoryAttribute(Properties.Resources.CategoryCommon)); + b.AddCustomAttributes(nameof(TabView.TabActionHeaderTemplate), + new CategoryAttribute(Properties.Resources.CategoryCommon), + new EditorBrowsableAttribute(EditorBrowsableState.Advanced) + ); + b.AddCustomAttributes(nameof(TabView.TabEndHeader), new CategoryAttribute(Properties.Resources.CategoryCommon)); + b.AddCustomAttributes(nameof(TabView.TabEndHeaderTemplate), + new CategoryAttribute(Properties.Resources.CategoryCommon), + new EditorBrowsableAttribute(EditorBrowsableState.Advanced) + ); + b.AddCustomAttributes(nameof(TabView.TabStartHeader), new CategoryAttribute(Properties.Resources.CategoryCommon)); + b.AddCustomAttributes(nameof(TabView.TabStartHeaderTemplate), + new CategoryAttribute(Properties.Resources.CategoryCommon), + new EditorBrowsableAttribute(EditorBrowsableState.Advanced) + ); + b.AddCustomAttributes(new ToolboxCategoryAttribute(ToolboxCategoryPaths.Toolkit, false)); + } + ); + } + } +} diff --git a/Microsoft.Toolkit.Uwp.UI.Controls/HeaderedTextBlock/HeaderedTextBlock.Properties.cs b/Microsoft.Toolkit.Uwp.UI.Controls/HeaderedTextBlock/HeaderedTextBlock.Properties.cs new file mode 100644 index 00000000000..7528591fc2b --- /dev/null +++ b/Microsoft.Toolkit.Uwp.UI.Controls/HeaderedTextBlock/HeaderedTextBlock.Properties.cs @@ -0,0 +1,172 @@ +// 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; +using Windows.UI.Xaml.Controls; + +namespace Microsoft.Toolkit.Uwp.UI.Controls +{ + /// + /// Defines the properties for the control. + /// + public partial class HeaderedTextBlock + { + /// + /// Defines the dependency property. + /// + public static readonly DependencyProperty HeaderTemplateProperty = DependencyProperty.Register( + nameof(HeaderTemplate), + typeof(DataTemplate), + typeof(HeaderedTextBlock), + new PropertyMetadata(null)); + + /// + /// Defines the dependency property. + /// + public static readonly DependencyProperty TextStyleProperty = DependencyProperty.Register( + nameof(TextStyle), + typeof(Style), + typeof(HeaderedTextBlock), + new PropertyMetadata(null)); + + /// + /// Defines the dependency property. + /// + public static readonly DependencyProperty HeaderProperty = DependencyProperty.Register( + nameof(Header), + typeof(string), + typeof(HeaderedTextBlock), + new PropertyMetadata(null, (d, e) => { ((HeaderedTextBlock)d).UpdateVisibility(); })); + + /// + /// Defines the dependency property. + /// + public static readonly DependencyProperty TextProperty = DependencyProperty.Register( + nameof(Text), + typeof(string), + typeof(HeaderedTextBlock), + new PropertyMetadata(null, (d, e) => { ((HeaderedTextBlock)d).UpdateVisibility(); })); + + /// + /// Defines the dependency property. + /// + public static readonly DependencyProperty OrientationProperty = DependencyProperty.Register( + nameof(Orientation), + typeof(Orientation), + typeof(HeaderedTextBlock), + new PropertyMetadata(Orientation.Vertical, (d, e) => { ((HeaderedTextBlock)d).UpdateForOrientation((Orientation)e.NewValue); })); + + /// + /// Defines the dependency property. + /// + public static readonly DependencyProperty HideTextIfEmptyProperty = DependencyProperty.Register( + nameof(HideTextIfEmpty), + typeof(bool), + typeof(HeaderedTextBlock), + new PropertyMetadata(false, (d, e) => { ((HeaderedTextBlock)d).UpdateVisibility(); })); + + /// + /// Gets or sets the header style. + /// + public DataTemplate HeaderTemplate + { + get + { + return (DataTemplate)GetValue(HeaderTemplateProperty); + } + + set + { + SetValue(HeaderTemplateProperty, value); + } + } + + /// + /// Gets or sets the text style. + /// + public Style TextStyle + { + get + { + return (Style)GetValue(TextStyleProperty); + } + + set + { + SetValue(TextStyleProperty, value); + } + } + + /// + /// Gets or sets the header. + /// + public string Header + { + get + { + return (string)GetValue(HeaderProperty); + } + + set + { + SetValue(HeaderProperty, value); + } + } + + /// + /// Gets or sets the text. + /// + public string Text + { + get + { + return (string)GetValue(TextProperty); + } + + set + { + SetValue(TextProperty, value); + } + } + + /// + /// Gets the collection of inline text elements within a Windows.UI.Xaml.Controls.TextBlock. + /// + /// + /// A collection that holds all inline text elements from the Windows.UI.Xaml.Controls.TextBlock. The default is an empty collection. + public InlineCollectionWrapper Inlines { get; } = new InlineCollectionWrapper(); + + /// + /// Gets or sets the orientation. + /// + public Orientation Orientation + { + get + { + return (Orientation)GetValue(OrientationProperty); + } + + set + { + SetValue(OrientationProperty, value); + } + } + + /// + /// Gets or sets a value indicating whether the Text TextBlock is hidden if its value is empty + /// + public bool HideTextIfEmpty + { + get + { + return (bool)GetValue(HideTextIfEmptyProperty); + } + + set + { + SetValue(HideTextIfEmptyProperty, value); + } + } + } +} \ No newline at end of file diff --git a/Microsoft.Toolkit.Uwp.UI.Controls/HeaderedTextBlock/HeaderedTextBlock.cs b/Microsoft.Toolkit.Uwp.UI.Controls/HeaderedTextBlock/HeaderedTextBlock.cs new file mode 100644 index 00000000000..b30dfea1d3d --- /dev/null +++ b/Microsoft.Toolkit.Uwp.UI.Controls/HeaderedTextBlock/HeaderedTextBlock.cs @@ -0,0 +1,76 @@ +// 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 Windows.UI.Xaml; +using Windows.UI.Xaml.Controls; +using Windows.UI.Xaml.Markup; + +namespace Microsoft.Toolkit.Uwp.UI.Controls +{ + /// + /// Defines a control for providing a header for read-only text. + /// + [TemplatePart(Name = "HeaderContentPresenter", Type = typeof(ContentPresenter))] + [ContentProperty(Name = nameof(Inlines))] + [Obsolete("The HeaderedTextBlock has been replaced with the more generic HeaderedContentControl.")] + public partial class HeaderedTextBlock : Control + { + private ContentPresenter _headerContentPresenter; + private TextBlock _textContent; + + /// + /// Initializes a new instance of the class. + /// + public HeaderedTextBlock() + { + DefaultStyleKey = typeof(HeaderedTextBlock); + } + + /// + /// Called when applying the control template. + /// + protected override void OnApplyTemplate() + { + base.OnApplyTemplate(); + + _headerContentPresenter = GetTemplateChild("HeaderContentPresenter") as ContentPresenter; + _textContent = GetTemplateChild("TextContent") as TextBlock; + + UpdateVisibility(); + Inlines.AddItemsToTextBlock(_textContent); + UpdateForOrientation(this.Orientation); + } + + private void UpdateVisibility() + { + if (_headerContentPresenter != null) + { + _headerContentPresenter.Visibility = _headerContentPresenter.Content == null + ? Visibility.Collapsed + : Visibility.Visible; + } + + if (_textContent != null) + { + _textContent.Visibility = string.IsNullOrWhiteSpace(_textContent.Text) && HideTextIfEmpty + ? Visibility.Collapsed + : Visibility.Visible; + } + } + + private void UpdateForOrientation(Orientation orientationValue) + { + switch (orientationValue) + { + case Orientation.Vertical: + VisualStateManager.GoToState(this, "Vertical", true); + break; + case Orientation.Horizontal: + VisualStateManager.GoToState(this, "Horizontal", true); + break; + } + } + } +} \ No newline at end of file diff --git a/Microsoft.Toolkit.Uwp.UI.Controls/HeaderedTextBlock/HeaderedTextBlock.xaml b/Microsoft.Toolkit.Uwp.UI.Controls/HeaderedTextBlock/HeaderedTextBlock.xaml new file mode 100644 index 00000000000..6f94a07ba82 --- /dev/null +++ b/Microsoft.Toolkit.Uwp.UI.Controls/HeaderedTextBlock/HeaderedTextBlock.xaml @@ -0,0 +1,60 @@ + + + + + + + \ No newline at end of file diff --git a/Microsoft.Toolkit.Uwp.UI.Controls/Microsoft.Toolkit.Uwp.UI.Controls.csproj b/Microsoft.Toolkit.Uwp.UI.Controls/Microsoft.Toolkit.Uwp.UI.Controls.csproj index 9b240477475..6174415e0f3 100644 --- a/Microsoft.Toolkit.Uwp.UI.Controls/Microsoft.Toolkit.Uwp.UI.Controls.csproj +++ b/Microsoft.Toolkit.Uwp.UI.Controls/Microsoft.Toolkit.Uwp.UI.Controls.csproj @@ -17,6 +17,7 @@ - GridSplitter: A the control that redistributes space between columns or rows of a Grid control. - HeaderedContentControl: Provides a header to content. - HeaderedItemsControl: Provides a header to items. + - HeaderedTextBlock: Provide a header for read-only text. - ImageCropper: ImageCropper control allows user to crop image freely. - ImageEx: Images are downloaded asynchronously showing a load indicator and can be stored in a local cache. - InAppNotification: Show local notifications in your application. diff --git a/Microsoft.Toolkit.Uwp.UI.Controls/Microsoft.Toolkit.Uwp.UI.Controls.csproj.DotSettings b/Microsoft.Toolkit.Uwp.UI.Controls/Microsoft.Toolkit.Uwp.UI.Controls.csproj.DotSettings index 8a263d98309..0dd5cea5b9e 100644 --- a/Microsoft.Toolkit.Uwp.UI.Controls/Microsoft.Toolkit.Uwp.UI.Controls.csproj.DotSettings +++ b/Microsoft.Toolkit.Uwp.UI.Controls/Microsoft.Toolkit.Uwp.UI.Controls.csproj.DotSettings @@ -5,6 +5,7 @@ True True True + True True True True diff --git a/Microsoft.Toolkit.Uwp.UI.Controls/Strings/en-US/Resources.resw b/Microsoft.Toolkit.Uwp.UI.Controls/Strings/en-US/Resources.resw index d3b003e8b80..66cce5b66ab 100644 --- a/Microsoft.Toolkit.Uwp.UI.Controls/Strings/en-US/Resources.resw +++ b/Microsoft.Toolkit.Uwp.UI.Controls/Strings/en-US/Resources.resw @@ -213,6 +213,14 @@ Notification The landmark name for the InAppNotification control. It is said by the narrator when using landmark navigation. + + Close tab + Narrator Resource for TabView Close Button. + + + Close tab + Tooltip for TabView Close Button. + Remove Label for TokenizingTextBox MenuFlyout 'Remove' option. diff --git a/Microsoft.Toolkit.Uwp.UI.Controls/TabView/TabClosingEventArgs.cs b/Microsoft.Toolkit.Uwp.UI.Controls/TabView/TabClosingEventArgs.cs new file mode 100644 index 00000000000..893776ff0be --- /dev/null +++ b/Microsoft.Toolkit.Uwp.UI.Controls/TabView/TabClosingEventArgs.cs @@ -0,0 +1,36 @@ +// 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.ComponentModel; + +namespace Microsoft.Toolkit.Uwp.UI.Controls +{ + /// + /// Event arguments for event. + /// + public class TabClosingEventArgs : CancelEventArgs + { + /// + /// Initializes a new instance of the class. + /// + /// Item being closed. + /// container being closed. + public TabClosingEventArgs(object item, TabViewItem tab) + { + Item = item; + Tab = tab; + } + + /// + /// Gets the Item being closed. + /// + public object Item { get; private set; } + + /// + /// Gets the Tab being closed. + /// + public TabViewItem Tab { get; private set; } + } +} \ No newline at end of file diff --git a/Microsoft.Toolkit.Uwp.UI.Controls/TabView/TabDraggedOutsideEventArgs.cs b/Microsoft.Toolkit.Uwp.UI.Controls/TabView/TabDraggedOutsideEventArgs.cs new file mode 100644 index 00000000000..dcf351aab9b --- /dev/null +++ b/Microsoft.Toolkit.Uwp.UI.Controls/TabView/TabDraggedOutsideEventArgs.cs @@ -0,0 +1,35 @@ +// 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; + +namespace Microsoft.Toolkit.Uwp.UI.Controls +{ + /// + /// A class used by the TabDraggedOutside Event + /// + public class TabDraggedOutsideEventArgs : EventArgs + { + /// + /// Initializes a new instance of the class. + /// + /// data context of element dragged + /// container being dragged. + public TabDraggedOutsideEventArgs(object item, TabViewItem tab) + { + Item = item; + Tab = tab; + } + + /// + /// Gets or sets the Item/Data Context of the item being dragged outside of the . + /// + public object Item { get; set; } + + /// + /// Gets the Tab being dragged outside of the . + /// + public TabViewItem Tab { get; private set; } + } +} diff --git a/Microsoft.Toolkit.Uwp.UI.Controls/TabView/TabView.HeaderLayout.cs b/Microsoft.Toolkit.Uwp.UI.Controls/TabView/TabView.HeaderLayout.cs new file mode 100644 index 00000000000..16885700b35 --- /dev/null +++ b/Microsoft.Toolkit.Uwp.UI.Controls/TabView/TabView.HeaderLayout.cs @@ -0,0 +1,221 @@ +// 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.Linq; +using Windows.UI.Xaml; + +namespace Microsoft.Toolkit.Uwp.UI.Controls +{ + /// + /// TabView methods related to calculating the width of the Headers. + /// + public partial class TabView + { + // Attached property for storing widths of tabs if set by other means during layout. + private static double GetOriginalWidth(TabViewItem obj) + { + return (double)obj.GetValue(OriginalWidthProperty); + } + + private static void SetOriginalWidth(TabViewItem obj, double value) + { + obj.SetValue(OriginalWidthProperty, value); + } + + // Using a DependencyProperty as the backing store for MyProperty. This enables animation, styling, binding, etc... + private static readonly DependencyProperty OriginalWidthProperty = + DependencyProperty.RegisterAttached("OriginalWidth", typeof(double), typeof(TabView), new PropertyMetadata(null)); + + private static void OnLayoutEffectingPropertyChanged(DependencyObject sender, DependencyPropertyChangedEventArgs args) + { + var tabview = sender as TabView; + if (tabview != null && tabview._hasLoaded) + { + tabview.TabView_SizeChanged(tabview, null); + } + } + + private void TabView_SizeChanged(object sender, SizeChangedEventArgs e) + { + // We need to do this calculation here in Size Changed as the + // Columns don't have their Actual Size calculated in Measure or Arrange. + if (_hasLoaded && _tabViewContainer != null) + { + // Look for our special columns to calculate size of other 'stuff' + var taken = _tabViewContainer.ColumnDefinitions.Sum(cd => GetIgnoreColumn(cd) ? 0 : cd.ActualWidth); + + // Get the column we want to work on for available space + var tabc = _tabViewContainer.ColumnDefinitions.FirstOrDefault(cd => GetConstrainColumn(cd)); + if (tabc != null) + { + var available = ActualWidth - taken; + var required = 0.0; + var mintabwidth = double.MaxValue; + + if (TabWidthBehavior == TabWidthMode.Actual) + { + if (_tabScroller != null) + { + // If we have a scroll container, get its size. + required = _tabScroller.ExtentWidth; + } + + // Restore original widths + foreach (var item in Items) + { + var tab = ContainerFromItem(item) as TabViewItem; + if (tab == null) + { + continue; // container not generated yet + } + + if (tab.ReadLocalValue(OriginalWidthProperty) != DependencyProperty.UnsetValue) + { + tab.Width = GetOriginalWidth(tab); + } + } + } + else if (available > 0) + { + // Calculate the width for each tab from the provider and determine how much space they take. + foreach (var item in Items) + { + var tab = ContainerFromItem(item) as TabViewItem; + if (tab == null) + { + continue; // container not generated yet + } + + mintabwidth = Math.Min(mintabwidth, tab.MinWidth); + + double width = double.NaN; + + switch (TabWidthBehavior) + { + case TabWidthMode.Equal: + width = ProvideEqualWidth(tab, item, available); + break; + case TabWidthMode.Compact: + width = ProvideCompactWidth(tab, item, available); + break; + } + + if (tab.ReadLocalValue(OriginalWidthProperty) == DependencyProperty.UnsetValue) + { + SetOriginalWidth(tab, tab.Width); + } + + if (width > double.Epsilon) + { + tab.Width = width; + required += Math.Max(Math.Min(width, tab.MaxWidth), tab.MinWidth); + } + else + { + tab.Width = GetOriginalWidth(tab); + required += tab.ActualWidth; + } + } + } + else + { + // Fix negative bounds. + available = 0.0; + + // Still need to determine a 'minimum' width (if available) + // TODO: Consolidate this logic with above better? + foreach (var item in Items) + { + var tab = ContainerFromItem(item) as TabViewItem; + if (tab == null) + { + continue; // container not generated yet + } + + mintabwidth = Math.Min(mintabwidth, tab.MinWidth); + } + } + + if (!(mintabwidth < double.MaxValue)) + { + mintabwidth = 0.0; // No Containers, no visual, 0 size. + } + + if (available > mintabwidth) + { + // Constrain the column based on our required and available space + tabc.MaxWidth = available; + } + + //// TODO: If it's less, should we move the selected tab to only be the one shown by default? + + if (available <= mintabwidth || Math.Abs(available - mintabwidth) < double.Epsilon) + { + tabc.Width = new GridLength(mintabwidth); + } + else if (required >= available) + { + // Fix size as we don't have enough space for all the tabs. + tabc.Width = new GridLength(available); + } + else + { + // We haven't filled up our space, so we want to expand to take as much as needed. + tabc.Width = GridLength.Auto; + } + } + } + } + + private double ProvideEqualWidth(TabViewItem tab, object item, double availableWidth) + { + if (double.IsNaN(SelectedTabWidth)) + { + if (Items.Count <= 1) + { + return availableWidth; + } + + return Math.Max(tab.MinWidth, availableWidth / Items.Count); + } + else if (Items.Count() <= 1) + { + // Default case of a single tab, make it full size. + return Math.Min(SelectedTabWidth, availableWidth); + } + else + { + var width = (availableWidth - SelectedTabWidth) / (Items.Count - 1); + + // Constrain between Min and Selected (Max) + if (width < tab.MinWidth) + { + width = tab.MinWidth; + } + else if (width > SelectedTabWidth) + { + width = SelectedTabWidth; + } + + // If it's selected make it full size, otherwise whatever the size should be. + return tab.IsSelected + ? Math.Min(SelectedTabWidth, availableWidth) + : width; + } + } + + private double ProvideCompactWidth(TabViewItem tab, object item, double availableWidth) + { + // If we're selected and have a value for that, then just return that. + if (tab.IsSelected && !double.IsNaN(SelectedTabWidth)) + { + return SelectedTabWidth; + } + + // Otherwise use min size. + return tab.MinWidth; + } + } +} diff --git a/Microsoft.Toolkit.Uwp.UI.Controls/TabView/TabView.ItemSources.cs b/Microsoft.Toolkit.Uwp.UI.Controls/TabView/TabView.ItemSources.cs new file mode 100644 index 00000000000..61f61623b6f --- /dev/null +++ b/Microsoft.Toolkit.Uwp.UI.Controls/TabView/TabView.ItemSources.cs @@ -0,0 +1,115 @@ +// 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.Reflection; +using Windows.Foundation.Collections; +using Windows.UI.Xaml; +using Windows.UI.Xaml.Controls.Primitives; + +namespace Microsoft.Toolkit.Uwp.UI.Controls +{ + /// + /// TabView methods related to tracking Items and ItemsSource changes. + /// + public partial class TabView + { + // Temporary tracking of previous collections for removing events. + private MethodInfo _removeItemsSourceMethod; + + /// + protected override void OnItemsChanged(object e) + { + IVectorChangedEventArgs args = (IVectorChangedEventArgs)e; + + base.OnItemsChanged(e); + + if (args.CollectionChange == CollectionChange.ItemRemoved && SelectedIndex == -1) + { + // If we remove the selected item we should select the previous item + int startIndex = (int)args.Index + 1; + if (startIndex > Items.Count) + { + startIndex = 0; + } + + SelectedIndex = FindNextTabIndex(startIndex, -1); + } + + // Update Sizing (in case there are less items now) + TabView_SizeChanged(this, null); + } + + private void ItemContainerGenerator_ItemsChanged(object sender, ItemsChangedEventArgs e) + { + var action = (CollectionChange)e.Action; + if (action == CollectionChange.Reset) + { + // Reset collection to reload later. + _hasLoaded = false; + } + } + + private void SetInitialSelection() + { + if (SelectedItem == null) + { + // If we have an index, but didn't get the selection, make the selection + if (SelectedIndex >= 0 && SelectedIndex < Items.Count) + { + SelectedItem = Items[SelectedIndex]; + } + + // Otherwise, select the first item by default + else if (Items.Count >= 1) + { + SelectedItem = Items[0]; + } + } + } + + // Finds the next visible & enabled tab index. + private int FindNextTabIndex(int startIndex, int direction) + { + int index = startIndex; + if (direction != 0) + { + for (int i = 0; i < Items.Count; i++) + { + index += direction; + + if (index >= Items.Count) + { + index = 0; + } + else if (index < 0) + { + index = Items.Count - 1; + } + + var tabItem = ContainerFromIndex(index) as TabViewItem; + if (tabItem != null && tabItem.IsEnabled && tabItem.Visibility == Visibility.Visible) + { + break; + } + } + } + + return index; + } + + private void ItemsSource_PropertyChanged(DependencyObject sender, DependencyProperty dp) + { + // Use reflection to store a 'Remove' method of any possible collection in ItemsSource + // Cache for efficiency later. + if (ItemsSource != null) + { + _removeItemsSourceMethod = ItemsSource.GetType().GetMethod("Remove"); + } + else + { + _removeItemsSourceMethod = null; + } + } + } +} diff --git a/Microsoft.Toolkit.Uwp.UI.Controls/TabView/TabView.Properties.cs b/Microsoft.Toolkit.Uwp.UI.Controls/TabView/TabView.Properties.cs new file mode 100644 index 00000000000..5d33b33f1cc --- /dev/null +++ b/Microsoft.Toolkit.Uwp.UI.Controls/TabView/TabView.Properties.cs @@ -0,0 +1,245 @@ +// 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; +using Windows.UI.Xaml.Controls; + +namespace Microsoft.Toolkit.Uwp.UI.Controls +{ + /// + /// TabView properties. + /// + public partial class TabView + { + /// + /// Gets or sets the content to appear to the left or above the tab strip. + /// + public object TabStartHeader + { + get { return (object)GetValue(TabStartHeaderProperty); } + set { SetValue(TabStartHeaderProperty, value); } + } + + /// + /// Identifies the dependency property. + /// + /// The identifier for the dependency property. + public static readonly DependencyProperty TabStartHeaderProperty = + DependencyProperty.Register(nameof(TabStartHeader), typeof(object), typeof(TabView), new PropertyMetadata(null, OnLayoutEffectingPropertyChanged)); + + /// + /// Gets or sets the for the . + /// + public DataTemplate TabStartHeaderTemplate + { + get { return (DataTemplate)GetValue(TabStartHeaderTemplateProperty); } + set { SetValue(TabStartHeaderTemplateProperty, value); } + } + + /// + /// Identifies the dependency property. + /// + /// The identifier for the dependency property. + public static readonly DependencyProperty TabStartHeaderTemplateProperty = + DependencyProperty.Register(nameof(TabStartHeaderTemplate), typeof(DataTemplate), typeof(TabView), new PropertyMetadata(null, OnLayoutEffectingPropertyChanged)); + + /// + /// Gets or sets the content to appear next to the tab strip. + /// + public object TabActionHeader + { + get { return (object)GetValue(TabActionHeaderProperty); } + set { SetValue(TabActionHeaderProperty, value); } + } + + /// + /// Identifies the dependency property. + /// + /// The identifier for the dependency property. + public static readonly DependencyProperty TabActionHeaderProperty = + DependencyProperty.Register(nameof(TabActionHeader), typeof(object), typeof(TabView), new PropertyMetadata(null, OnLayoutEffectingPropertyChanged)); + + /// + /// Gets or sets the for the . + /// + public DataTemplate TabActionHeaderTemplate + { + get { return (DataTemplate)GetValue(TabActionHeaderTemplateProperty); } + set { SetValue(TabActionHeaderTemplateProperty, value); } + } + + /// + /// Identifies the dependency property. + /// + /// The identifier for the dependency property. + public static readonly DependencyProperty TabActionHeaderTemplateProperty = + DependencyProperty.Register(nameof(TabActionHeaderTemplate), typeof(DataTemplate), typeof(TabView), new PropertyMetadata(null, OnLayoutEffectingPropertyChanged)); + + /// + /// Gets or sets the content to appear to the right or below the tab strip. + /// + public object TabEndHeader + { + get { return (object)GetValue(TabEndHeaderProperty); } + set { SetValue(TabEndHeaderProperty, value); } + } + + /// + /// Identifies the dependency property. + /// + /// The identifier for the dependency property. + public static readonly DependencyProperty TabEndHeaderProperty = + DependencyProperty.Register(nameof(TabEndHeader), typeof(object), typeof(TabView), new PropertyMetadata(null, OnLayoutEffectingPropertyChanged)); + + /// + /// Gets or sets the for the . + /// + public DataTemplate TabEndHeaderTemplate + { + get { return (DataTemplate)GetValue(TabEndHeaderTemplateProperty); } + set { SetValue(TabEndHeaderTemplateProperty, value); } + } + + /// + /// Identifies the dependency property. + /// + /// The identifier for the dependency property. + public static readonly DependencyProperty TabEndHeaderTemplateProperty = + DependencyProperty.Register(nameof(TabEndHeaderTemplate), typeof(DataTemplate), typeof(TabView), new PropertyMetadata(null, OnLayoutEffectingPropertyChanged)); + + /// + /// Gets or sets the default for the . + /// + public DataTemplate ItemHeaderTemplate + { + get { return (DataTemplate)GetValue(ItemHeaderTemplateProperty); } + set { SetValue(ItemHeaderTemplateProperty, value); } + } + + /// + /// Identifies the dependency property. + /// + /// The identifier for the dependency property. + public static readonly DependencyProperty ItemHeaderTemplateProperty = + DependencyProperty.Register(nameof(ItemHeaderTemplate), typeof(DataTemplate), typeof(TabView), new PropertyMetadata(null, OnLayoutEffectingPropertyChanged)); + + /// + /// Gets or sets a value indicating whether by default a Tab can be closed or not if no value to is provided. + /// + public bool CanCloseTabs + { + get { return (bool)GetValue(CanCloseTabsProperty); } + set { SetValue(CanCloseTabsProperty, value); } + } + + /// + /// Identifies the dependency property. + /// + /// The identifier for the dependency property. + public static readonly DependencyProperty CanCloseTabsProperty = + DependencyProperty.Register(nameof(CanCloseTabs), typeof(bool), typeof(TabView), new PropertyMetadata(false, OnLayoutEffectingPropertyChanged)); + + /// + /// Gets or sets a value indicating whether a Close Button should be included in layout calculations. + /// + public bool IsCloseButtonOverlay + { + get { return (bool)GetValue(IsCloseButtonOverlayProperty); } + set { SetValue(IsCloseButtonOverlayProperty, value); } + } + + /// + /// Identifies the dependency property. + /// + /// The identifier for the dependency property. + public static readonly DependencyProperty IsCloseButtonOverlayProperty = + DependencyProperty.Register(nameof(IsCloseButtonOverlay), typeof(bool), typeof(TabView), new PropertyMetadata(false, OnLayoutEffectingPropertyChanged)); + + /// + /// Gets or sets a value indicating the size of the selected tab. By default this is set to Auto and the selected tab size doesn't change. + /// + public double SelectedTabWidth + { + get { return (double)GetValue(SelectedTabWidthProperty); } + set { SetValue(SelectedTabWidthProperty, value); } + } + + /// + /// Identifies the dependency property. + /// + /// The identifier for the dependency property. + public static readonly DependencyProperty SelectedTabWidthProperty = + DependencyProperty.Register(nameof(SelectedTabWidth), typeof(double), typeof(TabView), new PropertyMetadata(double.NaN, OnLayoutEffectingPropertyChanged)); + + /// + /// Gets or sets the current which determins how tab headers' width behave. + /// + public TabWidthMode TabWidthBehavior + { + get { return (TabWidthMode)GetValue(TabWidthBehaviorProperty); } + set { SetValue(TabWidthBehaviorProperty, value); } + } + + /// + /// Identifies the dependency property. + /// + /// The identifier for the dependency property. + public static readonly DependencyProperty TabWidthBehaviorProperty = + DependencyProperty.Register(nameof(TabWidthBehavior), typeof(TabWidthMode), typeof(TabView), new PropertyMetadata(TabWidthMode.Actual, OnLayoutEffectingPropertyChanged)); + + /// + /// Gets the attached property value to indicate if this grid column should be ignored when calculating header sizes. + /// + /// Grid Column. + /// Boolean indicating if this column is ignored by TabViewHeader logic. + public static bool GetIgnoreColumn(ColumnDefinition obj) + { + return (bool)obj.GetValue(IgnoreColumnProperty); + } + + /// + /// Sets the attached property value for + /// + /// Grid Column. + /// Boolean value + public static void SetIgnoreColumn(ColumnDefinition obj, bool value) + { + obj.SetValue(IgnoreColumnProperty, value); + } + + /// + /// Identifies the attached property. + /// + /// The identifier for the IgnoreColumn attached property. + public static readonly DependencyProperty IgnoreColumnProperty = + DependencyProperty.RegisterAttached("IgnoreColumn", typeof(bool), typeof(TabView), new PropertyMetadata(false)); + + /// + /// Gets the attached value indicating this column should be restricted for the headers. + /// + /// Grid Column. + /// True if this column should be constrained. + public static bool GetConstrainColumn(ColumnDefinition obj) + { + return (bool)obj.GetValue(ConstrainColumnProperty); + } + + /// + /// Sets the attached property value for the + /// + /// Grid Column. + /// Boolean value. + public static void SetConstrainColumn(ColumnDefinition obj, bool value) + { + obj.SetValue(ConstrainColumnProperty, value); + } + + /// + /// Identifies the attached property. + /// + /// The identifier for the ConstrainColumn attached property. + public static readonly DependencyProperty ConstrainColumnProperty = + DependencyProperty.RegisterAttached("ConstrainColumn", typeof(bool), typeof(TabView), new PropertyMetadata(false)); + } +} diff --git a/Microsoft.Toolkit.Uwp.UI.Controls/TabView/TabView.cs b/Microsoft.Toolkit.Uwp.UI.Controls/TabView/TabView.cs new file mode 100644 index 00000000000..0e86ab398f5 --- /dev/null +++ b/Microsoft.Toolkit.Uwp.UI.Controls/TabView/TabView.cs @@ -0,0 +1,320 @@ +// 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.ComponentModel; +using System.Linq; +using Microsoft.Toolkit.Uwp.UI.Extensions; +using Windows.ApplicationModel.DataTransfer; +using Windows.UI.Xaml; +using Windows.UI.Xaml.Controls; +using Windows.UI.Xaml.Controls.Primitives; +using Windows.UI.Xaml.Data; + +namespace Microsoft.Toolkit.Uwp.UI.Controls +{ + /// + /// TabView is a control for displaying a set of tabs and their content. + /// + [Obsolete("Please migrate to the TabView control from WinUI, this control will be removed in a future release. https://aka.ms/winui")] + [TemplatePart(Name = TabContentPresenterName, Type = typeof(ContentPresenter))] + [TemplatePart(Name = TabViewContainerName, Type = typeof(Grid))] + [TemplatePart(Name = TabsItemsPresenterName, Type = typeof(ItemsPresenter))] + [TemplatePart(Name = TabsScrollViewerName, Type = typeof(ScrollViewer))] + [TemplatePart(Name = TabsScrollBackButtonName, Type = typeof(ButtonBase))] + [TemplatePart(Name = TabsScrollForwardButtonName, Type = typeof(ButtonBase))] + public partial class TabView : ListViewBase + { + private const int ScrollAmount = 50; // TODO: Should this be based on TabWidthMode + + private const string TabContentPresenterName = "TabContentPresenter"; + private const string TabViewContainerName = "TabViewContainer"; + private const string TabsItemsPresenterName = "TabsItemsPresenter"; + private const string TabsScrollViewerName = "ScrollViewer"; + private const string TabsScrollBackButtonName = "ScrollBackButton"; + private const string TabsScrollForwardButtonName = "ScrollForwardButton"; + + private ContentPresenter _tabContentPresenter; + private Grid _tabViewContainer; + private ItemsPresenter _tabItemsPresenter; + private ScrollViewer _tabScroller; + private ButtonBase _tabScrollBackButton; + private ButtonBase _tabScrollForwardButton; + + private bool _hasLoaded; + private bool _isDragging; + + /// + /// Initializes a new instance of the class. + /// + public TabView() + { + DefaultStyleKey = typeof(TabView); + + // Container Generation Hooks + RegisterPropertyChangedCallback(ItemsSourceProperty, ItemsSource_PropertyChanged); + ItemContainerGenerator.ItemsChanged += ItemContainerGenerator_ItemsChanged; + + // Drag and Layout Hooks + DragItemsStarting += TabView_DragItemsStarting; + DragItemsCompleted += TabView_DragItemsCompleted; + SizeChanged += TabView_SizeChanged; + + // Selection Hook + SelectionChanged += TabView_SelectionChanged; + } + + /// + /// Occurs when a tab is dragged by the user outside of the . Generally, this paradigm is used to create a new-window with the torn-off tab. + /// The creation and handling of the new-window is left to the app's developer. + /// + public event EventHandler TabDraggedOutside; + + /// + /// Occurs when a tab's Close button is clicked. Set to true to prevent automatic Tab Closure. + /// + public event EventHandler TabClosing; + + /// + protected override DependencyObject GetContainerForItemOverride() => new TabViewItem(); + + /// + protected override bool IsItemItsOwnContainerOverride(object item) + { + return item is TabViewItem; + } + + /// + protected override void OnApplyTemplate() + { + base.OnApplyTemplate(); + + if (_tabItemsPresenter != null) + { + _tabItemsPresenter.SizeChanged -= TabView_SizeChanged; + } + + if (_tabScroller != null) + { + _tabScroller.Loaded -= ScrollViewer_Loaded; + } + + _tabContentPresenter = GetTemplateChild(TabContentPresenterName) as ContentPresenter; + _tabViewContainer = GetTemplateChild(TabViewContainerName) as Grid; + _tabItemsPresenter = GetTemplateChild(TabsItemsPresenterName) as ItemsPresenter; + _tabScroller = GetTemplateChild(TabsScrollViewerName) as ScrollViewer; + + if (_tabItemsPresenter != null) + { + _tabItemsPresenter.SizeChanged += TabView_SizeChanged; + } + + if (_tabScroller != null) + { + _tabScroller.Loaded += ScrollViewer_Loaded; + } + } + + private void ScrollViewer_Loaded(object sender, RoutedEventArgs e) + { + _tabScroller.Loaded -= ScrollViewer_Loaded; + + if (_tabScrollBackButton != null) + { + _tabScrollBackButton.Click -= ScrollTabBackButton_Click; + } + + if (_tabScrollForwardButton != null) + { + _tabScrollForwardButton.Click -= ScrollTabForwardButton_Click; + } + + _tabScrollBackButton = _tabScroller.FindDescendantByName(TabsScrollBackButtonName) as ButtonBase; + _tabScrollForwardButton = _tabScroller.FindDescendantByName(TabsScrollForwardButtonName) as ButtonBase; + + if (_tabScrollBackButton != null) + { + _tabScrollBackButton.Click += ScrollTabBackButton_Click; + } + + if (_tabScrollForwardButton != null) + { + _tabScrollForwardButton.Click += ScrollTabForwardButton_Click; + } + } + + private void ScrollTabBackButton_Click(object sender, RoutedEventArgs e) + { + _tabScroller.ChangeView(Math.Max(0, _tabScroller.HorizontalOffset - ScrollAmount), null, null); + } + + private void ScrollTabForwardButton_Click(object sender, RoutedEventArgs e) + { + _tabScroller.ChangeView(Math.Min(_tabScroller.ScrollableWidth, _tabScroller.HorizontalOffset + ScrollAmount), null, null); + } + + private void TabView_SelectionChanged(object sender, SelectionChangedEventArgs e) + { + if (_isDragging) + { + // Skip if we're dragging, we'll reset when we're done. + return; + } + + if (_tabContentPresenter != null) + { + if (SelectedItem == null) + { + _tabContentPresenter.Content = null; + _tabContentPresenter.ContentTemplate = null; + } + else + { + if (ContainerFromItem(SelectedItem) is TabViewItem container) + { + _tabContentPresenter.Content = container.Content; + _tabContentPresenter.ContentTemplate = container.ContentTemplate; + } + } + } + + // If our width can be effected by the selection, need to run algorithm. + if (!double.IsNaN(SelectedTabWidth)) + { + TabView_SizeChanged(sender, null); + } + } + + /// + protected override void PrepareContainerForItemOverride(DependencyObject element, object item) + { + base.PrepareContainerForItemOverride(element, item); + + var tabitem = element as TabViewItem; + + tabitem.Loaded -= TabViewItem_Loaded; + tabitem.Closing -= TabViewItem_Closing; + tabitem.Loaded += TabViewItem_Loaded; + tabitem.Closing += TabViewItem_Closing; + + if (tabitem.Header == null) + { + tabitem.Header = item; + } + + if (tabitem.HeaderTemplate == null) + { + var headertemplatebinding = new Binding() + { + Source = this, + Path = new PropertyPath(nameof(ItemHeaderTemplate)), + Mode = BindingMode.OneWay + }; + tabitem.SetBinding(TabViewItem.HeaderTemplateProperty, headertemplatebinding); + } + + if (tabitem.IsClosable != true && tabitem.ReadLocalValue(TabViewItem.IsClosableProperty) == DependencyProperty.UnsetValue) + { + var iscloseablebinding = new Binding() + { + Source = this, + Path = new PropertyPath(nameof(CanCloseTabs)), + Mode = BindingMode.OneWay, + }; + tabitem.SetBinding(TabViewItem.IsClosableProperty, iscloseablebinding); + } + } + + private void TabViewItem_Loaded(object sender, RoutedEventArgs e) + { + var tabitem = sender as TabViewItem; + + tabitem.Loaded -= TabViewItem_Loaded; + + // Only need to do this once. + if (!_hasLoaded) + { + _hasLoaded = true; + + // Need to set a tab's selection on load, otherwise ListView resets to null. + SetInitialSelection(); + + // Need to make sure ContentPresenter is set to content based on selection. + TabView_SelectionChanged(this, null); + + // Need to make sure we've registered our removal method. + ItemsSource_PropertyChanged(this, null); + + // Make sure we complete layout now. + TabView_SizeChanged(this, null); + } + } + + private void TabViewItem_Closing(object sender, TabClosingEventArgs e) + { + var item = ItemFromContainer(e.Tab); + + var args = new TabClosingEventArgs(item, e.Tab); + TabClosing?.Invoke(this, args); + + if (!args.Cancel) + { + if (ItemsSource != null) + { + _removeItemsSourceMethod?.Invoke(ItemsSource, new object[] { item }); + } + else + { + Items.Remove(item); + } + } + } + + private void TabView_DragItemsStarting(object sender, DragItemsStartingEventArgs e) + { + // Keep track of drag so we don't modify content until done. + _isDragging = true; + } + + private void TabView_DragItemsCompleted(ListViewBase sender, DragItemsCompletedEventArgs args) + { + _isDragging = false; + + // args.DropResult == None when outside of area (e.g. create new window) + if (args.DropResult == DataPackageOperation.None) + { + var item = args.Items.FirstOrDefault(); + var tab = ContainerFromItem(item) as TabViewItem; + + if (tab == null && item is FrameworkElement fe) + { + tab = fe.FindParent(); + } + + if (tab == null) + { + // We still don't have a TabViewItem, most likely is a static TabViewItem in the template being dragged and not selected. + // This is a fallback scenario for static tabs. + // Note: This can be wrong if two TabViewItems share the exact same Content (i.e. a string), this should be unlikely in any practical scenario. + for (int i = 0; i < Items.Count; i++) + { + var tabItem = ContainerFromIndex(i) as TabViewItem; + if (ReferenceEquals(tabItem.Content, item)) + { + tab = tabItem; + break; + } + } + } + + TabDraggedOutside?.Invoke(this, new TabDraggedOutsideEventArgs(item, tab)); + } + else + { + // If dragging the active tab, there's an issue with the CP blanking. + TabView_SelectionChanged(this, null); + } + } + } +} diff --git a/Microsoft.Toolkit.Uwp.UI.Controls/TabView/TabView.xaml b/Microsoft.Toolkit.Uwp.UI.Controls/TabView/TabView.xaml new file mode 100644 index 00000000000..d3310aadf06 --- /dev/null +++ b/Microsoft.Toolkit.Uwp.UI.Controls/TabView/TabView.xaml @@ -0,0 +1,736 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 0 + 0,0,8,0 + 8,0,-8,0 + 0,1,1,0 + 16 + 48 + 40 + NaN + 32 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Microsoft.Toolkit.Uwp.UI.Controls/TabView/TabViewItem.Properties.cs b/Microsoft.Toolkit.Uwp.UI.Controls/TabView/TabViewItem.Properties.cs new file mode 100644 index 00000000000..3ce0f3e5483 --- /dev/null +++ b/Microsoft.Toolkit.Uwp.UI.Controls/TabView/TabViewItem.Properties.cs @@ -0,0 +1,79 @@ +// 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; +using Windows.UI.Xaml.Controls; + +namespace Microsoft.Toolkit.Uwp.UI.Controls +{ + /// + /// Item Container for a . + /// + public partial class TabViewItem + { + /// + /// Gets or sets the header content for the tab. + /// + public object Header + { + get { return (object)GetValue(HeaderProperty); } + set { SetValue(HeaderProperty, value); } + } + + /// + /// Identifies the dependency property. + /// + /// The identifier for the dependency property. + public static readonly DependencyProperty HeaderProperty = + DependencyProperty.Register(nameof(Header), typeof(object), typeof(TabViewItem), new PropertyMetadata(null)); + + /// + /// Gets or sets the icon to appear in the tab header. + /// + public IconElement Icon + { + get { return (IconElement)GetValue(IconProperty); } + set { SetValue(IconProperty, value); } + } + + /// + /// Identifies the dependency property. + /// + /// The identifier for the dependency property. + public static readonly DependencyProperty IconProperty = + DependencyProperty.Register(nameof(Icon), typeof(IconElement), typeof(TabViewItem), new PropertyMetadata(null)); + + /// + /// Gets or sets the template to override for the tab header. + /// + public DataTemplate HeaderTemplate + { + get { return (DataTemplate)GetValue(HeaderTemplateProperty); } + set { SetValue(HeaderTemplateProperty, value); } + } + + /// + /// Identifies the dependency property. + /// + /// The identifier for the dependency property. + public static readonly DependencyProperty HeaderTemplateProperty = + DependencyProperty.Register(nameof(HeaderTemplate), typeof(DataTemplate), typeof(TabViewItem), new PropertyMetadata(null)); + + /// + /// Gets or sets a value indicating whether the tab can be closed by the user with the close button. + /// + public bool IsClosable + { + get { return (bool)GetValue(IsClosableProperty); } + set { SetValue(IsClosableProperty, value); } + } + + /// + /// Identifies the dependency property. + /// + /// The identifier for the dependency property. + public static readonly DependencyProperty IsClosableProperty = + DependencyProperty.Register(nameof(IsClosable), typeof(bool), typeof(TabViewItem), new PropertyMetadata(null)); + } +} diff --git a/Microsoft.Toolkit.Uwp.UI.Controls/TabView/TabViewItem.cs b/Microsoft.Toolkit.Uwp.UI.Controls/TabView/TabViewItem.cs new file mode 100644 index 00000000000..69a968fd839 --- /dev/null +++ b/Microsoft.Toolkit.Uwp.UI.Controls/TabView/TabViewItem.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; +using Windows.Devices.Input; +using Windows.System; +using Windows.UI.Core; +using Windows.UI.Input; +using Windows.UI.Xaml; +using Windows.UI.Xaml.Controls; +using Windows.UI.Xaml.Controls.Primitives; +using Windows.UI.Xaml.Input; + +namespace Microsoft.Toolkit.Uwp.UI.Controls +{ + /// + /// Item Container for a . + /// + [TemplatePart(Name = TabCloseButtonName, Type = typeof(ButtonBase))] + public partial class TabViewItem : ListViewItem + { + private const string TabCloseButtonName = "CloseButton"; + + private ButtonBase _tabCloseButton; + + private bool _isMiddleClick; + + /// + /// Initializes a new instance of the class. + /// + public TabViewItem() + { + DefaultStyleKey = typeof(TabViewItem); + } + + /// + /// Fired when the Tab's close button is clicked. + /// + public event EventHandler Closing; + + /// + protected override void OnApplyTemplate() + { + base.OnApplyTemplate(); + + if (_tabCloseButton != null) + { + _tabCloseButton.Click -= TabCloseButton_Click; + } + + _tabCloseButton = GetTemplateChild(TabCloseButtonName) as ButtonBase; + + if (_tabCloseButton != null) + { + _tabCloseButton.Click += TabCloseButton_Click; + } + } + + /// + protected override void OnPointerPressed(PointerRoutedEventArgs e) + { + _isMiddleClick = false; + + if (e.Pointer.PointerDeviceType == PointerDeviceType.Mouse) + { + PointerPoint pointerPoint = e.GetCurrentPoint(this); + + // Record if middle button is pressed + if (pointerPoint.Properties.IsMiddleButtonPressed) + { + _isMiddleClick = true; + } + + // Disable unwanted behaviour inherited by ListViewItem: + // Disable "Ctrl + Left click" to deselect tab + // Or variant like "Ctrl + Shift + Left click" + // Or "Ctrl + Alt + Left click" + if (pointerPoint.Properties.IsLeftButtonPressed) + { + var ctrl = Window.Current.CoreWindow.GetKeyState(VirtualKey.Control); + if (ctrl.HasFlag(CoreVirtualKeyStates.Down)) + { + // return here so the event won't be picked up by the base class + // but keep this event unhandled so it can be picked up further + return; + } + } + } + + base.OnPointerPressed(e); + } + + /// + protected override void OnPointerReleased(PointerRoutedEventArgs e) + { + base.OnPointerReleased(e); + + // Close on Middle-Click + if (_isMiddleClick) + { + TabCloseButton_Click(this, null); + } + + _isMiddleClick = false; + } + + private void TabCloseButton_Click(object sender, RoutedEventArgs e) + { + if (IsClosable) + { + Closing?.Invoke(this, new TabClosingEventArgs(Content, this)); + } + } + } +} diff --git a/Microsoft.Toolkit.Uwp.UI.Controls/TabView/TabWidthMode.cs b/Microsoft.Toolkit.Uwp.UI.Controls/TabView/TabWidthMode.cs new file mode 100644 index 00000000000..65b1ce03844 --- /dev/null +++ b/Microsoft.Toolkit.Uwp.UI.Controls/TabView/TabWidthMode.cs @@ -0,0 +1,36 @@ +// 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; + +namespace Microsoft.Toolkit.Uwp.UI.Controls +{ + /// + /// Possible modes for how to layout a Header's Width in the . + /// + public enum TabWidthMode + { + /// + /// Each tab header takes up as much space as it needs. This is similar to how WPF and Visual Studio Code behave. + /// Suggest to keep set to false. + /// is ignored. + /// In this scenario, tab width behavior is effectively turned off. This can be useful when using custom styling or a custom panel for layout of as well. + /// + Actual, + + /// + /// Each tab header will use the minimal space set by on the . + /// Suggest to set the to show more content for the selected item. + /// + Compact, + + /// + /// Each tab header will fill to fit the available space. If is set, that will be used as a Maximum Width. + /// This is similar to how Microsoft Edge behaves when used with the . + /// Suggest to set to true. + /// Suggest to set to 200 and the TabViewItemHeaderMinWidth Resource to 90. + /// + Equal, + } +} diff --git a/Microsoft.Toolkit.Uwp.UI.Controls/Themes/Generic.xaml b/Microsoft.Toolkit.Uwp.UI.Controls/Themes/Generic.xaml index 9c17a3717bd..592fbb45626 100644 --- a/Microsoft.Toolkit.Uwp.UI.Controls/Themes/Generic.xaml +++ b/Microsoft.Toolkit.Uwp.UI.Controls/Themes/Generic.xaml @@ -9,6 +9,7 @@ + @@ -26,6 +27,7 @@ + diff --git a/Microsoft.Toolkit.Uwp.UI.Controls/TileControl/TileControl.cs b/Microsoft.Toolkit.Uwp.UI.Controls/TileControl/TileControl.cs index cdc4af642c5..6799439d67a 100644 --- a/Microsoft.Toolkit.Uwp.UI.Controls/TileControl/TileControl.cs +++ b/Microsoft.Toolkit.Uwp.UI.Controls/TileControl/TileControl.cs @@ -181,6 +181,13 @@ public class TileControl : ContentControl /// public event EventHandler ImageLoaded; + /// + /// Gets a value indicating whether the platform supports Composition. + /// + [Obsolete("This property is now obsolete and will be removed in a future version of the Toolkit.")] + public static bool IsCompositionSupported => !DesignTimeHelpers.IsRunningInLegacyDesignerMode && + ApiInformation.IsApiContractPresent("Windows.Foundation.UniversalApiContract", 3); // SDK >= 14393 + /// /// Initializes a new instance of the class. /// diff --git a/Microsoft.Toolkit.Uwp.UI.Controls/VisualStudioToolsManifest.xml b/Microsoft.Toolkit.Uwp.UI.Controls/VisualStudioToolsManifest.xml index 27fe2b2f0bc..e2bc71350ef 100644 --- a/Microsoft.Toolkit.Uwp.UI.Controls/VisualStudioToolsManifest.xml +++ b/Microsoft.Toolkit.Uwp.UI.Controls/VisualStudioToolsManifest.xml @@ -13,6 +13,7 @@ + @@ -32,6 +33,8 @@ + + diff --git a/Microsoft.Toolkit/Extensions/StringExtensions.cs b/Microsoft.Toolkit/Extensions/StringExtensions.cs index a39030a11a4..5e81855f128 100644 --- a/Microsoft.Toolkit/Extensions/StringExtensions.cs +++ b/Microsoft.Toolkit/Extensions/StringExtensions.cs @@ -86,6 +86,14 @@ public static bool IsDecimal(this string str) /// true if the string contains only letters; otherwise, false. public static bool IsCharacterString(this string str) => Regex.IsMatch(str, CharactersRegex); + /// + /// Returns a string representation of an object. + /// + /// The object to convert. + /// String representation of the object. + [Obsolete("Use value?.ToString() instead. This will be removed in the next release of the toolkit.")] + public static string ToSafeString(this object value) => value?.ToString(); + /// /// Returns a string with HTML comments, scripts, styles, and tags removed. /// From 75da45cb656323cdf2a06c4e7d2e27e379f6b7c0 Mon Sep 17 00:00:00 2001 From: Alexandre Zollinger Chohfi Date: Wed, 19 Aug 2020 19:41:50 -0700 Subject: [PATCH 409/595] Removed TabView/TabViewItem, HeaderedTextBlock, and some other smaller methods marked as Obsolete. --- .../Services/LinkedIn/LinkedInDataProvider.cs | 11 - .../Services/LinkedIn/LinkedInService.cs | 9 - .../Services/Twitter/TwitterDataProvider.cs | 11 - .../Services/Twitter/TwitterService.cs | 9 - .../Services/Weibo/WeiboDataProvider.cs | 11 - .../Services/Weibo/WeiboService.cs | 9 - .../Microsoft.Toolkit.Uwp.SampleApp.csproj | 21 - .../HeaderedTextBlock/HeaderedTextBlock.png | Bin 3308 -> 0 bytes .../HeaderedTextBlockCode.bind | 16 - .../HeaderedTextBlockPage.xaml | 9 - .../HeaderedTextBlockPage.xaml.cs | 18 - .../SamplePages/TabView/TabView.png | Bin 1392 -> 0 bytes .../SamplePages/TabView/TabViewPage.xaml | 19 - .../SamplePages/TabView/TabViewPage.xaml.cs | 73 -- .../SamplePages/TabView/TabViewXaml.bind | 97 --- .../SamplePages/samples.json | 24 - .../ConnectedAnimations/Connected.cs | 16 - .../HeaderedTextBlockMetadata.cs | 49 -- ...soft.Toolkit.Uwp.UI.Controls.Design.csproj | 3 - .../TabViewItemMetadata.cs | 43 - .../TabViewMetadata.cs | 54 -- .../HeaderedTextBlock.Properties.cs | 172 ---- .../HeaderedTextBlock/HeaderedTextBlock.cs | 76 -- .../HeaderedTextBlock/HeaderedTextBlock.xaml | 60 -- .../Microsoft.Toolkit.Uwp.UI.Controls.csproj | 1 - ...Toolkit.Uwp.UI.Controls.csproj.DotSettings | 1 - .../Strings/en-US/Resources.resw | 8 - .../TabView/TabClosingEventArgs.cs | 36 - .../TabView/TabDraggedOutsideEventArgs.cs | 35 - .../TabView/TabView.HeaderLayout.cs | 221 ------ .../TabView/TabView.ItemSources.cs | 115 --- .../TabView/TabView.Properties.cs | 245 ------ .../TabView/TabView.cs | 320 -------- .../TabView/TabView.xaml | 736 ------------------ .../TabView/TabViewItem.Properties.cs | 79 -- .../TabView/TabViewItem.cs | 116 --- .../TabView/TabWidthMode.cs | 36 - .../Themes/Generic.xaml | 2 - .../TileControl/TileControl.cs | 7 - .../VisualStudioToolsManifest.xml | 3 - .../Extensions/StringExtensions.cs | 8 - 41 files changed, 2779 deletions(-) delete mode 100644 Microsoft.Toolkit.Uwp.SampleApp/SamplePages/HeaderedTextBlock/HeaderedTextBlock.png delete mode 100644 Microsoft.Toolkit.Uwp.SampleApp/SamplePages/HeaderedTextBlock/HeaderedTextBlockCode.bind delete mode 100644 Microsoft.Toolkit.Uwp.SampleApp/SamplePages/HeaderedTextBlock/HeaderedTextBlockPage.xaml delete mode 100644 Microsoft.Toolkit.Uwp.SampleApp/SamplePages/HeaderedTextBlock/HeaderedTextBlockPage.xaml.cs delete mode 100644 Microsoft.Toolkit.Uwp.SampleApp/SamplePages/TabView/TabView.png delete mode 100644 Microsoft.Toolkit.Uwp.SampleApp/SamplePages/TabView/TabViewPage.xaml delete mode 100644 Microsoft.Toolkit.Uwp.SampleApp/SamplePages/TabView/TabViewPage.xaml.cs delete mode 100644 Microsoft.Toolkit.Uwp.SampleApp/SamplePages/TabView/TabViewXaml.bind delete mode 100644 Microsoft.Toolkit.Uwp.UI.Controls.Design/HeaderedTextBlockMetadata.cs delete mode 100644 Microsoft.Toolkit.Uwp.UI.Controls.Design/TabViewItemMetadata.cs delete mode 100644 Microsoft.Toolkit.Uwp.UI.Controls.Design/TabViewMetadata.cs delete mode 100644 Microsoft.Toolkit.Uwp.UI.Controls/HeaderedTextBlock/HeaderedTextBlock.Properties.cs delete mode 100644 Microsoft.Toolkit.Uwp.UI.Controls/HeaderedTextBlock/HeaderedTextBlock.cs delete mode 100644 Microsoft.Toolkit.Uwp.UI.Controls/HeaderedTextBlock/HeaderedTextBlock.xaml delete mode 100644 Microsoft.Toolkit.Uwp.UI.Controls/TabView/TabClosingEventArgs.cs delete mode 100644 Microsoft.Toolkit.Uwp.UI.Controls/TabView/TabDraggedOutsideEventArgs.cs delete mode 100644 Microsoft.Toolkit.Uwp.UI.Controls/TabView/TabView.HeaderLayout.cs delete mode 100644 Microsoft.Toolkit.Uwp.UI.Controls/TabView/TabView.ItemSources.cs delete mode 100644 Microsoft.Toolkit.Uwp.UI.Controls/TabView/TabView.Properties.cs delete mode 100644 Microsoft.Toolkit.Uwp.UI.Controls/TabView/TabView.cs delete mode 100644 Microsoft.Toolkit.Uwp.UI.Controls/TabView/TabView.xaml delete mode 100644 Microsoft.Toolkit.Uwp.UI.Controls/TabView/TabViewItem.Properties.cs delete mode 100644 Microsoft.Toolkit.Uwp.UI.Controls/TabView/TabViewItem.cs delete mode 100644 Microsoft.Toolkit.Uwp.UI.Controls/TabView/TabWidthMode.cs diff --git a/Microsoft.Toolkit.Services/Services/LinkedIn/LinkedInDataProvider.cs b/Microsoft.Toolkit.Services/Services/LinkedIn/LinkedInDataProvider.cs index f852688bc57..27c7739c333 100644 --- a/Microsoft.Toolkit.Services/Services/LinkedIn/LinkedInDataProvider.cs +++ b/Microsoft.Toolkit.Services/Services/LinkedIn/LinkedInDataProvider.cs @@ -154,17 +154,6 @@ public async Task LoginAsync() return false; } - /// - /// Log user out of LinkedIn. - /// - [Obsolete("Logout is deprecated, please use LogoutAsync instead.", true)] - public void Logout() - { -#pragma warning disable CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed - LogoutAsync(); -#pragma warning restore CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed - } - /// /// Log user out of LinkedIn. /// diff --git a/Microsoft.Toolkit.Services/Services/LinkedIn/LinkedInService.cs b/Microsoft.Toolkit.Services/Services/LinkedIn/LinkedInService.cs index 251e5fb711d..7a79b8fc344 100644 --- a/Microsoft.Toolkit.Services/Services/LinkedIn/LinkedInService.cs +++ b/Microsoft.Toolkit.Services/Services/LinkedIn/LinkedInService.cs @@ -92,15 +92,6 @@ public Task ShareActivityAsync(LinkedInShareRequest share return Provider.ShareDataAsync(shareRequest); } - /// - /// Log user out of LinkedIn. - /// - [Obsolete("Logout is deprecated, please use LogoutAsync instead.", true)] - public void Logout() - { - Provider.Logout(); - } - /// /// Log user out of LinkedIn. /// diff --git a/Microsoft.Toolkit.Services/Services/Twitter/TwitterDataProvider.cs b/Microsoft.Toolkit.Services/Services/Twitter/TwitterDataProvider.cs index a6eda59f07e..41db01d748b 100644 --- a/Microsoft.Toolkit.Services/Services/Twitter/TwitterDataProvider.cs +++ b/Microsoft.Toolkit.Services/Services/Twitter/TwitterDataProvider.cs @@ -273,17 +273,6 @@ public async Task LoginAsync() return false; } - /// - /// Log user out of Twitter. - /// - [Obsolete("Logout is deprecated, please use LogoutAsync instead.", true)] - public void Logout() - { -#pragma warning disable CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed - LogoutAsync(); -#pragma warning restore CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed - } - /// /// Log user out of Twitter. /// diff --git a/Microsoft.Toolkit.Services/Services/Twitter/TwitterService.cs b/Microsoft.Toolkit.Services/Services/Twitter/TwitterService.cs index 703657cf2fb..b461458ce8d 100644 --- a/Microsoft.Toolkit.Services/Services/Twitter/TwitterService.cs +++ b/Microsoft.Toolkit.Services/Services/Twitter/TwitterService.cs @@ -400,15 +400,6 @@ public Task LoginAsync() return Provider.LoginAsync(); } - /// - /// Log user out of Twitter. - /// - [Obsolete("Logout is deprecated, please use LogoutAsync instead.", true)] - public void Logout() - { - Provider.Logout(); - } - /// /// Log user out of Twitter. /// diff --git a/Microsoft.Toolkit.Services/Services/Weibo/WeiboDataProvider.cs b/Microsoft.Toolkit.Services/Services/Weibo/WeiboDataProvider.cs index 3dd6d59a8ac..6975a33d005 100644 --- a/Microsoft.Toolkit.Services/Services/Weibo/WeiboDataProvider.cs +++ b/Microsoft.Toolkit.Services/Services/Weibo/WeiboDataProvider.cs @@ -163,17 +163,6 @@ public async Task LoginAsync() return false; } - /// - /// Log user out of Weibo. - /// - [Obsolete("Logout is deprecated, please use LogoutAsync instead.", true)] - public void Logout() - { -#pragma warning disable CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed - LogoutAsync(); -#pragma warning restore CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed - } - /// /// Log user out of Weibo. /// diff --git a/Microsoft.Toolkit.Services/Services/Weibo/WeiboService.cs b/Microsoft.Toolkit.Services/Services/Weibo/WeiboService.cs index f5f8dd7ec99..ea2f6cea3b1 100644 --- a/Microsoft.Toolkit.Services/Services/Weibo/WeiboService.cs +++ b/Microsoft.Toolkit.Services/Services/Weibo/WeiboService.cs @@ -183,15 +183,6 @@ public Task LoginAsync() return Provider.LoginAsync(); } - /// - /// Log user out of Weibo. - /// - [Obsolete("Logout is deprecated, please use LogoutAsync instead.", true)] - public void Logout() - { - Provider.Logout(); - } - /// /// Log user out of Weibo. /// diff --git a/Microsoft.Toolkit.Uwp.SampleApp/Microsoft.Toolkit.Uwp.SampleApp.csproj b/Microsoft.Toolkit.Uwp.SampleApp/Microsoft.Toolkit.Uwp.SampleApp.csproj index 51c0fd428b1..5476e2d5c12 100644 --- a/Microsoft.Toolkit.Uwp.SampleApp/Microsoft.Toolkit.Uwp.SampleApp.csproj +++ b/Microsoft.Toolkit.Uwp.SampleApp/Microsoft.Toolkit.Uwp.SampleApp.csproj @@ -280,7 +280,6 @@ - @@ -317,7 +316,6 @@ - @@ -367,8 +365,6 @@ - - @@ -511,9 +507,6 @@ - - Designer - @@ -655,9 +648,6 @@ - - TabViewPage.xaml - UniformGridPage.xaml @@ -920,9 +910,6 @@ FadeBehaviorPage.xaml - - HeaderedTextBlockPage.xaml - ImageExPage.xaml @@ -1055,10 +1042,6 @@ Designer MSBuild:Compile - - MSBuild:Compile - Designer - MSBuild:Compile Designer @@ -1423,10 +1406,6 @@ MSBuild:Compile Designer - - Designer - MSBuild:Compile - MSBuild:Compile Designer diff --git a/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/HeaderedTextBlock/HeaderedTextBlock.png b/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/HeaderedTextBlock/HeaderedTextBlock.png deleted file mode 100644 index 4b495ab237ab617b406082956fa4df6764313695..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3308 zcmeH~**6=C7RE!&Lrtfps8CaTYN&%!Eipt@kydpu#nhIVrIa2af{KI|HIy1^o|V#| z#&Ag0P%+Q5LyRdWGMKM*|AV{MeYg*Iul3t|KYb71!?)MQJ%F0<^N8~R004f|yO4(f z0E_h5-p0jo)(gJ0)}0MDe?!wpTxS)+^&ATT@a3CA3?9AC+@AHtO42TM?bZ_&pUMlc zio3@h=)1=x=nuF=iKt-~BC$pCwht0Cu3TMhXMf{l43ZNQdh@pd#4?YK4<7#{Qs%v& zNJ02z+c(#+inVx`d(WiV)qW1^G5f#xfBlMVN}WBVc+aU?)91JPe1ih@s2RWdDE7b# zlSE;AJNxYW-cbeRgJqx>_4-4jy@15z@Bekg?02C>h|5THccD*?22UmI zbwaK!FtbuOkn44;^MrC+yHxb07glX%DTmXso0g#K@Er)LZ}tm=>AKK_ z_H^FFNs$+~FeR`+fi;hS+{J1mv$>dEhK3*Li%OHsMxWHc&@J9AHrLq0X!JNWUA->Q znfdlMQvc@MHI?3WDt^4UUz4w%B~1e5uL1q8hE_l~;aq%e>^`#VvFhc7%Ghwv7*r@L zhJQ{WpJT}ef>=`}iZr>sIHZ&uz`T;|sI{TiD4t&y#`F9{8n83{k`t!HGi2`iI~ruL zRg!dd;dp7c2%&21x2Nuyt`}+5ww!`E#?htvT^FoVZzQ)jB&~d974ys&)1S5pu?!S` z%`#z)41pmg*|#|6DsKk~Y$M0Zyu+<0vYk;}7A(I4)B1B}d2c^OdU+yBIe)}qoVA8d zJ}MQW$h|C61fT?tbfn~WYdf2YO7F0i^~&_n7ip7FYR+ka?6DnGr;JUB=8f-pWiNjd zrsW%?C_MTm_dDWw(Y>D!pG|{WJ>wWC!g0SyWJ^%ev)z&u|Ji8bt;{@ySKC9To=D; z@%-I41W!)12g42isvBIQTmOEI2*_kR;j-sEN-qI7-z^_|Sf&CA(lifK6|(nqcl4_q z`M7lOyu<%R)03fAr~XF{>L&e^6Jo#UnQl2_Cr~|1BtSDu0Iih$^+f&hoobE|())qO zw5Y0z_W>_~gQMA(Zxjcw4^}8S4C8Mi(yVzM6?j_BVE*npZM%o^K1NCZmYOsp|Ln1G zS;>9jkr}KXTaCFQ_q4-JqPk(|Q6${g~997JC(|!K8$_xdb*_Is>f~;#MYl*`^Cc9MbV`~SC z-@pvfdNMkD#KB*E_r)i+Pg{R{F0#G1@5wM-7(|KdNoN^S#Slkk#|hzuHEH{-YM>3x zoJq{Cx12{%3-b*4EvaiW~NYHy5BTs3#r1985Qm_qZU;sKUG7O$%Z)z9*r9we7n?X&+|VY5M3&6T_3O z@aNVtu24X5F}ffoUU(8H4z>tO8SHjFVk!P-0@xU{P291pu{^qp!H`Zn2Jc=+e<%4Y zxb>ZavuqZumUhBP@NJ94hc>{(WTJRN!EKI&0*(KaWpRBwEI|R5G@Bqlu;&m2P=p_= z;x!WQ$eYII z%;A?ogJ>E@oQUG$EkiG!<{y4W@EC3We|VOTcH;$|k?H%qlnV>W3%xFB-b5;A8iCU@ z4^A~BBCpK%8W9UYB!CtRp~~FXF?wBB3%&kI-R3PrWl55f5l#@yHoYj78cW_Fqvj30 zd|LQIfL_x5hfo9(xU&nGE|d9D$r5Td66OGyV}}Yk2L$Rb6Jo8KJ`{saB$;u{2oNWf zem!ox4@~@NsDO6EMXj#%EW3qggwG4*a{1xt2jlDuVwP`Ir-Q&>QmjkLG?T%@Vb%NB zR>#MC)@O~%eXO8WsMQgoc~yoYZr?K71i%?C5ZiTMIAh~JAWU9wSrKO#v_JV%NXBt) z?2m+5U<8v@7rF#rW7puRNiBjbfvd1Ncf&Cx%!?y)@5;XaCA5%MRl_f6MWx0 z7l1)IM*wVlH{(!}6fYr_A$=o~PM_*Yf%^ zdFvyF%(3`&=|D^Ooa4NCGr>76UjtNm-Bfb_7JS8_*ctlEF0Yk-rDgQ62p2(%?sRC{ z?wf}RSUMGV?Ygr)XP}F);%%sf&82EPtoQ&Qm4V5U3FcE3jOyiW3k?8c;Ov$IDo8Pc z()X6s#qFyNcPk3d@)d06$Ze;=5kjF5w}&b4wQGDT-?*rblDAA!Q5H_rU2r)tzwn1C(#X;J~ZVr({YkD zg6wo?IS;YBKdggVja@ppyo)+9+dMu3G!I>GjqOxfi^o0-hl(_<7hmjWt$Q?_A#1qe z+u01Zc++Enk6|X^pFA8*`JO?f1*}vjbu!#VElniGR!cel7I(2bJu(E|i&BPIR}@mE zG)qOxYmoZ3D2hzX5+=Sfjimr(E`$_}ch!O{%QSI)9>hmYT-8*GGZe^Z3(2r$~0rRsg07MSAnB+zwd#+H=^bD+VhE(_f9!uYP7w2 TO!KSG-WPzWF%(j1=oIlU(~cTl diff --git a/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/HeaderedTextBlock/HeaderedTextBlockCode.bind b/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/HeaderedTextBlock/HeaderedTextBlockCode.bind deleted file mode 100644 index a2db4b8dc80..00000000000 --- a/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/HeaderedTextBlock/HeaderedTextBlockCode.bind +++ /dev/null @@ -1,16 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/HeaderedTextBlock/HeaderedTextBlockPage.xaml b/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/HeaderedTextBlock/HeaderedTextBlockPage.xaml deleted file mode 100644 index a5795898a0e..00000000000 --- a/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/HeaderedTextBlock/HeaderedTextBlockPage.xaml +++ /dev/null @@ -1,9 +0,0 @@ - - - diff --git a/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/HeaderedTextBlock/HeaderedTextBlockPage.xaml.cs b/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/HeaderedTextBlock/HeaderedTextBlockPage.xaml.cs deleted file mode 100644 index 77cdba59f3e..00000000000 --- a/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/HeaderedTextBlock/HeaderedTextBlockPage.xaml.cs +++ /dev/null @@ -1,18 +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 Microsoft.Toolkit.Uwp.SampleApp.Models; - -using Windows.UI.Xaml.Navigation; - -namespace Microsoft.Toolkit.Uwp.SampleApp.SamplePages -{ - public sealed partial class HeaderedTextBlockPage - { - public HeaderedTextBlockPage() - { - InitializeComponent(); - } - } -} \ No newline at end of file diff --git a/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/TabView/TabView.png b/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/TabView/TabView.png deleted file mode 100644 index 9aeebd1d673de8670b59cb522700c93f1c75b887..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1392 zcmeAS@N?(olHy`uVBq!ia0y~yU{nCI4{)#n$t4^1Z39v)>5jgR3=A9lx&I`x0{NT; z9+AZi3_@Vc*rT+Q9VjSK;u=vBoS#-wo>-L1P>`6JSE84fTB2a0XQ*fNOZn_apgHe6 zT^vIy;@;jkn3<9;(sHr$a7C&>Me2+jB8``VnSxjjMP@u{{Ps+A*_%VZT#Kdr^5tW8 z?PLG@_VH)g2NUMU?`QbaU-$RJ?e_cje?B@kH25n!957^Jks+S?oXJ@AJU_4Ksap8< z*>{W?tq%CEyYu8#+2m?92lgE&r_PSKn^$z6vX!n@AaI&`f&Rut(%&YBXu(h{8E`O?i>b#Gwa0B+aHTQB0Y2Yh>nZ-My5VNg$EMEQ9q9}FxG6!zhBS5Tj$4m^~aMNz|xVy)78&qol`;+ E0K}H&HUIzs diff --git a/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/TabView/TabViewPage.xaml b/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/TabView/TabViewPage.xaml deleted file mode 100644 index 32f465f2c64..00000000000 --- a/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/TabView/TabViewPage.xaml +++ /dev/null @@ -1,19 +0,0 @@ - - - - - - - diff --git a/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/TabView/TabViewPage.xaml.cs b/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/TabView/TabViewPage.xaml.cs deleted file mode 100644 index 74d9a3db253..00000000000 --- a/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/TabView/TabViewPage.xaml.cs +++ /dev/null @@ -1,73 +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.ObjectModel; -using Microsoft.Toolkit.Uwp.UI.Controls; -using Microsoft.Toolkit.Uwp.UI.Extensions; -using Windows.UI.Popups; -using Windows.UI.Xaml; -using Windows.UI.Xaml.Controls; - -namespace Microsoft.Toolkit.Uwp.SampleApp.SamplePages -{ - public sealed partial class TabViewPage : Page, IXamlRenderListener - { - #pragma warning disable CS0618 // Type or member is obsolete - private TabView _tabs; - - private int _counter = 1; - - public TabViewPage() - { - this.InitializeComponent(); - } - - public void OnXamlRendered(FrameworkElement control) - { - _tabs = control.FindChildByName("Tabs") as TabView; - if (_tabs != null) - { - _tabs.TabDraggedOutside += Tabs_TabDraggedOutside; - _tabs.TabClosing += Tabs_TabClosing; - } - - var btn = control.FindDescendantByName("AddTabButtonUpper") as Button; - if (btn != null) - { - btn.Click += AddUpperTabClick; - } - } - - private void AddUpperTabClick(object sender, RoutedEventArgs e) - { - _tabs.Items.Add(new TabViewItem() - { - Header = "Untitled " + _counter, - Icon = new SymbolIcon(Symbol.Document), - Content = "This is new tab #" + _counter++ + "." - }); - } - - private void Tabs_TabClosing(object sender, TabClosingEventArgs e) - { - TabViewNotification.Show("You're closing the '" + e.Tab.Header + "' tab.", 2000); - } - - private void Tabs_TabDraggedOutside(object sender, TabDraggedOutsideEventArgs e) - { - // The sample app let's you drag items from a static TabView with TabViewItem's pre-defined. - // In the case of data bound scenarios e.Item should be your data item, and e.Tab should always be the TabViewItem. - var str = e.Item.ToString(); - - if (e.Tab != null) - { - str = e.Tab.Header.ToString(); - } - - TabViewNotification.Show("Tore Tab '" + str + "' Outside of TabView.", 2000); - } - #pragma warning restore CS0618 // Type or member is obsolete - } -} diff --git a/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/TabView/TabViewXaml.bind b/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/TabView/TabViewXaml.bind deleted file mode 100644 index e81732aa2a3..00000000000 --- a/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/TabView/TabViewXaml.bind +++ /dev/null @@ -1,97 +0,0 @@ - - - - - - - - - - 40 - 48 - 200 - - - - - - - - - - - The TabView control has multiple uses. - - - It has a lot of versitility out of the box for different scenarios. - - - You can enable drag-and-drop and reorder the tabs too. - - - This tab isn't closable because its IsClosable property is set to False, even when CanCloseTabs is True. - - - - - - - - - - - - - - - - - diff --git a/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/samples.json b/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/samples.json index f106b270277..2b8f564b7bb 100644 --- a/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/samples.json +++ b/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/samples.json @@ -14,18 +14,6 @@ "Icon": "/SamplePages/TextToolbar/TextToolbar.png", "DocumentationUrl": "https://raw.githubusercontent.com/MicrosoftDocs/WindowsCommunityToolkitDocs/master/docs/controls/TextToolbar.md" }, - { - "Name": "TabView", - "Type": "TabViewPage", - "Subcategory": "Layout", - "BadgeUpdateVersionRequired": "DEPRECATED", - "DeprecatedWarning": "Please migrate to the TabView control from WinUI, this control will be removed in a future release. https://aka.ms/winui", - "About": "A control for displaying multiple items in the same space and allows a user to easily switch between them.", - "CodeUrl": "https://github.com/windows-toolkit/WindowsCommunityToolkit/tree/master/Microsoft.Toolkit.Uwp.UI.Controls/TabView", - "XamlCodeFile": "TabViewXaml.bind", - "Icon": "/SamplePages/TabView/TabView.png", - "DocumentationUrl": "https://raw.githubusercontent.com/MicrosoftDocs/WindowsCommunityToolkitDocs/master/docs/controls/TabView.md" - }, { "Name": "DataGrid", "Type": "DataGridPage", @@ -86,18 +74,6 @@ "Icon": "/SamplePages/ImageEx/ImageEx.png", "DocumentationUrl": "https://raw.githubusercontent.com/MicrosoftDocs/WindowsCommunityToolkitDocs/master/docs/controls/ImageEx.md" }, - { - "Name": "HeaderedTextBlock", - "Type": "HeaderedTextBlockPage", - "Subcategory": "Layout", - "About": "The HeaderedTextBlock control is designed to provide a header for read only text. This control is useful for displaying read only forms.", - "CodeUrl": "https://github.com/windows-toolkit/WindowsCommunityToolkit/tree/master/Microsoft.Toolkit.Uwp.UI.Controls/HeaderedTextBlock", - "XamlCodeFile": "HeaderedTextBlockCode.bind", - "Icon": "/SamplePages/HeaderedTextBlock/HeaderedTextBlock.png", - "DocumentationUrl": "https://raw.githubusercontent.com/MicrosoftDocs/WindowsCommunityToolkitDocs/master/docs/controls/HeaderedTextBlock.md", - "BadgeUpdateVersionRequired": "DEPRECATED", - "DeprecatedWarning": "The HeaderedTextBlock has been replaced with the HeaderedContentControl and will be removed in a future major release." - }, { "Name": "MasterDetailsView", "Type": "MasterDetailsViewPage", diff --git a/Microsoft.Toolkit.Uwp.UI.Animations/ConnectedAnimations/Connected.cs b/Microsoft.Toolkit.Uwp.UI.Animations/ConnectedAnimations/Connected.cs index a0e5afa756d..999ea1d40c4 100644 --- a/Microsoft.Toolkit.Uwp.UI.Animations/ConnectedAnimations/Connected.cs +++ b/Microsoft.Toolkit.Uwp.UI.Animations/ConnectedAnimations/Connected.cs @@ -361,22 +361,6 @@ public static void UnregisterListItemForConnectedAnimation(this Page page, ListV } } - /// - /// Sets the object that will be used during next Frame navigation for - /// Connected Animation involving a list control (item must be an element of - /// ListViewBase.ItemsSource collection). - /// Useful if the parameter used during page navigation is different from the - /// data item in the list control. Also useful during back navigation if the - /// item navigating back to is different from the item that was navigated from. - /// - /// The Frame handling the navigation - /// The data item from a list control to be animated during next frame navigation - [Obsolete("Method is replaced by SetListDataItemForNextConnectedAnimation")] - public static void SetListDataItemForNextConnectedAnnimation(this Frame frame, object item) - { - GetConnectedAnimationHelper(frame)?.SetParameterForNextFrameNavigation(item); - } - /// /// Sets the object that will be used during next Frame navigation for /// Connected Animation involving a list control (item must be an element of diff --git a/Microsoft.Toolkit.Uwp.UI.Controls.Design/HeaderedTextBlockMetadata.cs b/Microsoft.Toolkit.Uwp.UI.Controls.Design/HeaderedTextBlockMetadata.cs deleted file mode 100644 index 3cba40e11f1..00000000000 --- a/Microsoft.Toolkit.Uwp.UI.Controls.Design/HeaderedTextBlockMetadata.cs +++ /dev/null @@ -1,49 +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 Microsoft.Toolkit.Uwp.UI.Controls.Design.Common; -using Microsoft.Windows.Design; -using Microsoft.Windows.Design.Features; -using Microsoft.Windows.Design.Metadata; -using Microsoft.Windows.Design.Model; -using Microsoft.Windows.Design.PropertyEditing; -using System.ComponentModel; - -namespace Microsoft.Toolkit.Uwp.UI.Controls.Design -{ - internal class HeaderedTextBlockDefaults : DefaultInitializer - { - public override void InitializeDefaults(ModelItem item) - { - item.Properties[nameof(HeaderedTextBlock.Header)].SetValue(string.Empty); - item.Properties[nameof(HeaderedTextBlock.Text)].SetValue(string.Empty); - } - } - internal class HeaderedTextBlockMetadata : AttributeTableBuilder - { - public HeaderedTextBlockMetadata() - : base() - { - AddCallback(typeof(Microsoft.Toolkit.Uwp.UI.Controls.HeaderedTextBlock), - b => - { - b.AddCustomAttributes(new FeatureAttribute(typeof(HeaderedTextBlockDefaults))); - b.AddCustomAttributes(nameof(HeaderedTextBlock.HeaderTemplate), - new CategoryAttribute(Properties.Resources.CategoryAppearance), - new EditorBrowsableAttribute(EditorBrowsableState.Advanced) - ); - b.AddCustomAttributes(nameof(HeaderedTextBlock.TextStyle), - new CategoryAttribute(Properties.Resources.CategoryAppearance), - new EditorBrowsableAttribute(EditorBrowsableState.Advanced) - ); - b.AddCustomAttributes(nameof(HeaderedTextBlock.Header), new CategoryAttribute(Properties.Resources.CategoryCommon)); - b.AddCustomAttributes(nameof(HeaderedTextBlock.Text), new CategoryAttribute(Properties.Resources.CategoryCommon)); - b.AddCustomAttributes(nameof(HeaderedTextBlock.Orientation), new CategoryAttribute(Properties.Resources.CategoryAppearance)); - b.AddCustomAttributes(nameof(HeaderedTextBlock.HideTextIfEmpty), new CategoryAttribute(Properties.Resources.CategoryAppearance)); - b.AddCustomAttributes(new ToolboxCategoryAttribute(ToolboxCategoryPaths.Toolkit, false)); - } - ); - } - } -} diff --git a/Microsoft.Toolkit.Uwp.UI.Controls.Design/Microsoft.Toolkit.Uwp.UI.Controls.Design.csproj b/Microsoft.Toolkit.Uwp.UI.Controls.Design/Microsoft.Toolkit.Uwp.UI.Controls.Design.csproj index b611399dd13..b7b241a6403 100644 --- a/Microsoft.Toolkit.Uwp.UI.Controls.Design/Microsoft.Toolkit.Uwp.UI.Controls.Design.csproj +++ b/Microsoft.Toolkit.Uwp.UI.Controls.Design/Microsoft.Toolkit.Uwp.UI.Controls.Design.csproj @@ -85,7 +85,6 @@ - @@ -93,7 +92,6 @@ - @@ -105,7 +103,6 @@ - Code diff --git a/Microsoft.Toolkit.Uwp.UI.Controls.Design/TabViewItemMetadata.cs b/Microsoft.Toolkit.Uwp.UI.Controls.Design/TabViewItemMetadata.cs deleted file mode 100644 index 6ed6bc37bf8..00000000000 --- a/Microsoft.Toolkit.Uwp.UI.Controls.Design/TabViewItemMetadata.cs +++ /dev/null @@ -1,43 +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 Microsoft.Toolkit.Uwp.UI.Controls.Design.Common; -using Microsoft.Windows.Design; -using Microsoft.Windows.Design.Features; -using Microsoft.Windows.Design.Metadata; -using Microsoft.Windows.Design.Model; -using Microsoft.Windows.Design.PropertyEditing; -using System.ComponentModel; - -namespace Microsoft.Toolkit.Uwp.UI.Controls.Design -{ - - internal class TabViewItemMetadata : AttributeTableBuilder - { - public TabViewItemMetadata() - : base() - { - AddCallback(typeof(TabViewItem), - b => - { - b.AddCustomAttributes(nameof(TabViewItem.Header), - new CategoryAttribute(Properties.Resources.CategoryCommon) - ); - b.AddCustomAttributes(nameof(TabViewItem.HeaderTemplate), - new CategoryAttribute(Properties.Resources.CategoryCommon), - new EditorBrowsableAttribute(EditorBrowsableState.Advanced) - ); - b.AddCustomAttributes(nameof(TabViewItem.IsClosable), - new CategoryAttribute(Properties.Resources.CategoryCommon) - ); - b.AddCustomAttributes(nameof(TabViewItem.Icon), - new CategoryAttribute(Properties.Resources.CategoryCommon) - ); - - b.AddCustomAttributes(new ToolboxCategoryAttribute(ToolboxCategoryPaths.Toolkit, false)); - } - ); - } - } -} \ No newline at end of file diff --git a/Microsoft.Toolkit.Uwp.UI.Controls.Design/TabViewMetadata.cs b/Microsoft.Toolkit.Uwp.UI.Controls.Design/TabViewMetadata.cs deleted file mode 100644 index 7bfa77541db..00000000000 --- a/Microsoft.Toolkit.Uwp.UI.Controls.Design/TabViewMetadata.cs +++ /dev/null @@ -1,54 +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 Microsoft.Toolkit.Uwp.UI.Controls.Design.Common; -using Microsoft.Windows.Design; -using Microsoft.Windows.Design.Metadata; -using Microsoft.Windows.Design.PropertyEditing; -using System.ComponentModel; - -namespace Microsoft.Toolkit.Uwp.UI.Controls.Design -{ - internal class TabViewMetadata : AttributeTableBuilder - { - public TabViewMetadata() - : base() - { - AddCallback(typeof(Microsoft.Toolkit.Uwp.UI.Controls.TabView), - b => - { - // Layout - b.AddCustomAttributes(nameof(TabView.TabWidthBehavior), new CategoryAttribute(Properties.Resources.CategoryLayout)); - b.AddCustomAttributes(nameof(TabView.SelectedTabWidth), new CategoryAttribute(Properties.Resources.CategoryLayout)); - b.AddCustomAttributes(nameof(TabView.IsCloseButtonOverlay), new CategoryAttribute(Properties.Resources.CategoryLayout)); - - // Interactions - b.AddCustomAttributes(nameof(TabView.CanCloseTabs), new CategoryAttribute(Properties.Resources.CategoryCommon)); - - // Templates - b.AddCustomAttributes(nameof(TabView.ItemHeaderTemplate), - new CategoryAttribute(Properties.Resources.CategoryCommon), - new EditorBrowsableAttribute(EditorBrowsableState.Advanced) - ); - b.AddCustomAttributes(nameof(TabView.TabActionHeader), new CategoryAttribute(Properties.Resources.CategoryCommon)); - b.AddCustomAttributes(nameof(TabView.TabActionHeaderTemplate), - new CategoryAttribute(Properties.Resources.CategoryCommon), - new EditorBrowsableAttribute(EditorBrowsableState.Advanced) - ); - b.AddCustomAttributes(nameof(TabView.TabEndHeader), new CategoryAttribute(Properties.Resources.CategoryCommon)); - b.AddCustomAttributes(nameof(TabView.TabEndHeaderTemplate), - new CategoryAttribute(Properties.Resources.CategoryCommon), - new EditorBrowsableAttribute(EditorBrowsableState.Advanced) - ); - b.AddCustomAttributes(nameof(TabView.TabStartHeader), new CategoryAttribute(Properties.Resources.CategoryCommon)); - b.AddCustomAttributes(nameof(TabView.TabStartHeaderTemplate), - new CategoryAttribute(Properties.Resources.CategoryCommon), - new EditorBrowsableAttribute(EditorBrowsableState.Advanced) - ); - b.AddCustomAttributes(new ToolboxCategoryAttribute(ToolboxCategoryPaths.Toolkit, false)); - } - ); - } - } -} diff --git a/Microsoft.Toolkit.Uwp.UI.Controls/HeaderedTextBlock/HeaderedTextBlock.Properties.cs b/Microsoft.Toolkit.Uwp.UI.Controls/HeaderedTextBlock/HeaderedTextBlock.Properties.cs deleted file mode 100644 index 7528591fc2b..00000000000 --- a/Microsoft.Toolkit.Uwp.UI.Controls/HeaderedTextBlock/HeaderedTextBlock.Properties.cs +++ /dev/null @@ -1,172 +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; -using Windows.UI.Xaml.Controls; - -namespace Microsoft.Toolkit.Uwp.UI.Controls -{ - /// - /// Defines the properties for the control. - /// - public partial class HeaderedTextBlock - { - /// - /// Defines the dependency property. - /// - public static readonly DependencyProperty HeaderTemplateProperty = DependencyProperty.Register( - nameof(HeaderTemplate), - typeof(DataTemplate), - typeof(HeaderedTextBlock), - new PropertyMetadata(null)); - - /// - /// Defines the dependency property. - /// - public static readonly DependencyProperty TextStyleProperty = DependencyProperty.Register( - nameof(TextStyle), - typeof(Style), - typeof(HeaderedTextBlock), - new PropertyMetadata(null)); - - /// - /// Defines the dependency property. - /// - public static readonly DependencyProperty HeaderProperty = DependencyProperty.Register( - nameof(Header), - typeof(string), - typeof(HeaderedTextBlock), - new PropertyMetadata(null, (d, e) => { ((HeaderedTextBlock)d).UpdateVisibility(); })); - - /// - /// Defines the dependency property. - /// - public static readonly DependencyProperty TextProperty = DependencyProperty.Register( - nameof(Text), - typeof(string), - typeof(HeaderedTextBlock), - new PropertyMetadata(null, (d, e) => { ((HeaderedTextBlock)d).UpdateVisibility(); })); - - /// - /// Defines the dependency property. - /// - public static readonly DependencyProperty OrientationProperty = DependencyProperty.Register( - nameof(Orientation), - typeof(Orientation), - typeof(HeaderedTextBlock), - new PropertyMetadata(Orientation.Vertical, (d, e) => { ((HeaderedTextBlock)d).UpdateForOrientation((Orientation)e.NewValue); })); - - /// - /// Defines the dependency property. - /// - public static readonly DependencyProperty HideTextIfEmptyProperty = DependencyProperty.Register( - nameof(HideTextIfEmpty), - typeof(bool), - typeof(HeaderedTextBlock), - new PropertyMetadata(false, (d, e) => { ((HeaderedTextBlock)d).UpdateVisibility(); })); - - /// - /// Gets or sets the header style. - /// - public DataTemplate HeaderTemplate - { - get - { - return (DataTemplate)GetValue(HeaderTemplateProperty); - } - - set - { - SetValue(HeaderTemplateProperty, value); - } - } - - /// - /// Gets or sets the text style. - /// - public Style TextStyle - { - get - { - return (Style)GetValue(TextStyleProperty); - } - - set - { - SetValue(TextStyleProperty, value); - } - } - - /// - /// Gets or sets the header. - /// - public string Header - { - get - { - return (string)GetValue(HeaderProperty); - } - - set - { - SetValue(HeaderProperty, value); - } - } - - /// - /// Gets or sets the text. - /// - public string Text - { - get - { - return (string)GetValue(TextProperty); - } - - set - { - SetValue(TextProperty, value); - } - } - - /// - /// Gets the collection of inline text elements within a Windows.UI.Xaml.Controls.TextBlock. - /// - /// - /// A collection that holds all inline text elements from the Windows.UI.Xaml.Controls.TextBlock. The default is an empty collection. - public InlineCollectionWrapper Inlines { get; } = new InlineCollectionWrapper(); - - /// - /// Gets or sets the orientation. - /// - public Orientation Orientation - { - get - { - return (Orientation)GetValue(OrientationProperty); - } - - set - { - SetValue(OrientationProperty, value); - } - } - - /// - /// Gets or sets a value indicating whether the Text TextBlock is hidden if its value is empty - /// - public bool HideTextIfEmpty - { - get - { - return (bool)GetValue(HideTextIfEmptyProperty); - } - - set - { - SetValue(HideTextIfEmptyProperty, value); - } - } - } -} \ No newline at end of file diff --git a/Microsoft.Toolkit.Uwp.UI.Controls/HeaderedTextBlock/HeaderedTextBlock.cs b/Microsoft.Toolkit.Uwp.UI.Controls/HeaderedTextBlock/HeaderedTextBlock.cs deleted file mode 100644 index b30dfea1d3d..00000000000 --- a/Microsoft.Toolkit.Uwp.UI.Controls/HeaderedTextBlock/HeaderedTextBlock.cs +++ /dev/null @@ -1,76 +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 Windows.UI.Xaml; -using Windows.UI.Xaml.Controls; -using Windows.UI.Xaml.Markup; - -namespace Microsoft.Toolkit.Uwp.UI.Controls -{ - /// - /// Defines a control for providing a header for read-only text. - /// - [TemplatePart(Name = "HeaderContentPresenter", Type = typeof(ContentPresenter))] - [ContentProperty(Name = nameof(Inlines))] - [Obsolete("The HeaderedTextBlock has been replaced with the more generic HeaderedContentControl.")] - public partial class HeaderedTextBlock : Control - { - private ContentPresenter _headerContentPresenter; - private TextBlock _textContent; - - /// - /// Initializes a new instance of the class. - /// - public HeaderedTextBlock() - { - DefaultStyleKey = typeof(HeaderedTextBlock); - } - - /// - /// Called when applying the control template. - /// - protected override void OnApplyTemplate() - { - base.OnApplyTemplate(); - - _headerContentPresenter = GetTemplateChild("HeaderContentPresenter") as ContentPresenter; - _textContent = GetTemplateChild("TextContent") as TextBlock; - - UpdateVisibility(); - Inlines.AddItemsToTextBlock(_textContent); - UpdateForOrientation(this.Orientation); - } - - private void UpdateVisibility() - { - if (_headerContentPresenter != null) - { - _headerContentPresenter.Visibility = _headerContentPresenter.Content == null - ? Visibility.Collapsed - : Visibility.Visible; - } - - if (_textContent != null) - { - _textContent.Visibility = string.IsNullOrWhiteSpace(_textContent.Text) && HideTextIfEmpty - ? Visibility.Collapsed - : Visibility.Visible; - } - } - - private void UpdateForOrientation(Orientation orientationValue) - { - switch (orientationValue) - { - case Orientation.Vertical: - VisualStateManager.GoToState(this, "Vertical", true); - break; - case Orientation.Horizontal: - VisualStateManager.GoToState(this, "Horizontal", true); - break; - } - } - } -} \ No newline at end of file diff --git a/Microsoft.Toolkit.Uwp.UI.Controls/HeaderedTextBlock/HeaderedTextBlock.xaml b/Microsoft.Toolkit.Uwp.UI.Controls/HeaderedTextBlock/HeaderedTextBlock.xaml deleted file mode 100644 index 6f94a07ba82..00000000000 --- a/Microsoft.Toolkit.Uwp.UI.Controls/HeaderedTextBlock/HeaderedTextBlock.xaml +++ /dev/null @@ -1,60 +0,0 @@ - - - - - - - \ No newline at end of file diff --git a/Microsoft.Toolkit.Uwp.UI.Controls/Microsoft.Toolkit.Uwp.UI.Controls.csproj b/Microsoft.Toolkit.Uwp.UI.Controls/Microsoft.Toolkit.Uwp.UI.Controls.csproj index 6174415e0f3..9b240477475 100644 --- a/Microsoft.Toolkit.Uwp.UI.Controls/Microsoft.Toolkit.Uwp.UI.Controls.csproj +++ b/Microsoft.Toolkit.Uwp.UI.Controls/Microsoft.Toolkit.Uwp.UI.Controls.csproj @@ -17,7 +17,6 @@ - GridSplitter: A the control that redistributes space between columns or rows of a Grid control. - HeaderedContentControl: Provides a header to content. - HeaderedItemsControl: Provides a header to items. - - HeaderedTextBlock: Provide a header for read-only text. - ImageCropper: ImageCropper control allows user to crop image freely. - ImageEx: Images are downloaded asynchronously showing a load indicator and can be stored in a local cache. - InAppNotification: Show local notifications in your application. diff --git a/Microsoft.Toolkit.Uwp.UI.Controls/Microsoft.Toolkit.Uwp.UI.Controls.csproj.DotSettings b/Microsoft.Toolkit.Uwp.UI.Controls/Microsoft.Toolkit.Uwp.UI.Controls.csproj.DotSettings index 0dd5cea5b9e..8a263d98309 100644 --- a/Microsoft.Toolkit.Uwp.UI.Controls/Microsoft.Toolkit.Uwp.UI.Controls.csproj.DotSettings +++ b/Microsoft.Toolkit.Uwp.UI.Controls/Microsoft.Toolkit.Uwp.UI.Controls.csproj.DotSettings @@ -5,7 +5,6 @@ True True True - True True True True diff --git a/Microsoft.Toolkit.Uwp.UI.Controls/Strings/en-US/Resources.resw b/Microsoft.Toolkit.Uwp.UI.Controls/Strings/en-US/Resources.resw index 66cce5b66ab..d3b003e8b80 100644 --- a/Microsoft.Toolkit.Uwp.UI.Controls/Strings/en-US/Resources.resw +++ b/Microsoft.Toolkit.Uwp.UI.Controls/Strings/en-US/Resources.resw @@ -213,14 +213,6 @@ Notification The landmark name for the InAppNotification control. It is said by the narrator when using landmark navigation. - - Close tab - Narrator Resource for TabView Close Button. - - - Close tab - Tooltip for TabView Close Button. - Remove Label for TokenizingTextBox MenuFlyout 'Remove' option. diff --git a/Microsoft.Toolkit.Uwp.UI.Controls/TabView/TabClosingEventArgs.cs b/Microsoft.Toolkit.Uwp.UI.Controls/TabView/TabClosingEventArgs.cs deleted file mode 100644 index 893776ff0be..00000000000 --- a/Microsoft.Toolkit.Uwp.UI.Controls/TabView/TabClosingEventArgs.cs +++ /dev/null @@ -1,36 +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.ComponentModel; - -namespace Microsoft.Toolkit.Uwp.UI.Controls -{ - /// - /// Event arguments for event. - /// - public class TabClosingEventArgs : CancelEventArgs - { - /// - /// Initializes a new instance of the class. - /// - /// Item being closed. - /// container being closed. - public TabClosingEventArgs(object item, TabViewItem tab) - { - Item = item; - Tab = tab; - } - - /// - /// Gets the Item being closed. - /// - public object Item { get; private set; } - - /// - /// Gets the Tab being closed. - /// - public TabViewItem Tab { get; private set; } - } -} \ No newline at end of file diff --git a/Microsoft.Toolkit.Uwp.UI.Controls/TabView/TabDraggedOutsideEventArgs.cs b/Microsoft.Toolkit.Uwp.UI.Controls/TabView/TabDraggedOutsideEventArgs.cs deleted file mode 100644 index dcf351aab9b..00000000000 --- a/Microsoft.Toolkit.Uwp.UI.Controls/TabView/TabDraggedOutsideEventArgs.cs +++ /dev/null @@ -1,35 +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; - -namespace Microsoft.Toolkit.Uwp.UI.Controls -{ - /// - /// A class used by the TabDraggedOutside Event - /// - public class TabDraggedOutsideEventArgs : EventArgs - { - /// - /// Initializes a new instance of the class. - /// - /// data context of element dragged - /// container being dragged. - public TabDraggedOutsideEventArgs(object item, TabViewItem tab) - { - Item = item; - Tab = tab; - } - - /// - /// Gets or sets the Item/Data Context of the item being dragged outside of the . - /// - public object Item { get; set; } - - /// - /// Gets the Tab being dragged outside of the . - /// - public TabViewItem Tab { get; private set; } - } -} diff --git a/Microsoft.Toolkit.Uwp.UI.Controls/TabView/TabView.HeaderLayout.cs b/Microsoft.Toolkit.Uwp.UI.Controls/TabView/TabView.HeaderLayout.cs deleted file mode 100644 index 16885700b35..00000000000 --- a/Microsoft.Toolkit.Uwp.UI.Controls/TabView/TabView.HeaderLayout.cs +++ /dev/null @@ -1,221 +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.Linq; -using Windows.UI.Xaml; - -namespace Microsoft.Toolkit.Uwp.UI.Controls -{ - /// - /// TabView methods related to calculating the width of the Headers. - /// - public partial class TabView - { - // Attached property for storing widths of tabs if set by other means during layout. - private static double GetOriginalWidth(TabViewItem obj) - { - return (double)obj.GetValue(OriginalWidthProperty); - } - - private static void SetOriginalWidth(TabViewItem obj, double value) - { - obj.SetValue(OriginalWidthProperty, value); - } - - // Using a DependencyProperty as the backing store for MyProperty. This enables animation, styling, binding, etc... - private static readonly DependencyProperty OriginalWidthProperty = - DependencyProperty.RegisterAttached("OriginalWidth", typeof(double), typeof(TabView), new PropertyMetadata(null)); - - private static void OnLayoutEffectingPropertyChanged(DependencyObject sender, DependencyPropertyChangedEventArgs args) - { - var tabview = sender as TabView; - if (tabview != null && tabview._hasLoaded) - { - tabview.TabView_SizeChanged(tabview, null); - } - } - - private void TabView_SizeChanged(object sender, SizeChangedEventArgs e) - { - // We need to do this calculation here in Size Changed as the - // Columns don't have their Actual Size calculated in Measure or Arrange. - if (_hasLoaded && _tabViewContainer != null) - { - // Look for our special columns to calculate size of other 'stuff' - var taken = _tabViewContainer.ColumnDefinitions.Sum(cd => GetIgnoreColumn(cd) ? 0 : cd.ActualWidth); - - // Get the column we want to work on for available space - var tabc = _tabViewContainer.ColumnDefinitions.FirstOrDefault(cd => GetConstrainColumn(cd)); - if (tabc != null) - { - var available = ActualWidth - taken; - var required = 0.0; - var mintabwidth = double.MaxValue; - - if (TabWidthBehavior == TabWidthMode.Actual) - { - if (_tabScroller != null) - { - // If we have a scroll container, get its size. - required = _tabScroller.ExtentWidth; - } - - // Restore original widths - foreach (var item in Items) - { - var tab = ContainerFromItem(item) as TabViewItem; - if (tab == null) - { - continue; // container not generated yet - } - - if (tab.ReadLocalValue(OriginalWidthProperty) != DependencyProperty.UnsetValue) - { - tab.Width = GetOriginalWidth(tab); - } - } - } - else if (available > 0) - { - // Calculate the width for each tab from the provider and determine how much space they take. - foreach (var item in Items) - { - var tab = ContainerFromItem(item) as TabViewItem; - if (tab == null) - { - continue; // container not generated yet - } - - mintabwidth = Math.Min(mintabwidth, tab.MinWidth); - - double width = double.NaN; - - switch (TabWidthBehavior) - { - case TabWidthMode.Equal: - width = ProvideEqualWidth(tab, item, available); - break; - case TabWidthMode.Compact: - width = ProvideCompactWidth(tab, item, available); - break; - } - - if (tab.ReadLocalValue(OriginalWidthProperty) == DependencyProperty.UnsetValue) - { - SetOriginalWidth(tab, tab.Width); - } - - if (width > double.Epsilon) - { - tab.Width = width; - required += Math.Max(Math.Min(width, tab.MaxWidth), tab.MinWidth); - } - else - { - tab.Width = GetOriginalWidth(tab); - required += tab.ActualWidth; - } - } - } - else - { - // Fix negative bounds. - available = 0.0; - - // Still need to determine a 'minimum' width (if available) - // TODO: Consolidate this logic with above better? - foreach (var item in Items) - { - var tab = ContainerFromItem(item) as TabViewItem; - if (tab == null) - { - continue; // container not generated yet - } - - mintabwidth = Math.Min(mintabwidth, tab.MinWidth); - } - } - - if (!(mintabwidth < double.MaxValue)) - { - mintabwidth = 0.0; // No Containers, no visual, 0 size. - } - - if (available > mintabwidth) - { - // Constrain the column based on our required and available space - tabc.MaxWidth = available; - } - - //// TODO: If it's less, should we move the selected tab to only be the one shown by default? - - if (available <= mintabwidth || Math.Abs(available - mintabwidth) < double.Epsilon) - { - tabc.Width = new GridLength(mintabwidth); - } - else if (required >= available) - { - // Fix size as we don't have enough space for all the tabs. - tabc.Width = new GridLength(available); - } - else - { - // We haven't filled up our space, so we want to expand to take as much as needed. - tabc.Width = GridLength.Auto; - } - } - } - } - - private double ProvideEqualWidth(TabViewItem tab, object item, double availableWidth) - { - if (double.IsNaN(SelectedTabWidth)) - { - if (Items.Count <= 1) - { - return availableWidth; - } - - return Math.Max(tab.MinWidth, availableWidth / Items.Count); - } - else if (Items.Count() <= 1) - { - // Default case of a single tab, make it full size. - return Math.Min(SelectedTabWidth, availableWidth); - } - else - { - var width = (availableWidth - SelectedTabWidth) / (Items.Count - 1); - - // Constrain between Min and Selected (Max) - if (width < tab.MinWidth) - { - width = tab.MinWidth; - } - else if (width > SelectedTabWidth) - { - width = SelectedTabWidth; - } - - // If it's selected make it full size, otherwise whatever the size should be. - return tab.IsSelected - ? Math.Min(SelectedTabWidth, availableWidth) - : width; - } - } - - private double ProvideCompactWidth(TabViewItem tab, object item, double availableWidth) - { - // If we're selected and have a value for that, then just return that. - if (tab.IsSelected && !double.IsNaN(SelectedTabWidth)) - { - return SelectedTabWidth; - } - - // Otherwise use min size. - return tab.MinWidth; - } - } -} diff --git a/Microsoft.Toolkit.Uwp.UI.Controls/TabView/TabView.ItemSources.cs b/Microsoft.Toolkit.Uwp.UI.Controls/TabView/TabView.ItemSources.cs deleted file mode 100644 index 61f61623b6f..00000000000 --- a/Microsoft.Toolkit.Uwp.UI.Controls/TabView/TabView.ItemSources.cs +++ /dev/null @@ -1,115 +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.Reflection; -using Windows.Foundation.Collections; -using Windows.UI.Xaml; -using Windows.UI.Xaml.Controls.Primitives; - -namespace Microsoft.Toolkit.Uwp.UI.Controls -{ - /// - /// TabView methods related to tracking Items and ItemsSource changes. - /// - public partial class TabView - { - // Temporary tracking of previous collections for removing events. - private MethodInfo _removeItemsSourceMethod; - - /// - protected override void OnItemsChanged(object e) - { - IVectorChangedEventArgs args = (IVectorChangedEventArgs)e; - - base.OnItemsChanged(e); - - if (args.CollectionChange == CollectionChange.ItemRemoved && SelectedIndex == -1) - { - // If we remove the selected item we should select the previous item - int startIndex = (int)args.Index + 1; - if (startIndex > Items.Count) - { - startIndex = 0; - } - - SelectedIndex = FindNextTabIndex(startIndex, -1); - } - - // Update Sizing (in case there are less items now) - TabView_SizeChanged(this, null); - } - - private void ItemContainerGenerator_ItemsChanged(object sender, ItemsChangedEventArgs e) - { - var action = (CollectionChange)e.Action; - if (action == CollectionChange.Reset) - { - // Reset collection to reload later. - _hasLoaded = false; - } - } - - private void SetInitialSelection() - { - if (SelectedItem == null) - { - // If we have an index, but didn't get the selection, make the selection - if (SelectedIndex >= 0 && SelectedIndex < Items.Count) - { - SelectedItem = Items[SelectedIndex]; - } - - // Otherwise, select the first item by default - else if (Items.Count >= 1) - { - SelectedItem = Items[0]; - } - } - } - - // Finds the next visible & enabled tab index. - private int FindNextTabIndex(int startIndex, int direction) - { - int index = startIndex; - if (direction != 0) - { - for (int i = 0; i < Items.Count; i++) - { - index += direction; - - if (index >= Items.Count) - { - index = 0; - } - else if (index < 0) - { - index = Items.Count - 1; - } - - var tabItem = ContainerFromIndex(index) as TabViewItem; - if (tabItem != null && tabItem.IsEnabled && tabItem.Visibility == Visibility.Visible) - { - break; - } - } - } - - return index; - } - - private void ItemsSource_PropertyChanged(DependencyObject sender, DependencyProperty dp) - { - // Use reflection to store a 'Remove' method of any possible collection in ItemsSource - // Cache for efficiency later. - if (ItemsSource != null) - { - _removeItemsSourceMethod = ItemsSource.GetType().GetMethod("Remove"); - } - else - { - _removeItemsSourceMethod = null; - } - } - } -} diff --git a/Microsoft.Toolkit.Uwp.UI.Controls/TabView/TabView.Properties.cs b/Microsoft.Toolkit.Uwp.UI.Controls/TabView/TabView.Properties.cs deleted file mode 100644 index 5d33b33f1cc..00000000000 --- a/Microsoft.Toolkit.Uwp.UI.Controls/TabView/TabView.Properties.cs +++ /dev/null @@ -1,245 +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; -using Windows.UI.Xaml.Controls; - -namespace Microsoft.Toolkit.Uwp.UI.Controls -{ - /// - /// TabView properties. - /// - public partial class TabView - { - /// - /// Gets or sets the content to appear to the left or above the tab strip. - /// - public object TabStartHeader - { - get { return (object)GetValue(TabStartHeaderProperty); } - set { SetValue(TabStartHeaderProperty, value); } - } - - /// - /// Identifies the dependency property. - /// - /// The identifier for the dependency property. - public static readonly DependencyProperty TabStartHeaderProperty = - DependencyProperty.Register(nameof(TabStartHeader), typeof(object), typeof(TabView), new PropertyMetadata(null, OnLayoutEffectingPropertyChanged)); - - /// - /// Gets or sets the for the . - /// - public DataTemplate TabStartHeaderTemplate - { - get { return (DataTemplate)GetValue(TabStartHeaderTemplateProperty); } - set { SetValue(TabStartHeaderTemplateProperty, value); } - } - - /// - /// Identifies the dependency property. - /// - /// The identifier for the dependency property. - public static readonly DependencyProperty TabStartHeaderTemplateProperty = - DependencyProperty.Register(nameof(TabStartHeaderTemplate), typeof(DataTemplate), typeof(TabView), new PropertyMetadata(null, OnLayoutEffectingPropertyChanged)); - - /// - /// Gets or sets the content to appear next to the tab strip. - /// - public object TabActionHeader - { - get { return (object)GetValue(TabActionHeaderProperty); } - set { SetValue(TabActionHeaderProperty, value); } - } - - /// - /// Identifies the dependency property. - /// - /// The identifier for the dependency property. - public static readonly DependencyProperty TabActionHeaderProperty = - DependencyProperty.Register(nameof(TabActionHeader), typeof(object), typeof(TabView), new PropertyMetadata(null, OnLayoutEffectingPropertyChanged)); - - /// - /// Gets or sets the for the . - /// - public DataTemplate TabActionHeaderTemplate - { - get { return (DataTemplate)GetValue(TabActionHeaderTemplateProperty); } - set { SetValue(TabActionHeaderTemplateProperty, value); } - } - - /// - /// Identifies the dependency property. - /// - /// The identifier for the dependency property. - public static readonly DependencyProperty TabActionHeaderTemplateProperty = - DependencyProperty.Register(nameof(TabActionHeaderTemplate), typeof(DataTemplate), typeof(TabView), new PropertyMetadata(null, OnLayoutEffectingPropertyChanged)); - - /// - /// Gets or sets the content to appear to the right or below the tab strip. - /// - public object TabEndHeader - { - get { return (object)GetValue(TabEndHeaderProperty); } - set { SetValue(TabEndHeaderProperty, value); } - } - - /// - /// Identifies the dependency property. - /// - /// The identifier for the dependency property. - public static readonly DependencyProperty TabEndHeaderProperty = - DependencyProperty.Register(nameof(TabEndHeader), typeof(object), typeof(TabView), new PropertyMetadata(null, OnLayoutEffectingPropertyChanged)); - - /// - /// Gets or sets the for the . - /// - public DataTemplate TabEndHeaderTemplate - { - get { return (DataTemplate)GetValue(TabEndHeaderTemplateProperty); } - set { SetValue(TabEndHeaderTemplateProperty, value); } - } - - /// - /// Identifies the dependency property. - /// - /// The identifier for the dependency property. - public static readonly DependencyProperty TabEndHeaderTemplateProperty = - DependencyProperty.Register(nameof(TabEndHeaderTemplate), typeof(DataTemplate), typeof(TabView), new PropertyMetadata(null, OnLayoutEffectingPropertyChanged)); - - /// - /// Gets or sets the default for the . - /// - public DataTemplate ItemHeaderTemplate - { - get { return (DataTemplate)GetValue(ItemHeaderTemplateProperty); } - set { SetValue(ItemHeaderTemplateProperty, value); } - } - - /// - /// Identifies the dependency property. - /// - /// The identifier for the dependency property. - public static readonly DependencyProperty ItemHeaderTemplateProperty = - DependencyProperty.Register(nameof(ItemHeaderTemplate), typeof(DataTemplate), typeof(TabView), new PropertyMetadata(null, OnLayoutEffectingPropertyChanged)); - - /// - /// Gets or sets a value indicating whether by default a Tab can be closed or not if no value to is provided. - /// - public bool CanCloseTabs - { - get { return (bool)GetValue(CanCloseTabsProperty); } - set { SetValue(CanCloseTabsProperty, value); } - } - - /// - /// Identifies the dependency property. - /// - /// The identifier for the dependency property. - public static readonly DependencyProperty CanCloseTabsProperty = - DependencyProperty.Register(nameof(CanCloseTabs), typeof(bool), typeof(TabView), new PropertyMetadata(false, OnLayoutEffectingPropertyChanged)); - - /// - /// Gets or sets a value indicating whether a Close Button should be included in layout calculations. - /// - public bool IsCloseButtonOverlay - { - get { return (bool)GetValue(IsCloseButtonOverlayProperty); } - set { SetValue(IsCloseButtonOverlayProperty, value); } - } - - /// - /// Identifies the dependency property. - /// - /// The identifier for the dependency property. - public static readonly DependencyProperty IsCloseButtonOverlayProperty = - DependencyProperty.Register(nameof(IsCloseButtonOverlay), typeof(bool), typeof(TabView), new PropertyMetadata(false, OnLayoutEffectingPropertyChanged)); - - /// - /// Gets or sets a value indicating the size of the selected tab. By default this is set to Auto and the selected tab size doesn't change. - /// - public double SelectedTabWidth - { - get { return (double)GetValue(SelectedTabWidthProperty); } - set { SetValue(SelectedTabWidthProperty, value); } - } - - /// - /// Identifies the dependency property. - /// - /// The identifier for the dependency property. - public static readonly DependencyProperty SelectedTabWidthProperty = - DependencyProperty.Register(nameof(SelectedTabWidth), typeof(double), typeof(TabView), new PropertyMetadata(double.NaN, OnLayoutEffectingPropertyChanged)); - - /// - /// Gets or sets the current which determins how tab headers' width behave. - /// - public TabWidthMode TabWidthBehavior - { - get { return (TabWidthMode)GetValue(TabWidthBehaviorProperty); } - set { SetValue(TabWidthBehaviorProperty, value); } - } - - /// - /// Identifies the dependency property. - /// - /// The identifier for the dependency property. - public static readonly DependencyProperty TabWidthBehaviorProperty = - DependencyProperty.Register(nameof(TabWidthBehavior), typeof(TabWidthMode), typeof(TabView), new PropertyMetadata(TabWidthMode.Actual, OnLayoutEffectingPropertyChanged)); - - /// - /// Gets the attached property value to indicate if this grid column should be ignored when calculating header sizes. - /// - /// Grid Column. - /// Boolean indicating if this column is ignored by TabViewHeader logic. - public static bool GetIgnoreColumn(ColumnDefinition obj) - { - return (bool)obj.GetValue(IgnoreColumnProperty); - } - - /// - /// Sets the attached property value for - /// - /// Grid Column. - /// Boolean value - public static void SetIgnoreColumn(ColumnDefinition obj, bool value) - { - obj.SetValue(IgnoreColumnProperty, value); - } - - /// - /// Identifies the attached property. - /// - /// The identifier for the IgnoreColumn attached property. - public static readonly DependencyProperty IgnoreColumnProperty = - DependencyProperty.RegisterAttached("IgnoreColumn", typeof(bool), typeof(TabView), new PropertyMetadata(false)); - - /// - /// Gets the attached value indicating this column should be restricted for the headers. - /// - /// Grid Column. - /// True if this column should be constrained. - public static bool GetConstrainColumn(ColumnDefinition obj) - { - return (bool)obj.GetValue(ConstrainColumnProperty); - } - - /// - /// Sets the attached property value for the - /// - /// Grid Column. - /// Boolean value. - public static void SetConstrainColumn(ColumnDefinition obj, bool value) - { - obj.SetValue(ConstrainColumnProperty, value); - } - - /// - /// Identifies the attached property. - /// - /// The identifier for the ConstrainColumn attached property. - public static readonly DependencyProperty ConstrainColumnProperty = - DependencyProperty.RegisterAttached("ConstrainColumn", typeof(bool), typeof(TabView), new PropertyMetadata(false)); - } -} diff --git a/Microsoft.Toolkit.Uwp.UI.Controls/TabView/TabView.cs b/Microsoft.Toolkit.Uwp.UI.Controls/TabView/TabView.cs deleted file mode 100644 index 0e86ab398f5..00000000000 --- a/Microsoft.Toolkit.Uwp.UI.Controls/TabView/TabView.cs +++ /dev/null @@ -1,320 +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.ComponentModel; -using System.Linq; -using Microsoft.Toolkit.Uwp.UI.Extensions; -using Windows.ApplicationModel.DataTransfer; -using Windows.UI.Xaml; -using Windows.UI.Xaml.Controls; -using Windows.UI.Xaml.Controls.Primitives; -using Windows.UI.Xaml.Data; - -namespace Microsoft.Toolkit.Uwp.UI.Controls -{ - /// - /// TabView is a control for displaying a set of tabs and their content. - /// - [Obsolete("Please migrate to the TabView control from WinUI, this control will be removed in a future release. https://aka.ms/winui")] - [TemplatePart(Name = TabContentPresenterName, Type = typeof(ContentPresenter))] - [TemplatePart(Name = TabViewContainerName, Type = typeof(Grid))] - [TemplatePart(Name = TabsItemsPresenterName, Type = typeof(ItemsPresenter))] - [TemplatePart(Name = TabsScrollViewerName, Type = typeof(ScrollViewer))] - [TemplatePart(Name = TabsScrollBackButtonName, Type = typeof(ButtonBase))] - [TemplatePart(Name = TabsScrollForwardButtonName, Type = typeof(ButtonBase))] - public partial class TabView : ListViewBase - { - private const int ScrollAmount = 50; // TODO: Should this be based on TabWidthMode - - private const string TabContentPresenterName = "TabContentPresenter"; - private const string TabViewContainerName = "TabViewContainer"; - private const string TabsItemsPresenterName = "TabsItemsPresenter"; - private const string TabsScrollViewerName = "ScrollViewer"; - private const string TabsScrollBackButtonName = "ScrollBackButton"; - private const string TabsScrollForwardButtonName = "ScrollForwardButton"; - - private ContentPresenter _tabContentPresenter; - private Grid _tabViewContainer; - private ItemsPresenter _tabItemsPresenter; - private ScrollViewer _tabScroller; - private ButtonBase _tabScrollBackButton; - private ButtonBase _tabScrollForwardButton; - - private bool _hasLoaded; - private bool _isDragging; - - /// - /// Initializes a new instance of the class. - /// - public TabView() - { - DefaultStyleKey = typeof(TabView); - - // Container Generation Hooks - RegisterPropertyChangedCallback(ItemsSourceProperty, ItemsSource_PropertyChanged); - ItemContainerGenerator.ItemsChanged += ItemContainerGenerator_ItemsChanged; - - // Drag and Layout Hooks - DragItemsStarting += TabView_DragItemsStarting; - DragItemsCompleted += TabView_DragItemsCompleted; - SizeChanged += TabView_SizeChanged; - - // Selection Hook - SelectionChanged += TabView_SelectionChanged; - } - - /// - /// Occurs when a tab is dragged by the user outside of the . Generally, this paradigm is used to create a new-window with the torn-off tab. - /// The creation and handling of the new-window is left to the app's developer. - /// - public event EventHandler TabDraggedOutside; - - /// - /// Occurs when a tab's Close button is clicked. Set to true to prevent automatic Tab Closure. - /// - public event EventHandler TabClosing; - - /// - protected override DependencyObject GetContainerForItemOverride() => new TabViewItem(); - - /// - protected override bool IsItemItsOwnContainerOverride(object item) - { - return item is TabViewItem; - } - - /// - protected override void OnApplyTemplate() - { - base.OnApplyTemplate(); - - if (_tabItemsPresenter != null) - { - _tabItemsPresenter.SizeChanged -= TabView_SizeChanged; - } - - if (_tabScroller != null) - { - _tabScroller.Loaded -= ScrollViewer_Loaded; - } - - _tabContentPresenter = GetTemplateChild(TabContentPresenterName) as ContentPresenter; - _tabViewContainer = GetTemplateChild(TabViewContainerName) as Grid; - _tabItemsPresenter = GetTemplateChild(TabsItemsPresenterName) as ItemsPresenter; - _tabScroller = GetTemplateChild(TabsScrollViewerName) as ScrollViewer; - - if (_tabItemsPresenter != null) - { - _tabItemsPresenter.SizeChanged += TabView_SizeChanged; - } - - if (_tabScroller != null) - { - _tabScroller.Loaded += ScrollViewer_Loaded; - } - } - - private void ScrollViewer_Loaded(object sender, RoutedEventArgs e) - { - _tabScroller.Loaded -= ScrollViewer_Loaded; - - if (_tabScrollBackButton != null) - { - _tabScrollBackButton.Click -= ScrollTabBackButton_Click; - } - - if (_tabScrollForwardButton != null) - { - _tabScrollForwardButton.Click -= ScrollTabForwardButton_Click; - } - - _tabScrollBackButton = _tabScroller.FindDescendantByName(TabsScrollBackButtonName) as ButtonBase; - _tabScrollForwardButton = _tabScroller.FindDescendantByName(TabsScrollForwardButtonName) as ButtonBase; - - if (_tabScrollBackButton != null) - { - _tabScrollBackButton.Click += ScrollTabBackButton_Click; - } - - if (_tabScrollForwardButton != null) - { - _tabScrollForwardButton.Click += ScrollTabForwardButton_Click; - } - } - - private void ScrollTabBackButton_Click(object sender, RoutedEventArgs e) - { - _tabScroller.ChangeView(Math.Max(0, _tabScroller.HorizontalOffset - ScrollAmount), null, null); - } - - private void ScrollTabForwardButton_Click(object sender, RoutedEventArgs e) - { - _tabScroller.ChangeView(Math.Min(_tabScroller.ScrollableWidth, _tabScroller.HorizontalOffset + ScrollAmount), null, null); - } - - private void TabView_SelectionChanged(object sender, SelectionChangedEventArgs e) - { - if (_isDragging) - { - // Skip if we're dragging, we'll reset when we're done. - return; - } - - if (_tabContentPresenter != null) - { - if (SelectedItem == null) - { - _tabContentPresenter.Content = null; - _tabContentPresenter.ContentTemplate = null; - } - else - { - if (ContainerFromItem(SelectedItem) is TabViewItem container) - { - _tabContentPresenter.Content = container.Content; - _tabContentPresenter.ContentTemplate = container.ContentTemplate; - } - } - } - - // If our width can be effected by the selection, need to run algorithm. - if (!double.IsNaN(SelectedTabWidth)) - { - TabView_SizeChanged(sender, null); - } - } - - /// - protected override void PrepareContainerForItemOverride(DependencyObject element, object item) - { - base.PrepareContainerForItemOverride(element, item); - - var tabitem = element as TabViewItem; - - tabitem.Loaded -= TabViewItem_Loaded; - tabitem.Closing -= TabViewItem_Closing; - tabitem.Loaded += TabViewItem_Loaded; - tabitem.Closing += TabViewItem_Closing; - - if (tabitem.Header == null) - { - tabitem.Header = item; - } - - if (tabitem.HeaderTemplate == null) - { - var headertemplatebinding = new Binding() - { - Source = this, - Path = new PropertyPath(nameof(ItemHeaderTemplate)), - Mode = BindingMode.OneWay - }; - tabitem.SetBinding(TabViewItem.HeaderTemplateProperty, headertemplatebinding); - } - - if (tabitem.IsClosable != true && tabitem.ReadLocalValue(TabViewItem.IsClosableProperty) == DependencyProperty.UnsetValue) - { - var iscloseablebinding = new Binding() - { - Source = this, - Path = new PropertyPath(nameof(CanCloseTabs)), - Mode = BindingMode.OneWay, - }; - tabitem.SetBinding(TabViewItem.IsClosableProperty, iscloseablebinding); - } - } - - private void TabViewItem_Loaded(object sender, RoutedEventArgs e) - { - var tabitem = sender as TabViewItem; - - tabitem.Loaded -= TabViewItem_Loaded; - - // Only need to do this once. - if (!_hasLoaded) - { - _hasLoaded = true; - - // Need to set a tab's selection on load, otherwise ListView resets to null. - SetInitialSelection(); - - // Need to make sure ContentPresenter is set to content based on selection. - TabView_SelectionChanged(this, null); - - // Need to make sure we've registered our removal method. - ItemsSource_PropertyChanged(this, null); - - // Make sure we complete layout now. - TabView_SizeChanged(this, null); - } - } - - private void TabViewItem_Closing(object sender, TabClosingEventArgs e) - { - var item = ItemFromContainer(e.Tab); - - var args = new TabClosingEventArgs(item, e.Tab); - TabClosing?.Invoke(this, args); - - if (!args.Cancel) - { - if (ItemsSource != null) - { - _removeItemsSourceMethod?.Invoke(ItemsSource, new object[] { item }); - } - else - { - Items.Remove(item); - } - } - } - - private void TabView_DragItemsStarting(object sender, DragItemsStartingEventArgs e) - { - // Keep track of drag so we don't modify content until done. - _isDragging = true; - } - - private void TabView_DragItemsCompleted(ListViewBase sender, DragItemsCompletedEventArgs args) - { - _isDragging = false; - - // args.DropResult == None when outside of area (e.g. create new window) - if (args.DropResult == DataPackageOperation.None) - { - var item = args.Items.FirstOrDefault(); - var tab = ContainerFromItem(item) as TabViewItem; - - if (tab == null && item is FrameworkElement fe) - { - tab = fe.FindParent(); - } - - if (tab == null) - { - // We still don't have a TabViewItem, most likely is a static TabViewItem in the template being dragged and not selected. - // This is a fallback scenario for static tabs. - // Note: This can be wrong if two TabViewItems share the exact same Content (i.e. a string), this should be unlikely in any practical scenario. - for (int i = 0; i < Items.Count; i++) - { - var tabItem = ContainerFromIndex(i) as TabViewItem; - if (ReferenceEquals(tabItem.Content, item)) - { - tab = tabItem; - break; - } - } - } - - TabDraggedOutside?.Invoke(this, new TabDraggedOutsideEventArgs(item, tab)); - } - else - { - // If dragging the active tab, there's an issue with the CP blanking. - TabView_SelectionChanged(this, null); - } - } - } -} diff --git a/Microsoft.Toolkit.Uwp.UI.Controls/TabView/TabView.xaml b/Microsoft.Toolkit.Uwp.UI.Controls/TabView/TabView.xaml deleted file mode 100644 index d3310aadf06..00000000000 --- a/Microsoft.Toolkit.Uwp.UI.Controls/TabView/TabView.xaml +++ /dev/null @@ -1,736 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 0 - 0,0,8,0 - 8,0,-8,0 - 0,1,1,0 - 16 - 48 - 40 - NaN - 32 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/Microsoft.Toolkit.Uwp.UI.Controls/TabView/TabViewItem.Properties.cs b/Microsoft.Toolkit.Uwp.UI.Controls/TabView/TabViewItem.Properties.cs deleted file mode 100644 index 3ce0f3e5483..00000000000 --- a/Microsoft.Toolkit.Uwp.UI.Controls/TabView/TabViewItem.Properties.cs +++ /dev/null @@ -1,79 +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; -using Windows.UI.Xaml.Controls; - -namespace Microsoft.Toolkit.Uwp.UI.Controls -{ - /// - /// Item Container for a . - /// - public partial class TabViewItem - { - /// - /// Gets or sets the header content for the tab. - /// - public object Header - { - get { return (object)GetValue(HeaderProperty); } - set { SetValue(HeaderProperty, value); } - } - - /// - /// Identifies the dependency property. - /// - /// The identifier for the dependency property. - public static readonly DependencyProperty HeaderProperty = - DependencyProperty.Register(nameof(Header), typeof(object), typeof(TabViewItem), new PropertyMetadata(null)); - - /// - /// Gets or sets the icon to appear in the tab header. - /// - public IconElement Icon - { - get { return (IconElement)GetValue(IconProperty); } - set { SetValue(IconProperty, value); } - } - - /// - /// Identifies the dependency property. - /// - /// The identifier for the dependency property. - public static readonly DependencyProperty IconProperty = - DependencyProperty.Register(nameof(Icon), typeof(IconElement), typeof(TabViewItem), new PropertyMetadata(null)); - - /// - /// Gets or sets the template to override for the tab header. - /// - public DataTemplate HeaderTemplate - { - get { return (DataTemplate)GetValue(HeaderTemplateProperty); } - set { SetValue(HeaderTemplateProperty, value); } - } - - /// - /// Identifies the dependency property. - /// - /// The identifier for the dependency property. - public static readonly DependencyProperty HeaderTemplateProperty = - DependencyProperty.Register(nameof(HeaderTemplate), typeof(DataTemplate), typeof(TabViewItem), new PropertyMetadata(null)); - - /// - /// Gets or sets a value indicating whether the tab can be closed by the user with the close button. - /// - public bool IsClosable - { - get { return (bool)GetValue(IsClosableProperty); } - set { SetValue(IsClosableProperty, value); } - } - - /// - /// Identifies the dependency property. - /// - /// The identifier for the dependency property. - public static readonly DependencyProperty IsClosableProperty = - DependencyProperty.Register(nameof(IsClosable), typeof(bool), typeof(TabViewItem), new PropertyMetadata(null)); - } -} diff --git a/Microsoft.Toolkit.Uwp.UI.Controls/TabView/TabViewItem.cs b/Microsoft.Toolkit.Uwp.UI.Controls/TabView/TabViewItem.cs deleted file mode 100644 index 69a968fd839..00000000000 --- a/Microsoft.Toolkit.Uwp.UI.Controls/TabView/TabViewItem.cs +++ /dev/null @@ -1,116 +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 Windows.Devices.Input; -using Windows.System; -using Windows.UI.Core; -using Windows.UI.Input; -using Windows.UI.Xaml; -using Windows.UI.Xaml.Controls; -using Windows.UI.Xaml.Controls.Primitives; -using Windows.UI.Xaml.Input; - -namespace Microsoft.Toolkit.Uwp.UI.Controls -{ - /// - /// Item Container for a . - /// - [TemplatePart(Name = TabCloseButtonName, Type = typeof(ButtonBase))] - public partial class TabViewItem : ListViewItem - { - private const string TabCloseButtonName = "CloseButton"; - - private ButtonBase _tabCloseButton; - - private bool _isMiddleClick; - - /// - /// Initializes a new instance of the class. - /// - public TabViewItem() - { - DefaultStyleKey = typeof(TabViewItem); - } - - /// - /// Fired when the Tab's close button is clicked. - /// - public event EventHandler Closing; - - /// - protected override void OnApplyTemplate() - { - base.OnApplyTemplate(); - - if (_tabCloseButton != null) - { - _tabCloseButton.Click -= TabCloseButton_Click; - } - - _tabCloseButton = GetTemplateChild(TabCloseButtonName) as ButtonBase; - - if (_tabCloseButton != null) - { - _tabCloseButton.Click += TabCloseButton_Click; - } - } - - /// - protected override void OnPointerPressed(PointerRoutedEventArgs e) - { - _isMiddleClick = false; - - if (e.Pointer.PointerDeviceType == PointerDeviceType.Mouse) - { - PointerPoint pointerPoint = e.GetCurrentPoint(this); - - // Record if middle button is pressed - if (pointerPoint.Properties.IsMiddleButtonPressed) - { - _isMiddleClick = true; - } - - // Disable unwanted behaviour inherited by ListViewItem: - // Disable "Ctrl + Left click" to deselect tab - // Or variant like "Ctrl + Shift + Left click" - // Or "Ctrl + Alt + Left click" - if (pointerPoint.Properties.IsLeftButtonPressed) - { - var ctrl = Window.Current.CoreWindow.GetKeyState(VirtualKey.Control); - if (ctrl.HasFlag(CoreVirtualKeyStates.Down)) - { - // return here so the event won't be picked up by the base class - // but keep this event unhandled so it can be picked up further - return; - } - } - } - - base.OnPointerPressed(e); - } - - /// - protected override void OnPointerReleased(PointerRoutedEventArgs e) - { - base.OnPointerReleased(e); - - // Close on Middle-Click - if (_isMiddleClick) - { - TabCloseButton_Click(this, null); - } - - _isMiddleClick = false; - } - - private void TabCloseButton_Click(object sender, RoutedEventArgs e) - { - if (IsClosable) - { - Closing?.Invoke(this, new TabClosingEventArgs(Content, this)); - } - } - } -} diff --git a/Microsoft.Toolkit.Uwp.UI.Controls/TabView/TabWidthMode.cs b/Microsoft.Toolkit.Uwp.UI.Controls/TabView/TabWidthMode.cs deleted file mode 100644 index 65b1ce03844..00000000000 --- a/Microsoft.Toolkit.Uwp.UI.Controls/TabView/TabWidthMode.cs +++ /dev/null @@ -1,36 +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; - -namespace Microsoft.Toolkit.Uwp.UI.Controls -{ - /// - /// Possible modes for how to layout a Header's Width in the . - /// - public enum TabWidthMode - { - /// - /// Each tab header takes up as much space as it needs. This is similar to how WPF and Visual Studio Code behave. - /// Suggest to keep set to false. - /// is ignored. - /// In this scenario, tab width behavior is effectively turned off. This can be useful when using custom styling or a custom panel for layout of as well. - /// - Actual, - - /// - /// Each tab header will use the minimal space set by on the . - /// Suggest to set the to show more content for the selected item. - /// - Compact, - - /// - /// Each tab header will fill to fit the available space. If is set, that will be used as a Maximum Width. - /// This is similar to how Microsoft Edge behaves when used with the . - /// Suggest to set to true. - /// Suggest to set to 200 and the TabViewItemHeaderMinWidth Resource to 90. - /// - Equal, - } -} diff --git a/Microsoft.Toolkit.Uwp.UI.Controls/Themes/Generic.xaml b/Microsoft.Toolkit.Uwp.UI.Controls/Themes/Generic.xaml index 592fbb45626..9c17a3717bd 100644 --- a/Microsoft.Toolkit.Uwp.UI.Controls/Themes/Generic.xaml +++ b/Microsoft.Toolkit.Uwp.UI.Controls/Themes/Generic.xaml @@ -9,7 +9,6 @@ - @@ -27,7 +26,6 @@ - diff --git a/Microsoft.Toolkit.Uwp.UI.Controls/TileControl/TileControl.cs b/Microsoft.Toolkit.Uwp.UI.Controls/TileControl/TileControl.cs index 6799439d67a..cdc4af642c5 100644 --- a/Microsoft.Toolkit.Uwp.UI.Controls/TileControl/TileControl.cs +++ b/Microsoft.Toolkit.Uwp.UI.Controls/TileControl/TileControl.cs @@ -181,13 +181,6 @@ public class TileControl : ContentControl /// public event EventHandler ImageLoaded; - /// - /// Gets a value indicating whether the platform supports Composition. - /// - [Obsolete("This property is now obsolete and will be removed in a future version of the Toolkit.")] - public static bool IsCompositionSupported => !DesignTimeHelpers.IsRunningInLegacyDesignerMode && - ApiInformation.IsApiContractPresent("Windows.Foundation.UniversalApiContract", 3); // SDK >= 14393 - /// /// Initializes a new instance of the class. /// diff --git a/Microsoft.Toolkit.Uwp.UI.Controls/VisualStudioToolsManifest.xml b/Microsoft.Toolkit.Uwp.UI.Controls/VisualStudioToolsManifest.xml index e2bc71350ef..27fe2b2f0bc 100644 --- a/Microsoft.Toolkit.Uwp.UI.Controls/VisualStudioToolsManifest.xml +++ b/Microsoft.Toolkit.Uwp.UI.Controls/VisualStudioToolsManifest.xml @@ -13,7 +13,6 @@ - @@ -33,8 +32,6 @@ - - diff --git a/Microsoft.Toolkit/Extensions/StringExtensions.cs b/Microsoft.Toolkit/Extensions/StringExtensions.cs index 5e81855f128..a39030a11a4 100644 --- a/Microsoft.Toolkit/Extensions/StringExtensions.cs +++ b/Microsoft.Toolkit/Extensions/StringExtensions.cs @@ -86,14 +86,6 @@ public static bool IsDecimal(this string str) /// true if the string contains only letters; otherwise, false. public static bool IsCharacterString(this string str) => Regex.IsMatch(str, CharactersRegex); - /// - /// Returns a string representation of an object. - /// - /// The object to convert. - /// String representation of the object. - [Obsolete("Use value?.ToString() instead. This will be removed in the next release of the toolkit.")] - public static string ToSafeString(this object value) => value?.ToString(); - /// /// Returns a string with HTML comments, scripts, styles, and tags removed. /// From 24c2da528f9f4823cdd7897d6e501cd2608263b8 Mon Sep 17 00:00:00 2001 From: Alexandre Zollinger Chohfi Date: Thu, 20 Aug 2020 14:08:18 -0700 Subject: [PATCH 410/595] Removed RSS parsers. --- .../Microsoft.Toolkit.Parsers.csproj | 4 +- Microsoft.Toolkit.Parsers/Rss/AtomParser.cs | 136 ------ .../Rss/BaseRssParser.cs | 59 --- .../Rss/Enums/RssType.cs | 27 - Microsoft.Toolkit.Parsers/Rss/Rss2Parser.cs | 214 -------- Microsoft.Toolkit.Parsers/Rss/RssHelper.cs | 460 ------------------ Microsoft.Toolkit.Parsers/Rss/RssParser.cs | 43 -- Microsoft.Toolkit.Parsers/Rss/RssSchema.cs | 65 --- .../Microsoft.Toolkit.Uwp.SampleApp.csproj | 7 - .../SamplePages/RssParser/RssParserPage.xaml | 46 -- .../RssParser/RssParserPage.xaml.cs | 70 --- .../SamplePages/samples.json | 8 - readme.md | 2 +- 13 files changed, 2 insertions(+), 1139 deletions(-) delete mode 100644 Microsoft.Toolkit.Parsers/Rss/AtomParser.cs delete mode 100644 Microsoft.Toolkit.Parsers/Rss/BaseRssParser.cs delete mode 100644 Microsoft.Toolkit.Parsers/Rss/Enums/RssType.cs delete mode 100644 Microsoft.Toolkit.Parsers/Rss/Rss2Parser.cs delete mode 100644 Microsoft.Toolkit.Parsers/Rss/RssHelper.cs delete mode 100644 Microsoft.Toolkit.Parsers/Rss/RssParser.cs delete mode 100644 Microsoft.Toolkit.Parsers/Rss/RssSchema.cs delete mode 100644 Microsoft.Toolkit.Uwp.SampleApp/SamplePages/RssParser/RssParserPage.xaml delete mode 100644 Microsoft.Toolkit.Uwp.SampleApp/SamplePages/RssParser/RssParserPage.xaml.cs diff --git a/Microsoft.Toolkit.Parsers/Microsoft.Toolkit.Parsers.csproj b/Microsoft.Toolkit.Parsers/Microsoft.Toolkit.Parsers.csproj index 49d99bd104f..a141b75c84b 100644 --- a/Microsoft.Toolkit.Parsers/Microsoft.Toolkit.Parsers.csproj +++ b/Microsoft.Toolkit.Parsers/Microsoft.Toolkit.Parsers.csproj @@ -8,10 +8,8 @@ Markdown: Allows you to parse a Markdown String into a Markdown Document, and then Render it with a Markdown Renderer. - RSS: Allows you to parse an RSS content String into an RSS Schema. - - UWP Toolkit Windows Parsers Parsing Markdown RSS + UWP Toolkit Windows Parsers Parsing Markdown diff --git a/Microsoft.Toolkit.Parsers/Rss/AtomParser.cs b/Microsoft.Toolkit.Parsers/Rss/AtomParser.cs deleted file mode 100644 index 65c2a56a6b7..00000000000 --- a/Microsoft.Toolkit.Parsers/Rss/AtomParser.cs +++ /dev/null @@ -1,136 +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.Collections.Generic; -using System.Collections.ObjectModel; -using System.Linq; -using System.Xml.Linq; -using Microsoft.Toolkit.Extensions; - -namespace Microsoft.Toolkit.Parsers.Rss -{ - /// - /// Parser for Atom endpoints. - /// - internal class AtomParser : BaseRssParser - { - /// - /// Atom reader implementation to parse Atom content. - /// - /// XDocument to parse. - /// Strong typed response. - public override IEnumerable LoadFeed(XDocument doc) - { - Collection feed = new Collection(); - - if (doc.Root == null) - { - return feed; - } - - var items = doc.Root.Elements(doc.Root.GetDefaultNamespace() + "entry").Select(item => GetRssSchema(item)).ToList(); - - feed = new Collection(items); - - return feed; - } - - /// - /// Retrieves strong type for passed item. - /// - /// XElement to parse. - /// Strong typed object. - private static RssSchema GetRssSchema(XElement item) - { - RssSchema rssItem = new RssSchema - { - Author = GetItemAuthor(item), - Title = item.GetSafeElementString("title").Trim().DecodeHtml(), - ImageUrl = GetItemImage(item), - PublishDate = item.GetSafeElementDate("published"), - FeedUrl = item.GetLink("alternate"), - }; - - var content = GetItemContent(item); - - // Removes scripts from html - if (!string.IsNullOrEmpty(content)) - { - rssItem.Summary = ProcessHtmlSummary(content); - rssItem.Content = ProcessHtmlContent(content); - } - - string id = item.GetSafeElementString("guid").Trim(); - if (string.IsNullOrEmpty(id)) - { - id = item.GetSafeElementString("id").Trim(); - if (string.IsNullOrEmpty(id)) - { - id = rssItem.FeedUrl; - } - } - - rssItem.InternalID = id; - return rssItem; - } - - /// - /// Retrieves item author from XElement. - /// - /// XElement item. - /// String of Item Author. - private static string GetItemAuthor(XElement item) - { - var content = string.Empty; - - if (item != null && item.Element(item.GetDefaultNamespace() + "author") != null) - { - content = item.Element(item.GetDefaultNamespace() + "author").GetSafeElementString("name"); - } - - if (string.IsNullOrEmpty(content)) - { - content = item.GetSafeElementString("author"); - } - - return content; - } - - /// - /// Returns item image from XElement item. - /// - /// XElement item. - /// String pointing to item image. - private static string GetItemImage(XElement item) - { - if (!string.IsNullOrEmpty(item.GetSafeElementString("image"))) - { - return item.GetSafeElementString("image"); - } - - return item.GetImage(); - } - - /// - /// Returns item content from XElement item. - /// - /// XElement item. - /// String of item content. - private static string GetItemContent(XElement item) - { - var content = item.GetSafeElementString("description"); - if (string.IsNullOrEmpty(content)) - { - content = item.GetSafeElementString("content"); - } - - if (string.IsNullOrEmpty(content)) - { - content = item.GetSafeElementString("summary"); - } - - return content; - } - } -} \ No newline at end of file diff --git a/Microsoft.Toolkit.Parsers/Rss/BaseRssParser.cs b/Microsoft.Toolkit.Parsers/Rss/BaseRssParser.cs deleted file mode 100644 index d268cf5d10c..00000000000 --- a/Microsoft.Toolkit.Parsers/Rss/BaseRssParser.cs +++ /dev/null @@ -1,59 +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.Collections.Generic; -using System.Xml.Linq; -using Microsoft.Toolkit.Extensions; - -namespace Microsoft.Toolkit.Parsers.Rss -{ - /// - /// Base class for Rss Parser(s). - /// - internal abstract class BaseRssParser - { - /// - /// Retrieve feed type from XDocument. - /// - /// XDocument doc. - /// Return feed type. - public static RssType GetFeedType(XDocument doc) - { - if (doc.Root == null) - { - return RssType.Unknown; - } - - XNamespace defaultNamespace = doc.Root.GetDefaultNamespace(); - return defaultNamespace.NamespaceName.EndsWith("Atom") ? RssType.Atom : RssType.Rss; - } - - /// - /// Abstract method to be override by specific implementations of the reader. - /// - /// XDocument doc. - /// Returns list of strongly typed results. - public abstract IEnumerable LoadFeed(XDocument doc); - - /// - /// Fix up the HTML content. - /// - /// Content to be fixed up. - /// Fixed up content. - protected internal static string ProcessHtmlContent(string htmlContent) - { - return htmlContent.FixHtml().SanitizeString(); - } - - /// - /// Create a summary of the HTML content. - /// - /// Content to be processed. - /// Summary of the content. - protected internal static string ProcessHtmlSummary(string htmlContent) - { - return htmlContent.DecodeHtml().Trim().Truncate(500).SanitizeString(); - } - } -} \ No newline at end of file diff --git a/Microsoft.Toolkit.Parsers/Rss/Enums/RssType.cs b/Microsoft.Toolkit.Parsers/Rss/Enums/RssType.cs deleted file mode 100644 index 99ae9570887..00000000000 --- a/Microsoft.Toolkit.Parsers/Rss/Enums/RssType.cs +++ /dev/null @@ -1,27 +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. - -namespace Microsoft.Toolkit.Parsers.Rss -{ - /// - /// Type of Rss. - /// - internal enum RssType - { - /// - /// Atom - /// - Atom, - - /// - /// RSS - /// - Rss, - - /// - /// Unknown - /// - Unknown - } -} \ No newline at end of file diff --git a/Microsoft.Toolkit.Parsers/Rss/Rss2Parser.cs b/Microsoft.Toolkit.Parsers/Rss/Rss2Parser.cs deleted file mode 100644 index e693f0994ac..00000000000 --- a/Microsoft.Toolkit.Parsers/Rss/Rss2Parser.cs +++ /dev/null @@ -1,214 +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.Collections.Generic; -using System.Collections.ObjectModel; -using System.Linq; -using System.Xml.Linq; -using Microsoft.Toolkit.Extensions; - -namespace Microsoft.Toolkit.Parsers.Rss -{ - /// - /// Rss reader implementation to parse Rss content. - /// - internal class Rss2Parser : BaseRssParser - { - /// - /// RDF Namespace Uri. - /// - private static readonly XNamespace NsRdfNamespaceUri = "http://www.w3.org/1999/02/22-rdf-syntax-ns#"; - - /// - /// RDF Elements Namespace Uri. - /// - private static readonly XNamespace NsRdfElementsNamespaceUri = "http://purl.org/dc/elements/1.1/"; - - /// - /// RDF Content Namespace Uri. - /// - private static readonly XNamespace NsRdfContentNamespaceUri = "http://purl.org/rss/1.0/modules/content/"; - - /// - /// This override load and parses the document and return a list of RssSchema values. - /// - /// XDocument to be loaded. - /// Strongly typed list of feeds. - public override IEnumerable LoadFeed(XDocument doc) - { - bool isRDF = false; - var feed = new Collection(); - XNamespace defaultNamespace = string.Empty; - - if (doc.Root != null) - { - isRDF = doc.Root.Name == (NsRdfNamespaceUri + "RDF"); - defaultNamespace = doc.Root.GetDefaultNamespace(); - } - - foreach (var item in doc.Descendants(defaultNamespace + "item")) - { - var rssItem = isRDF ? ParseRDFItem(item) : ParseRssItem(item); - feed.Add(rssItem); - } - - return feed; - } - - /// - /// Parses XElement item into strong typed object. - /// - /// XElement item to parse. - /// Strong typed object. - private static RssSchema ParseItem(XElement item) - { - var rssItem = new RssSchema(); - rssItem.Title = item.GetSafeElementString("title").Trim().DecodeHtml(); - rssItem.FeedUrl = item.GetSafeElementString("link"); - - rssItem.Author = GetItemAuthor(item); - - string content = item.GetSafeElementString("encoded", NsRdfContentNamespaceUri); - if (string.IsNullOrEmpty(content)) - { - content = item.GetSafeElementString("description"); - if (string.IsNullOrEmpty(content)) - { - content = item.GetSafeElementString("content"); - } - } - - var summary = item.GetSafeElementString("description"); - if (string.IsNullOrEmpty(summary)) - { - summary = item.GetSafeElementString("encoded", NsRdfContentNamespaceUri); - } - - // Removes scripts from html - if (!string.IsNullOrEmpty(summary)) - { - rssItem.Summary = ProcessHtmlSummary(summary); - } - - if (!string.IsNullOrEmpty(content)) - { - rssItem.Content = ProcessHtmlContent(content); - } - - string id = item.GetSafeElementString("guid").Trim(); - if (string.IsNullOrEmpty(id)) - { - id = item.GetSafeElementString("id").Trim(); - if (string.IsNullOrEmpty(id)) - { - id = rssItem.FeedUrl; - } - } - - rssItem.InternalID = id; - - return rssItem; - } - - /// - /// Parses RSS version 1.0 objects. - /// - /// XElement item. - /// Strong typed object. - private static RssSchema ParseRDFItem(XElement item) - { - XNamespace ns = "http://search.yahoo.com/mrss/"; - var rssItem = ParseItem(item); - - rssItem.PublishDate = item.GetSafeElementDate("date", NsRdfElementsNamespaceUri); - - string image = item.GetSafeElementString("image"); - if (string.IsNullOrEmpty(image) && item.Elements(ns + "thumbnail").LastOrDefault() != null) - { - var element = item.Elements(ns + "thumbnail").Last(); - image = element.Attribute("url").Value; - } - - if (string.IsNullOrEmpty(image) && item.ToString().Contains("thumbnail")) - { - image = item.GetSafeElementString("thumbnail"); - } - - if (string.IsNullOrEmpty(image)) - { - image = item.GetImage(); - } - - rssItem.ImageUrl = image; - - return rssItem; - } - - /// - /// Parses RSS version 2.0 objects. - /// - /// XElement item. - /// Strong typed object. - private static RssSchema ParseRssItem(XElement item) - { - XNamespace ns = "http://search.yahoo.com/mrss/"; - var rssItem = ParseItem(item); - - rssItem.PublishDate = item.GetSafeElementDate("pubDate"); - - string image = item.GetSafeElementString("image"); - if (string.IsNullOrEmpty(image)) - { - image = item.GetImageFromEnclosure(); - } - - if (string.IsNullOrEmpty(image) && item.Elements(ns + "content").LastOrDefault() != null) - { - var element = item.Elements(ns + "content").Last(); - if (element.Attribute("type") != null && element.Attribute("type").Value.Contains("image/")) - { - image = element.Attribute("url").Value; - } - } - - if (string.IsNullOrEmpty(image) && item.Elements(ns + "thumbnail").LastOrDefault() != null) - { - var element = item.Elements(ns + "thumbnail").Last(); - image = element.Attribute("url").Value; - } - - if (string.IsNullOrEmpty(image) && item.ToString().Contains("thumbnail")) - { - image = item.GetSafeElementString("thumbnail"); - } - - if (string.IsNullOrEmpty(image)) - { - image = item.GetImage(); - } - - rssItem.Categories = item.GetSafeElementsString("category"); - - rssItem.ImageUrl = image; - - return rssItem; - } - - /// - /// Retrieve item author from item. - /// - /// XElement item. - /// String of item author. - private static string GetItemAuthor(XElement item) - { - var content = item.GetSafeElementString("creator", NsRdfElementsNamespaceUri).Trim(); - if (string.IsNullOrEmpty(content)) - { - content = item.GetSafeElementString("author"); - } - - return content; - } - } -} \ No newline at end of file diff --git a/Microsoft.Toolkit.Parsers/Rss/RssHelper.cs b/Microsoft.Toolkit.Parsers/Rss/RssHelper.cs deleted file mode 100644 index 5b4b19d5af3..00000000000 --- a/Microsoft.Toolkit.Parsers/Rss/RssHelper.cs +++ /dev/null @@ -1,460 +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.Diagnostics; -using System.Globalization; -using System.Linq; -using System.Text.RegularExpressions; -using System.Xml.Linq; - -namespace Microsoft.Toolkit.Parsers.Rss -{ - /// - /// Class with utilities for Rss related works. - /// - internal static class RssHelper - { - /// - /// String for regular expression for image pattern. - /// - private const string ImagePattern = @""; - - /// - /// String for regular expression for hyperlink pattern. - /// - private const string HyperlinkPattern = @"]*?\s+)?href=""([^ ""]*)"""; - - /// - /// String for regular expression for height pattern. - /// - private const string HeightPattern = @"height=(?:(['""])(?(?:(?!\1).)*)\1|(?\S+))"; - - /// - /// String for regular expression for width pattern. - /// - private const string WidthPattern = @"width=(?:(['""])(?(?:(?!\1).)*)\1|(?\S+))"; - - /// - /// Regular expression for image pattern. - /// - private static readonly Regex RegexImages = new Regex(ImagePattern, RegexOptions.IgnoreCase); - - /// - /// Regular expression for hyperlink pattern. - /// - private static readonly Regex RegexLinks = new Regex(HyperlinkPattern, RegexOptions.IgnoreCase); - - /// - /// Regular expression for height pattern. - /// - private static readonly Regex RegexHeight = new Regex(HeightPattern, RegexOptions.IgnoreCase | RegexOptions.Singleline); - - /// - /// Regular expression for width pattern. - /// - private static readonly Regex RegexWidth = new Regex(WidthPattern, RegexOptions.IgnoreCase | RegexOptions.Singleline); - - /// - /// Removes \t characters in the string and trim additional space and carriage returns. - /// - /// Text string. - /// Sanitized string. - public static string SanitizeString(this string text) - { - if (string.IsNullOrEmpty(text)) - { - return string.Empty; - } - - var textArray = text.Split(new[] { "\t" }, StringSplitOptions.RemoveEmptyEntries); - string sanitizedText = string.Empty; - foreach (var item in textArray.ToList()) - { - sanitizedText += item.Trim(); - } - - sanitizedText = string.Join(" ", Regex.Split(sanitizedText, @"(?:\r\n|\n|\r)")); - - return sanitizedText; - } - - /// - /// Get item date from and element name. - /// - /// XElement item. - /// Name of element. - /// Item date. - public static DateTime GetSafeElementDate(this XElement item, string elementName) - { - return GetSafeElementDate(item, elementName, item.GetDefaultNamespace()); - } - - /// - /// Get item date from , element name and . - /// - /// XElement item. - /// Name of element. - /// XNamespace namespace. - /// Item date. - public static DateTime GetSafeElementDate(this XElement item, string elementName, XNamespace xNamespace) - { - DateTime date; - XElement element = item.Element(xNamespace + elementName); - if (element == null) - { - return DateTime.Now; - } - - if (TryParseDateTime(element.Value, out date)) - { - return date; - } - - return DateTime.Now; - } - - /// - /// Get item string value for and element name. - /// - /// XElement item. - /// Name of element. - /// Safe string. - public static string GetSafeElementString(this XElement item, string elementName) - { - if (item == null) - { - return string.Empty; - } - - return GetSafeElementString(item, elementName, item.GetDefaultNamespace()); - } - - /// - /// Get item string values for and element name. - /// - /// XElement item. - /// Name of the element. - /// Safe list of string values. - public static IEnumerable GetSafeElementsString(this XElement item, string elementName) - { - return GetSafeElementsString(item, elementName, item.GetDefaultNamespace()); - } - - /// - /// Get item string values for , element name and namespace. - /// - /// XElement item. - /// Name of element. - /// XNamespace namespace. - /// Safe list of string values. - public static IEnumerable GetSafeElementsString(this XElement item, string elementName, XNamespace xNamespace) - { - if (item != null) - { - IEnumerable values = item.Elements(xNamespace + elementName); - return values.Where(f => !string.IsNullOrEmpty(f.Value)) - .Select(f => f.Value); - } - - return Enumerable.Empty(); - } - - /// - /// Get item string value for , element name and namespace. - /// - /// XElement item. - /// Name of element. - /// XNamespace namespace. - /// Safe string. - public static string GetSafeElementString(this XElement item, string elementName, XNamespace xNamespace) - { - if (item == null) - { - return string.Empty; - } - - XElement value = item.Element(xNamespace + elementName); - if (value != null) - { - return value.Value; - } - - return string.Empty; - } - - /// - /// Get feed url to see full original information. - /// - /// XElement item. - /// rel attribute value. - /// String link. - public static string GetLink(this XElement item, string rel) - { - IEnumerable links = item.Elements(item.GetDefaultNamespace() + "link"); - var xElements = links as XElement[] ?? links.ToArray(); - IEnumerable link = from l in xElements - let xAttribute = l.Attribute("rel") - where xAttribute != null && xAttribute.Value == rel - let attribute = l.Attribute("href") - where attribute != null - select attribute.Value; - var enumerable = link as string[] ?? link.ToArray(); - if (!enumerable.Any() && xElements.Any()) - { - return xElements.FirstOrDefault().Attributes().First().Value; - } - - return enumerable.FirstOrDefault(); - } - - /// - /// Get feed image. - /// - /// XElement item. - /// Feed data image. - [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Justification = "The general catch is intended to avoid breaking the Data Provider by a Html decode exception")] - public static string GetImage(this XElement item) - { - string feedDataImage = null; - try - { - feedDataImage = GetImagesInHTMLString(item.Value).FirstOrDefault(); - if (!string.IsNullOrEmpty(feedDataImage) && feedDataImage.EndsWith("'")) - { - feedDataImage = feedDataImage.Remove(feedDataImage.Length - 1); - } - } - catch (Exception ex) - { - Debug.WriteLine(ex); - } - - return feedDataImage; - } - - /// - /// Get the item image from the enclosure element http://www.w3schools.com/rss/rss_tag_enclosure.asp - /// - /// XElement item. - /// Feed data image. - [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Justification = "The general catch is intended to avoid breaking the Data Provider by a Html decode exception")] - public static string GetImageFromEnclosure(this XElement item) - { - string feedDataImage = null; - try - { - XElement element = item.Element(item.GetDefaultNamespace() + "enclosure"); - if (element == null) - { - return string.Empty; - } - - var typeAttribute = element.Attribute("type"); - if (!string.IsNullOrEmpty(typeAttribute?.Value) && typeAttribute.Value.StartsWith("image")) - { - var urlAttribute = element.Attribute("url"); - feedDataImage = (!string.IsNullOrEmpty(urlAttribute?.Value)) ? - urlAttribute.Value : string.Empty; - } - } - catch (Exception ex) - { - Debug.WriteLine(ex); - } - - return feedDataImage; - } - - /// - /// Tries to parse the original string to a datetime format. - /// - /// Input string. - /// Parsed datetime. - /// True if success - public static bool TryParseDateTime(string s, out DateTime result) - { - if (DateTime.TryParse(s, DateTimeFormatInfo.InvariantInfo, DateTimeStyles.AllowWhiteSpaces, out result)) - { - return true; - } - - int tzIndex = s.LastIndexOf(" "); - if (tzIndex >= 0) - { - string tz = s.Substring(tzIndex, s.Length - tzIndex); - string offset = TimeZoneToOffset(tz); - if (offset != null) - { - string offsetDate = string.Format("{0} {1}", s.Substring(0, tzIndex), offset); - return TryParseDateTime(offsetDate, out result); - } - } - - result = default(DateTime); - return false; - } - - /// - /// Calculate and return timezone. - /// - /// Input string. - /// Parsed timezone. - public static string TimeZoneToOffset(string tz) - { - if (tz == null) - { - return null; - } - - tz = tz.ToUpper().Trim(); - - if (TimeZones.ContainsKey(tz)) - { - return TimeZones[tz].First(); - } - - return null; - } - - /// - /// Retrieve images from HTML string. - /// - /// String of HTML. - /// List of images. - private static IEnumerable GetImagesInHTMLString(string htmlString) - { - var images = new List(); - foreach (Match match in RegexImages.Matches(htmlString)) - { - bool include = true; - string tag = match.Value; - - // Ignores images with low size - var matchHeight = RegexHeight.Match(tag); - if (matchHeight.Success) - { - var heightValue = matchHeight.Groups["height"].Value; - if (int.TryParse(heightValue, out var heightIntValue) && heightIntValue < 10) - { - include = false; - } - } - - var matchWidth = RegexWidth.Match(tag); - if (matchWidth.Success) - { - var widthValue = matchWidth.Groups["width"].Value; - if (int.TryParse(widthValue, out var widthIntValue) && widthIntValue < 10) - { - include = false; - } - } - - if (include) - { - images.Add(match.Groups[1].Value); - } - } - - foreach (Match match in RegexLinks.Matches(htmlString)) - { - var value = match.Groups[1].Value; - if (value.Contains(".jpg") || value.Contains(".png")) - { - images.Add(value); - } - } - - return images; - } - - /// - /// Dictionary of timezones. - /// - private static readonly Dictionary TimeZones = new Dictionary - { - { "ACDT", new[] { "-1030", "Australian Central Daylight" } }, - { "ACST", new[] { "-0930", "Australian Central Standard" } }, - { "ADT", new[] { "+0300", "(US) Atlantic Daylight" } }, - { "AEDT", new[] { "-1100", "Australian East Daylight" } }, - { "AEST", new[] { "-1000", "Australian East Standard" } }, - { "AHDT", new[] { "+0900", string.Empty } }, - { "AHST", new[] { "+1000", string.Empty } }, - { "AST", new[] { "+0400", "(US) Atlantic Standard" } }, - { "AT", new[] { "+0200", "Azores" } }, - { "AWDT", new[] { "-0900", "Australian West Daylight" } }, - { "AWST", new[] { "-0800", "Australian West Standard" } }, - { "BAT", new[] { "-0300", "Baghdad" } }, - { "BDST", new[] { "-0200", "British Double Summer" } }, - { "BET", new[] { "+1100", "Bering Standard" } }, - { "BST", new[] { "+0300", "Brazil Standard" } }, - { "BT", new[] { "-0300", "Baghdad" } }, - { "BZT2", new[] { "+0300", "Brazil Zone 2" } }, - { "CADT", new[] { "-1030", "Central Australian Daylight" } }, - { "CAST", new[] { "-0930", "Central Australian Standard" } }, - { "CAT", new[] { "+1000", "Central Alaska" } }, - { "CCT", new[] { "-0800", "China Coast" } }, - { "CDT", new[] { "+0500", "(US) Central Daylight" } }, - { "CED", new[] { "-0200", "Central European Daylight" } }, - { "CET", new[] { "-0100", "Central European" } }, - { "CST", new[] { "+0600", "(US) Central Standard" } }, - { "EAST", new[] { "-1000", "Eastern Australian Standard" } }, - { "EDT", new[] { "+0400", "(US) Eastern Daylight" } }, - { "EED", new[] { "-0300", "Eastern European Daylight" } }, - { "EET", new[] { "-0200", "Eastern Europe" } }, - { "EEST", new[] { "-0300", "Eastern Europe Summer" } }, - { "EST", new[] { "+0500", "(US) Eastern Standard" } }, - { "FST", new[] { "-0200", "French Summer" } }, - { "FWT", new[] { "-0100", "French Winter" } }, - { "GMT", new[] { "+0000", "Greenwich Mean" } }, - { "GST", new[] { "-1000", "Guam Standard" } }, - { "HDT", new[] { "+0900", "Hawaii Daylight" } }, - { "HST", new[] { "+1000", "Hawaii Standard" } }, - { "IDLE", new[] { "-1200", "International Date Line East" } }, - { "IDLW", new[] { "+1200", "International Date Line West" } }, - { "IST", new[] { "-0530", "Indian Standard" } }, - { "IT", new[] { "-0330", "Iran" } }, - { "JST", new[] { "-0900", "Japan Standard" } }, - { "JT", new[] { "-0700", "Java" } }, - { "MDT", new[] { "+0600", "(US) Mountain Daylight" } }, - { "MED", new[] { "-0200", "Middle European Daylight" } }, - { "MET", new[] { "-0100", "Middle European" } }, - { "MEST", new[] { "-0200", "Middle European Summer" } }, - { "MEWT", new[] { "-0100", "Middle European Winter" } }, - { "MST", new[] { "+0700", "(US) Mountain Standard" } }, - { "MT", new[] { "-0800", "Moluccas" } }, - { "NDT", new[] { "+0230", "Newfoundland Daylight" } }, - { "NFT", new[] { "+0330", "Newfoundland" } }, - { "NT", new[] { "+1100", "Nome" } }, - { "NST", new[] { "-0630", "North Sumatra" } }, - { "NZ", new[] { "-1100", "New Zealand " } }, - { "NZST", new[] { "-1200", "New Zealand Standard" } }, - { "NZDT", new[] { "-1300", "New Zealand Daylight" } }, - { "NZT", new[] { "-1200", "New Zealand" } }, - { "PDT", new[] { "+0700", "(US) Pacific Daylight" } }, - { "PST", new[] { "+0800", "(US) Pacific Standard" } }, - { "ROK", new[] { "-0900", "Republic of Korea" } }, - { "SAD", new[] { "-1000", "South Australia Daylight" } }, - { "SAST", new[] { "-0900", "South Australia Standard" } }, - { "SAT", new[] { "-0900", "South Australia Standard" } }, - { "SDT", new[] { "-1000", "South Australia Daylight" } }, - { "SST", new[] { "-0200", "Swedish Summer" } }, - { "SWT", new[] { "-0100", "Swedish Winter" } }, - { "USZ3", new[] { "-0400", "Volga Time (Russia)" } }, - { "USZ4", new[] { "-0500", "Ural Time (Russia)" } }, - { "USZ5", new[] { "-0600", "West-Siberian Time (Russia) " } }, - { "USZ6", new[] { "-0700", "Yenisei Time (Russia)" } }, - { "UT", new[] { "+0000", "Universal Coordinated" } }, - { "UTC", new[] { "+0000", "Universal Coordinated" } }, - { "UZ10", new[] { "-1100", "Okhotsk Time (Russia)" } }, - { "WAT", new[] { "+0100", "West Africa" } }, - { "WET", new[] { "+0000", "West European" } }, - { "WST", new[] { "-0800", "West Australian Standard" } }, - { "YDT", new[] { "+0800", "Yukon Daylight" } }, - { "YST", new[] { "+0900", "Yukon Standard" } } - }; - } -} \ No newline at end of file diff --git a/Microsoft.Toolkit.Parsers/Rss/RssParser.cs b/Microsoft.Toolkit.Parsers/Rss/RssParser.cs deleted file mode 100644 index e3f1952523c..00000000000 --- a/Microsoft.Toolkit.Parsers/Rss/RssParser.cs +++ /dev/null @@ -1,43 +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.Collections.Generic; -using System.Xml.Linq; - -namespace Microsoft.Toolkit.Parsers.Rss -{ - /// - /// The RSS Parser allows you to parse an RSS content String into RSS Schema. - /// - public class RssParser : IParser - { - /// - /// Parse an RSS content string into RSS Schema. - /// - /// Input string. - /// Strong type. - public IEnumerable Parse(string data) - { - if (string.IsNullOrEmpty(data)) - { - return null; - } - - var doc = XDocument.Parse(data); - var type = BaseRssParser.GetFeedType(doc); - - BaseRssParser rssParser; - if (type == RssType.Rss) - { - rssParser = new Rss2Parser(); - } - else - { - rssParser = new AtomParser(); - } - - return rssParser.LoadFeed(doc); - } - } -} \ No newline at end of file diff --git a/Microsoft.Toolkit.Parsers/Rss/RssSchema.cs b/Microsoft.Toolkit.Parsers/Rss/RssSchema.cs deleted file mode 100644 index 3ea8b7453ee..00000000000 --- a/Microsoft.Toolkit.Parsers/Rss/RssSchema.cs +++ /dev/null @@ -1,65 +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; - -namespace Microsoft.Toolkit.Parsers.Rss -{ - /// - /// Implementation of the RssSchema class. - /// - public class RssSchema : SchemaBase - { - /// - /// Gets or sets title. - /// - public string Title { get; set; } - - /// - /// Gets or sets summary. - /// - public string Summary { get; set; } - - /// - /// Gets or sets content. - /// - public string Content { get; set; } - - /// - /// Gets or sets image Url. - /// - public string ImageUrl { get; set; } - - /// - /// Gets or sets extra Image Url. - /// - public string ExtraImageUrl { get; set; } - - /// - /// Gets or sets media Url. - /// - public string MediaUrl { get; set; } - - /// - /// Gets or sets feed Url. - /// - public string FeedUrl { get; set; } - - /// - /// Gets or sets author. - /// - public string Author { get; set; } - - /// - /// Gets or sets publish Date. - /// - public DateTime PublishDate { get; set; } - - /// - /// Gets or sets item's categories. - /// - public IEnumerable Categories { get; set; } - } -} \ No newline at end of file diff --git a/Microsoft.Toolkit.Uwp.SampleApp/Microsoft.Toolkit.Uwp.SampleApp.csproj b/Microsoft.Toolkit.Uwp.SampleApp/Microsoft.Toolkit.Uwp.SampleApp.csproj index 5476e2d5c12..5d4e14a81fb 100644 --- a/Microsoft.Toolkit.Uwp.SampleApp/Microsoft.Toolkit.Uwp.SampleApp.csproj +++ b/Microsoft.Toolkit.Uwp.SampleApp/Microsoft.Toolkit.Uwp.SampleApp.csproj @@ -762,9 +762,6 @@ RemoteDevicePickerControlPage.xaml - - RssParserPage.xaml - RadialGradientBrushPage.xaml @@ -1226,10 +1223,6 @@ Designer MSBuild:Compile - - Designer - MSBuild:Compile - Designer MSBuild:Compile diff --git a/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/RssParser/RssParserPage.xaml b/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/RssParser/RssParserPage.xaml deleted file mode 100644 index e844fc9773d..00000000000 --- a/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/RssParser/RssParserPage.xaml +++ /dev/null @@ -1,46 +0,0 @@ - - - - - - - - - - -