Skip to content

Commit

Permalink
Using reflection to add handler for user preference events (dotnet/co…
Browse files Browse the repository at this point in the history
…refx#37853)

* using reflection to add handler for user preference events

* adding comments, correcting typo and using GetType

* adding debug.asserts

* pushing down color translator

* using open unbound delegate pointing directly to getter method of the eventargs category property

* No longer using colorTable on netcoreapp 2.1 in system.Drawing.Common

* reverting the use of extension method.

* doing  s_color check first and adding null check for category getter

* missing semicolon

* improving comments and adding tests

* removing extra using

* using already defined known color

* skipping the test on nano and non-windows platform

* introducing a custom property


Commit migrated from dotnet/corefx@d24b3e3
  • Loading branch information
Anipik authored and msftbot[bot] committed May 30, 2019
1 parent 1107721 commit 608a743
Show file tree
Hide file tree
Showing 10 changed files with 139 additions and 27 deletions.
69 changes: 57 additions & 12 deletions src/libraries/Common/src/System/Drawing/KnownColorTable.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,13 @@
// See the LICENSE file in the project root for more information.

using System.Diagnostics;
using System.Reflection;

namespace System.Drawing
{
#if FEATURE_SYSTEM_EVENTS
using Microsoft.Win32;
using System.Drawing.Internal;
#endif

static internal class KnownColorTable
{
private static Func<EventArgs, int> s_categoryGetter;
private static int[] s_colorTable;
private static string[] s_colorNameTable;

Expand Down Expand Up @@ -59,9 +56,48 @@ private static void InitColorTable()
{
int[] values = new int[(unchecked((int)KnownColor.MenuHighlight)) + 1];

#if FEATURE_SYSTEM_EVENTS
SystemEvents.UserPreferenceChanging += new UserPreferenceChangingEventHandler(OnUserPreferenceChanging);
#endif
// When Microsoft.Win32.SystemEvents is available, we can react to the UserPreferenceChanging events by updating
// SystemColors. In order to avoid a static dependency on SystemEvents since it is not available on all platforms
// and as such we don't want to bring it into the shared framework. We use the desktop identity for SystemEvents
// since it is stable and will remain stable for compatibility whereas the core identity could change.
Type systemEventsType = Type.GetType("Microsoft.Win32.SystemEvents, System, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089", throwOnError: false);
EventInfo upEventInfo = systemEventsType?.GetEvent("UserPreferenceChanging", BindingFlags.Public | BindingFlags.Static);

if (upEventInfo != null)
{
// Delegate TargetType
Type userPrefChangingDelegateType = Type.GetType("Microsoft.Win32.UserPreferenceChangingEventHandler, System, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089", throwOnError: false);
Debug.Assert(userPrefChangingDelegateType != null);

if (userPrefChangingDelegateType != null)
{
// we are using MethodInfo overload because it allows relaxed signature binding i.e. the types dont need to be exact match. It allows base classes as well.
MethodInfo mi = typeof(KnownColorTable).GetMethod(nameof(OnUserPreferenceChanging), BindingFlags.NonPublic | BindingFlags.Static);
Debug.Assert(mi != null);

if (mi != null)
{
// Creating a delegate to use it as event handler.
Delegate handler = Delegate.CreateDelegate(userPrefChangingDelegateType, mi);
upEventInfo.AddEventHandler(null, handler);
}

// Retrieving getter of the category property of the UserPreferenceChangingEventArgs.
Type argsType = Type.GetType("Microsoft.Win32.UserPreferenceChangingEventArgs, System, Version = 4.0.0.0, Culture = neutral, PublicKeyToken = b77a5c561934e089", throwOnError: false);
PropertyInfo categoryProperty = argsType?.GetProperty("Category", BindingFlags.Instance | BindingFlags.Public);
MethodInfo categoryGetter = categoryProperty?.GetGetMethod();

Debug.Assert(categoryGetter != null);
if (categoryGetter != null)
{
// Creating a Delegate pointing to the getter method.
s_categoryGetter = (Func<EventArgs, int>)typeof(KnownColorTable).GetMethod(nameof(CreateGetter), BindingFlags.NonPublic | BindingFlags.Static)
.MakeGenericMethod(argsType, categoryGetter.ReturnType)
.Invoke(null, new object[] { categoryGetter });
}
}
}

UpdateSystemColors(values);

// just consts...
Expand Down Expand Up @@ -210,6 +246,15 @@ private static void InitColorTable()
s_colorTable = values;
}

// Generic method that we specialize at runtime once we've loaded the UserPreferenceChangingEventArgs type.
// It permits creating an unbound delegate so that we can avoid reflection after the initial
// lightup code completes.
private static Func<EventArgs, int> CreateGetter<TInstance, TProperty>(MethodInfo method) where TInstance : EventArgs where TProperty : Enum
{
Func<TInstance, TProperty> typedDelegate = (Func<TInstance, TProperty>)Delegate.CreateDelegate(typeof(Func<TInstance, TProperty>), null, method);
return (EventArgs instance) => (int)(object)typedDelegate((TInstance)instance);
}

private static void EnsureColorNameTable()
{
// no need to lock... worse case is a double create of the table...
Expand Down Expand Up @@ -438,15 +483,15 @@ private static int FromWin32Value(int value)
}
#endif

#if FEATURE_SYSTEM_EVENTS
private static void OnUserPreferenceChanging(object sender, UserPreferenceChangingEventArgs e)
private static void OnUserPreferenceChanging(object sender, EventArgs args)
{
if (e.Category == UserPreferenceCategory.Color && s_colorTable != null)
Debug.Assert(s_categoryGetter != null);
// Access UserPreferenceChangingEventArgs.Category value through cached delegate.
if (s_colorTable != null && s_categoryGetter != null && s_categoryGetter(args) == 2) // UserPreferenceCategory.Color = 2
{
UpdateSystemColors(s_colorTable);
}
}
#endif

private static void UpdateSystemColors(int[] colorTable)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,5 @@
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.

[assembly: System.Runtime.CompilerServices.TypeForwardedTo(typeof(System.Drawing.SystemColors))]
[assembly: System.Runtime.CompilerServices.TypeForwardedTo(typeof(System.Drawing.SystemColors))]
[assembly: System.Runtime.CompilerServices.TypeForwardedTo(typeof(System.Drawing.ColorTranslator))]
Original file line number Diff line number Diff line change
Expand Up @@ -238,15 +238,6 @@ public partial struct CharacterRange
public static bool operator ==(System.Drawing.CharacterRange cr1, System.Drawing.CharacterRange cr2) { throw null; }
public static bool operator !=(System.Drawing.CharacterRange cr1, System.Drawing.CharacterRange cr2) { throw null; }
}
public static partial class ColorTranslator
{
public static System.Drawing.Color FromHtml(string htmlColor) { throw null; }
public static System.Drawing.Color FromOle(int oleColor) { throw null; }
public static System.Drawing.Color FromWin32(int win32Color) { throw null; }
public static string ToHtml(System.Drawing.Color c) { throw null; }
public static int ToOle(System.Drawing.Color c) { throw null; }
public static int ToWin32(System.Drawing.Color c) { throw null; }
}
public enum ContentAlignment
{
TopLeft = 1,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,16 @@
// See the LICENSE file in the project root for more information.

namespace System.Drawing
{
{
public static partial class ColorTranslator
{
public static System.Drawing.Color FromHtml(string htmlColor) { throw null; }
public static System.Drawing.Color FromOle(int oleColor) { throw null; }
public static System.Drawing.Color FromWin32(int win32Color) { throw null; }
public static string ToHtml(System.Drawing.Color c) { throw null; }
public static int ToOle(System.Drawing.Color c) { throw null; }
public static int ToWin32(System.Drawing.Color c) { throw null; }
}
public static partial class SystemColors
{
public static System.Drawing.Color ActiveBorder { get { throw null; } }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@
<Compile Include="System\Drawing\BufferedGraphicsContext.cs" />
<Compile Include="System\Drawing\Brushes.cs" />
<Compile Include="System\Drawing\CharacterRange.cs" />
<Compile Include="System\Drawing\ColorTranslator.cs" />
<Compile Include="System\Drawing\ContentAlignment.cs" />
<Compile Include="System\Drawing\IDeviceContext.cs" />
<Compile Include="System\Drawing\GdiplusNative.cs" />
Expand Down Expand Up @@ -154,19 +153,22 @@
<Compile Include="System\Drawing\Imaging\ImageCodecInfo.cs" />
<Compile Include="System\Drawing\Imaging\ImageCodecInfoPrivate.cs" />
<Compile Include="System\Drawing\Imaging\MetafileFrameUnit.cs" />
<Compile Include="$(CommonPath)\System\Drawing\ColorConverterCommon.cs">
<Compile Condition="'$(TargetGroup)' == 'netcoreapp2.0'" Include="$(CommonPath)\System\Drawing\ColorConverterCommon.cs">
<Link>System\Drawing\ColorConverterCommon.cs</Link>
</Compile>
<Compile Include="$(CommonPath)\System\Drawing\ColorTable.cs">
<Compile Condition="'$(TargetGroup)' == 'netcoreapp2.0'" Include="$(CommonPath)\System\Drawing\ColorTable.cs">
<Link>System\Drawing\ColorTable.cs</Link>
</Compile>
<Compile Condition="'$(TargetGroup)' == 'netcoreapp2.0'" Include="$(CommonPath)\System\Drawing\ColorTranslator.cs">
<Link>System\Drawing\ColorTranslator.cs</Link>
</Compile>
<Compile Condition="'$(TargetGroup)' == 'netcoreapp2.0'" Include="$(CommonPath)\System\Drawing\ColorUtil.netcoreapp20.cs">
<Link>System\Drawing\ColorUtil.netcoreapp20.cs</Link>
</Compile>
<Compile Condition="'$(TargetGroup)' != 'netcoreapp2.0'" Include="$(CommonPath)\System\Drawing\ColorUtil.netcoreapp21.cs">
<Link>System\Drawing\ColorUtil.netcoreapp21.cs</Link>
</Compile>
<Compile Include="$(CommonPath)\System\Drawing\KnownColorTable.cs">
<Compile Condition="'$(TargetGroup)' == 'netcoreapp2.0'" Include="$(CommonPath)\System\Drawing\KnownColorTable.cs">
<Link>System\Drawing\KnownColorTable.cs</Link>
</Compile>
<Compile Include="$(CommonPath)\System\Runtime\InteropServices\FunctionWrapper.cs">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -180,6 +180,15 @@ namespace System.Drawing
public System.Drawing.KnownColor ToKnownColor() { throw null; }
public override string ToString() { throw null; }
}
public static partial class ColorTranslator
{
public static System.Drawing.Color FromHtml(string htmlColor) { throw null; }
public static System.Drawing.Color FromOle(int oleColor) { throw null; }
public static System.Drawing.Color FromWin32(int win32Color) { throw null; }
public static string ToHtml(System.Drawing.Color c) { throw null; }
public static int ToOle(System.Drawing.Color c) { throw null; }
public static int ToWin32(System.Drawing.Color c) { throw null; }
}
public enum KnownColor
{
ActiveBorder = 1,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,12 @@
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<data name="ConvertInvalidPrimitive" xml:space="preserve">
<value>{0} is not a valid value for {1}.</value>
</data>
<data name="InvalidColor" xml:space="preserve">
<value>Color '{0}' is not valid.</value>
</data>
<data name="InvalidEx2BoundArgument" xml:space="preserve">
<value>Value of '{1}' is not valid for '{0}'. '{0}' should be greater than or equal to {2} and less than or equal to {3}.</value>
</data>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,15 @@
<Compile Include="System\Drawing\Size.cs" />
<Compile Include="System\Drawing\SizeF.cs" />
<Compile Include="System\Drawing\Color.cs" />
<Compile Include="$(CommonPath)\System\Drawing\ColorConverterCommon.cs">
<Link>System\Drawing\ColorConverterCommon.cs</Link>
</Compile>
<Compile Include="$(CommonPath)\System\Drawing\ColorTable.cs">
<Link>System\Drawing\ColorTable.cs</Link>
</Compile>
<Compile Include="$(CommonPath)\System\Drawing\ColorTranslator.cs">
<Link>System\Drawing\ColorTranslator.cs</Link>
</Compile>
<Compile Include="$(CommonPath)\System\Drawing\ColorUtil.netcoreapp21.cs">
<Link>System\Drawing\ColorUtil.netcoreapp21.cs</Link>
</Compile>
Expand Down
43 changes: 43 additions & 0 deletions src/libraries/System.Drawing.Primitives/tests/ColorTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,15 @@
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Runtime.InteropServices;
using Xunit;

namespace System.Drawing.Primitives.Tests
{
public partial class ColorTests
{
public static bool SupportsSystemEvents => PlatformDetection.IsWindows && !PlatformDetection.IsUap && PlatformDetection.IsNotWindowsNanoServer;

public static readonly IEnumerable<object[]> NamedArgbValues =
new[]
{
Expand Down Expand Up @@ -491,5 +494,45 @@ public void DebuggerAttributesAreValid()
DebuggerAttributes.ValidateDebuggerDisplayReferences(Color.Aquamarine);
DebuggerAttributes.ValidateDebuggerDisplayReferences(Color.FromArgb(4, 3, 2, 1));
}

[ConditionalFact(nameof(SupportsSystemEvents))]
public void UserPreferenceChangingEventTest()
{
int element = 12; // Win32SystemColors.AppWorkSpace.
Color oldColor = System.Drawing.SystemColors.AppWorkspace;

// A call to ToArgb is necessary before changing the system colors because it initializes the knownColorTable.
int oldColorArgb = oldColor.ToArgb();
int oldColorAbgr = GetColorRefValue(oldColor);

Color newColor = oldColor != Color.Gold ? Color.Gold : Color.Silver;
int newColorArgb = newColor.ToArgb();
int newColorAbgr = GetColorRefValue(newColor);

Assert.NotEqual(newColorArgb, oldColorArgb);

try
{
Assert.Equal(1, SetSysColors(1, new int[] { element }, new int[] { newColorAbgr }));

RetryHelper.Execute(() =>
{
Assert.Equal(newColorArgb, oldColor.ToArgb());
});
}
finally
{
Assert.Equal(1, SetSysColors(1, new int[] { element }, new int[] { oldColorAbgr }));
}
}

[DllImport("user32.dll", SetLastError = true)]
private static extern int SetSysColors(int cElements, int[] lpaElements, int[] lpaRgbValues);

private static int GetColorRefValue(Color color)
{
// The COLORREF value has the following hexadecimal form: 0x00bbggrr.
return color.B << 16 | color.G << 8 | color.R;
}
}
}

0 comments on commit 608a743

Please sign in to comment.