Marco Di Feo – Blog Development, Hobby, Everything

11Sep/110

VB.NET – Enable Buttons in Applications with API calls

Every once in a while I stumble across some applications that have disabled control buttons without (for me) any reason. As a primary web developer I would like to have an easy way to enable these buttons. There exist some tools for firefox which you can enable buttons easily. For windows applications its a bit different. There are some win32 unlocker tools, but my virusscan doesn't like those tools at all and I would like to learn how to do it on my own. I finally ended up with my IDE and some ideas on how to achieve this.
I found some tutorials on how to solve this problem with C#.net, but I haven't installed C#.net on my box. I therefore tried to find a way to get through this with visual basic.net.

The Problem

There is a small, well known tool, that comes with Windows 7, that I use from time to time: The Calculator.
I like the windows calculator but my girlfriend mentioned that the "%" button is disabled if you change the calculator from standard to scientific. For me, there is no reason why this button should be disabled, so I tried to find a way to enable it. Here we go.

First of all we have to find out, what the name of this control (window) is. I use a little tool called spy++ to get into the application window structure. This tool lists all running applications with its elements and displays them in a tree structure. The easiest way to find the % button is to click on find (spy++) and aim at any application element. If you release your mousebutton spy++ selects the wanted element in the treeview. In this case I aim at the windows calculator (the disabled % button) and here you can see what it looks like:

spy++

It is a bit cryptic, but if you play around with it you will find out how applications and elements are grouped.

You can see that there is no caption for this button (... 000310D0 "" Button ...) so we can't use a "find" call to get the element we want. We have to find an other way.

First of all we need some api calls to get the handle of our main window.

	' Function used to enable windows (buttons, listviews, etc...)
    <DllImport("user32.dll")> _
    Private Function EnableWindow(ByVal hWnd As IntPtr, ByVal bEnable As Boolean) As Boolean
    End Function

	' API Call to find windows based on its classname, or windowname
    Private Declare Function FindWindow Lib "user32.dll" Alias "FindWindowA" (ByVal lpClassName As String, ByVal lpWindowName As String) As IntPtr
	' API Call to find windows based on a existing window handle
    Private Declare Function FindWindowEx Lib "user32.dll" Alias "FindWindowExA" (ByVal hWndParent As IntPtr, ByVal hWndChildAfter As Integer, ByVal lpClassName As String, ByVal lpWindowName As String) As IntPtr

After we have declared our API calls we need to know, how to use them. As you can see in the image above, our button is a subelement of the second #32770 (Dialog). This is itself a subelement of calcframe, which in turn is a subelement of calcframe (weird, isn't it?).

This is important, because we have to go all the way down to get a handle of our button, so we just start to find the calculator window with the following call:

Dim hwnd As IntPtr = FindWindow("CalcFrame", vbNullString)

With this call, we search the root tree and get the first entry of an window that matches our "search request". I used the classname parameter to identify the calculator. You can use the second parameter "lpWindowName" to get the handle by its window name, but in this case, the calculator window can have different names based on the language of windows installation, so "CalcFrame" is the more unique identifier for this particular scenario.
After we get the handle of our calculator, we have to drill down to the next subelements.

            Dim frameHwnd As IntPtr = FindWindowEx(hwnd, 0, "CalcFrame", vbNullString)
            Dim dialogHwnd As IntPtr = FindWindowEx(frameHwnd, 0, "#32770", vbNullString)
            Dim dialogHwnd2 As IntPtr = FindWindowEx(frameHwnd, dialogHwnd, "#32770", vbNullString)

Here we drill down to the groupbox which has all the button elements as subelements. The second parameter of FindWindowEx can be used as a startpointer, so you can iterate among elements within the same hierarchy.

Now, after we get the roothandle of our buttonholder, we can start with enabling the % button. As mentioned above, the button window itself has no caption, so we cannot get a direct handle to the window but, after some testing I found out, that the button we are looking for is always the 26st subelement. I was a bit lazy, so I wrote a small loop to enable the first 100 elements

        Dim button As IntPtr = vbNullString
        For I = 0 To 100
            Try
                button = FindWindowEx(dialogHandle, button, "Button", vbNullString)
                If button Then
                    Call EnableWindow(button, True)
                End If
            Catch ex As Exception
            End Try
        Next i

To iterate through all button windows I create an empty button variable with type intptr as startpointer and start the loop. Every cycle I write the current found button window to this variable to use it as startposition for the next cycle. In the end we call the EnableWindow API to enable the button and then, we are done and our % button is enabled, yay!

Maybe there is an easier and even faster way to get this thing to work, but for me I learned a lot about applications and api calls and how applcation elements are threatened by windows.

Here is the complete source code for the vb.net console application:

Imports System.Runtime.InteropServices

Module Module1
	' Function used to enable windows (buttons, listviews, etc...)
    <DllImport("user32.dll")> _
    Private Function EnableWindow(ByVal hWnd As IntPtr, ByVal bEnable As Boolean) As Boolean
    End Function

	' API Call to find windows based on its classname, or windowname
    Private Declare Function FindWindow Lib "user32.dll" Alias "FindWindowA" (ByVal lpClassName As String, ByVal lpWindowName As String) As IntPtr
	' API Call to find windows based on a existing window handle
    Private Declare Function FindWindowEx Lib "user32.dll" Alias "FindWindowExA" (ByVal hWndParent As IntPtr, ByVal hWndChildAfter As Integer, ByVal lpClassName As String, ByVal lpWindowName As String) As IntPtr

    Sub Main()
        Dim hwnd As IntPtr = FindWindow("CalcFrame", vbNullString)
        If hwnd Then
            Dim frameHwnd As IntPtr = FindWindowEx(hwnd, 0, "CalcFrame", vbNullString)
            Dim dialogHwnd As IntPtr = FindWindowEx(frameHwnd, 0, "#32770", vbNullString)
            Dim dialogHwnd2 As IntPtr = FindWindowEx(frameHwnd, dialogHwnd, "#32770", vbNullString)
            activeButtons(dialogHwnd2)
        Else
            Console.WriteLine("Calculator not found. Running already?")
        End If
        'Console.ReadLine()
    End Sub

    Private Sub activeButtons(ByVal dialogHandle As IntPtr)
        Dim button As IntPtr = vbNullString
        For i = 0 To 100
            Try
                button = FindWindowEx(dialogHandle, button, "Button", vbNullString)
                If button Then
                    Call EnableWindow(button, True)
                    Console.WriteLine("Activate Button #" & i.ToString() & " @Address: " & button.ToString())
                End If
            Catch ex As Exception
            End Try
        Next i
    End Sub

End Module
Kommentare (0) Trackbacks (0)

Zu diesem Artikel wurden noch keine Kommentare geschrieben.


Leave a comment

Noch keine Trackbacks.