Ahhh, I won't use SetForegroundWindow either for the same reason :)
(instead, I'll use SetActiveWindow)
Printable View
Ahhh, I won't use SetForegroundWindow either for the same reason :)
(instead, I'll use SetActiveWindow)
Update:
1. Using SetForegroundWindow to enable the use of SendKeys, always makes my app the return hWnd.
2. If I use SetActiveWindow to overcome this, I can no longer use SendKeys!
3. The delima is If I now use PostMessage to emulate SendKeys (%{Tab}) I would require the hWmd of the now non-focused window..........
Still trying :)
Another idea... Use GetNextWindow (or call GetWindow which does the same thing) with GW_HWNDNEXT to get the next window in the ZOrder.
Will give it a shot.
I must say I was surprised when I couldn't find any posts similar to this (I think the question was answered but no solution). I would be keen to get this up nad running as
others may benifit too.
Thanks for your inputs on this Joacim, I appreciate it :).
Cheers,
Was successful, but flakey :)
I tried many combinations, even to the point of not having to use SendKeys (as the Next Windows hWnd was made available).
Sorry, I stopped reading after the word "successful" :)
:thumb: :DQuote:
Originally Posted by Joacim Andersson
Through sheer determination I revisited the above APIs' - SUCCESS!
In short, and NO Sub-Classing, I used:
SetForeGroundWindow(myApps hWnd)
GetNextWindow(myApps hWnd, GW_HWNDNEXT)
SetForeGroundWindow(retval of GetNextWindow)
Viola....
Clicking the SysTray Icon takes the focus, SetForegroundWindow ensures myApp is ready for GetNext,
then capture the Next hWnd,
Now set the focus back to the losing apps Window......
I will post the entire app in the .Net Forum once done.
I want to keep this un-resolved, as I would like feedback on a Global (Sub-Classed) method
using the WM_ACTIVEAPP as M$ states it should work.
At this stage many, many thanks to Joacim for his persistance :)
Regards,
Initial testing seems OK with Access, Excel.
Joacim, at this stage I cannot 'rate' you as I need to share it around. In the interim, please take ten points outa the bit bucket :)
Bruce.
Beta .Net code now posted here (post #19):
http://www.vbforums.com/showthread.php?t=366585
OK,
I've been playing with this code under VB6 for a while. The method as spelled out in the .Net forum doesn't work in VB6 since the "Next Window" is not the one we are looking for. I find that I have to first iterate through all the "tooltips_class32" windows first.
In addition, once we find the top level window we can't just send a WM_COPY message to that window since we really want the Textbox child of that window. I've been testing on Notepad and EditPadPro. Here is what I've found works so far.Like you, I didn't get the WM_APPACTIVATE code to work, but I think I can so I'll keep working on it.VB Code:
[FONT=Courier New][COLOR=#0000FF]Private[/COLOR] [COLOR=#0000FF]Sub[/COLOR] DoIt() [COLOR=#0000FF]Dim[/COLOR] lhWnd [COLOR=#0000FF]As[/COLOR] [COLOR=#0000FF]Long [/COLOR] [COLOR=#0000FF]Dim[/COLOR] lThreadID [COLOR=#0000FF]As[/COLOR] [COLOR=#0000FF]Long [/COLOR] [COLOR=#0000FF]Dim[/COLOR] lProcessID [COLOR=#0000FF]As[/COLOR] [COLOR=#0000FF]Long [/COLOR] [COLOR=#0000FF]Dim[/COLOR] RetInfo [COLOR=#0000FF]As[/COLOR] GUITHREADINFO [COLOR=#0000FF]Dim[/COLOR] lpClassName [COLOR=#0000FF]As[/COLOR] [COLOR=#0000FF]String [/COLOR] lhWnd = GetForegroundWindow [COLOR=#007A00]'the next few windows are tooltips_class32 class windows [/COLOR] [COLOR=#007A00]'associated with the taskbar [/COLOR] [COLOR=#0000FF]While[/COLOR] GetNextClassName(lhWnd) = "[COLOR=#7A0000]tooltips_class32[/COLOR]" DoEvents [COLOR=#0000FF]Wend [/COLOR] [COLOR=#007A00]'Whoops don't do this again [/COLOR] 'Text1 = Text1 & GetNextClassName(lhWnd) & vbTab & Hex(lhWnd) & vbCrLf [COLOR=#007A00]'get threadID of thatwindow [/COLOR] lThreadID = GetWindowThreadProcessId(lhWnd, lProcessID) [COLOR=#007A00]'and set focus back to that window [/COLOR] SetForegroundWindow lhWnd [COLOR=#007A00]'now get thread info [/COLOR] RetInfo.cbSize = LenB(RetInfo) [COLOR=#0000FF]Call[/COLOR] GetGUIThreadInfo(lThreadID, RetInfo) [COLOR=#007A00]'hwndCaret is the child window we want [/COLOR] Text1 = Text1 & Hex(RetInfo.hwndCaret) & vbTab & Hex(RetInfo.hwndActive) & vbCrLf Clipboard.Clear [COLOR=#0000FF]Call[/COLOR] PostMessage(RetInfo.hwndCaret, WM_COPY, 0&, 0&) DoEvents [COLOR=#007A00]'Check for valid Pasted data [/COLOR] [COLOR=#0000FF]If[/COLOR] [COLOR=#0000FF]Not[/COLOR] Clipboard.GetFormat(vbCFText) [COLOR=#0000FF]Then [/COLOR] MsgBox "[COLOR=#7A0000]Please select some text first.[/COLOR]", vbOKOnly [COLOR=#0000FF]Else [/COLOR] Text1 = Text1 & Clipboard.GetText & vbCrLf [COLOR=#007A00]'CheckSpelling Clipboard.GetText [/COLOR] [COLOR=#0000FF]End[/COLOR] [COLOR=#0000FF]If [/COLOR] [COLOR=#0000FF]End[/COLOR] [COLOR=#0000FF]Sub [/COLOR] [COLOR=#0000FF]Private[/COLOR] [COLOR=#0000FF]Function[/COLOR] GetNextClassName(lhWnd [COLOR=#0000FF]As[/COLOR] [COLOR=#0000FF]Long[/COLOR]) [COLOR=#0000FF]As[/COLOR] [COLOR=#0000FF]String [/COLOR] [COLOR=#0000FF]Dim[/COLOR] lpClassName [COLOR=#0000FF]As[/COLOR] [COLOR=#0000FF]String [/COLOR] lhWnd = GetWindow(lhWnd, GW_HWNDNEXT) lpClassName = Space(255) GetClassName lhWnd, lpClassName, 255 GetNextClassName = Left(lpClassName, InStr(lpClassName, Chr(0)) - 1) [COLOR=#0000FF]End[/COLOR] [COLOR=#0000FF]Function[/COLOR][/FONT]
Also I never liked the Word dialog box so I'll adapt my spellchecker to this code. It is in my "RichtextBox Tricks and Tips" thread. That code brings up a context menu with spelling suggestions rather than that ugly Word Box.
Another thought I had was instead of using a task tray icon to activate we could install a global keyboard hook and look for a hot-key. This way we would already have the handle to the edit window.
@moeur, even though I really like the way you where thinking in the above solotion :thumb: there is a huge problem in that logic... The problem itself is well known, using GetWindow in a loop can freeze your app if a user happens to close a window while the loop is running... Even the MSDN Library tells you to not use this approach (and I'm quoting)Quote:
An application that calls GetWindow to perform this task [read EnumWindows] (in a loop) risks being caught in an infinite loop or referencing a handle to a window that has been destroyed.
It's simple enough to put a counter in the loop that exits the loop on too many iterations. In either case you have to work through the windows to get to the one you want.
All I'm saying ís that you should use EnumWindows instead. You can apply your logic in the callback procedure just as well... Please remember: I liked your logic :thumb:
But I don't see how EnumWindows will give us the "Next Window" in Z-order.
Some more comments on this:
WM_APPACTIVATE is not going to work for the task tray icon method because what you'd have to subclass would be the tray.
The method I posted above is still kind of flaky since sometimes there are other windows besides tooltips_class32 windows between your window and the previous window. For instance I've seen a window with a class name "WorkerW".
This method also does not work for things like the VBForums edit box.
I have come up with a robust solution to this problem.
Whenever a top-level window is activated, a global message is sent that can be trapped with a global WH_SHELL hook. If we keep track of which program was activated last, then when our icon is clicked, we have all the info we need.
Instead of having to set a global WH_SHELL hook (which would require C++) we can instead use an API function that Penagate mentioned in another thread called RegisterShellHookWindow. This function directs all shell messages to our window procedure so we just have to subclass our own window.
To set up this functionality, you do something like thisIn this code, I use my subclasser class (clsSubClass) and taskicon OCX control (NotifyIcon) but you can do it any way you would like. Once this is done we will receive the shell messages in our window procedure.VB Code:
[FONT=Courier New][COLOR=#0000FF]Option Explicit [/COLOR] [COLOR=#0000FF]Private[/COLOR] [COLOR=#0000FF]Declare[/COLOR] [COLOR=#0000FF]Function[/COLOR] RegisterShellHookWindow [COLOR=#0000FF]Lib[/COLOR] "[COLOR=#7A0000]user32[/COLOR]" ( _ [COLOR=#0000FF]ByVal[/COLOR] hwnd [COLOR=#0000FF]As[/COLOR] [COLOR=#0000FF]Long[/COLOR] _ ) [COLOR=#0000FF]As[/COLOR] [COLOR=#0000FF]Long [/COLOR] [COLOR=#0000FF]Private[/COLOR] [COLOR=#0000FF]Declare[/COLOR] [COLOR=#0000FF]Function[/COLOR] RegisterWindowMessage [COLOR=#0000FF]Lib[/COLOR] "[COLOR=#7A0000]user32[/COLOR]" [COLOR=#0000FF]Alias[/COLOR] "[COLOR=#7A0000]RegisterWindowMessageW[/COLOR]" ( _ [COLOR=#0000FF]ByVal[/COLOR] lpString [COLOR=#0000FF]As[/COLOR] [COLOR=#0000FF]Long[/COLOR] _ ) [COLOR=#0000FF]As[/COLOR] [COLOR=#0000FF]Long [/COLOR] [COLOR=#0000FF]Private[/COLOR] WM_SHELLHOOKMESSAGE [COLOR=#0000FF]As[/COLOR] [COLOR=#0000FF]Long Private[/COLOR] [COLOR=#0000FF]Const[/COLOR] HSHELL_WINDOWCREATED = 1 [COLOR=#0000FF]Private[/COLOR] [COLOR=#0000FF]Const[/COLOR] HSHELL_WINDOWDESTROYED = 2 [COLOR=#0000FF]Private[/COLOR] [COLOR=#0000FF]Const[/COLOR] HSHELL_ACTIVATESHELLWINDOW = 3 [COLOR=#0000FF]Private[/COLOR] [COLOR=#0000FF]Const[/COLOR] HSHELL_WINDOWACTIVATED = 4 [COLOR=#0000FF]Private[/COLOR] [COLOR=#0000FF]Const[/COLOR] HSHELL_GETMINRECT = 5 [COLOR=#0000FF]Private[/COLOR] [COLOR=#0000FF]Const[/COLOR] HSHELL_REDRAW = 6 [COLOR=#0000FF]Private[/COLOR] [COLOR=#0000FF]Const[/COLOR] HSHELL_TASKMAN = 7 [COLOR=#0000FF]Private[/COLOR] [COLOR=#0000FF]Const[/COLOR] HSHELL_LANGUAGE = 8 [COLOR=#0000FF]Private[/COLOR] [COLOR=#0000FF]Const[/COLOR] HSHELL_SYSMENU = 9 [COLOR=#0000FF]Private[/COLOR] [COLOR=#0000FF]Const[/COLOR] HSHELL_ENDTASK = 10 [COLOR=#0000FF]Private[/COLOR] [COLOR=#0000FF]Const[/COLOR] HSHELL_ACCESSIBILITYSTATE = 11 [COLOR=#0000FF]Private[/COLOR] [COLOR=#0000FF]Const[/COLOR] HSHELL_APPCOMMAND = 12 [COLOR=#0000FF]Private[/COLOR] [COLOR=#0000FF]Const[/COLOR] HSHELL_WINDOWREPLACED = 13 [COLOR=#0000FF]Private[/COLOR] [COLOR=#0000FF]Const[/COLOR] HSHELL_WINDOWREPLACING = 14 [COLOR=#0000FF]Private[/COLOR] [COLOR=#0000FF]Const[/COLOR] HSHELL_HIGHBIT = &H8000 [COLOR=#0000FF]Private[/COLOR] [COLOR=#0000FF]Const[/COLOR] HSHELL_FLASH = HSHELL_REDRAW [COLOR=#0000FF]Or[/COLOR] HSHELL_HIGHBIT [COLOR=#0000FF]Private[/COLOR] [COLOR=#0000FF]Const[/COLOR] HSHELL_RUDEAPPACTIVATED = HSHELL_WINDOWACTIVATED [COLOR=#0000FF]Or[/COLOR] HSHELL_HIGHBIT [COLOR=#0000FF]Private[/COLOR] [COLOR=#0000FF]Sub[/COLOR] Form_Load() [COLOR=#0000FF]Set[/COLOR] FormSubClass = [COLOR=#0000FF]New[/COLOR] clsSubClass [COLOR=#007A00]'get dynamic shell message number [/COLOR] WM_SHELLHOOKMESSAGE = RegisterWindowMessage(StrPtr("[COLOR=#7A0000]SHELLHOOK[/COLOR]")) [COLOR=#007A00]'we want to receive shell messages [/COLOR] RegisterShellHookWindow Me.hwnd FormSubClass.Enable Me.hwnd [COLOR=#007A00]'add icon to task tray [/COLOR] [COLOR=#0000FF]With[/COLOR] NotifyIcon .Icon = Me.Icon .Tip = "[COLOR=#7A0000]Spell Checker[/COLOR]" .Add [COLOR=#0000FF]End[/COLOR] [COLOR=#0000FF]With [/COLOR] Text1 = "" Me.Hide [COLOR=#0000FF]End[/COLOR] [COLOR=#0000FF]Sub[/COLOR][/FONT]
What we want to do is keep track of the last window that was activated, so in the window procedureNow the only thing left to do is allow the user to start the spell checking by left clicking on the system tray icon that we added. In this code I call a CheckSpelling function that checks the spelling of the text on the clipboard and returns the corrected results to the clipboard.VB Code:
[FONT=Courier New][COLOR=#0000FF]Private[/COLOR] [COLOR=#0000FF]Sub[/COLOR] FormSubClass_WMArrival(hwnd [COLOR=#0000FF]As[/COLOR] [COLOR=#0000FF]Long[/COLOR], uMsg [COLOR=#0000FF]As[/COLOR] [COLOR=#0000FF]Long[/COLOR], wParam [COLOR=#0000FF]As[/COLOR] [COLOR=#0000FF]Long[/COLOR], lParam [COLOR=#0000FF]As[/COLOR] [COLOR=#0000FF]Long[/COLOR], lRetVal [COLOR=#0000FF]As[/COLOR] [COLOR=#0000FF]Long[/COLOR]) [COLOR=#007A00]'don't do anything if spell checking is in progress [/COLOR] [COLOR=#0000FF]If[/COLOR] PauseMessages [COLOR=#0000FF]Then[/COLOR] [COLOR=#0000FF]Exit[/COLOR] [COLOR=#0000FF]Sub [/COLOR] [COLOR=#0000FF]If[/COLOR] uMsg = WM_SHELLHOOKMESSAGE [COLOR=#0000FF]Then [/COLOR] [COLOR=#0000FF]Select[/COLOR] [COLOR=#0000FF]Case[/COLOR] wParam [COLOR=#0000FF]Case[/COLOR] HSHELL_WINDOWDESTROYED [COLOR=#007A00]' ShowText "Window Destroyed: " & Hex(lParam) [/COLOR] [COLOR=#0000FF]Case[/COLOR] HSHELL_WINDOWCREATED [COLOR=#007A00]'ShowText "Window Created: " & Hex(lParam) [/COLOR] [COLOR=#0000FF]Case[/COLOR] HSHELL_WINDOWACTIVATED [COLOR=#007A00]'lParam contains the handle of the window activated [/COLOR] [COLOR=#0000FF]If[/COLOR] lParam > 0 [COLOR=#0000FF]And[/COLOR] lParam <> Me.hwnd [COLOR=#0000FF]Then [/COLOR] [COLOR=#007A00]'save handle, threadID and Thread info [/COLOR] LastActivehWnd = lParam lastThreadID = GetWindowThreadProcessId(lParam, lastProcessID) [COLOR=#007A00]'now get thread info [/COLOR] LastThreadInfo.cbSize = LenB(LastThreadInfo) GetGUIThreadInfo lastThreadID, LastThreadInfo [COLOR=#0000FF]End[/COLOR] [COLOR=#0000FF]If [/COLOR] [COLOR=#0000FF]End[/COLOR] [COLOR=#0000FF]Select [/COLOR] [COLOR=#0000FF]End[/COLOR] [COLOR=#0000FF]If End[/COLOR] [COLOR=#0000FF]Sub[/COLOR][/FONT]Attached is a project that illustrates the technique.VB Code:
[FONT=Courier New][COLOR=#0000FF]Private[/COLOR] [COLOR=#0000FF]Sub[/COLOR] NotifyIcon_Notify(Msg [COLOR=#0000FF]As[/COLOR] [COLOR=#0000FF]Long[/COLOR]) [COLOR=#0000FF]Dim[/COLOR] strText [COLOR=#0000FF]As[/COLOR] [COLOR=#0000FF]String [/COLOR] [COLOR=#0000FF]Select[/COLOR] [COLOR=#0000FF]Case[/COLOR] Msg [COLOR=#0000FF]Case[/COLOR] WM_RBUTTONDOWN PopupMenu mnMain [COLOR=#0000FF]Case[/COLOR] WM_LBUTTONDOWN [COLOR=#007A00]'get threadID of LAST ACTIVE WINDOW [/COLOR] [COLOR=#0000FF]If[/COLOR] LastActivehWnd > 0 [COLOR=#0000FF]Then [/COLOR] [COLOR=#007A00]'prevent changes to data while we work [/COLOR] PauseMessages = True [COLOR=#0000FF]With[/COLOR] LastThreadInfo [COLOR=#007A00]'set focus back to previous window [/COLOR] SetForegroundWindow LastActivehWnd DoEvents Clipboard.Clear [COLOR=#0000FF]If[/COLOR] .hwndCaret > 0 [COLOR=#0000FF]Then [/COLOR] [COLOR=#0000FF]Call[/COLOR] PostMessage(.hwndCaret, WM_COPY, 0&, 0&) hWndEdit = .hwndCaret ElseIf .hwndFocus > 0 [COLOR=#0000FF]Then [/COLOR] [COLOR=#0000FF]Call[/COLOR] PostMessage(.hwndFocus, WM_COPY, 0&, 0&) hWndEdit = .hwndFocus ElseIf .hwndActive > 0 [COLOR=#0000FF]Then [/COLOR] [COLOR=#0000FF]Call[/COLOR] PostMessage(.hwndActive, WM_COPY, 0&, 0&) hWndEdit = .hwndActive [COLOR=#0000FF]Else [/COLOR] PauseMessages = False [COLOR=#0000FF]Exit[/COLOR] [COLOR=#0000FF]Sub [/COLOR] [COLOR=#0000FF]End[/COLOR] [COLOR=#0000FF]If [/COLOR] [COLOR=#0000FF]End[/COLOR] [COLOR=#0000FF]With [/COLOR] DoEvents [COLOR=#007A00]'Check for valid Pasted data [/COLOR] [COLOR=#0000FF]If[/COLOR] [COLOR=#0000FF]Not[/COLOR] Clipboard.GetFormat(vbCFText) [COLOR=#0000FF]Then [/COLOR] Label1 = "[COLOR=#7A0000]No Data from [/COLOR]" & Hex(hWndEdit) [COLOR=#0000FF]Else [/COLOR] Label1 = Clipboard.GetText [COLOR=#0000FF]If[/COLOR] CheckSpelling [COLOR=#0000FF]Then [/COLOR] SetForegroundWindow LastActivehWnd PostMessage hWndEdit, WM_PASTE, 0&, 0& [COLOR=#0000FF]End[/COLOR] [COLOR=#0000FF]If [/COLOR] [COLOR=#0000FF]End[/COLOR] [COLOR=#0000FF]If [/COLOR] PauseMessages = False [COLOR=#0000FF]End[/COLOR] [COLOR=#0000FF]If [/COLOR] [COLOR=#0000FF]End[/COLOR] [COLOR=#0000FF]Select End[/COLOR] [COLOR=#0000FF]Sub[/COLOR][/FONT]
ok so if u have the text that u need to copy allready selected u can copy it to clipboard with sendkeys.send("{^c}') then u can retrieve it with clipboard.gettext how this is what you are looking for