简体   繁体   English

使用winapi更改颜色对话框上的文本,但2个静态控件具有相同的控件ID

[英]Using winapi to change text on color dialog but 2 static controls have the same control id

Let me start by saying that I am no stranger to using winapi calls to manipulate other windows, but this is the first time I have seen a window that has two identical control IDs. 首先,我对使用winapi调用来操作其他窗口并不陌生,但这是我第一次看到具有两个相同控件ID的窗口。 It seems that the color dialog hasn't changed much between windows versions and I can confirm that this behavior exists on all color dialogs from Windows Vista through to Windows 10 (possibly exists in win xp and lower as well but I can't be bothered to check). 在Windows版本之间,颜色对话框似乎并没有太大变化,我可以确认从Windows Vista到Windows 10的所有颜色对话框中都存在此行为(可能在win xp及更低版本中也存在,但我不能打扰去检查)。

What I am attempting to do is use winapi calls to localize the text in a color dialog control in C#. 我正在尝试使用winapi调用在C#中的颜色对话框控件中本地化文本。 The best way I have found to do this is to use GetDlgItem() to get a handle to the control I wish to change and then use SetWindowText() to actually change the text. 我发现做到这一点的最佳方法是使用GetDlgItem()获取要更改的控件的句柄,然后使用SetWindowText()实际更改文本。 This works great for all controls on the color dialog except for the 'Basic colors:' and 'Custom colors:' labels, which both have a control ID of 0xFFFF (decimal value: 65535). 这对于颜色对话框上的所有控件都很有用,除了“基本颜色:”和“自定义颜色:”标签,它们的控件ID均为0xFFFF(十进制值:65535)。

I use an app called WinID to do this type of work (I find it much easier than using Spy++) and you can see from the screenshots below that the ID of the two text labels do in-fact register as the same ID. 我使用一个名为WinID的应用程序来完成这种类型的工作(我发现它比使用Spy ++容易得多),并且您可以从下面的屏幕截图中看到,两个文本标签的ID实际上注册为相同的ID。

NOTE: I have tested this using Spy++ and of course I get the same values as shown below: 注意:我已经使用Spy ++进行了测试,当然我得到的值如下所示:

基本颜色信息

在此处输入图片说明

I would like to know 2 things: 我想知道两件事:

  1. How is it possible for 2 controls to have the same control id? 2个控件如何具有相同的控件ID?
  2. Is there a 'better way' to get a handle to a control from an external dialog/app using winapi calls? 是否有“更好的方法”使用winapi调用从外部对话框/应用程序获取控件的句柄? Please keep in mind that using something like FindWindowEx(nColorDialogHandle, IntPtr.Zero, "Static", "&custom colors:"); 请记住,使用类似FindWindowEx(nColorDialogHandle, IntPtr.Zero, "Static", "&custom colors:"); works, but is not useful to me because I must be able to find the handle without relying on the text in English since this must also work on color dialogs from a non-English version of Windows. 可以,但是对我没有用,因为我必须能够不依赖英文文本而找到句柄,因为这也必须适用于非英文版Windows的颜色对话框。

Below is some sample code to demonstrate how I am currently able to change the text on a color dialog. 下面是一些示例代码,以演示我当前如何在颜色对话框中更改文本。 I am happy with the code except that I am unable to get a direct handle to the 'Custom colors:' label since using GetDlgItem() with the control id of 0xFFFF seems to return a handle to the first instance of the control with that ID (in this case it always returns a handle to the 'Basic colors:' label). 我对代码感到满意,但是我无法直接获得“自定义颜色:”标签的句柄,因为使用控件ID为0xFFFF的GetDlgItem()似乎会返回具有该ID的控件的第一个实例的句柄(在这种情况下,它始终会返回“基本颜色:”标签的句柄)。 The only way I am able to get the 'Custom colors:' handle is by using an indirect method of looping through all controls on the color dialog until I find one with text that has not already been changed. 我能够获得“自定义颜色:”句柄的唯一方法是使用一种间接方法来循环遍历颜色对话框上的所有控件,直到找到一个文本尚未更改的控件。 This works fine but I would like to know if there is a more direct way to get this handle without looping through controls: 这可以正常工作,但我想知道是否有一种更直接的方法来获取该句柄而无需遍历控件:

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace WindowsFormsApplication1
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }

        private void Form1_Load(object sender, EventArgs e)
        {
            // Open the color dialog before the form actually loads

            ColorDialogEx oColorDialog = new ColorDialogEx(this.CreateGraphics());

            oColorDialog.FullOpen = true;
            oColorDialog.ShowDialog();
        }
    }

    public class ColorDialogEx : ColorDialog
    {
        private const Int32 WM_INITDIALOG = 0x0110; // Windows Message Constant
        private Graphics oGraphics;
        private const uint GW_HWNDLAST = 1;
        private const uint GW_HWNDPREV = 3;

        private string sColorPickerText = "1-Color Picker";
        private string sBasicColorsText = "2-Basic colors:";
        private string sDefineCustomColorsButtonText = "3-Define Custom Colors >>";
        private string sOKButtonText = "4-OK";
        private string sCancelButtonText = "5-Cancel";
        private string sAddToCustomColorsButtonText = "6-Add to Custom Colors";
        private string sColorText = "7-Color";
        private string sSolidText = "|8-Solid";
        private string sHueText = "9-Hue:";
        private string sSatText = "10-Sat:";
        private string sLumText = "11-Lum:";
        private string sRedText = "12-Red:";
        private string sGreenText = "13-Green:";
        private string sBlueText = "14-Blue:";
        private string sCustomColorsText = "15-Custom colors:";

        // WinAPI definitions

        [DllImport("user32.dll", CharSet = CharSet.Auto)]
        private static extern bool SetWindowText(IntPtr hWnd, string text);

        [DllImport("user32.dll", SetLastError = true)]
        internal static extern bool MoveWindow(IntPtr hWnd, int X, int Y, int nWidth, int nHeight, bool bRepaint);

        [DllImport("user32.dll")]
        public static extern long GetWindowRect(int hWnd, ref Rectangle lpRect);

        [DllImport("user32.dll")]
        [return: MarshalAs(UnmanagedType.Bool)]
        static extern bool GetTitleBarInfo(IntPtr hwnd, ref TITLEBARINFO pti);

        [DllImport("user32.dll", SetLastError = true)]
        static extern IntPtr FindWindowEx(IntPtr hwndParent, IntPtr hwndChildAfter, string lpszClass, string lpszWindow);

        [DllImport("user32.dll")]
        private static extern IntPtr GetDlgItem(IntPtr hDlg, int nIDDlgItem);

        [DllImport("user32.dll", SetLastError = true)]
        static extern IntPtr GetWindow(IntPtr hWnd, uint uCmd);

        [DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Auto)]
        private static extern int GetClassName(IntPtr hWnd, System.Text.StringBuilder lpClassName, int nMaxCount);

        [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
        static extern int GetWindowText(IntPtr hWnd, StringBuilder lpString, int nMaxCount);

        [StructLayout(LayoutKind.Sequential)]
        struct TITLEBARINFO
        {
            public const int CCHILDREN_TITLEBAR = 5;
            public uint cbSize;
            public RECT rcTitleBar;
            [MarshalAs(UnmanagedType.ByValArray, SizeConst = CCHILDREN_TITLEBAR + 1)]
            public uint[] rgstate;
        }

        [StructLayout(LayoutKind.Sequential)]
        public struct RECT
        {
            public int Left, Top, Right, Bottom;

            public RECT(int left, int top, int right, int bottom)
            {
                Left = left;
                Top = top;
                Right = right;
                Bottom = bottom;
            }

            public RECT(System.Drawing.Rectangle r) : this(r.Left, r.Top, r.Right, r.Bottom) { }

            public int X
            {
                get { return Left; }
                set { Right -= (Left - value); Left = value; }
            }

            public int Y
            {
                get { return Top; }
                set { Bottom -= (Top - value); Top = value; }
            }

            public int Height
            {
                get { return Bottom - Top; }
                set { Bottom = value + Top; }
            }

            public int Width
            {
                get { return Right - Left; }
                set { Right = value + Left; }
            }

            public System.Drawing.Point Location
            {
                get { return new System.Drawing.Point(Left, Top); }
                set { X = value.X; Y = value.Y; }
            }

            public System.Drawing.Size Size
            {
                get { return new System.Drawing.Size(Width, Height); }
                set { Width = value.Width; Height = value.Height; }
            }

            public static implicit operator System.Drawing.Rectangle(RECT r)
            {
                return new System.Drawing.Rectangle(r.Left, r.Top, r.Width, r.Height);
            }

            public static implicit operator RECT(System.Drawing.Rectangle r)
            {
                return new RECT(r);
            }

            public static bool operator ==(RECT r1, RECT r2)
            {
                return r1.Equals(r2);
            }

            public static bool operator !=(RECT r1, RECT r2)
            {
                return !r1.Equals(r2);
            }

            public bool Equals(RECT r)
            {
                return r.Left == Left && r.Top == Top && r.Right == Right && r.Bottom == Bottom;
            }

            public override bool Equals(object obj)
            {
                if (obj is RECT)
                    return Equals((RECT)obj);
                else if (obj is System.Drawing.Rectangle)
                    return Equals(new RECT((System.Drawing.Rectangle)obj));
                return false;
            }

            public override int GetHashCode()
            {
                return ((System.Drawing.Rectangle)this).GetHashCode();
            }

            public override string ToString()
            {
                return string.Format(System.Globalization.CultureInfo.CurrentCulture, "{{Left={0},Top={1},Right={2},Bottom={3}}}", Left, Top, Right, Bottom);
            }
        }

        public ColorDialogEx(Graphics g)
        {
            oGraphics = g;
        }

        protected override IntPtr HookProc(IntPtr nColorDialogHandle, int msg, IntPtr wparam, IntPtr lparam)
        {
            IntPtr returnValue = base.HookProc(nColorDialogHandle, msg, wparam, lparam);

            if (msg == WM_INITDIALOG)
            {
                IntPtr[] oStaticHandleArray = new IntPtr[9];

                // Change the window title

                SetWindowText(nColorDialogHandle, sColorPickerText);

                // Get titlebar info for calculations later

                TITLEBARINFO oTITLEBARINFO = new TITLEBARINFO();
                oTITLEBARINFO.cbSize = (uint)System.Runtime.InteropServices.Marshal.SizeOf(oTITLEBARINFO);
                GetTitleBarInfo(nColorDialogHandle, ref oTITLEBARINFO);

                // Change the text of the "Basic colors:" label

                oStaticHandleArray[0] = GetDlgItem(nColorDialogHandle, 0xFFFF);

                SetWindowText(oStaticHandleArray[0], sBasicColorsText);

                // Change the text of the "Define Custom Colors >>" button

                SetWindowText(GetDlgItem(nColorDialogHandle, 0x2CF), sDefineCustomColorsButtonText);

                // Save the "OK" button size and new width

                Rectangle oOKButtonRect = new Rectangle();
                int nOKButtonWidth = (int)oGraphics.MeasureString(sOKButtonText, new Font("Microsoft Sans Serif", 8, FontStyle.Regular)).Width + 20;  // +20 accounts for extra +10 padding on either side

                // Find the "OK" Button

                IntPtr nChildHandle = GetDlgItem(nColorDialogHandle, 0x1);

                if (nChildHandle.ToInt32() > 0)
                {
                    // The "OK" button was found
                    // Now save the current size and position

                    GetWindowRect(nChildHandle.ToInt32(), ref oOKButtonRect);

                    // We have to subtract oOKButtonRect.X value from oOKButtonRect.Width to obtain the "real" button width (same thing with subtracting Y value from Height)

                    oOKButtonRect.Width = oOKButtonRect.Width - oOKButtonRect.X;
                    oOKButtonRect.Height = oOKButtonRect.Height - oOKButtonRect.Y;

                    // Resize the "OK" button so that the new text fits properly
                    // NOTE: I cannot be sure 100% if it is correct to use the titlebar to find the position of the button or not but the math works out in all of my tests

                    MoveWindow(nChildHandle, oOKButtonRect.X - oTITLEBARINFO.rcTitleBar.X, oOKButtonRect.Y - oTITLEBARINFO.rcTitleBar.Y - oTITLEBARINFO.rcTitleBar.Height, nOKButtonWidth, oOKButtonRect.Height, true);

                    // Finally, change the button text

                    SetWindowText(nChildHandle, sOKButtonText);
                }

                // Find the "Cancel" Button

                nChildHandle = GetDlgItem(nColorDialogHandle, 0x2);

                if (nChildHandle.ToInt32() > 0)
                {
                    // The "Cancel" button was found
                    // Now get the current size and position

                    Rectangle oCancelButtonRect = new Rectangle();
                    GetWindowRect(nChildHandle.ToInt32(), ref oCancelButtonRect);

                    // We have to subtract oCancelButtonRect.X value from oCancelButtonRect.Width to obtain the "real" button width (same thing with subtracting Y value from Height)

                    oCancelButtonRect.Width = oCancelButtonRect.Width - oCancelButtonRect.X;
                    oCancelButtonRect.Height = oCancelButtonRect.Height - oCancelButtonRect.Y;

                    // Resize the "Cancel" button so that the new text fits properly
                    // NOTE: I cannot be sure 100% if it correct to use the titlebar to find the position of the button or not but the math works out in all of my tests

                    MoveWindow(nChildHandle, oOKButtonRect.X + nOKButtonWidth - oTITLEBARINFO.rcTitleBar.X + 6, oCancelButtonRect.Y - oTITLEBARINFO.rcTitleBar.Y - oTITLEBARINFO.rcTitleBar.Height, (int)oGraphics.MeasureString(sCancelButtonText, new Font("Microsoft Sans Serif", 8, FontStyle.Regular)).Width + 20, oCancelButtonRect.Height, true);

                    // Finally, change the button text

                    SetWindowText(nChildHandle, sCancelButtonText);
                }

                // Change the text of the "Add to Custom Colors" button

                SetWindowText(GetDlgItem(nColorDialogHandle, 0x2C8), sAddToCustomColorsButtonText);

                // Change the text of the "Color" label text

                oStaticHandleArray[1] = GetDlgItem(nColorDialogHandle, 0x2DA);

                SetWindowText(oStaticHandleArray[1], sColorText);

                // Change the text of the "Solid" label text

                oStaticHandleArray[2] = GetDlgItem(nColorDialogHandle, 0x2DB);

                SetWindowText(oStaticHandleArray[2], sSolidText);

                // Change the text of the "Hue:" label

                oStaticHandleArray[3] = GetDlgItem(nColorDialogHandle, 0x2D3);

                SetWindowText(oStaticHandleArray[3], sHueText);

                // Change the text of the "Sat:" label

                oStaticHandleArray[4] = GetDlgItem(nColorDialogHandle, 0x2D4);

                SetWindowText(oStaticHandleArray[4], sSatText);

                // Change the text of the "Lum:" label

                oStaticHandleArray[5] = GetDlgItem(nColorDialogHandle, 0x2D5);

                SetWindowText(oStaticHandleArray[5], sLumText);

                // Change the text of the "Red:" label

                oStaticHandleArray[6] = GetDlgItem(nColorDialogHandle, 0x2D6);

                SetWindowText(oStaticHandleArray[6], sRedText);

                // Change the text of the "Green:" label

                oStaticHandleArray[7] = GetDlgItem(nColorDialogHandle, 0x2D7);

                SetWindowText(oStaticHandleArray[7], sGreenText);

                // Change the text of the "Blue:" label

                oStaticHandleArray[8] = GetDlgItem(nColorDialogHandle, 0x2D8);

                SetWindowText(oStaticHandleArray[8], sBlueText);

                // Change the text of the "Custom Colors:" label

                SetCustomColorsText(nColorDialogHandle, oStaticHandleArray);
            }

            return returnValue;
        }

        private static string GetClassName(IntPtr nHandle)
        {
            // Create the stringbuilder object that is used to get the window class name from the GetClassName win api function

            System.Text.StringBuilder sClassName = new System.Text.StringBuilder(100);
            GetClassName(nHandle, sClassName, sClassName.Capacity);
            return sClassName.ToString();
        }

        private static string GetWindowText(IntPtr nHandle)
        {
            // Create the stringbuilder object that is used to get the window text from the GetWindowText win api function

            System.Text.StringBuilder sWindowText = new System.Text.StringBuilder(100);
            GetWindowText(nHandle, sWindowText, sWindowText.Capacity);
            return sWindowText.ToString();
        }

        private void SetCustomColorsText(IntPtr nHandle, IntPtr[] oStaticHandleArray)
        {
            // Find the last control based on the handle to the main window

            IntPtr nWorkingHandle = GetWindow(FindWindowEx(nHandle, IntPtr.Zero, null, null), GW_HWNDLAST);
            bool bFound = false;

            do
            {
                // Look only for "Static" controls that we have not already changed

                if (GetClassName(nWorkingHandle) == "Static" && oStaticHandleArray.Contains(nWorkingHandle) == false)
                {
                    // Found a "Static" control
                    // Check to see if it is the one we are looking for

                    string sControlText = GetWindowText(nWorkingHandle);

                    if (sControlText != "")
                    {
                        // Found the "Custom Colors:" label
                        // Change the text of the "Custom Colors:" label

                        SetWindowText(nWorkingHandle, sCustomColorsText);
                        bFound = true;
                    }
                }

                // Working backwards we look for the previous control

                nWorkingHandle = GetWindow(nWorkingHandle, GW_HWNDPREV);

                // Jump out of the loop when the working handle doesn't find anymore controls

                if (nWorkingHandle == IntPtr.Zero)
                    break;
            } while (bFound == false);
        }
    }
}

This dialog is already localized. 该对话框已经本地化。 You can look at it with Visual Studio. 您可以使用Visual Studio进行查看。 Copy c:\\windows\\system32\\en-US\\comdlg32.dll.mui to, say, c:\\temp\\test.dll. 将c:\\ windows \\ system32 \\ en-US \\ comdlg32.dll.mui复制到c:\\ temp \\ test.dll。 Replace "en-US" with your local language tag. 将“ en-US”替换为您的本地语言标签。 In VS use File > Open > File and pick test.dll. 在VS中,使用文件>打开>文件,然后选择test.dll。 You'll see the resources in the MUI file, open the Dialog node and double-click the one named "CHOOSECOLOR". 您将在MUI文件中看到资源,打开Dialog节点,然后双击一个名为“ CHOOSECOLOR”的资源。 The resource editor opens, you can pick an item in the dialog template and look at its properties in the Property window. 资源编辑器打开,您可以在对话框模板中选择一个项目,然后在“属性”窗口中查看其属性。

Hopefully it is obvious why the STATIC control has the default IDSTATIC id (65535), there is no need for Windows to do anything to change its properties so no need to find it back. 希望显而易见的是,为什么STATIC控件具有默认的IDSTATIC id(65535),Windows无需进行任何更改其属性的操作,因此也无需找到它。 And not for you either, your user will have his own copy of the MUI file that contains the dialog with strings in his native language. 也不是对您也有用,您的用户将拥有自己的MUI文件副本,该文件包含带有使用其母语的字符串的对话框。

Do note that a machine usually only has MUI files for a single language. 请注意,一台机器通常只有一种语言的MUI文件。 If you need to do this to, say, create screenshots for documentation then start here . 例如,如果您需要执行此操作以创建文档的屏幕截图,请从此处开始

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM