65.9K
CodeProject 正在变化。 阅读更多。
Home

重新激活禁用控件

starIconstarIconstarIconstarIconstarIcon

5.00/5 (6投票s)

2012 年 11 月 2 日

CPOL

3分钟阅读

viewsIcon

33983

downloadIcon

1719

重新激活禁用的窗口控件


介绍 

您是否曾遇到过灰色的控件,您必须单击它……例如浏览或后退按钮。 本文介绍如何使用 Windows API 重新激活控件。

背景 

我正在从虚拟机卸载 Visual Studio 2010,由于某种原因,浏览按钮被禁用了。 最初的安装是在一个额外的虚拟硬盘上进行的,该硬盘已被移除,而 VS 就是拒绝卸载。 我认为如果我能更改安装目录,一切都会很好,而要实现这一点,必须启用浏览按钮。 我开始了一项任务,无论如何都要重新启用该按钮。

使用代码

该代码使用了一个包装类 (Win32),该类公开了 Windows user32.dll 中的方法。
我们拥有这个包装类很好,但是我们如何使用它呢?

首先,我们必须在 dll 中公开正确的方法

[DllImport("user32.dll")]
[return: MarshalAs(UnmanagedType.Bool)] //http://msdn.microsoft.com/en-us/library/windows/desktop/ms646291(v=vs.85).aspx
public static extern bool EnableWindow(IntPtr hWnd, bool bEnable); 

要重新启用控件,我们必须将控件的句柄和新状态传递给 Enablewindow 方法。

太好了……所以我知道如何传递一个 bool 值,但是我如何获得控件的句柄呢? 毕竟,它不在我的代码中……它在一个外部应用程序中!!

嗯,实际上比您想象的要容易。

我们在 user32.dll 中公开 WindowFromPoint 方法。(这会在特定点获取窗口/控件句柄。

[DllImport("user32.dll")] //http://msdn.microsoft.com/en-us/library/windows/desktop/ms633558(v=vs.85).aspx
public static extern IntPtr WindowFromPoint(Point pt); 

并使用光标的位置调用它。

// capture the window under the cursor's position
IntPtr hWnd = Win32.WindowFromPoint(Cursor.Position); 

现在,hWnd 变量包含指向该控件句柄的指针,有了该句柄,我们基本上就控制了该控件! 以下方法正是这样做的,它将控件的句柄和状态传递给 EnableWindow 方法。

/// <summary>
        /// Activate the Control
        /// </summary>
        /// <param name="status">Boolean to Enabled or Disabled</param>
        private void ReActiveControl(bool status)
        {

            //Ask the WinApi to enable/disable the control/form
            Win32.EnableWindow(currentHandle, status);

            //Change the Controls style by removing/adding WM_Disabled if it exists in the style
            ChangeStyle(status);

            //Send the WM_Enable message to the given status
            Win32.SendMessage(currentHandle, (Int32)Win32.WindowMessages.WM_ENABLE, Convert.ToInt32(status), 0);

            //Post the Enabled Message to the Parent to refresh its child(ren)
            Win32.PostMessage(currentParent, (Int32)Win32.WindowMessages.WM_COMMAND, (IntPtr)Win32.WindowMessages.WM_ENABLE, currentHandle);
            Win32.PostMessage(currentHandle, (Int32)Win32.WindowMessages.WM_COMMAND, (IntPtr)Win32.WindowMessages.WM_ENABLE, IntPtr.Zero);
        }   

我发现 DotNet 控件的行为不像其他按钮那样。 一旦您重新激活它们,它们看起来可以单击,但当您单击它们时,什么也不会发生。 为此,我发现必须更改控件的样式。

WinAPI 允许我们获取和设置样式

[DllImport("User32.dll")] //http://msdn.microsoft.com/en-us/library/windows/desktop/ms633584(v=vs.85).aspx
        public static extern WindowStyles GetWindowLong(IntPtr hWnd, int index);
[DllImport("user32.dll")] //http://msdn.microsoft.com/en-us/library/windows/desktop/ms633591(v=vs.85).aspx
        public static extern int SetWindowLong(IntPtr hWnd, int nIndex, long dwNewLong);  

可以通过我们的 Win32 包装类使用该代码,如下所示:

/// <summary>
<pre>        /// Dotnet controls dont act like other controls. Changing the style help here
        /// </summary>
        /// <param name="status"></param>
        private void ChangeStyle(bool status)
        {
            //Get the current handles style (-16 = Retrieve the window styles)
            long Style = (long)Win32.GetWindowLong(currentHandle, -16);
            if (status)
            {
                Style &= ~(long)Win32.WindowStyles.WS_DISABLED; //Remove the WS_Disabled Style
            }
            else
            {
                Style |= (long)Win32.WindowStyles.WS_DISABLED;//Add if required
            }
            try
            {
                //Check if the OS is Win7 x64 or higher
                if (Environment.Is64BitOperatingSystem && Environment.OSVersion.Version.Major >= 6)
                {
                    //Win32.SetWindowLong(currentHandle, -16, Style); TODO: Find work around (Doesn't work on x64 Win 7+)
                }
                else
                {
                    //Apperantly doesn't work on Win 7 64bit
                    Win32.SetWindowLong(currentHandle, -16, Style);
                }
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex.Message);
            }
        }  

典型! 这个家伙现在跳过了 PostMessage 方法的作用!

 [DllImport("User32.dll")] //http://msdn.microsoft.com/en-us/library/windows/desktop/ms644944(v=vs.85).aspx
public static extern int PostMessage(IntPtr hWnd, int uMsg, IntPtr wParam, IntPtr lParam); 

在 ReActiveControl 方法中较早使用的 PostMessage 方法允许我们向 windows 发布一条消息,描述它是一个命令 (WM_COMMAND),并且该命令是一个启用命令 (WM_ENABLE)。

兴趣点 

在玩这个代码时,我决定添加一些额外的功能,例如更改控件的文本。(一旦您重新启动应用程序,您更改的文本将恢复到原来的样子,但它仍然很有趣。

为此,我使用了 WinAPI 的 SendMessage 方法。

这里我们公开了该方法的不同重载

[DllImport("User32.dll")]
public static extern int SendMessage(IntPtr hWnd, int uMsg, IntPtr wParam, IntPtr lParam);
[DllImport("user32.dll")]
public static extern int SendMessage(IntPtr hWnd, int msg, int wParam, int lParam);
[DllImport("user32.dll")]
public static extern int SendMessage(IntPtr hWnd, int msg, int wParam, StringBuilder lParam);   

要发送新文本,我们使用 StringBuilder 来构建新文本,并将其作为参数传递给 SendMessage 方法。

//Build up some string to send to the control 

StringBuilder sbText = new StringBuilder(256);  

int length = Win32.SendMessage(currentHandle , (Int32)Win32.WindowMessages.WM_GETTEXTLENGTH, 0, 0);
sbText.Append(_textBoxText.Text);
//Send the password to the password field
Win32.SendMessage(currentHandle, (Int32)Win32.WindowMessages.WM_SETTEXT, length, sbText);  

在玩这个代码时,我还发现我的查找工具实际上并没有拾取禁用的控件。
完整的解决方案代码包含一个查找父级的所有子控件的方法。 这对于访问一些隐藏的控件特别方便。

历史

此版本在 Windows 7 x64 上仍然无法正常工作。 将来我会更新它以支持 x64。

© . All rights reserved.