From f9a2815e21e9d14fc5a7535a8b8c7e9cd548f9e0 Mon Sep 17 00:00:00 2001 From: Jan Karger Date: Thu, 26 Apr 2018 23:32:33 +0200 Subject: [PATCH 1/4] (GH-3227) ThemeManager: overloaded ctors and methods to allow add accent and theme with resource dictionaries instead URIs --- src/MahApps.Metro/ThemeManager/Accent.cs | 23 ++++++-- src/MahApps.Metro/ThemeManager/AppTheme.cs | 24 +++++++-- .../ThemeManager/ThemeManager.cs | 52 +++++++++++++++++-- 3 files changed, 89 insertions(+), 10 deletions(-) diff --git a/src/MahApps.Metro/ThemeManager/Accent.cs b/src/MahApps.Metro/ThemeManager/Accent.cs index 382470bf94..891b693205 100644 --- a/src/MahApps.Metro/ThemeManager/Accent.cs +++ b/src/MahApps.Metro/ThemeManager/Accent.cs @@ -1,9 +1,11 @@ // ReSharper disable once CheckNamespace + namespace MahApps.Metro { using System; using System.Diagnostics; using System.Windows; + using JetBrains.Annotations; /// /// An object that represents the foreground color for a . @@ -25,20 +27,35 @@ public class Accent /// Initializes a new instance of the Accent class. /// public Accent() - { } + { + } /// /// Initializes a new instance of the Accent class. /// /// The name of the new Accent. /// The URI of the accent ResourceDictionary. - public Accent(string name, Uri resourceAddress) + public Accent([NotNull] string name, [NotNull] Uri resourceAddress) { if (name == null) throw new ArgumentNullException(nameof(name)); if (resourceAddress == null) throw new ArgumentNullException(nameof(resourceAddress)); this.Name = name; - this.Resources = new ResourceDictionary {Source = resourceAddress}; + this.Resources = new ResourceDictionary { Source = resourceAddress }; + } + + /// + /// Initializes a new instance of the Accent class. + /// + /// The name of the new Accent. + /// The ResourceDictionary of the accent. + public Accent([NotNull] string name, [NotNull] ResourceDictionary resourceDictionary) + { + if (name == null) throw new ArgumentNullException(nameof(name)); + if (resourceDictionary == null) throw new ArgumentNullException(nameof(resourceDictionary)); + + this.Name = name; + this.Resources = resourceDictionary; } } } \ No newline at end of file diff --git a/src/MahApps.Metro/ThemeManager/AppTheme.cs b/src/MahApps.Metro/ThemeManager/AppTheme.cs index 326b33c8a6..358383fdc3 100644 --- a/src/MahApps.Metro/ThemeManager/AppTheme.cs +++ b/src/MahApps.Metro/ThemeManager/AppTheme.cs @@ -1,9 +1,11 @@ // ReSharper disable once CheckNamespace + namespace MahApps.Metro { using System; using System.Diagnostics; using System.Windows; + using JetBrains.Annotations; internal class AppName { @@ -19,7 +21,7 @@ public class AppTheme /// /// The ResourceDictionary that represents this application theme. /// - public ResourceDictionary Resources {get; } + public ResourceDictionary Resources { get; } /// /// Gets the name of the application theme. @@ -30,14 +32,28 @@ public class AppTheme /// Initializes a new instance of the AppTheme class. /// /// The name of the new AppTheme. - /// The URI of the accent ResourceDictionary. - public AppTheme(string name, Uri resourceAddress) + /// The URI of the AppTheme ResourceDictionary. + public AppTheme([NotNull] string name, [NotNull] Uri resourceAddress) { if (name == null) throw new ArgumentNullException(nameof(name)); if (resourceAddress == null) throw new ArgumentNullException(nameof(resourceAddress)); this.Name = name; - this.Resources = new ResourceDictionary {Source = resourceAddress}; + this.Resources = new ResourceDictionary { Source = resourceAddress }; + } + + /// + /// Initializes a new instance of the AppTheme class. + /// + /// The name of the new AppTheme. + /// The ResourceDictionary of the accent. + public AppTheme([NotNull] string name, [NotNull] ResourceDictionary resourceDictionary) + { + if (name == null) throw new ArgumentNullException(nameof(name)); + if (resourceDictionary == null) throw new ArgumentNullException(nameof(resourceDictionary)); + + this.Name = name; + this.Resources = resourceDictionary; } } } \ No newline at end of file diff --git a/src/MahApps.Metro/ThemeManager/ThemeManager.cs b/src/MahApps.Metro/ThemeManager/ThemeManager.cs index 3e5813db7d..aa4effecc2 100644 --- a/src/MahApps.Metro/ThemeManager/ThemeManager.cs +++ b/src/MahApps.Metro/ThemeManager/ThemeManager.cs @@ -84,10 +84,12 @@ public static IEnumerable AppThemes } /// - /// Adds an accent with the given name. + /// Adds an accent with the given name and uniform resource identfier. /// + /// The name of the new Accent. + /// The URI of the accent ResourceDictionary. /// true if the accent does not exists and can be added. - public static bool AddAccent(string name, Uri resourceAddress) + public static bool AddAccent([NotNull] string name, [NotNull] Uri resourceAddress) { if (name == null) throw new ArgumentNullException(nameof(name)); if (resourceAddress == null) throw new ArgumentNullException(nameof(resourceAddress)); @@ -102,11 +104,34 @@ public static bool AddAccent(string name, Uri resourceAddress) return true; } + /// + /// Adds an accent with the given name and resource dictionary. + /// + /// The name of the new Accent. + /// The ResourceDictionary of the accent. + /// true if the accent does not exists and can be added. + public static bool AddAccent(string name, ResourceDictionary resourceDictionary) + { + if (name == null) throw new ArgumentNullException(nameof(name)); + if (resourceDictionary == null) throw new ArgumentNullException(nameof(resourceDictionary)); + + var accentExists = GetAccent(name) != null; + if (accentExists) + { + return false; + } + + _accents.Add(new Accent(name, resourceDictionary)); + return true; + } + /// /// Adds an app theme with the given name. /// + /// The name of the new AppTheme. + /// The URI of the AppTheme ResourceDictionary. /// true if the app theme does not exists and can be added. - public static bool AddAppTheme(string name, Uri resourceAddress) + public static bool AddAppTheme([NotNull] string name, [NotNull] Uri resourceAddress) { if (name == null) throw new ArgumentNullException(nameof(name)); if (resourceAddress == null) throw new ArgumentNullException(nameof(resourceAddress)); @@ -121,6 +146,27 @@ public static bool AddAppTheme(string name, Uri resourceAddress) return true; } + /// + /// Adds an app theme with the given name. + /// + /// The name of the new AppTheme. + /// The ResourceDictionary of the accent. + /// true if the app theme does not exists and can be added. + public static bool AddAppTheme([NotNull] string name, [NotNull] ResourceDictionary resourceDictionary) + { + if (name == null) throw new ArgumentNullException(nameof(name)); + if (resourceDictionary == null) throw new ArgumentNullException(nameof(resourceDictionary)); + + var appThemeExists = GetAppTheme(name) != null; + if (appThemeExists) + { + return false; + } + + _appThemes.Add(new AppTheme(name, resourceDictionary)); + return true; + } + /// /// Gets app theme with the given resource dictionary. /// From c32acadae97c0c70dd51df7a164ed7c2a9dd1083 Mon Sep 17 00:00:00 2001 From: Jan Karger Date: Mon, 30 Apr 2018 21:53:08 +0200 Subject: [PATCH 2/4] Use xUnit v2 --- src/Directory.build.props | 1 - .../MahApps.Metro.Caliburn.Demo.csproj | 17 +++--- .../MahApps.Metro.Demo.csproj | 27 ++++---- src/MahApps.Metro.sln | 6 ++ src/MahApps.Metro/MahApps.Metro.csproj | 11 ++-- .../MahApps.Metro.Tests.csproj | 60 +++++++++--------- src/Mahapps.Metro.Tests/TestApp.xaml.cs | 6 +- .../TestHelpers/AutomationTestBase.cs | 61 ++++++++++++++++--- .../TestHelpers/TestHost.cs | 1 + src/Mahapps.Metro.Tests/Tests/FlyoutTest.cs | 3 +- .../Views/AnimatedTabControlWindow.xaml.cs | 13 ++-- .../Views/AutoWatermarkTestWindow.xaml.cs | 14 ++--- .../Views/ButtonWindow.xaml.cs | 6 +- .../Views/CleanWindow.xaml.cs | 6 +- .../Views/DateAndTimePickerWindow.xaml.cs | 6 +- .../Views/DialogWindow.xaml.cs | 6 +- .../Views/FlyoutWindow.xaml.cs | 6 +- .../HiddenMinMaxCloseButtonsWindow.xaml.cs | 6 +- .../Views/TextBoxHelperTestWindow.xaml.cs | 6 +- src/Mahapps.Metro.Tests/paket.references | 8 ++- src/build.cake | 4 +- src/paket.dependencies | 17 +++++- src/paket.lock | 44 +++++++++++-- 23 files changed, 232 insertions(+), 103 deletions(-) diff --git a/src/Directory.build.props b/src/Directory.build.props index c215b6c628..53944e3a2d 100644 --- a/src/Directory.build.props +++ b/src/Directory.build.props @@ -16,7 +16,6 @@ - diff --git a/src/MahApps.Metro.Samples/MahApps.Metro.Caliburn.Demo/MahApps.Metro.Caliburn.Demo.csproj b/src/MahApps.Metro.Samples/MahApps.Metro.Caliburn.Demo/MahApps.Metro.Caliburn.Demo.csproj index c29b02a9b7..2a3998c4ef 100644 --- a/src/MahApps.Metro.Samples/MahApps.Metro.Caliburn.Demo/MahApps.Metro.Caliburn.Demo.csproj +++ b/src/MahApps.Metro.Samples/MahApps.Metro.Caliburn.Demo/MahApps.Metro.Caliburn.Demo.csproj @@ -8,24 +8,27 @@ WinExe - + SA1652 mahapps.metro.logo2.ico app.manifest - + - + - - - + + + $(AssemblyName).config - + + + + \ No newline at end of file diff --git a/src/MahApps.Metro.Samples/MahApps.Metro.Demo/MahApps.Metro.Demo.csproj b/src/MahApps.Metro.Samples/MahApps.Metro.Demo/MahApps.Metro.Demo.csproj index 9903e666ab..6ece743ca0 100644 --- a/src/MahApps.Metro.Samples/MahApps.Metro.Demo/MahApps.Metro.Demo.csproj +++ b/src/MahApps.Metro.Samples/MahApps.Metro.Demo/MahApps.Metro.Demo.csproj @@ -9,29 +9,32 @@ WinExe - + SA1652 mahapps.metro.logo2.ico app.manifest - + - - - - - - + + + + + + - - - + + + $(AssemblyName).config - + + + + \ No newline at end of file diff --git a/src/MahApps.Metro.sln b/src/MahApps.Metro.sln index 6f2689e8f7..de3fceea82 100644 --- a/src/MahApps.Metro.sln +++ b/src/MahApps.Metro.sln @@ -16,6 +16,12 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MahApps.Metro.Caliburn.Demo EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MahApps.Metro.Tests", "Mahapps.Metro.Tests\MahApps.Metro.Tests.csproj", "{40048BCF-D1C7-46CC-B781-05718BE25BFC}" EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{EC5373FC-2098-4D7B-8990-B15E9C631AE8}" + ProjectSection(SolutionItems) = preProject + Directory.build.props = Directory.build.props + Directory.build.targets = Directory.build.targets + EndProjectSection +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU diff --git a/src/MahApps.Metro/MahApps.Metro.csproj b/src/MahApps.Metro/MahApps.Metro.csproj index 94de47e682..cbf87fec87 100644 --- a/src/MahApps.Metro/MahApps.Metro.csproj +++ b/src/MahApps.Metro/MahApps.Metro.csproj @@ -8,12 +8,15 @@ true - - + + - + - + + + + \ No newline at end of file diff --git a/src/Mahapps.Metro.Tests/MahApps.Metro.Tests.csproj b/src/Mahapps.Metro.Tests/MahApps.Metro.Tests.csproj index 5e5ab95370..f873be32d4 100644 --- a/src/Mahapps.Metro.Tests/MahApps.Metro.Tests.csproj +++ b/src/Mahapps.Metro.Tests/MahApps.Metro.Tests.csproj @@ -1,31 +1,33 @@ - + - - - - net40 - MahApps.Metro.Tests - MahApps.Metro.Tests - false - - - - - - - - - - - - - - - $(AssemblyName).config - - - - - - + + + net47;net46;net45;net40 + MahApps.Metro.Tests + MahApps.Metro.Tests + false + + + + + + + + + + + + + + + $(AssemblyName).config + + + + + + + + + \ No newline at end of file diff --git a/src/Mahapps.Metro.Tests/TestApp.xaml.cs b/src/Mahapps.Metro.Tests/TestApp.xaml.cs index 6b88790ad4..6c85ef22db 100644 --- a/src/Mahapps.Metro.Tests/TestApp.xaml.cs +++ b/src/Mahapps.Metro.Tests/TestApp.xaml.cs @@ -1,6 +1,8 @@ -namespace MahApps.Metro.Tests +using System.Windows; + +namespace MahApps.Metro.Tests { - public partial class TestApp + public partial class TestApp : Application { public TestApp() { diff --git a/src/Mahapps.Metro.Tests/TestHelpers/AutomationTestBase.cs b/src/Mahapps.Metro.Tests/TestHelpers/AutomationTestBase.cs index 2ae643a350..c45efdcee7 100644 --- a/src/Mahapps.Metro.Tests/TestHelpers/AutomationTestBase.cs +++ b/src/Mahapps.Metro.Tests/TestHelpers/AutomationTestBase.cs @@ -1,24 +1,59 @@ -using System.Linq; +using System; +using System.Diagnostics; +using System.Linq; +using System.Reflection; +using System.Threading; using System.Windows; using MahApps.Metro.Controls; +using Xunit; + +#if !NET40 +using System.Windows.Threading; +using Xunit.Sdk; +#endif namespace MahApps.Metro.Tests.TestHelpers { - using System; - using System.Diagnostics; - using System.Reflection; - using System.Threading; - using Xunit; + public class ApplicationFixture : IDisposable + { + public ApplicationFixture() + { + // ... initialize + + TestHost.Initialize(); + } + + public void Dispose() + { + // ... clean up + +#if !NET40 + Application.Current.Dispatcher.Invoke(Application.Current.Shutdown); +#endif + } + } + +#if !NET40 + [CollectionDefinition("ApplicationFixtureCollection")] + public class ApplicationFixtureCollectionClass : ICollectionFixture + { + } + +#endif /// /// This is the base class for all of our UI tests. /// +#if !NET40 + [Collection("ApplicationFixtureCollection")] +#endif public class AutomationTestBase : IDisposable +#if NET40 + , IUseFixture +#endif { public AutomationTestBase() { - TestHost.Initialize(); - var message = $"Create test class '{this.GetType().Name}' with Thread.CurrentThread: {Thread.CurrentThread.ManagedThreadId}" + $" and Current.Dispatcher.Thread: {Application.Current.Dispatcher.Thread.ManagedThreadId}"; Debug.WriteLine(message); @@ -41,6 +76,14 @@ public AutomationTestBase() }); } +#if NET40 + private ApplicationFixture fixture; + public void SetFixture(ApplicationFixture data) + { + this.fixture = data; + } +#endif + /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. public void Dispose() { @@ -57,7 +100,6 @@ public override void Before(MethodInfo methodUnderTest) var message = $"Setup for test '{methodUnderTest.Name}' with Thread.CurrentThread: {Thread.CurrentThread.ManagedThreadId}" + $" and Current.Dispatcher.Thread: {Application.Current.Dispatcher.Thread.ManagedThreadId}"; Debug.WriteLine(message); - //Console.WriteLine("Setup for test '{0}.'", methodUnderTest.Name); } public override void After(MethodInfo methodUnderTest) @@ -65,7 +107,6 @@ public override void After(MethodInfo methodUnderTest) var message = $"TearDown for test '{methodUnderTest.Name}' with Thread.CurrentThread: {Thread.CurrentThread.ManagedThreadId}" + $" and Current.Dispatcher.Thread: {Application.Current.Dispatcher.Thread.ManagedThreadId}"; Debug.WriteLine(message); - //Console.WriteLine("TearDown for test '{0}.'", methodUnderTest.Name); } } } \ No newline at end of file diff --git a/src/Mahapps.Metro.Tests/TestHelpers/TestHost.cs b/src/Mahapps.Metro.Tests/TestHelpers/TestHost.cs index 2132d90e45..a2129435b9 100644 --- a/src/Mahapps.Metro.Tests/TestHelpers/TestHost.cs +++ b/src/Mahapps.Metro.Tests/TestHelpers/TestHost.cs @@ -50,6 +50,7 @@ private void StartDispatcher() var message = $"Exit TestApp with Thread.CurrentThread: {Thread.CurrentThread.ManagedThreadId}" + $" and Current.Dispatcher.Thread: {Application.Current.Dispatcher.Thread.ManagedThreadId}"; Debug.WriteLine(message); + }; app.Startup += (sender, args) => { diff --git a/src/Mahapps.Metro.Tests/Tests/FlyoutTest.cs b/src/Mahapps.Metro.Tests/Tests/FlyoutTest.cs index c8fb567fdb..d46bcb6045 100644 --- a/src/Mahapps.Metro.Tests/Tests/FlyoutTest.cs +++ b/src/Mahapps.Metro.Tests/Tests/FlyoutTest.cs @@ -192,11 +192,12 @@ public async Task FindFlyoutWithFindChildren() var window = await WindowHelpers.CreateInvisibleWindowAsync(); - Assert.DoesNotThrow(() => { + var ex = Record.Exception(() => { var flyouts = (window.Content as DependencyObject).FindChildren(true); var flyoutOnGrid = flyouts.FirstOrDefault(f => f.Name == "FlyoutOnGrid"); Assert.NotNull(flyoutOnGrid); }); + Assert.Null(ex); } [Fact] diff --git a/src/Mahapps.Metro.Tests/Views/AnimatedTabControlWindow.xaml.cs b/src/Mahapps.Metro.Tests/Views/AnimatedTabControlWindow.xaml.cs index 5c4e148627..ebac481cb7 100644 --- a/src/Mahapps.Metro.Tests/Views/AnimatedTabControlWindow.xaml.cs +++ b/src/Mahapps.Metro.Tests/Views/AnimatedTabControlWindow.xaml.cs @@ -1,10 +1,11 @@ -namespace MahApps.Metro.Tests -{ - using System.Collections.ObjectModel; - using System.Windows; - using System.Windows.Controls; +using System.Collections.ObjectModel; +using System.Windows; +using System.Windows.Controls; +using MahApps.Metro.Controls; - public partial class AnimatedTabControlWindow +namespace MahApps.Metro.Tests +{ + public partial class AnimatedTabControlWindow : MetroWindow { public AnimatedTabControlWindow() { diff --git a/src/Mahapps.Metro.Tests/Views/AutoWatermarkTestWindow.xaml.cs b/src/Mahapps.Metro.Tests/Views/AutoWatermarkTestWindow.xaml.cs index 07440aaa0a..628fd9e274 100644 --- a/src/Mahapps.Metro.Tests/Views/AutoWatermarkTestWindow.xaml.cs +++ b/src/Mahapps.Metro.Tests/Views/AutoWatermarkTestWindow.xaml.cs @@ -1,11 +1,11 @@ -namespace MahApps.Metro.Tests -{ - using System; - using System.Collections.ObjectModel; - using System.ComponentModel.DataAnnotations; - using MahApps.Metro.Controls; +using System; +using System.Collections.ObjectModel; +using System.ComponentModel.DataAnnotations; +using MahApps.Metro.Controls; - public partial class AutoWatermarkTestWindow +namespace MahApps.Metro.Tests +{ + public partial class AutoWatermarkTestWindow : MetroWindow { public AutoWatermarkTestWindow() { diff --git a/src/Mahapps.Metro.Tests/Views/ButtonWindow.xaml.cs b/src/Mahapps.Metro.Tests/Views/ButtonWindow.xaml.cs index f29b813dba..44e7cd73ac 100644 --- a/src/Mahapps.Metro.Tests/Views/ButtonWindow.xaml.cs +++ b/src/Mahapps.Metro.Tests/Views/ButtonWindow.xaml.cs @@ -1,6 +1,8 @@ -namespace MahApps.Metro.Tests +using MahApps.Metro.Controls; + +namespace MahApps.Metro.Tests { - public partial class ButtonWindow + public partial class ButtonWindow : MetroWindow { public ButtonWindow() { diff --git a/src/Mahapps.Metro.Tests/Views/CleanWindow.xaml.cs b/src/Mahapps.Metro.Tests/Views/CleanWindow.xaml.cs index b44364e718..6f473ef591 100644 --- a/src/Mahapps.Metro.Tests/Views/CleanWindow.xaml.cs +++ b/src/Mahapps.Metro.Tests/Views/CleanWindow.xaml.cs @@ -1,6 +1,8 @@ -namespace MahApps.Metro.Tests +using MahApps.Metro.Controls; + +namespace MahApps.Metro.Tests { - public partial class CleanWindow + public partial class CleanWindow : MetroWindow { public CleanWindow() { diff --git a/src/Mahapps.Metro.Tests/Views/DateAndTimePickerWindow.xaml.cs b/src/Mahapps.Metro.Tests/Views/DateAndTimePickerWindow.xaml.cs index f15b70b718..2459fff595 100644 --- a/src/Mahapps.Metro.Tests/Views/DateAndTimePickerWindow.xaml.cs +++ b/src/Mahapps.Metro.Tests/Views/DateAndTimePickerWindow.xaml.cs @@ -1,6 +1,8 @@ -namespace MahApps.Metro.Tests +using MahApps.Metro.Controls; + +namespace MahApps.Metro.Tests { - public partial class DateAndTimePickerWindow + public partial class DateAndTimePickerWindow : MetroWindow { public DateAndTimePickerWindow() { diff --git a/src/Mahapps.Metro.Tests/Views/DialogWindow.xaml.cs b/src/Mahapps.Metro.Tests/Views/DialogWindow.xaml.cs index 3dd95bb5b5..f11fe2f188 100644 --- a/src/Mahapps.Metro.Tests/Views/DialogWindow.xaml.cs +++ b/src/Mahapps.Metro.Tests/Views/DialogWindow.xaml.cs @@ -1,6 +1,8 @@ -namespace MahApps.Metro.Tests +using MahApps.Metro.Controls; + +namespace MahApps.Metro.Tests { - public partial class DialogWindow + public partial class DialogWindow : MetroWindow { public DialogWindow() { diff --git a/src/Mahapps.Metro.Tests/Views/FlyoutWindow.xaml.cs b/src/Mahapps.Metro.Tests/Views/FlyoutWindow.xaml.cs index f66cfbd6e6..8fb94e5269 100644 --- a/src/Mahapps.Metro.Tests/Views/FlyoutWindow.xaml.cs +++ b/src/Mahapps.Metro.Tests/Views/FlyoutWindow.xaml.cs @@ -1,6 +1,8 @@ -namespace MahApps.Metro.Tests +using MahApps.Metro.Controls; + +namespace MahApps.Metro.Tests { - public partial class FlyoutWindow + public partial class FlyoutWindow : MetroWindow { public FlyoutWindow() { diff --git a/src/Mahapps.Metro.Tests/Views/HiddenMinMaxCloseButtonsWindow.xaml.cs b/src/Mahapps.Metro.Tests/Views/HiddenMinMaxCloseButtonsWindow.xaml.cs index 743784cc30..d76e576830 100644 --- a/src/Mahapps.Metro.Tests/Views/HiddenMinMaxCloseButtonsWindow.xaml.cs +++ b/src/Mahapps.Metro.Tests/Views/HiddenMinMaxCloseButtonsWindow.xaml.cs @@ -1,6 +1,8 @@ -namespace MahApps.Metro.Tests +using MahApps.Metro.Controls; + +namespace MahApps.Metro.Tests { - public partial class HiddenMinMaxCloseButtonsWindow + public partial class HiddenMinMaxCloseButtonsWindow : MetroWindow { public HiddenMinMaxCloseButtonsWindow() { diff --git a/src/Mahapps.Metro.Tests/Views/TextBoxHelperTestWindow.xaml.cs b/src/Mahapps.Metro.Tests/Views/TextBoxHelperTestWindow.xaml.cs index 575671d01b..4b2ab6f0f6 100644 --- a/src/Mahapps.Metro.Tests/Views/TextBoxHelperTestWindow.xaml.cs +++ b/src/Mahapps.Metro.Tests/Views/TextBoxHelperTestWindow.xaml.cs @@ -1,6 +1,8 @@ -namespace MahApps.Metro.Tests +using MahApps.Metro.Controls; + +namespace MahApps.Metro.Tests { - public partial class TextBoxHelperTestWindow + public partial class TextBoxHelperTestWindow : MetroWindow { public TextBoxHelperTestWindow() { diff --git a/src/Mahapps.Metro.Tests/paket.references b/src/Mahapps.Metro.Tests/paket.references index faf9d87bad..18d6f8ad3c 100644 --- a/src/Mahapps.Metro.Tests/paket.references +++ b/src/Mahapps.Metro.Tests/paket.references @@ -1,8 +1,12 @@ JetBrains.Annotations -xunit -xunit.extensions ExposedObject Microsoft.Bcl redirects: on, framework: net40 Microsoft.Bcl.Async framework: net40 Microsoft.Bcl.Build import_targets: true, framework: net40 + +group xunit40 + xunit framework: net40 + +group xunit45 + xunit framework: >=net45 diff --git a/src/build.cake b/src/build.cake index 8cc38815f1..5ad82c694d 100644 --- a/src/build.cake +++ b/src/build.cake @@ -123,9 +123,9 @@ Task("Unit-Tests") //.WithCriteria(ShouldRunRelease()) .Does(() => { - XUnit( + XUnit2( "./Mahapps.Metro.Tests/bin/" + configuration + "/**/*.Tests.dll", - new XUnitSettings { ToolPath = "./packages/cake/xunit.runner.console/tools/net452/xunit.console.exe" } + new XUnit2Settings { ToolTimeout = TimeSpan.FromMinutes(5) } ); }); diff --git a/src/paket.dependencies b/src/paket.dependencies index 163ca7535d..57eb60b2da 100644 --- a/src/paket.dependencies +++ b/src/paket.dependencies @@ -13,8 +13,6 @@ nuget MahApps.Metro.IconPacks nuget MaterialDesignThemes prerelease nuget NHotkey 1.2.1 nuget NHotkey.Wpf 1.2.1 -nuget xunit ~> 1.9 -nuget xunit.extensions ~> 1.9 nuget ControlzEx < 4.0 nuget ExposedObject < 1.3 @@ -22,6 +20,21 @@ nuget Microsoft.Bcl nuget Microsoft.Bcl.Async nuget Microsoft.Bcl.Build +group xunit40 + framework: net40 + source https://nuget.org/api/v2 + + nuget xunit ~> 1.9 + nuget xunit.extensions ~> 1.9 + +group xunit45 + framework: >= net45 + source https://nuget.org/api/v2 + + nuget xunit + nuget Microsoft.NET.Test.Sdk + nuget xunit.runner.visualstudio + group cake framework: net45 source https://nuget.org/api/v2 diff --git a/src/paket.lock b/src/paket.lock index 7d8b156ed7..dbbaa3fb7f 100644 --- a/src/paket.lock +++ b/src/paket.lock @@ -25,10 +25,7 @@ NUGET NHotkey (1.2.1) NHotkey.Wpf (1.2.1) NHotkey (>= 1.2.1) - WpfAnalyzers (2.1.2) - xunit (1.9.2) - xunit.extensions (1.9.2) - xunit (1.9.2) + WpfAnalyzers (2.1.2.1) GROUP cake RESTRICTION: == net45 @@ -41,3 +38,42 @@ NUGET gitreleasemanager (0.7) GitVersion.CommandLine (4.0.0-beta0012) xunit.runner.console (2.3.1) + +GROUP xunit40 +RESTRICTION: == net40 +NUGET + remote: https://www.nuget.org/api/v2 + xunit (1.9.2) + xunit.extensions (1.9.2) + xunit (1.9.2) + +GROUP xunit45 +RESTRICTION: >= net45 +NUGET + remote: https://www.nuget.org/api/v2 + Microsoft.CodeCoverage (1.0.3) + Microsoft.NET.Test.Sdk (15.7) + Microsoft.CodeCoverage (>= 1.0.3) + Microsoft.NETCore.Platforms (2.0.2) - restriction: || (&& (>= net45) (< net452) (< netstandard1.3)) (&& (>= net45) (>= netcoreapp2.0)) (&& (>= net45) (< netstandard1.0)) (&& (>= net45) (< netstandard1.3) (>= wpa81)) (&& (>= net45) (< netstandard1.5) (>= uap10.0)) (&& (>= net45) (< portable-net45+win8+wpa81)) (&& (>= net45) (>= uap10.1)) (&& (>= net45) (>= wp8)) (&& (< net452) (>= net46) (< netstandard1.4)) (&& (< net452) (>= net461)) + NETStandard.Library (2.0.2) - restriction: && (>= net45) (< net452) + Microsoft.NETCore.Platforms (>= 1.1) + System.Runtime.InteropServices.RuntimeInformation (>= 4.3) - restriction: || (&& (>= net45) (< netstandard1.0)) (&& (>= net45) (< netstandard1.3)) (&& (>= net45) (< netstandard1.5) (>= uap10.0)) (&& (>= net46) (< netstandard1.4)) + System.Runtime.InteropServices.RuntimeInformation (4.3) - restriction: || (&& (>= net45) (< net452) (< netstandard1.3)) (&& (>= net45) (< netstandard1.0)) (&& (>= net45) (< netstandard1.3) (>= wpa81)) (&& (>= net45) (< netstandard1.5) (>= uap10.0)) (&& (< net452) (>= net46) (< netstandard1.4)) + xunit (2.3.1) + xunit.analyzers (>= 0.7) + xunit.assert (2.3.1) + xunit.core (2.3.1) + xunit.abstractions (2.0.1) + xunit.analyzers (0.8) + xunit.assert (2.3.1) + NETStandard.Library (>= 1.6.1) - restriction: && (>= net45) (< net452) + xunit.core (2.3.1) + xunit.extensibility.core (2.3.1) + xunit.extensibility.execution (2.3.1) + xunit.extensibility.core (2.3.1) + NETStandard.Library (>= 1.6.1) - restriction: && (>= net45) (< net452) + xunit.abstractions (>= 2.0.1) + xunit.extensibility.execution (2.3.1) + NETStandard.Library (>= 1.6.1) - restriction: && (>= net45) (< net452) + xunit.extensibility.core (2.3.1) + xunit.runner.visualstudio (2.3.1) From 4442aecd1dc87502646c1f572d6518daee980c93 Mon Sep 17 00:00:00 2001 From: Jan Karger Date: Mon, 30 Apr 2018 22:38:07 +0200 Subject: [PATCH 3/4] net47 and net46 not working for now --- src/Mahapps.Metro.Tests/MahApps.Metro.Tests.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Mahapps.Metro.Tests/MahApps.Metro.Tests.csproj b/src/Mahapps.Metro.Tests/MahApps.Metro.Tests.csproj index f873be32d4..ce0b4428ed 100644 --- a/src/Mahapps.Metro.Tests/MahApps.Metro.Tests.csproj +++ b/src/Mahapps.Metro.Tests/MahApps.Metro.Tests.csproj @@ -2,7 +2,7 @@ - net47;net46;net45;net40 + net45;net40 MahApps.Metro.Tests MahApps.Metro.Tests false From 86a5047fee14656763675fdf222ed7f63a68294b Mon Sep 17 00:00:00 2001 From: Jan Karger Date: Mon, 30 Apr 2018 22:49:59 +0200 Subject: [PATCH 4/4] (GH-3227) ThemeManager: enable dynamic accents (and themes) to change on the fly --- .../ThemeManager/ThemeManager.cs | 50 ++++++++--- .../TestHelpers/AccentHelper.cs | 90 +++++++++++++++++++ .../Tests/ThemeManagerTest.cs | 26 ++++++ 3 files changed, 153 insertions(+), 13 deletions(-) create mode 100644 src/Mahapps.Metro.Tests/TestHelpers/AccentHelper.cs diff --git a/src/MahApps.Metro/ThemeManager/ThemeManager.cs b/src/MahApps.Metro/ThemeManager/ThemeManager.cs index aa4effecc2..c5fd1eb935 100644 --- a/src/MahApps.Metro/ThemeManager/ThemeManager.cs +++ b/src/MahApps.Metro/ThemeManager/ThemeManager.cs @@ -176,7 +176,7 @@ public static AppTheme GetAppTheme(ResourceDictionary resources) { if (resources == null) throw new ArgumentNullException(nameof(resources)); - return AppThemes.FirstOrDefault(x => AreResourceDictionarySourcesEqual(x.Resources.Source, resources.Source)); + return AppThemes.FirstOrDefault(x => AreResourceDictionarySourcesEqual(x.Resources, resources)); } /// @@ -237,7 +237,7 @@ public static Accent GetAccent(ResourceDictionary resources) { if (resources == null) throw new ArgumentNullException(nameof(resources)); - var builtInAccent = Accents.FirstOrDefault(x => AreResourceDictionarySourcesEqual(x.Resources.Source, resources.Source)); + var builtInAccent = Accents.FirstOrDefault(x => AreResourceDictionarySourcesEqual(x.Resources, resources)); if (builtInAccent != null) { return builtInAccent; @@ -413,14 +413,13 @@ private static void ChangeAppStyle(ResourceDictionary resources, Tuple x.Source != null).FirstOrDefault(d => d.Source.ToString().ToLower() == key); + var oldAccentResource = resources.MergedDictionaries.FirstOrDefault(d => AreResourceDictionarySourcesEqual(d, oldAccent.Resources)); if (oldAccentResource != null) { resources.MergedDictionaries.Remove(oldAccentResource); } + + resources.MergedDictionaries.Add(newAccent.Resources); themeChanged = true; } @@ -428,15 +427,14 @@ private static void ChangeAppStyle(ResourceDictionary resources, Tuple x.Source != null).FirstOrDefault(d => d.Source.ToString().ToLower() == key); + var oldThemeResource = resources.MergedDictionaries.FirstOrDefault(d => AreResourceDictionarySourcesEqual(d, oldTheme.Resources)); if (oldThemeResource != null) { resources.MergedDictionaries.Remove(oldThemeResource); } + resources.MergedDictionaries.Add(newTheme.Resources); + themeChanged = true; } } @@ -644,10 +642,36 @@ private static void OnThemeChanged(Accent newAccent, AppTheme newTheme) IsThemeChanged?.Invoke(Application.Current, new OnThemeChangedEventArgs(newTheme, newAccent)); } - private static bool AreResourceDictionarySourcesEqual(Uri first, Uri second) + private static bool AreResourceDictionarySourcesEqual(ResourceDictionary first, ResourceDictionary second) { - return Uri.Compare(first, second, - UriComponents.Host | UriComponents.Path, UriFormat.SafeUnescaped, StringComparison.OrdinalIgnoreCase) == 0; + if (first == null || second == null) + { + return false; + } + + if (first.Source == null || second.Source == null) + { + try + { + foreach (var key in first.Keys) + { + var isTheSame = second.Contains(key) && Equals(first[key], second[key]); + if (!isTheSame) + { + return false; + } + } + } + catch (Exception exception) + { + Trace.TraceError($"Could not compare resource dictionaries: {exception} {Environment.NewLine} {exception.StackTrace}"); + return false; + } + + return true; + } + + return Uri.Compare(first.Source, second.Source, UriComponents.Host | UriComponents.Path, UriFormat.SafeUnescaped, StringComparison.OrdinalIgnoreCase) == 0; } } diff --git a/src/Mahapps.Metro.Tests/TestHelpers/AccentHelper.cs b/src/Mahapps.Metro.Tests/TestHelpers/AccentHelper.cs new file mode 100644 index 0000000000..93069bf36c --- /dev/null +++ b/src/Mahapps.Metro.Tests/TestHelpers/AccentHelper.cs @@ -0,0 +1,90 @@ +using System; +using System.Windows; +using System.Windows.Media; + +namespace MahApps.Metro.Tests.TestHelpers +{ + public static class AccentHelper + { + public static void ApplyColor(Color color, string accentName = null) + { + // create a runtime accent resource dictionary + + var resDictName = accentName ?? $"ApplicationAccent_{color.ToString().Replace("#", string.Empty)}.xaml"; + + var resourceDictionary = new ResourceDictionary(); + + resourceDictionary.Add("HighlightColor", color); + resourceDictionary.Add("AccentBaseColor", color); + resourceDictionary.Add("AccentColor", Color.FromArgb((byte)(204), color.R, color.G, color.B)); + resourceDictionary.Add("AccentColor2", Color.FromArgb((byte)(153), color.R, color.G, color.B)); + resourceDictionary.Add("AccentColor3", Color.FromArgb((byte)(102), color.R, color.G, color.B)); + resourceDictionary.Add("AccentColor4", Color.FromArgb((byte)(51), color.R, color.G, color.B)); + + resourceDictionary.Add("HighlightBrush", GetSolidColorBrush((Color)resourceDictionary["HighlightColor"])); + resourceDictionary.Add("AccentBaseColorBrush", GetSolidColorBrush((Color)resourceDictionary["AccentBaseColor"])); + resourceDictionary.Add("AccentColorBrush", GetSolidColorBrush((Color)resourceDictionary["AccentColor"])); + resourceDictionary.Add("AccentColorBrush2", GetSolidColorBrush((Color)resourceDictionary["AccentColor2"])); + resourceDictionary.Add("AccentColorBrush3", GetSolidColorBrush((Color)resourceDictionary["AccentColor3"])); + resourceDictionary.Add("AccentColorBrush4", GetSolidColorBrush((Color)resourceDictionary["AccentColor4"])); + + resourceDictionary.Add("WindowTitleColorBrush", GetSolidColorBrush((Color)resourceDictionary["AccentColor"])); + + resourceDictionary.Add("ProgressBrush", new LinearGradientBrush( + new GradientStopCollection(new[] + { + new GradientStop((Color)resourceDictionary["HighlightColor"], 0), + new GradientStop((Color)resourceDictionary["AccentColor3"], 1) + }), + // StartPoint="1.002,0.5" EndPoint="0.001,0.5" + startPoint: new Point(1.002, 0.5), endPoint: new Point(0.001, 0.5))); + + resourceDictionary.Add("CheckmarkFill", GetSolidColorBrush((Color)resourceDictionary["AccentColor"])); + resourceDictionary.Add("RightArrowFill", GetSolidColorBrush((Color)resourceDictionary["AccentColor"])); + + resourceDictionary.Add("IdealForegroundColor", IdealTextColor(color)); + resourceDictionary.Add("IdealForegroundColorBrush", GetSolidColorBrush((Color)resourceDictionary["IdealForegroundColor"])); + resourceDictionary.Add("IdealForegroundDisabledBrush", GetSolidColorBrush((Color)resourceDictionary["IdealForegroundColor"], 0.4)); + resourceDictionary.Add("AccentSelectedColorBrush", GetSolidColorBrush((Color)resourceDictionary["IdealForegroundColor"])); + + resourceDictionary.Add("MetroDataGrid.HighlightBrush", GetSolidColorBrush((Color)resourceDictionary["AccentColor"])); + resourceDictionary.Add("MetroDataGrid.HighlightTextBrush", GetSolidColorBrush((Color)resourceDictionary["IdealForegroundColor"])); + resourceDictionary.Add("MetroDataGrid.MouseOverHighlightBrush", GetSolidColorBrush((Color)resourceDictionary["AccentColor3"])); + resourceDictionary.Add("MetroDataGrid.FocusBorderBrush", GetSolidColorBrush((Color)resourceDictionary["AccentColor"])); + resourceDictionary.Add("MetroDataGrid.InactiveSelectionHighlightBrush", GetSolidColorBrush((Color)resourceDictionary["AccentColor2"])); + resourceDictionary.Add("MetroDataGrid.InactiveSelectionHighlightTextBrush", GetSolidColorBrush((Color)resourceDictionary["IdealForegroundColor"])); + + resourceDictionary.Add("MahApps.Metro.Brushes.ToggleSwitchButton.OnSwitchBrush.Win10", GetSolidColorBrush((Color)resourceDictionary["AccentColor"])); + resourceDictionary.Add("MahApps.Metro.Brushes.ToggleSwitchButton.OnSwitchMouseOverBrush.Win10", GetSolidColorBrush((Color)resourceDictionary["AccentColor2"])); + resourceDictionary.Add("MahApps.Metro.Brushes.ToggleSwitchButton.ThumbIndicatorCheckedBrush.Win10", GetSolidColorBrush((Color)resourceDictionary["IdealForegroundColor"])); + + // applying theme to MahApps + ThemeManager.AddAccent(resDictName, resourceDictionary); + var newAccent = ThemeManager.GetAccent(resDictName); + // detect current application theme + Tuple applicationTheme = ThemeManager.DetectAppStyle(Application.Current); + ThemeManager.ChangeAppStyle(Application.Current, newAccent, applicationTheme.Item1); + } + + /// + /// Determining Ideal Text Color Based on Specified Background Color + /// http://www.codeproject.com/KB/GDI-plus/IdealTextColor.aspx + /// + /// The bg. + /// + private static Color IdealTextColor(Color color) + { + const int nThreshold = 105; + var bgDelta = System.Convert.ToInt32((color.R * 0.299) + (color.G * 0.587) + (color.B * 0.114)); + var foreColor = (255 - bgDelta < nThreshold) ? Colors.Black : Colors.White; + return foreColor; + } + + private static SolidColorBrush GetSolidColorBrush(Color color, double opacity = 1d) + { + var brush = new SolidColorBrush(color) { Opacity = opacity }; + brush.Freeze(); + return brush; + } + } +} \ No newline at end of file diff --git a/src/Mahapps.Metro.Tests/Tests/ThemeManagerTest.cs b/src/Mahapps.Metro.Tests/Tests/ThemeManagerTest.cs index 13e052e63a..4d3041a71e 100644 --- a/src/Mahapps.Metro.Tests/Tests/ThemeManagerTest.cs +++ b/src/Mahapps.Metro.Tests/Tests/ThemeManagerTest.cs @@ -2,6 +2,7 @@ using System.Linq; using System.Threading.Tasks; using System.Windows; +using System.Windows.Media; using MahApps.Metro.Tests.TestHelpers; using MahApps.Metro.Controls; using Xunit; @@ -162,5 +163,30 @@ public async Task GetAccentWithUriIsCaseInsensitive() Assert.NotNull(detected); Assert.Equal("Blue", detected.Name); } + + [Fact] + [DisplayTestMethodName] + public async Task CreateDynamicAccentWithColor() + { + await TestHost.SwitchToAppThread(); + + var applicationTheme = ThemeManager.DetectAppStyle(Application.Current); + + var ex = Record.Exception(() => AccentHelper.ApplyColor(Colors.Red, "CustomAccentRed")); + Assert.Null(ex); + + var detected = ThemeManager.DetectAppStyle(Application.Current); + Assert.NotNull(detected); + Assert.Equal("CustomAccentRed", detected.Item2.Name); + + ex = Record.Exception(() => AccentHelper.ApplyColor(Colors.Green, "CustomAccentGreen")); + Assert.Null(ex); + + detected = ThemeManager.DetectAppStyle(Application.Current); + Assert.NotNull(detected); + Assert.Equal("CustomAccentGreen", detected.Item2.Name); + + ThemeManager.ChangeAppStyle(Application.Current, applicationTheme.Item2, applicationTheme.Item1); + } } } \ No newline at end of file