【问题标题】:Changing system colors for a single application (Windows, .NET)更改单个应用程序的系统颜色(Windows、.NET)
【发布时间】:2010-11-08 16:20:25
【问题描述】:

我知道我通常应该避免弄乱此类系统设置,但我的应用程序确实已经使用了非标准颜色,我对此没有任何影响。我希望能够在某些地方添加标准的 .NET 控件,但它们的颜色不匹配。我想要一个 hack 来替换这个应用程序的系统颜色。还有一点需要注意的是,它是一个 .NET 应用程序。

到目前为止,我的(不完整的)想法是:

  • 用替换的 GetSysColor 创建代理 User32.dll 库,但这会非常繁琐(要重定向 731 个函数,要替换 1 个函数)而且我不知道如何强制我的应用程序使用该特定副本。
  • 以某种方式拦截对 GetSysColors 的调用(不幸的是,我认为它位于 CLR 中的某个位置)。
  • 以某种方式修改 .NET 类 SystemColors(在内存中?可能吗?)。

您有什么想法,实现这一目标的最佳(和完整)方法是什么?

【问题讨论】:

  • 我已经成功连接了 GetSysColor 和 GetSysColorBrush。不幸的是,仅仅为控件着色还不够,我仍然不知道为什么。
  • 这还不够,因为 1. Gdi+ 在内部缓存系统颜色值和画笔,您需要清理这些缓存或确保在 Gdi+ 初始化之前进行挂钩。 2.一些控件缓存画笔。这可以通过触摸control.BackColor = control.BackColor 3 等颜色属性来解决。某些控件需要有FlatStyle.Flat 才能完全依赖BackColor / ForeColor 属性

标签: .net winapi colors user32 systemcolors


【解决方案1】:

我希望能够在某些地方添加标准的 .NET 控件,但它们的颜色不匹配。我想要一个 hack 来替换这个应用程序的系统颜色。

这就像用大锤敲钉子一样。

您可以做的是继承您想要使用的每个库存控件中的一个新控件,而不是在系统本身内弄脏颜色。因此,您可以从普通的 TextBox 控件继承来创建自己的ThemedTextBox,而不是纯文本框。您将这个新控件设置为默认使用您的应用程序的配色方案,并且因为它 就继承结构而言仍然是一个 TextBox,您可以在使用普通文本框的任何地方使用它,包括在 winforms 设计器中。

【讨论】:

  • 不幸的是,在 .NET 中无法更改继承类中的颜色。我必须从头开始画所有的画。即使这样也不够,因为可能存在各种视觉风格(例如 XP 与 Vista)。
  • 当然,您可以更改颜色。只需更新继承类的构造函数以设置适当的属性。
  • 问题是在 WinFoms 中没有“适当的属性”。控件类只有 ForeColor 和 BackColor,但这还不是全部。对于 TextBox,我们只能改变背景颜色,但对于 ScrollBar,我们什么都不能改变。
【解决方案2】:

在我早期,我开发了一个程序,该程序注册了一个全局消息挂钩来所有者绘制窗口边框 - 我可以为所有窗口设置主题。这对于单个应用程序也应该是可能的。然而,这不是简单的任务。

否则我认为这是不可能的。使用Krypton Toolkit等可主题化的第三方控件怎么样?

【讨论】:

  • 实际上我使用了一些 Divelements 控件,但它们不提供按钮或滚动条等常规控件。我会检查氪。谢谢你的建议。你能把你说的这个着色程序发给我吗?如果您提供任何其他详细信息,我将不胜感激。
  • 抱歉,无法向您发送该工具的源代码 - 我一定是在 1992 年左右用 Borland Pascal 写的。来源早已失传……
  • 我已经检查过了——我确实可以使用系统挂钩拦截 WM_PAINT 事件,但它什么也没给我。我可以在我的 C# 代码中更简单地覆盖 OnPaint。而且我仍然无法改变颜色。也许只有边界是可能的(知道客户矩形),但这还不够。
【解决方案3】:

System.Drawing.SystemColor 值缓存在私有数组colorTable 中的KnownColorsTable 类中。随着系统颜色的变化使用 Win32 API 请求填充和更新数组的内容。

要更改应用程序范围内的系统颜色,请将colorTable 中存储的颜色值更改为所需的值。

那么你还需要清除SystemBrushesSystemPens类的缓存,因为钢笔和画笔不会自己重新读取RBG值。

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; }
    }
}

更完整的example 我如何为我的爱好项目做到这一点。

应在加载应用程序之前更改系统颜色,以确保控件在初始化时看到更改的值。您将需要使用 Win32 API 直接呈现控件的其他技巧,例如 ListViewTreeView

您可以阅读有关 here 的详细信息。这是俄语的机器翻译。

【讨论】:

  • 在将上述方法应用于GitExtensions 时,我发现通过将Win32API 调用挂钩到GetSysColorGetSysColorBrush 方法可以获得更好的结果。挂钩是使用 EasyHook 库完成的。
猜你喜欢
  • 2012-09-17
  • 2011-12-25
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-05-02
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多