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


LVL 2
Rick BeckerRetired-SortaAsked:
Who is Participating?
I wear a lot of hats...

"The solutions and answers provided on Experts Exchange have been extremely helpful to me over the last few years. I wear a lot of hats - Developer, Database Administrator, Help Desk, etc., so I know a lot of things but not a lot about one thing. Experts Exchange gives me answers from people who do know a lot about one thing, in a easy to use platform." -Todd S.

nffvrxqgrcfqvvcCommented:
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
Rick BeckerRetired-SortaAuthor Commented:
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

nffvrxqgrcfqvvcCommented:
In that case how about using a keyboard hook approach? When you press a combination of ALT + S then the the data will be sent to the window with focus...For example I belive the easiest way to fix this problem would to simply hide your form ..so you can basically split your application into two sections if you would like..what I mean by that is when the user wants to use the sendkeys aspect of it tell them that the form will be hidden while you are using this feature...and then when they are done tell them to press some shortcut key to show the form again just like they use the shortcut key to use sendkeys...example below...This is all I can think of, The problem seems to be that sendkeys is sending to your application so if your application is not visible then that would fix the problem however using a keyboard hook can be  more user friendly then a button of some sort.

ALT + V = form will show
ALT + H = form hides
ALT + END = close program
ALT + S = sendkeys to window with cursor..

Like I mentioned before make sure the when the user wants to use sendkeys that you inform them to press ALT + H before they continue.
If you want to test it then press ALT + H then focus on some window..ie. internet explorer or start/run dialoge.  Press ALT + S typ what you want to send to the window and press OK.

'ADD the following code to a module
'********************************

Option Explicit
Declare Sub Sleep Lib "kernel32" (ByVal dwMilliseconds As Long)
Declare Function SetTimer Lib "user32" (ByVal hwnd As Long, ByVal nIDEvent As Long, ByVal uElapse As Long, ByVal lpTimerFunc As Long) As Long
Declare Function KillTimer Lib "user32" (ByVal hwnd As Long, ByVal nIDEvent As Long) As Long
Declare Function GetAsyncKeyState Lib "user32" (ByVal vKey As Long) As Integer
Global gblAction As String, gblRemember As String
Public myInput As String
Function GetPressedKey() As String
If GetAsyncKeyState(vbKeyMenu) And CBool(GetAsyncKeyState(vbKeyS)) Then
    GetPressedKey = "sendkeys"
    Else
If GetAsyncKeyState(vbKeyMenu) And CBool(GetAsyncKeyState(vbKeyEnd)) Then
    GetPressedKey = "terminate_form"
    Else
If GetAsyncKeyState(vbKeyMenu) And CBool(GetAsyncKeyState(vbKeyV)) Then
    GetPressedKey = "show form"
    Else
    If GetAsyncKeyState(vbKeyMenu) And CBool(GetAsyncKeyState(vbKeyH)) Then
    GetPressedKey = "hide form"
    End If
    End If
    End If
    End If
End Function

Sub TimerProc(ByVal hwnd As Long, ByVal nIDEvent As Long, ByVal uElapse As Long, ByVal lpTimerFunc As Long)
    gblRemember = GetPressedKey
    If gblRemember <> gblAction Then
        gblAction = gblRemember
        If gblAction = vbNullString Then Exit Sub
        Select Case gblAction
       
        Case "sendkeys"
        'ALT + S - sendkeys to window with cursor.
   
        myInput = InputBox("What would you like to send to this window", "Sendkeys", "Hi, How are you today?")
        If myInput = vbNullString Then Exit Sub
            Sleep 1200 'Very important...
            DoEvents
            SendKeys myInput
           
        Case "show form"
        'ALT + V = show the form
        Form1.Visible = True
       
        Case "hide form"
        'ALT + H = Hide the form
        Form1.Visible = False
       
        Case "terminate_form"
         'ALT + End - Exit out of application
        MsgBox "Application exited..", vbSystemModal
        Unload Form1
        End Select
    End If
End Sub




'********************************
'ADD the following code to a FORM
'********************************


Option Explicit

Private Sub Form_Load()
If App.PrevInstance = True Then End
App.TaskVisible = False
SetTimer Me.hwnd, 0, 1, AddressOf TimerProc
End Sub

Private Sub Form_Unload(Cancel As Integer)
KillTimer Me.hwnd, 0
End Sub
Your Guide to Achieving IT Business Success

The IT Service Excellence Tool Kit has best practices to keep your clients happy and business booming. Inside, you’ll find everything you need to increase client satisfaction and retention, become more competitive, and increase your overall success.

nffvrxqgrcfqvvcCommented:
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.
nffvrxqgrcfqvvcCommented:
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
Rick BeckerRetired-SortaAuthor Commented:
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
nffvrxqgrcfqvvcCommented:
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.
nffvrxqgrcfqvvcCommented:
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
Rick BeckerRetired-SortaAuthor Commented:
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
BurbbleCommented:
I found this searching through MSDN:

WS_EX_NOACTIVATE
Windows 2000/XP: A top-level window created with this style does not become the foreground window when the user clicks it. The system does not bring this window to the foreground when the user minimizes or closes the foreground window.
To activate the window, use the SetActiveWindow or SetForegroundWindow function.

The window does not appear on the taskbar by default. To force the window to appear on the taskbar, use the WS_EX_APPWINDOW style.


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

Experts Exchange Solution brought to you by

Your issues matter to us.

Facing a tech roadblock? Get the help and guidance you need from experienced professionals who care. Ask your question anytime, anywhere, with no hassle.

Start your 7-day free trial
Rick BeckerRetired-SortaAuthor Commented:
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
BurbbleCommented:
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.
Rick BeckerRetired-SortaAuthor Commented:
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
Rick BeckerRetired-SortaAuthor Commented:
Hi All,

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

rrbecker
Rick BeckerRetired-SortaAuthor Commented:
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
Rick BeckerRetired-SortaAuthor Commented:
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

BurbbleCommented:
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

BurbbleCommented:
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
Rick BeckerRetired-SortaAuthor Commented:
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
Rick BeckerRetired-SortaAuthor Commented:
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
BurbbleCommented:
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 :)
Rick BeckerRetired-SortaAuthor Commented:
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

It's more than this solution.Get answers and train to solve all your tech problems - anytime, anywhere.Try it for free Edge Out The Competitionfor your dream job with proven skills and certifications.Get started today Stand Outas the employee with proven skills.Start learning today for free Move Your Career Forwardwith certification training in the latest technologies.Start your trial today
Visual Basic Classic

From novice to tech pro — start learning today.