Changing system colors for a single application (Windows, .NET)
Asked Answered
F

3

7

I know I should generally avoid messing up with such system settings, but my application do already use nonstandard colors and I have no influence on that. I would like to be able to add standard .NET controls in some places, but their colors do not match. I would like to have a hack that would replace system colors for this one application. One more important thing to note is that it is a .NET application.

My (incomplete) ideas so far were:

  • To create a proxy User32.dll library with replaced GetSysColor, but it would be very tedious (731 functions to be redirected, 1 to be replaced) and I do not know how to force my application to use that particular copy.
  • To intercept somehow invocations to GetSysColors (unfortunatelly it is somewhere in the CLR I think).
  • To modify somehow .NET class SystemColors (in memory? is it possible?).

Do you have any idea, what is the best (and complete) way to achieve this?

Forked answered 7/7, 2009 at 12:3 Comment(2)
I've succesfully hooked GetSysColor and also GetSysColorBrush. Unfortunately, it is not enough to color the controls, and I still don't know why.Gerlach
it was not enough because 1. Gdi+ caches system color values and brushes internally, you need to either clean these caches or make sure your hooking happens before Gdi+ is initialized. 2. Some controls cache brushes. This is fixed by touching color properties like control.BackColor = control.BackColor 3. Some controls need to have FlatStyle.Flat to completely rely on BackColor / ForeColor propertiesLitigation
P
5

I would like to be able to add standard .NET controls in some places, but their colors do not match. I would like to have a hack that would replace system colors for this one application.

That's like driving a nail with a sledgehammer.

Rather than mucking up colors within the system itself, what you can do is inherit a new control from each of the stock controls you want to use. So instead of a plain TextBox you inherit from the stock TextBox control to create your own ThemedTextBox. You setup this new control to use your app's color scheme by default, and because it is still a TextBox as far as the inheritance structure is concerned you can use it anywhere you'd use a normal textbox, including in the winforms designer.

Pallette answered 7/7, 2009 at 12:49 Comment(3)
Unfortunately in the .NET it is not possible to change colors in the inherited class. I would have to do all the drawing from scratch. And even that wouldn't be enough because of various visual styles possible (eg. XP vs Vista).Forked
Sure, you can change the colors. Just update the constructor for the inherited class to set the appropriate properties.Pallette
The problem is that in WinFoms there are no "appropriate properties". Control class has only ForeColor and BackColor, but this is not everything. For TextBox we can change only the back color, but for ScrollBar we can change nothing.Forked
C
2

In my early days I developed a program that registered a global message hook to owner draw window borders - I could theme all windows. This should be possible for a single application, too. However, this is not a simple task.

Otherwise I don't think that this will be possible. How about using themable third party controls such as Krypton Toolkit?

Corinnecorinth answered 7/7, 2009 at 12:26 Comment(3)
Actually I use some Divelements Controls, but they do not provide regular controls like buttons or scrollbars. I will check Krypton. Thank you for this suggestion. Would it be possible for you to send me this coloring program you are talking about? I would be thankful for any additional details.Forked
Sorry, can't send you the source for the tool - I must have written that around 1992 in Borland Pascal. The sources have been long lost...Corinnecorinth
I have checked that - I do can intercept WM_PAINT event using system hooks, but it gives me nothing. I can do it simpler overriding OnPaint in my C# code. And I still cannot change colors in general. Maybe only the borders would be possible (knowing the client rectangle), but this is not enough.Forked
L
2

System.Drawing.SystemColor values are cached within KnownColorsTable class in a private array colorTable. The content of the array is populated and updated as system colors change using Win32 API requests.

To change the system colors in scope of your application, change color values stored in colorTable by the values you want.

Then you also need to clear the cache for SystemBrushes and SystemPens class, because the pens and brushes will not re-read RBG values by themselves.

using System.Drawing;
using System.Reflection;

namespace Example
{
    public class SystemColorsUtility
    {
        public SystemColorsUtility()
        {
            // force init color table
            byte unused = SystemColors.Window.R;

            var colorTableField = typeof(Color).Assembly.GetType("System.Drawing.KnownColorTable")
                .GetField("colorTable", BindingFlags.Static | BindingFlags.NonPublic);

            _colorTable = (int[]) colorTableField.GetValue(null);

            _threadDataProperty = systemDrawingAssembly.GetType("System.Drawing.SafeNativeMethods")
                .GetNestedType("Gdip", BindingFlags.NonPublic)
                .GetProperty("ThreadData", BindingFlags.Static | BindingFlags.NonPublic);

            SystemBrushesKey = typeof(SystemBrushes)
                .GetField("SystemBrushesKey", BindingFlags.Static | BindingFlags.NonPublic)
                .GetValue(null);

            SystemPensKey = typeof(SystemPens)
                .GetField("SystemPensKey", BindingFlags.Static | BindingFlags.NonPublic)
                .GetValue(null);
        }

        public void SetColor(KnownColor knownColor, Color value)
        {
            _colorTable[(int) knownColor] = value.ToArgb();
            ThreadData[SystemBrushesKey] = null;
            ThreadData[SystemPensKey] = null;
        }

        private int[] _colorTable;
        private object SystemBrushesKey { get; }
        private object SystemPensKey { get; }
    }
}

A more complete example of how I did it for my hobby project.

System colors should be changed before the application is loaded to ensure the controls see changed values on initialization. You will need additional tricks with controls using Win32 API directly to render themselves, such as ListView or TreeView.

You can read details on that here. It's a machine translation from Russian.

Litigation answered 2/10, 2018 at 17:46 Comment(1)
While applying the above approach to GitExtensions I discovered that better results can be achieved by hooking Win32API calls to GetSysColor and GetSysColorBrush methods. Hooking is done using EasyHook library.Litigation

© 2022 - 2024 — McMap. All rights reserved.