重新激活禁用控件





5.00/5 (6投票s)
重新激活禁用的窗口控件


介绍
您是否曾遇到过灰色的控件,您必须单击它……例如浏览或后退按钮。 本文介绍如何使用 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。