Link to home
Start Free TrialLog in
Avatar of ampapa
ampapa

asked on

Is button visible ona form, API

How can I tell if a button is visible on a form "given the text" via the Windows API?

Avatar of Clif
Clif
Flag of United States of America image

Look into GetWindowLong() and GWL_STYLE and WS_VISIBLE

Private Declare Function GetWindowLong _
    Lib "user32" Alias "GetWindowLongA" _
    (ByVal hWnd As Long, _
    ByVal nIndex As Long) _
    As Long

Private Const GWL_STYLE = (-16)
Private Const WS_VISIBLE = &H10000000


lRes = GetWindowLong(btn_hwnd, GWL_STYLE)

bIsVisible = ((lRes And WS_VISIBLE) = WS_VISIBLE)
Avatar of ampapa
ampapa

ASKER

Does this function only work if I know what the handle of the button is, I've only got the "caption" of the button?

Is there a way to Enumerate all the objects on a form?

So, I'm assuming GWL_STYLE is the button type what are the other variables checkbox, radiobutton, etc?

Use FindWindowEx to find the window button having the specified caption( use the windows class name as well to make sure you get the button, not other control type). After the window was found use GetWindowLong to get the visible/hidden state of the control.
   ' assumes within form; otherwise, you'll need a reference to the Form object in place of Me, below.
    Dim oControl As Control
    Dim strCaption As String
    Dim blnFound As Boolean
   
    blnFound = False ' default
    strCaption = "Command1" ' or passed as parameter
    For Each oControl In Me.Controls
        If TypeOf oControl Is CommandButton Then
            If oControl.Caption = strCaption Then
                blnFound = True
                Exit For
            End If
        End If
    Next
And BTW, this is in response to your question "Is there a way to Enumerate all the objects on a form?"  Since you asked for an API solution initially, this doesn't meet that requirement.  It also doesn't meet your initial criterion of determining if the button is visible (although you could check the .Visible property once you find the caption desired).
Avatar of ampapa

ASKER

aParser, thanks for the snippet of code but I do need an API solution to this problem as I am not in control of this application that I am trying manage.

This window is not visible at the time so I am unable to get the handle to get the button handle...

Here is what I've got going, maybe I'm approaching this wrong...

I have an application that I need to uninstall remotely so all questions asked by this program when uninstalling need to be answered automatically. The application initially asks for permission to uninstall with a simple YES/NO and I now have this problem solved.

The next few windows that are generated by the uninstall are done on the fly so the hwnd will also be changing but the window captions aren't, this is causing me problems. I've been trying to catch new windows appearing and old windows closing by using the following.

'******
Do Until bClicked = True Or lngreturn <> 0
    'Get's handle of Main Hyperion window to confirm uninstall
    lngreturn = FindWindow(vbEmpty, "Confirm File Deletion")
    DoEvents
Loop

'****
Do Until lngreturn = 0
    DoEvents
Loop

Obviously the uninstall takes some time so I need a way to wait for certain processes? Maybe I need a routine that will catch any new windows that are generated and destroyed, are they destroyed if the aren't visible? Anyone have any ideas or thoughts?
What you need is a callback hook to discover when windows are opening and closing.

This infor isn't exactly what you need, but it should give you an idea:
http://vbnet.mvps.org/index.html?code/hooks/messageboxhook.htm
Avatar of ampapa

ASKER

I came accross that link...  In my search.

Is this something you can help me with?

Certainly.  What would you like to know specifically?
Avatar of ampapa

ASKER

Most of the examples that I have been able to find are for Mouse and Keyboard hooks. Do you have any example coding refering to hooking a window?

I'm certain that unless I had some example to follow that I would probably would not be able to implementthis solution, which I also feel is the correct way.

Thanks for the help.

The example I posted above is a hook to windows (forms)

Very basically, you want to create a windowsproc in a bas module to which will be passed the parameters of the message:

Public Function WindowsProc(ByVal uMsg As Long, ByVal wParam As Long, ByVal lParam As Long) As Long
    'Within the proc, you loog for the message passed and, when it's the one you want, do your processing
    If uMsg = HCBT_ACTIVATE Then '(or HCBT_CREATEWND, I'm not sure which will work best for you)
        'At this point, look for the particular window you want to process on and do it.

    End If
    'Don't forget to pass the message on to the queue
    SendMessage hwnd, uMsg, wParam, lParam
End Function

Now, in your application start procedure (main form load?), you set the hook:
   
   hInstance = GetWindowLong(0, GWL_HINSTANCE) '0 is the desktop, the parent of everything
   hThreadId = GetCurrentThreadId()

  'set up the MSGBOX_HOOK_PARAMS values
  'By specifying a Windows hook as one
  'of the params, we can intercept messages
  'sent by Windows and thereby manipulate
  'the dialog
   With MSGHOOK
      .hwndOwner = 0
      .hHook = SetWindowsHookEx(WH_CBT, AddressOf WindowsProc, hInstance, hThreadId)
   End With

Finally, at you application close, drop the hook
    UnhookWindowsHookEx MSGHOOK.hHook

NOTE!!!  MAKE ABSOLUTELY SURE YOU DROP THE HOOK BEFORE YOUR APPLICATION ENDS!!!!!
Avatar of ampapa

ASKER

hInstance = GetWindowLong(0, GWL_HINSTANCE) '0 is the desktop, the parent of everything

By passing "0" to the above function is there no need for the GetDesktopWindow function?

So, let's say this new window that is created how would I know if it is visible or not?
Avatar of ampapa

ASKER

Hang with me Clif I think approaching where I need to be.

It appears that HCBT_SETFOCUS might be what I'm looking for? Correct me if I'm wrong but if I am able to establish the WindowHook this function will supply "the HWND to the window losing the keyboard focus" and "the HWND of the window recieiving keyboard focus".
ampapa,
Actually realizing that ME, 2000, and XP have changed the rules, perhaps GetDesktopWindow() wouldn't be such a bad idea.

As far is discovering whether or not the new window is visible, I covered that in my first post in this thread.
Avatar of ampapa

ASKER

'misc constants
Private Const WH_CBT = 5
Private Const GWL_HINSTANCE = (-6)
Private Const HCBT_ACTIVATE = 5


Can I just change "HCBT_ACTIVATE" to  "HCBT_SETFOCUS" would then the constant "WH_CBT" remain 5?
No, I don't think that's quite what you want.  The reason being is that the users (or another app) might open a window causing the windows you're looking for to lose focus and the new unrelated window to gain focus.

Here's what you want in basic steps:
1. Your application is running, the target application is not.
2. Hook the windows messenger looking for new windows.  You will use the instance of the desktop in your hook.
3. Every time a new window starts, use GetWindowText() to see if it's the target app starting.
4. Once your target app has started, start looking for it's child windows to open.  To do this, you'll need a new hook using the instance of the target app.
5. Every time a child window opens, use GetWindowText() to see if it's the dialog window you're looking for.
6. Once you have the dialog box you're looking for, do what ever processing you want to it.
7. Before your app closes, drop all hooks.
Avatar of ampapa

ASKER

That's sort of how I'm doing it now with FindWindow but the issue is that there is more than 1 window with similar text... So, current code identify's the window as being open and the code continues.

User intervention in this particular case will be non-existent so what do you think about "HCBT_SETFOCUS"

This is what I've got in a module and form:

MODULE
'misc constants
Private Const WH_CBT = 5
Private Const GWL_HINSTANCE = (-6)
'Private Const HCBT_ACTIVATE = 5

'UDT for passing data through the hook
Private Type MSGBOX_HOOK_PARAMS
   hwndOwner   As Long
   hHook       As Long
End Type

'need this declared at module level as
'it is used in the call and the hook proc
Private MSGHOOK As MSGBOX_HOOK_PARAMS

Private Declare Function GetCurrentThreadId Lib "kernel32" () As Long

'Public Declare Function GetDesktopWindow Lib "user32" () As Long

Private Declare Function GetWindowLong Lib "user32" _
   Alias "GetWindowLongA" _
  (ByVal hwnd As Long, _
   ByVal nIndex As Long) As Long

Private Declare Function SetWindowsHookEx Lib "user32" _
   Alias "SetWindowsHookExA" _
  (ByVal idHook As Long, _
   ByVal lpfn As Long, _
   ByVal hmod As Long, _
   ByVal dwThreadId As Long) As Long
   
Private Declare Function UnhookWindowsHookEx Lib "user32" _
   (ByVal hHook As Long) As Long



Public Sub GetWindowData()

    If uMsg = HCBT_ACTIVATE Then '(or HCBT_CREATEWND, I'm not sure which will work best for you)
        'At this point, look for the particular window you want to process on and do it.

        msgbox hwnd

    End If
    'Don't forget to pass the message on to the queue
    SendMessage hwnd, uMsg, wParam, lParam
End Sub


FORM

Private Sub Form_Load()
   hInstance = GetWindowLong(0, GWL_HINSTANCE) '0 is the desktop, the parent of everything
   hThreadId = GetCurrentThreadId()

  'set up the MSGBOX_HOOK_PARAMS values
  'By specifying a Windows hook as one
  'of the params, we can intercept messages
  'sent by Windows and thereby manipulate
  'the dialog
   With MSGHOOK
      .hwndOwner = 0
      .hHook = SetWindowsHookEx(WH_CBT, AddressOf WindowsProc, hInstance, hThreadId)
   End With
End Sub

Private Sub Form_Unload(Cancel As Integer)
    UnhookWindowsHookEx MSGHOOK.hHook
End Sub
You are missing the function WindowsProc from your module, or you forgot to change the name of the function in your AddressOf() statment in your SetWindowsHookEx() call.

Actually it looks like the latter.

Your sub GetWindowData should be a function:
Public Function GetWindowData(ByVal uMsg As Long, ByVal wParam As Long, ByVal lParam As Long) As Long

    If uMsg = HCBT_ACTIVATE Then '(or HCBT_CREATEWND, I'm not sure which will work best for you)
        'At this point, look for the particular window you want to process on and do it.

        msgbox hwnd  'In this case hWnd is the handle of the desktop

    End If
    'Don't forget to pass the message on to the queue
    SendMessage hwnd, uMsg, wParam, lParam
End Sub

And your call to SetWindowsHookEx should be:
      .hHook = SetWindowsHookEx(WH_CBT, AddressOf GetWindowData, hInstance, hThreadId)

-----
It's your app.  But I'm still not comfortable relying on a lost focus/got focus to control the app, even if it's running unattended.  There's the potential that some other unattended app will throw an error causing a messagebox to be displayed, which will then gain focus throwing off your application.
Avatar of ampapa

ASKER

1. Your application is running, the target application is not.
2. Hook the windows messenger looking for new windows. You will use the instance of the desktop in your hook.

Currently this is what we are doing correct?

3. Every time a new window starts, use GetWindowText() to see if it's the target app starting.

So, if I a m able to see all messages with this hook I'm currently creating won't I be able to get the HWND of the New Windows being created? If I can get to this point I think I'm home free...

Steps 4 and 5 can be eliminated

6. Once you have the dialog box you're looking for, do what ever processing you want to it.
7. Before your app closes, drop all hooks.


'**********************************************************************
"At this point, look for the particular window you want to process on and do it."

When I added the msgbox you said "'In this case hWnd is the handle of the desktop" is that only because no other windows have been opened??? As windows are opened though shouldn't change to the handle of the window that was opened?


Avatar of ampapa

ASKER

Here is what I've got at the moment when firing this up it crashes Visual Basic:

MODULE
'******

Public Function WindowsProc(ByVal uMsg As Long, ByVal wParam As Long, ByVal lParam As Long) As Long
    'Within the proc, you loog for the message passed and, when it's the one you want, do your processing
    If uMsg = HCBT_ACTIVATE Then '(or HCBT_CREATEWND, I'm not sure which will work best for you)
        'At this point, look for the particular window you want to process on and do it.
       
        MsgBox hwnd  'This should be the handle of the new Window

    End If
    'Don't forget to pass the message on to the queue
    SendMessage hwnd, uMsg, wParam, lParam
End Function



FORM
'****

'UDT for passing data through the hook
Private Type MSGBOX_HOOK_PARAMS
   hwndOwner   As Long
   hHook       As Long
End Type

'need this declared at module level as
'it is used in the call and the hook proc
Private MSGHOOK As MSGBOX_HOOK_PARAMS

'Public Declare Function GetDesktopWindow Lib "user32" () As Long

Private Declare Function SetWindowsHookEx Lib "user32" _
   Alias "SetWindowsHookExA" _
  (ByVal idHook As Long, _
   ByVal lpfn As Long, _
   ByVal hmod As Long, _
   ByVal dwThreadId As Long) As Long
   
Private Declare Function UnhookWindowsHookEx Lib "user32" _
   (ByVal hHook As Long) As Long

Private Declare Function GetWindowLong Lib "user32" _
   Alias "GetWindowLongA" _
  (ByVal hwnd As Long, _
   ByVal nIndex As Long) As Long
   
Private Declare Function GetCurrentThreadId Lib "kernel32" () As Long

'misc constants
Private Const WH_CBT = 5
Private Const GWL_HINSTANCE = (-6)
Private Const HCBT_ACTIVATE = 5
   
   

Private Sub Form_Load()
   hInstance = GetWindowLong(0, GWL_HINSTANCE) '0 is the desktop, the parent of everything
   hThreadId = GetCurrentThreadId()

  'set up the MSGBOX_HOOK_PARAMS values
  'By specifying a Windows hook as one
  'of the params, we can intercept messages
  'sent by Windows and thereby manipulate
  'the dialog
   With MSGHOOK
      .hwndOwner = 0
      .hHook = SetWindowsHookEx(WH_CBT, AddressOf WindowsProc, hInstance, hThreadId)
   End With
End Sub

Private Sub Form_Unload(Cancel As Integer)
    UnhookWindowsHookEx MSGHOOK.hHook
End Sub
I'm sorry, but I have to put helping you on hold for a while.  I'm in the middle of a four hour long process.  If I run any tests that crash my system, I'll have to start the who process over again.  :)

Over the weekend, I'll see what I can come up with on this.

Sorry I have to do this to you.
Avatar of ampapa

ASKER

Thanks for the post.

I'll keep my eyes on the forum this weekend.
Avatar of ampapa

ASKER

CLif, I found a bitof code on MS site and it's helped me a long a bit. I'm having problems still identifying a particular window. Let's sayI'm looking for the "calculater" how can I get a msgbox with the HWND when it's window is launched?

FORM
'********************************************

      Private Sub Command1_Click()
      Dim hInst As Long
      Dim Thread As Long

         'Set up the CBT hook
         hInst = GetWindowLong(Me.hwnd, GWL_HINSTANCE)
         Thread = GetCurrentThreadId()
         hHook = SetWindowsHookEx(WH_CBT, AddressOf WinProc1, hInst, _
                                  Thread)
           
         'Display the message box
         MsgBox "This message box has been positioned at (0,0)."
         
      End Sub

MODULE
'*************************************************

      Public Declare Function UnhookWindowsHookEx Lib "user32" ( _
         ByVal hHook As Long) As Long
      Public Declare Function GetWindowLong Lib "user32" Alias _
         "GetWindowLongA" (ByVal hwnd As Long, ByVal nIndex As Long) _
         As Long
      Public Declare Function GetCurrentThreadId Lib "kernel32" () As Long
      Public Declare Function SetWindowsHookEx Lib "user32" Alias _
         "SetWindowsHookExA" (ByVal idHook As Long, ByVal lpfn As Long, _
         ByVal hmod As Long, ByVal dwThreadId As Long) As Long
      Public Declare Function SetWindowPos Lib "user32" ( _
         ByVal hwnd As Long, ByVal hWndInsertAfter As Long, _
         ByVal x As Long, ByVal y As Long, ByVal cx As Long, _
         ByVal cy As Long, ByVal wFlags As Long) As Long
      Public Declare Function GetWindowRect Lib "user32" (ByVal hwnd _
         As Long, lpRect As RECT) As Long


      Public Const GWL_HINSTANCE = (-6)
      Public Const SWP_NOSIZE = &H1
      Public Const SWP_NOZORDER = &H4
      Public Const SWP_NOACTIVATE = &H10
      Public Const HCBT_ACTIVATE = 5
      Public Const WH_CBT = 5

      Public hHook As Long

      Function WinProc1(ByVal lMsg As Long, ByVal wParam As Long, _
         ByVal lParam As Long) As Long

         If lMsg = HCBT_ACTIVATE Then
            'Show the MsgBox at a fixed location (0,0)
            SetWindowPos wParam, 0, 0, 0, 0, 0, _
                         SWP_NOSIZE Or SWP_NOZORDER Or SWP_NOACTIVATE
             'Release the CBT hook
            UnhookWindowsHookEx hHook

         End If
         WinProc1 = False

      End Function
ASKER CERTIFIED SOLUTION
Avatar of Clif
Clif
Flag of United States of America image

Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial
Avatar of ampapa

ASKER

Clif, thanks for the update. I'll take a better look at the link later this afternoon.

I've found in my search that I am actually trying to do something that can't be done through VB, well sort of unless it's accompanied by a .DLL that monitors the entire system. A 'local' application that I create can use a 'local' hook similar to the code I posted above, which came from Microsoft's site.

Your original code supplied above uses a given HWND to identify a button do you have a routine that loops through all the objects on a form given it's HWND?

Avatar of ampapa

ASKER

Clif, thanks for all your great help. I certainly understand "hooks" much better than I did when I started and that's really all that matters, sometimes things just can't be done or answered...

Thanks again.
TFTP.

Sorry I couldn't be of more help.