Link to home
Start Free TrialLog in
Avatar of Rick Becker
Rick BeckerFlag for United States of America

asked on

Forms have 'Topmost' and 'Focus' problem but only with Windows 98

Greetings All,

I have a project that I have been working on for a while and have it mostly complete. (or so I thought) I do my development on a windows 2000 machine and have tested my program on  Windows XP. All works well there. However, I have now given the program to a few associates to 'test' and have found that my program does not function properly on Windows 98.

Here is what my program does:
(Sorry for the long lead in)

It acts like an onscreen keyboard, in as much as it sends character strings to other 'open' applications, (such as Notepad, Outlook Express, Microsoft Word.....) whenever a 'button' is clicked on my program. (I use sendkeys)

I have received help from EE members in getting my program to 'Stay On Top' (Topmost) without actually receiving focus, which is the desired state. This works for fine for  Windows 2000 and Windows XP, However, on Windows 98, whenever I click a button on  my program, the 'Focus' is moved, from the application I am working with ( notepad for example ) to my program, thus causing the desired string of characters to be sent elsewhere and Not to the application that 'Had' focus. (ie Notepad or Word or....)

If any one has any ideas about what is going on and how to fix it I would be very greatful. Upon request I will post code snippets that cause this functionality to work for Windows 2000 and Windows XP.

Thanks in advance for any help you can give,

rrbecker


Avatar of nffvrxqgrcfqvvc
nffvrxqgrcfqvvc

The most effiective way to manage this sort of behavior is to NOT use sendkeys. I would replace all sendkeys with SendMessageByString API as well as using Findwindow and FindwindowEx. The problem with sendkeys is it will not work consistantly.  The only time it is really good to use sendkeys is when you are automating some sort of clipboard(paste method) because it will send the copied text into the window with the cursor (or focus).

<<<<<< It acts like an onscreen keyboard, in as much as it sends character strings to other 'open' applications, (such as Notepad, Outlook Express, Microsoft Word.....)


Option Explicit
Public Declare Function FindWindow Lib "user32" Alias "FindWindowA" (ByVal lpClassName As String, ByVal lpWindowName As String) As Long
Public Declare Function FindWindowEx Lib "user32" Alias "FindWindowExA" (ByVal hWnd1 As Long, ByVal hWnd2 As Long, ByVal lpsz1 As String, ByVal lpsz2 As String) As Long
Public Declare Function SendMessageByString Lib "user32" Alias "SendMessageA" (ByVal hwnd As Long, ByVal wMsg As Long, ByVal wParam As Long, ByVal lParam As String) As Long
Public Const WM_SETTEXT = &HC

Public Sub Notepad(ByVal bstrString As String)
Dim Notepad As Long
Dim editx As Long
'Send string to notepad.
Notepad = FindWindow("notepad", vbNullString)
editx = FindWindowEx(Notepad, 0&, "edit", vbNullString)
Call SendMessageByString(editx, WM_SETTEXT, 0&, bstrString)

If editx = 0 Then MsgBox "Error: Cannot find window": Exit Sub
End Sub

Public Sub Outlook_Express(bstrTO As String, bstrCC As String, bstrSubject As String)
Dim athnote As Long
Dim oeenvelope As Long
Dim richeditw As Long
'Send text to TO: CC: Subject: of outlook express (NEW MESSAGE -Create mail)
athnote = FindWindow("ath_note", vbNullString)
oeenvelope = FindWindowEx(athnote, 0&, "oe_envelope", vbNullString)
richeditw = FindWindowEx(oeenvelope, 0&, "richedit20w", vbNullString)
richeditw = FindWindowEx(oeenvelope, richeditw, "richedit20w", vbNullString)
Call SendMessageByString(richeditw, WM_SETTEXT, 0&, bstrTO)
richeditw = FindWindowEx(oeenvelope, richeditw, "richedit20w", vbNullString)
Call SendMessageByString(richeditw, WM_SETTEXT, 0&, bstrCC)
richeditw = FindWindowEx(oeenvelope, richeditw, "richedit20w", vbNullString)
Call SendMessageByString(richeditw, WM_SETTEXT, 0&, bstrSubject)


If richeditw = 0 Then MsgBox "Error: Cannot find window": Exit Sub
End Sub
Avatar of Rick Becker

ASKER

Hi egl1044,

Thanks for your input,

<<<<The only time it is really good to use sendkeys is when you are automating some sort of clipboard(paste method) because it will send the copied text into the window with the cursor (or focus).>>>>>>>>

Actually this is 'Exactly' what I am doing and produces the 'Desired' results as long as I am on a Windows 2000 or XP machine. I need this approach to work on Windows 98  and ME.

<<<<<< It acts like an onscreen keyboard, in as much as it sends character strings to other 'open' applications, (such as Notepad, Outlook Express, Microsoft Word.....)

Notepad, Outlook Express, Microsoft Word.... are only examples of programs that receive input from the keyboard and is not a definitive list. I will Never know which application a user may be running and want to use my program with.

Again the expected results is to have a string of 'data' (text) echoed out to the application that currently has the 'Focus' and at the current cursor location (insertion point).

Thanks for the interest and reply. Maybe you have given me the answer and I just 'can't set it' yet. If I am missing something please help me understand your approach better.

Thanks again

rrbecker

SOLUTION
Avatar of nffvrxqgrcfqvvc
nffvrxqgrcfqvvc

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
I forgot to mention that you might want to change the shortcut keys I have in my example to some shortcut keys that no other program will use.
ALT + S is usually the file save or outlooks express send email shortcut..just a reminder.
The vbkey constants are located in the object browser.
Something like CTRL + ALT + somekey...would be OK.

If GetAsyncKeyState(vbKeyControl) And CBool(GetAsyncKeyState(vbKeyMenu) And CBool(GetAsyncKeyState(vbKeyS))) Then 'ctrl + alt + s = sendkeys
If GetAsyncKeyState(vbKeyControl) And CBool(GetAsyncKeyState(vbKeyMenu) And CBool(GetAsyncKeyState(vbKeyV))) Then 'ctrl + alt + v = show form
If GetAsyncKeyState(vbKeyControl) And CBool(GetAsyncKeyState(vbKeyMenu) And CBool(GetAsyncKeyState(vbKeyH))) Then 'ctrl + alt + h = hide form
Hi egl1044

I really appreciate all of the thought that you have put into this, however, please understand that this application works perfectly fine in Windows 2000 and XP.

<<<<<<<The problem seems to be that sendkeys is sending to your application>>>>>>>>
The above is a very acurate statement.

The problem that ocurrs in Windows 98 is that the application that 'currently' has focus (example: Notepad) looses focus to My application anytime a button on my application is Clicked even though I have code implemented which is supose to keep my application from receiving focus.

---------example snippet-------------------------------------------------
Option Explicit

Private Declare Function SetWindowPos Lib "user32" (ByVal hwnd As Long, ByVal hWndInsertAfter As Long, ByVal X As Long, Y, ByVal cx As Long, ByVal cy As Long, ByVal wFlags As Long) As Long

Private Declare Function SetWindowLong Lib "user32" Alias "SetWindowLongA" _
    (ByVal hwnd As Long, ByVal nIndex As Long, ByVal dwNewLong As Long) As Long



Private Const HWND_TOPMOST = -1
Private Const HWND_NOTOPMOST = -2
Private Const SWP_NOMOVE = &H2
Private Const SWP_NOSIZE = &H1
Private Const TOPMOST_FLAGS = SWP_NOMOVE Or SWP_NOSIZE

Private Const WS_EX_NOACTIVATE = &H8000000
Private Const GWL_EXSTYLE = (-20)

Private Sub Form_Load()

Call SetWindowPos(Me.hwnd, HWND_TOPMOST, 0, 0, 0, 0, TOPMOST_FLAGS)
Call SetWindowLong(Me.hwnd, GWL_EXSTYLE, GetWindowLong(Me.hwnd, GWL_EXSTYLE) Or WS_EX_NOACTIVATE)

End Sub
---------end example snippet-------------------------------------------------

Again thanks for your interest and help...

rrbecker
Thats why I mentioned to not use a button to send the text and hide the form and use a shortcut key instead. make sense? Hiding the form should fix the problem.
The only other way I know how to do this is to find the title of the window itself and set it as the forground window. However this doesn't take into account application that you don't know the title of..

Option Explicit
Private Declare Function FindWindow Lib "user32" Alias "FindWindowA" (ByVal lpClassName As String, ByVal lpWindowName As String) As Long
Private Declare Function SetForegroundWindow Lib "user32" (ByVal hwnd As Long) As Long
Private Declare Sub Sleep Lib "kernel32" (ByVal dwMilliseconds As Long)

Private Sub Command1_Click()
Dim lHandle As Long
    lHandle = FindWindow(vbNullString, "Untitled - Notepad")
    lHandle = SetForegroundWindow(lHandle)
    Sleep 500
    DoEvents
    If lHandle = 0 Then Exit Sub
    SendKeys "hello this is a test"
End Sub
Hi egl1044

Again I really appreciate all the work and thought that you have put into this, However I still don't think that you quite understand the issue.You have helped me before and I feel that maybe you can help if you had a chance to see the application in action.

If you like you can go to this link and watch a demo of my program in action. After which you may be able to able to say "Ok, now I get it... For Windows 98 you have to do the following...."

http://www.doolicity.com/demo1.html

Thanks for your interest,

rrbecker
ASKER CERTIFIED SOLUTION
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
Hi Burbble,

<<<<So it seems that this constant is only supported by 2000/XP... I don't know of an equivalent for 98/ME>>>

While this is not what I wanted to hear, It is probably the 'Correct' and 'True' anwser.

If there is a codeing 'workaround' that you know of, then I would like to hear about it.

Thanks for your input, If an idea comes to mind then let me know.

rrbecker
I will ponder it for a while and get back if I think of anything.

Otherwise, I think egl1044's idea of using shortcut keys is a good one.
Hi all,

I may have found a solution, I'm going to implement it and let you all know how it goes. If it works I'll post the fix.

rrbecker
Hi All,

Well, 'False Alarm'.... it didn't work, so I'm back to where I started.

rrbecker
Hi All,

I'm going to leave this open for a couple more days in case there is someone else that has an idea. If there are no more response by Tuesday then I'll PAQ this in and split the points for all of your efforts.

Thanks,

rrbecker
Hi All,

I have done some more research to see if I could find some other programs out there that do what I want and run under Windows 98/ME. All I am finding are applications that take and use the approach outlined by egl1044. So this tells me that the functions I need to do what I want do not exist (or at least not readily available) for Windows 98/ME.

My application works fine for Window 2000/XP so I will leave it at that. Thanks for all your help.

I am going to split points because of all the work and effort provided. I am going to 'Accept' Burbble  answer because this is the true answer to 'My' question.

egl1044 get points because for the most part this would be the standard apporach to an application as I had described it. (mine just takes a little different approach)

Thanks again to all,
rrbecker

This was the best I could come up with:

(Put a CommandButton and Timer on a form, set interval to 1)



Private Declare Function GetForegroundWindow Lib "user32" () As Long
Private Declare Function SetForegroundWindow Lib "user32" (ByVal hwnd As Long) As Long
Private Declare Function SetActiveWindow Lib "user32.dll" (ByVal hwnd As Long) As Long

Dim ActiveHwnd As Long

Private Sub Command1_Click()
'    SetActiveWindow ActiveHwnd
    SetForegroundWindow ActiveHwnd
    SendKeys "testing"
End Sub

Private Sub Timer1_Timer()
    If GetForegroundWindow() <> Me.hwnd Then ActiveHwnd = GetForegroundWindow()
End Sub

Slight modification to make it more reliable:


Private Declare Function GetForegroundWindow Lib "user32" () As Long
Private Declare Function SetForegroundWindow Lib "user32" (ByVal hwnd As Long) As Long
Private Declare Function SetActiveWindow Lib "user32.dll" (ByVal hwnd As Long) As Long

Dim ActiveHwnd As Long

Private Sub Command1_Click()
    If SetForegroundWindow(ActiveHwnd) <> 0 Then
        SendKeys "This is the example text." & vbNewLine
    End If
End Sub

Private Sub Timer1_Timer()
    Dim GetHwnd As Long
    GetHwnd = GetForegroundWindow()
    If GetHwnd <> Me.hwnd And GetHwnd <> 0 Then ActiveHwnd = GetHwnd
End Sub
Hi Burbble,

Thanks for the added input.

I have created the test script that you suggested and  what you have provided does work in the manner that I expect. (mostly....)

I have not tried it with Win98 but I will in a little bit. If I can get it to work with my application then I will want to find a way to give you some extra points for the 'Extra' effort.

I'll post here when I have some news...

rrbecker
Hi Burbble (Luke),

Get back to me, I made it work by combining a couple of different approaches, one of which was yours.

I want to award you some additional points so I am going to post an additional question, which you can answer and I'll accept. ( Let me know when you are ready to do this.)

Thanks a bunch,

rrbecker
Ooh that's great. How did you do it, if I may ask?

Also, I would recommend detecting the operating system, and using the different SendKeys methods depending on which OS is running. (You could also let the user switch between the different methods if using Windows 2000/XP)

Anyways... no additional points needed. I really just answered this question, and it's against EE rules to award more than 500 points for one question.

Glad to help :)
Hi Burbble,

Thanks for all your help.
In answer to both, your question and your addition suggestion:

I proivided a code snippet  that had the API's that I was and still am using. (You know, the ones you commented on)
---------example snippet-------------------------------------------------
Option Explicit

Private Declare Function SetWindowPos Lib "user32" (ByVal hwnd As Long, ByVal hWndInsertAfter As Long, ByVal X As Long, Y, ByVal cx As Long, ByVal cy As Long, ByVal wFlags As Long) As Long

Private Declare Function SetWindowLong Lib "user32" Alias "SetWindowLongA" _
    (ByVal hwnd As Long, ByVal nIndex As Long, ByVal dwNewLong As Long) As Long



Private Const HWND_TOPMOST = -1
Private Const HWND_NOTOPMOST = -2
Private Const SWP_NOMOVE = &H2
Private Const SWP_NOSIZE = &H1
Private Const TOPMOST_FLAGS = SWP_NOMOVE Or SWP_NOSIZE

Private Const WS_EX_NOACTIVATE = &H8000000
Private Const GWL_EXSTYLE = (-20)

Private Sub Form_Load()

Call SetWindowPos(Me.hwnd, HWND_TOPMOST, 0, 0, 0, 0, TOPMOST_FLAGS)
Call SetWindowLong(Me.hwnd, GWL_EXSTYLE, GetWindowLong(Me.hwnd, GWL_EXSTYLE) Or WS_EX_NOACTIVATE)

End Sub
---------end example snippet-------------------------------------------------

Anyway this chunck of code allows My application to stay on top (TopMost) as well as 'Not' receiving 'Focus' when a button is clicked. Of course this only worked for 'NT' based OS's.

Your Idea of including a 'Timer' and to switch back to the 'Active' application now gives my applicatio n the 'appearence' that it is 'Not' receiving focus even on Window 98 (havent tried ME yet but I'm going to.)

To facilitate your 'code', I just added the 'IF' statement:

".....If SetForegroundWindow(ActiveHwnd) <> 0 Then
        'This is where all my processing is done'
End If...."

To my 'BUtton Handler' which produces the desired result, no matter which OS I am or my users are running.

This is exactly what I was looking for, a Simple 'Do the Jop'  fix... Thanks.

Thanks again,

rrbecker