Introduction
Have you ever had a grayed out control you just had to click.. Like a browse or back button. This Article describes how you can use the windows API to Re-activate the control..
Background
I was uninstalling Visual Studio 2010 from a Virtual Machine and for some reason the browse button was disabled. The original install was on an additional virtual harddrive that had been removed and VS just refused to uninstall. In my mind all would be well if I could just change the Install Directory and for that to happen the browse button had to be enabled. I set out on a quest to re-enable that button no matter what.
Using the code
The code makes use of a wrapper class (Win32) that exposes the methods in the Windows user32.dll.
Its all good and well that we have this wrapper class but how do we use it?
First we have to expose the correct methods in the dll
[DllImport("user32.dll")]
[return: MarshalAs(UnmanagedType.Bool)] public static extern bool EnableWindow(IntPtr hWnd, bool bEnable);
To re-enable the control we have to pass the Enablewindow method the controls handle and the new status.
Great.. So I know how to pass a bool but how do I get the controls handle. After all, its not in my code.. it's in an external application!!
Well, Its actually easier than you think.
We expose the WindowFromPoint method in the user32.dll. (This gets a window/control hanlde at a specific point.)
[DllImport("user32.dll")] public static extern IntPtr WindowFromPoint(Point pt);
and call it using using our cursor's position.
IntPtr hWnd = Win32.WindowFromPoint(Cursor.Position);
The hWnd
variable now contains a pointer to that controls handle and with the handle we basically Pwn the control! The following method does exactly that, it passes the controls hande and a status to the EnableWindow Method.
private void ReActiveControl(bool status)
{
Win32.EnableWindow(currentHandle, status);
ChangeStyle(status);
Win32.SendMessage(currentHandle, (Int32)Win32.WindowMessages.WM_ENABLE, Convert.ToInt32(status), 0);
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);
}
I found that DotNet controls don't act like other buttons do. Once you re-Activate them they look clickable but nothing happens when you click on them. For this I found one has to change the controls Style.
The WinAPI allows us to get and set the style
[DllImport("User32.dll")] public static extern WindowStyles GetWindowLong(IntPtr hWnd, int index);
[DllImport("user32.dll")] public static extern int SetWindowLong(IntPtr hWnd, int nIndex, long dwNewLong);
The code can be used via our Win32 wrapper class as follows:
<pre> private void ChangeStyle(bool status)
{
long Style = (long)Win32.GetWindowLong(currentHandle, -16);
if (status)
{
Style &= ~(long)Win32.WindowStyles.WS_DISABLED; }
else
{
Style |= (long)Win32.WindowStyles.WS_DISABLED; }
try
{
if (Environment.Is64BitOperatingSystem && Environment.OSVersion.Version.Major >= 6)
{
}
else
{
Win32.SetWindowLong(currentHandle, -16, Style);
}
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
}
Typical! This guys now skipped what the PostMessage method does!
[DllImport("User32.dll")] public static extern int PostMessage(IntPtr hWnd, int uMsg, IntPtr wParam, IntPtr lParam);
Well the PostMessage method used earlier in the ReActiveControl Method allows us to post a message to windows describing that its a command (WM_COMMAND) and that command is an enable command (WM_ENABLE).
Points of Interest
While playing with the code i decided to add some additional features like changing the text of a control. (Once you restart the application the text you change will be back to how it was, but its still fun.)
For this I made use of the WinAPI's SendMessage method.
Here we expose the different overloads of the method:
[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);
To send the new text we use a StringBuilder
to build up the new text and pass it as a parameter to the SendMessage method.
StringBuilder sbText = new StringBuilder(256);
int length = Win32.SendMessage(currentHandle , (Int32)Win32.WindowMessages.WM_GETTEXTLENGTH, 0, 0);
sbText.Append(_textBoxText.Text);
Win32.SendMessage(currentHandle, (Int32)Win32.WindowMessages.WM_SETTEXT, length, sbText);
While playing with the code I also discovered that my finder tool doesn't actually pick up the disabled controls.
The full solution code contains a method to find all the child controls of a parent. This is particularly handy to get to some of the hidden controls.
History
This version still doesn't behave on Windows 7 x64. In future I will update it to support x64.