Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Re-Active Disabled Controls

0.00/5 (No votes)
2 Nov 2012 1  
Re-Activate disabled windows controls

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)] //http://msdn.microsoft.com/en-us/library/windows/desktop/ms646291(v=vs.85).aspx
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")] //http://msdn.microsoft.com/en-us/library/windows/desktop/ms633558(v=vs.85).aspx
public static extern IntPtr WindowFromPoint(Point pt); 

and call it using using our cursor's position. 

// capture the window under the 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. 

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

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")] //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);  

The code can be used via our Win32 wrapper class as follows: 

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

Typical! This guys now skipped what the PostMessage method does!

 [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); 

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. 

//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);  

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.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here