Select a menu item in another application - vb6

I am using VB6 to try and select a menu item in a sub menu of a third-party application. I can get the ID for the menu item I want to click but now I am not sure how to actually click the button in order to have the related actions run. Here is my code so far:
hwnd = FindWindow(psClassname, vbNullString)
If hwnd > 0 Then
Call SetForegroundWindow(hwnd)
mwnd = GetMenu(hwnd)
sub_menu = GetSubMenu(mwnd, 0)
button_ID = GetMenuItemID(sub_menu, 0)
Call SetFocus(button_ID)
I get the error:
Wrong number of arguments or invalid property assignment
I've also tried using:
Call SendMessage(button_ID, BM_CLICK, 0, 0)
but this didn't work either. Any ideas would be greatly appreciated!

If you have the ID of the menu item, you can just send/post a WM_COMMAND message to its parent that includes the ID. For example:
Private Const WM_COMMAND As Long = &H111
SendMessage hwnd, WM_COMMAND, button_ID, ByVal 0&

Related

'Close' option is not available when program minimized to the taskbar?

I am programming VB6 in Win7. I have a program with a borderless window, no caption, no icon, no control box, etc. just a window. Using a command button, I can minimize the window to the Task Bar, and from there return it back.
My problem is, when minimized to the Task Bar, I right-click on the icon, and I wish to close the program from there. Win7 won't let me close the program via the pop-up menu. The close option is on the menu, but it does nothing.
How can I close this program from the task bar menu?
This seems to be a bug in VB6 Forms subsystem -- when form's BorderStyle is set to none Close menu on the taskbar and Alt+F4 shortcut as well just stop working as there is no system menu on the form.
Unfortunately a workaround involves subclassing and here is one way to deal with the issue:
Option Explicit
Private Const WM_SYSCOMMAND As Long = &H112
Private Const SC_CLOSE As Long = &HF060&
Private m_pSubclass As IUnknown
Private Property Get pvAddressOfSubclassProc() As Form1 '-- change Form1 to current form name
Set pvAddressOfSubclassProc = InitAddressOfMethod(Me, 5)
End Property
Private Sub Form_Load()
Set m_pSubclass = InitSubclassingThunk(hWnd, Me, pvAddressOfSubclassProc.SubclassProc(0, 0, 0, 0, 0))
End Sub
Public Function SubclassProc(ByVal hWnd As Long, ByVal wMsg As Long, ByVal wParam As Long, ByVal lParam As Long, Handled As Boolean) As Long
Select Case wMsg
Case WM_SYSCOMMAND
If wParam = SC_CLOSE Then
Unload Me
Handled = True
End If
End Select
End Function
This will need mdModernSubclassing.bas from Moderen Subclassing Thunk repository added to your project for the IDE-safe subclassing implementation.

Handling both mouse move and mouse click on a tray icon

My VB6 application is usually hidden and has a tray icon. It currently handles a user-defined callback as per this code:
Public Const WM_USER = &H400
Public Const TRAY_CALLBACK = (WM_USER + 1001&)
With GTStruct
.uID = mId
.hwnd = frm.hwnd
.hIcon = frm.Icon.Handle
.UFlags = NIF_ICON Or NIF_MESSAGE
.uCallbackMessage = TRAY_CALLBACK
.cbSize = Len(GTStruct)
End With
Shell_NotifyIcon NIM_ADD, GTStruct
A WindowProc handles TRAY_CALLBACK and the click message:
Const WM_NCDESTROY = &H82
Const WM_CLOSE = &H10
' If we're being destroyed, remove the tray icon
' and restore the original WindowProc.
If Msg = WM_NCDESTROY Or Msg = WM_CLOSE Then
RemoveFromTray
ElseIf Msg = TRAY_CALLBACK Then
' The user clicked on the tray icon.
' Look for click events.
If lParam = WM_RBUTTONUP Then
' On right click, show the menu.
SetForegroundWindow TheForm.hwnd
TheForm.PopupMenu TheMenu
If Not (TheForm Is Nothing) Then
PostMessage TheForm.hwnd, WM_NULL, ByVal 0&, ByVal 0&
End If
Exit Function
End If
End If
As it is, the application works and right-clickign the icon brings up the menu. I want to handle the mouse_move message in addition to this existing callback message so I changed the callback to this:
.uCallbackMessage = TRAY_CALLBACK Or WM_MOUSEMOVE
However, this ignores the mousemove message. If I use :
.uCallbackMessage = WM_MOUSEMOVE
The mouse_move message works and Form_MouseMove is called properly but then the menu stops working.
The question
How can I specify multiple callbacks in order to handle both the mouse move and the mouse click?
You can only have one callback message per icon; in your case, this is TRAY_CALLBACK. When you receive this message the lParam value indicates which mouse message triggered your callback.
In your example code you're already comparing lParam against WM_RBUTTONUP. You just need to add an additional check against WM_MOUSEMOVE.

ShowWindowAsync() don't show hidden window (SW_SHOW)

hello
i'm using Visual Basic 2008
here is part of my code:
Private Const SW_HIDE As Integer = 0
Private Const SW_SHOW As Integer = 5
<DllImport("user32.dll", SetLastError:=True, CharSet:=CharSet.Auto)> _
Private Shared Function ShowWindowAsync(ByVal hwnd As IntPtr, ByVal nCmdShow As Integer) As Boolean
End Function
button1 code:
Try
Dim p() As Process = Process.GetProcessesByName("notepad")
Dim mwh = p(0).MainWindowHandle
ShowWindowAsync(mwh, SW_HIDE)
Catch ex As Exception
MsgBox("error")
End Try
button2 code:
Try
Dim p() As Process = Process.GetProcessesByName("notepad")
Dim mwh = p(0).MainWindowHandle
ShowWindowAsync(mwh, SW_SHOW)
Catch ex As Exception
MsgBox("error")
End Try
when i click button 1 ( to hide the window ) it works good, but then when i want to display window back ( by clicking button 2 ) the function returns FALSE, who can tell me why? and how to get it to work, to hide window and then to show it again?
Because ShowWindowAsync() returns zero when the window was previously hidden according to the MSDN documentation, and zero is interpreted as FALSE. The return value does not indicate whether the function succeeded or not.
So the first time you call ShowWindowAsync(mwh, SW_HIDE) on a visible window the function returns TRUE because ShowWindowAsync() returns a non-zero value indicating the window (that is now/will be hidden) used to be visible.
Then the second call ShowWindowAsync(mwh, SW_SHOW) on a hidden window returns FALSE because ShowWindowAsync() returns a zero value indicating the window (that is now/will be visible) used to be hidden.
In other words, this is by design and the function should work as intended (unless the mwh window is not responding to messages or is invalid).
But the real problem here is that once you hide a window, Process::MainWindowHandle property doesn't have the handle anymore.
A process has a main window associated
with it only if the process has a
graphical interface. If the associated
process does not have a main window,
the MainWindowHandle value is zero.
The value is also zero for processes
that have been hidden, that is,
processes that are not visible in the
taskbar. This can be the case for
processes that appear as icons in the
notification area, at the far right of
the taskbar.
What you should do is to obtain the window handle via p(0).MainWindowHandle once and save the returned handle somewhere, so that you can show/hide the window. Of course, you should make sure that you zero out the saved handle when the window gets destroyed by the target process. In Notepad's case, you can use the Process::Exited event.
Sounds like "by design". Don't treat the return value of "false" as an error.
As per MSDN:
If the window was previously visible, the return value is nonzero.
If the window was previously hidden, the return value is zero.
http://msdn.microsoft.com/en-us/library/ms633549%28VS.85%29.aspx
You could likely block the exception from occurring by declaring the interop import of the function as a 32-bit integer type instead of a Boolean.
Private Shared Function ShowWindowAsync(ByVal hwnd As IntPtr, ByVal nCmdShow As Integer) As Integer
When hiding I did :
WindowHandle = Proc(0).MainWindowHandle
ShowWindowAsync(Proc(0).MainWindowHandle, ShowWindowConstants.SW_HIDE)
Then when showing I used the WindowHandle variable:
ShowWindowAsync(WindowHandle, ShowWindowConstants.SW_SHOW)
To Maximize a hidden window of another process you must do this 3 steps (very easy):
1- Get the MainWindowHandle
Because the application is hidden the MainWindowHandle is 0 (false). To overcome this problem you can use the FindWindow
//Get the MainWindowHandle
IntPtr hwnd = FindWindow(null, <WINDOW TITLE>);
2- Use ShowWindowAsync()
bool result = ShowWindowAsync(hwnd, 3); //3= Maximize application
3- Implement one of this application events: "GotFocus", "Activated" or "IsVisibleChanged"
(I only tested the "Activated" event)
private void WindowApplication_Activated(object sender, EventArgs e)
{
Application.Current.MainWindow.Show();
}
You can find more information on this url:
http://isolvable.blogspot.pt/2009/09/restoring-wpf-window-of-another-process.html
Hope this information can help someone!

Capture Access-Application Window Restore/Maximize Event

Scenario: In an Access project a main form has to be positioned and rearranged depending on the size of the Access-Application window. This should be done using VBA.
As far as I know there is no way in Microsoft Access VBA to capture the Restore/Maximize-Event of the Access-Application window (I refer to the Access Window itself not any form inside this).
Is there a way to solve this issue using WIN32 API?
I don't know of any way to use the WIN32 API to capture the Restore/Maximize Event. The best workaround I can think of is to use the Win32 API in conjunction with the Timer event of a form that is always open (either the Main Menu or some hidden form) and periodically poll the main access window to determine whether it's currently maximized.
Enum WindowSize
wsMax = 1
wsMin
wsRestore
End Enum
'Functions return 1 for true and 0 for false; multiply result by -1 to use as Boolean'
Private Declare Function IsZoomed Lib "User32" (ByVal hWnd As Long) As Integer
Private Declare Function IsIconic Lib "User32" (ByVal hWnd As Long) As Integer
Function IsMaximized(hWnd As Long) As Boolean
IsMaximized = IsZoomed(hWnd) * -1
End Function
Function IsMinimized(hWnd As Long) As Boolean
IsMinimized = IsIconic(hWnd) * -1
End Function
Private Sub Form_Timer()
Static PrevWinSize As WindowSize
If IsMaximized(hWndAccessApp) Then
If PrevWinSize <> wsMax Then
'Window has been maximized since we last checked'
MsgBox "Main Access window is maximized"
PrevWinSize = wsMax
End If
ElseIf IsMinimized(hWndAccessApp) Then
If PrevWinSize <> wsMin Then
'Window has been minimized since we last checked'
MsgBox "Main Access window is minimized"
PrevWinSize = wsMin
End If
Else
If PrevWinSize <> wsRestore Then
'Window has been restored since we last checked'
MsgBox "Main Access window is restored"
PrevWinSize = wsRestore
End If
End If
End Sub
You'll need to set an interval in the form's TimerInterval property to control how frequently you need to poll the window size.
EDIT: Obviously you'll want to keep track of the main window's previous state so that you don't do any unnecessary processing. The code as posted reflects this.

How do I click a button on a vb6 form?

I have a vb6 form with an ocx control on it. The ocx control has a button on it that I want to press from code. How do I do this?
I have:
Dim b As CommandButton
Set b = ocx.GetButton("btnPrint")
SendMessage ocx.hwnd, WM_COMMAND, GetWindowLong(b.hwnd, GWL_ID), b.hwnd
but it doesn't seem to work.
I believe the following will work:
Dim b As CommandButton
Set b = ocx.GetButton("btnPrint")
b = True
CommandButtons actually have two functions. One is the usual click button and the other is a toggle button that acts similar to a CheckBox. The default property of the CommandButton is actually the Value property that indicates whether a button is toggled. By setting the property, the Click event is generated. This is done even if the button is not styled as a ToggleButton and therefore doesn't change its state.
If you have access to the OCX code, you could expose the associated event handler and invoke it directly.
Don't know if an equivalent of .Net Button's Click() method existed back in VB6 days
For keypress you can also use sendmessage sending both keydown and keyup:
Private Declare Function SendMessage Lib "user32" Alias "SendMessageA" (ByVal hWnd As Long, ByVal wMsg As Long, ByVal wParam As Long, lParam As Long) As Long
Const WM_KEYDOWN As Integer = &H100
Const WM_KEYUP As Integer = &H101
Const VK_SPACE = &H20
Private Sub cmdCommand1_Click()
Dim b As CommandButton
Set b = ocx.GetButton("btnPrint")
SendMessage b.hWnd, WM_KEYDOWN, VK_SPACE, 0&
SendMessage b.hWnd, WM_KEYUP, VK_SPACE, 0&
End Sub
This:
Dim b As CommandButton
Set b = ocx.GetButton("btnPrint")
b = True
does work. Completely unintuitive. I'd expect it to throw an error since a bool is not a valid CommandButton, but it is because of the default property thing.
WM_LBUTTONDOWN would be a mouse click, what I want is a button click (button as in a hwnd button, not a mouse button).
I don't have access to the source of the ocx (it's a 3rd party control). If I did, I would expose the function that I wanted to call (the original writer of the ocx should have exposed it).
Do you have access to the OCX code? You shouldn't really be directly invoking the click of a button. You should refactor the code so that the OCX button click code calls a function, e.g.
CMyWindow::OnLButtonDown()
{
this->FooBar();
}
Then from your VB6 app, directly call the FooBar method. If you can't directly call functions from VB6 you can wrap the FooBar() method with a windows message proc function, e.g.
#define WM_FOOBAR WM_APP + 1
Then use SendMessage in the VB6, like SendMessage(WM_FOOBAR, ...)

Resources