Intel® Fortran Compiler
Build applications that can scale for the future with optimized code designed for Intel® Xeon® and compatible processors.

QuickWin and keyboard accelerators

NotThatItMatters
Beginner
2,141 Views

I have constructed a QuickWin application in Visual Fortran. The application uses its own menu system, but retains many of the standard features of QuickWin (e.g., status bar).

I am trying to write my own shutdown for the app. I cannot use the WINEXIT call or the standard close icon on the caption bar because it will mean auxiliary files are not closed properly. I am using the WINSTATE function to Pause/Resume execution. I have noted that using this function activates Ctrl-S/Ctrl-Q for the menu item in theapp.

1) How might I attach Ctrl-C (or others) to my menu commands? In the Windows API example of "Creating a Run-Time Accelerator Table", the example always has a handle for the existing accelerator table which is revised and then (automatically) reloaded. How can I obtain the handle to the accelerator table of QuickWin?

2) How do I capture the mouse event click of theclose icon on the caption bar for a safe shutdown?

Slippery

0 Kudos
15 Replies
Jugoslav_Dujic
Valued Contributor II
2,141 Views
JIMB@GEMINISI.COM:

1) How might I attach Ctrl-C (or others) to my menu commands? In the Windows API example of "Creating a Run-Time Accelerator Table", the example always has a handle for the existing accelerator table which is revised and then (automatically) reloaded. How can I obtain the handle to the accelerator table of QuickWin?

Yes, classic accelerators are out in QuickWin, as you don't have a place to put TranslateAccelerator into. Instead, use a keyboard hook, and just call appropriate menu handler routine when you detect the appropriate key combination. See this old thread for a sample.

To indicate the shortcut in the menu, separate it with a tab ("t") character in menu title. For example:

"&Open	Ctrl+O"

JIMB@GEMINISI.COM:

2) How do I capture the mouse event click of theclose icon on the caption bar for a safe shutdown?



You have to subclass the frame window (SetWindowLong(GWL_WNDPROC)) and catch the WM_CLOSE. See this thread with a sample.
0 Kudos
NotThatItMatters
Beginner
2,141 Views

Thank you for the info. I am still having some trouble capturing keyboard events. Consider the attached code. When run, I get a 'valid' keyboard hook value, and yet the KeyboardProc produces no output.

SUBROUTINE

CREATE_ACCELERATOR_TABLE()

USE kernel32

INTEGER (KIND = HANDLE) ABOUTMENU_HANDLE, FILEMENU_HANDLE, MENU_HANDLE, RESTARTMENU_HANDLE

! TYPE (T_ACCEL), DIMENSION(3) :: ACCEL_ITEM

! Find the handle to the frame window menu bar

WIN_HANDLE = GETHWNDQQ(QWIN$FRAMEWINDOW)

CHILD_HANDLE = GETHWNDQQ(0_4)

MENU_HANDLE = GETMENU(WIN_HANDLE)

FILEMENU_HANDLE = GETSUBMENU(MENU_HANDLE, 1_SINT)

IDSTOP = GETMENUITEMID(FILEMENU_HANDLE, 1_SINT)

! ACCEL_ITEM(1)%fVirt = FCONTROL

! ACCEL_ITEM(1)%key = IACHAR('C')

! ACCEL_ITEM(1)%cmd = IDSTOP

RESTARTMENU_HANDLE = GETSUBMENU(MENU_HANDLE, 3_SINT)

IDAPPEND = GETMENUITEMID(RESTARTMENU_HANDLE, 0_SINT)

! ACCEL_ITEM(2)%fVirt = FCONTROL

! ACCEL_ITEM(2)%key = IACHAR('A')

! ACCEL_ITEM(2)%cmd = IDAPPEND

IDOVERWRITE = GETMENUITEMID(RESTARTMENU_HANDLE, 1_SINT)

! ACCEL_ITEM(3)%fVirt = FCONTROL

! ACCEL_ITEM(3)%key = IACHAR('O')

! ACCEL_ITEM(3)%cmd = IDOVERWRITE

ABOUTMENU_HANDLE = GETSUBMENU(MENU_HANDLE, 4_SINT)

WRITE

(UNIT = 6, FMT = '(A,1X,Z8.8)') 'Window =', WIN_HANDLE, 'Menu =', MENU_HANDLE, &

'File =', FILEMENU_HANDLE, 'Restart =', RESTARTMENU_HANDLE, 'About =', ABOUTMENU_HANDLE, &

'Child =', CHILD_HANDLE

WRITE

(UNIT = 6, FMT = '(A,1X,Z8.8)') 'STOP =', IDSTOP, 'APPEND =', IDAPPEND, 'OVERWRITE=', IDOVERWRITE

! ACCEL_TABLE_HANDLE = CREATEACCELERATORTABLE(ACCEL_ITEM(1), 3_SINT)

!WRITE(UNIT = 6, FMT = '(A,1X,Z8.8)') 'ACCEL_TABLE_HANDLE =', ACCEL_TABLE_HANDLE

!Of course, explicit interface for KeyboardProc here.

hKbHook = SetWindowsHookEx(WH_KEYBOARD,

LOC(KeyboardProc), NULL, GetCurrentThreadId())

WRITE

(UNIT = 6, FMT = '(A,1X,Z8.8)') 'Handle to the hook procedure =', hKbHook

END SUBROUTINE

CREATE_ACCELERATOR_TABLE

!==============================================================

INTEGER

(KIND = 4) FUNCTION KeyboardProc(nCode, wParam, lParam)

!DEC$ATTRIBUTES STDCALL :: KeyboardProc

INTEGER (KIND = SINT), INTENT(IN) :: nCode

INTEGER (KIND = UINT_ PTR), INTENT(IN) :: wParam

INTEGER (KIND = LONG_PTR), INTENT(IN) :: lParam

INTEGER (KIND = HANDLE) hFocus

INTEGER iEvent, nKey

INTEGER (KIND = HANDLE) :: hFrame, hChild

hFrame = WIN_HANDLE

hChild = CHILD_HANDLE

WRITE

(UNIT = 6, FMT = '(A,1X,Z8.8)') 'nCode =', nCode

IF (nCode >= 0) THEN

!Bit 31 specifies transition state of the key

nKey = wParam

IF (IAND(lParam, ISHFT(1, 31)) /= 0) THEN

iEvent = WM_KEYUP

ELSE

iEvent = WM_KEYDOWN

END IF

hFocus = GetFocus()

WRITE

(UNIT = 6, FMT = '(A,1X,Z8.8)') 'hFocus =', hFocus

IF ((hFocus == hFrame .OR. hFocus == hChild) .AND. iEvent == WM_KEYUP) THEN

SELECT CASE (wParam)

CASE DEFAULT

WRITE

(UNIT = 6, FMT = '(''KeyboardProc wParam = '',Z8.8)') nKey

END SELECT

END IF

END IF

KeyboardProc = CallNextHookEx(hKbHook, nCode, wParam, lParam)

END FUNCTION

KeyboardProc

If I run the code with the call

hKbHook = SetWindowsHookEx(WH_KEYBOARD, LOC(KeyboardProc), NULL, 0)

then hKbHook is returned as NULL. Note that all variables undeclared here (including hKbHook)are declared in a MODULE and that 'USE user32' is declared in the module.

Slippery

0 Kudos
Jugoslav_Dujic
Valued Contributor II
2,141 Views
Try calling GetLastError immediately after SetWindowsHookEx. Offhand, I suppose the problem is that SetWindowsHookEx is called from the wrong thread -- the QuickWin PROGRAM and its subprograms run from a non-GUI thread (no message loop) and SWHE probably fails because of that.

If I'm right (you can verify that by temporarily moving the call to SetWindowsHookEx into any menu callback and then just check if it works), the cure is to subclass the frame window and call SWHE from there. Then, just define your own message and send it from the PROGRAM to the main window, like:
INTEGER, PARAMETER:: WM_INSTALLHOOK = WM_APP+1
lpOldWndProc = SetWindowLong(hFrame, &
 GWL_WNDPROC, LOC(MainWndProc))
...
iRet = SendMessage(hFrame, WM_INSTALLHOOK, 0, 0)
...
INTEGER FUNCTION MainWndProc(...)
...
SELECT CASE(Msg)
CASE (WM_INSTALLHOOK)
 hKbHook = SetWindowsHookEx(WH_KEYBOARD, LOC(KeyboardProc), NULL, 0)
MainWndProc = 0
CASE(...)
0 Kudos
NotThatItMatters
Beginner
2,141 Views

I am a little confused as to where each of the calls should lie.

At present, I have a PROGRAM which calls a MODULE which has all the windows processing. Ultimately, the PROGRAM will become a SUBROUTINE for a larger PROGRAM.

In an effort to keep the Windows processing separate from the PROGRAM, I have tried to make all explicit Windows calls a part of the MODULE. Thus, I built FUNCTION MainWndProc andKeyboardProc as part of the MODULE. I also created a SUBROUTINE called SENDMESSAGETOFRAME which does the SendMessage FUNCTION call to MainWndProc toinitialize the Windows hook.

When this is done, the program hangs on the SendMessage FUNCTION. I am assuming this means that the MainWndProc is not properly handling the message loop.

My MainWndProc only does explicit processing of the WM_INSTALLHOOK message, in which case it returns 0. In the case of any other message, it returns DefWindowProc. Should it instead be returning the call to lpOldWndProc? In any case, why should the SendMessage FUNCTION cause the system to hang?

I am getting thoroughly confused.

Slippery

0 Kudos
Jugoslav_Dujic
Valued Contributor II
2,141 Views
JIMB@GEMINISI.COM:

My MainWndProc only does explicit processing of the WM_INSTALLHOOK message, in which case it returns 0. In the case of any other message, it returns DefWindowProc. Should it instead be returning the call to lpOldWndProc? In any case, why should the SendMessage FUNCTION cause the system to hang?


Yes, it should return CallWindowProc(lpOldWndProc...) indeed. But, are you sure it is ever entered? Place a breakpoint in CASE(WM_INSTALLHOOK) and check it out. Are you sure it doesn't hang in the hook function instead? I can't tell offhand, but I can imagine that it can screw up the application badly; make sure you properly call CallNextHookEx from the hook function. (and don't forget !DEC$ATTRIBUTES STDCALL for the wndproc and the hookproc)

0 Kudos
NotThatItMatters
Beginner
2,141 Views

Yes, I forgot the DEC$ATTRIBUTES STDCALL for the MainWndProc.Now theprogram no longer hangs.

However, I am now back to the SAME problem I had earlier. Here is the code for the MainWndProc.

INTEGER

(KIND = LRESULT) FUNCTION MainWndProc(hwnd, uMsg, wParam, lParam)

!DEC$ATTRIBUTES STDCALL :: MainWndProc

INTEGER (KIND = HANDLE), INTENT(IN) :: hwnd

INTEGER (KIND = UINT), INTENT(IN) :: uMsg

INTEGER (KIND = UINT_PTR), INTENT(IN) :: wParam

INTEGER (KIND = LONG_PTR), INTENT(IN) :: lParam

WRITE

(UNIT = 6, FMT = '(''MainWndProc entered '',I0)') uMsg

SELECT CASE (uMsg)

CASE (WM_INSTALLHOOK)

WRITE

(UNIT = 6, FMT = '(''CALL SetWindowsHookEx'')')

hKbHook = SetWindowsHookEx(WH_KEYBOARD,

LOC(KeyboardProc), NULL, 0_DWORD)

WRITE

(UNIT = 6, FMT = '(''Keyboard hook '',Z8.8)') hKbHook

MainWndProc = 0_LRESULT

CASE DEFAULT

MainWndProc = CallWindowProc(lpOldWndProc, hwnd, uMsg, wParam, lParam)

END SELECT

END FUNCTION

MainWndProc

And here is the resulting output when this routine is called with the message WM_INSTALLHOOK:

MainWndProc entered 32769
CALL SetWindowsHookEx
Keyboard hook 00000000

So the call is not working.

Slippery

0 Kudos
Jugoslav_Dujic
Valued Contributor II
2,141 Views
The last argument of SetWindowsHookEx should be GetCurrentThreadId(). A hook of any type can be either system-wide or only for current thread: however, in the former case (dwThreadId=0), the hook procedure must be in a Dll. Since you want to monitor only your application's thread, you should specify the thread ID.
0 Kudos
NotThatItMatters
Beginner
2,141 Views

Thank you for the help. I am now capturing Ctrl+* keyboard events.

I had not realized that SetWindowsHookEx was only capable of capturing Ctrl+* keyboard events. This is what I need to do, so it is not a problem.

I have also written a shutdown function which unhooks the Window using a call to UnhookWindowsHookEx.

Right now, my App hangs on completion of the code. That is, it runs to completion, posts a message boxthat 'Program Terminated with exit code 0 Exit Window?' but the message box entry has been disabled. If I comment out the code to UnhookWindowsHookEx, it does not matter. The program still hangs.

Slippery

0 Kudos
Steven_L_Intel1
Employee
2,141 Views
Perhaps you want to call SETEXITQQ(QWIN$EXITNOPERSIST) to disable that messgae box.
0 Kudos
NotThatItMatters
Beginner
2,141 Views

Yes, that has been done. The App is now hanging without the message box.

Slippery

0 Kudos
Jugoslav_Dujic
Valued Contributor II
2,141 Views
JIMB@GEMINISI.COM:
I had not realized that SetWindowsHookEx was only capable of capturing Ctrl+* keyboard events. This is what I need to do, so it is not a problem.

Um, it isn't supposed to be so. You should get all the keyboard messages (actually, all the WM_KEY** messages before they reach the message loop).

Now, about the hanging: don't forget to call CallNextHookEx from your KeyboardProc (see the Return Value section); I kind of recall the problems when I forgot to do it (although it was some 5-6 years ago).

The other issue is that you come to get the "Program Terminated with exit code..." message at all. Normally, a QuickWin PROGRAM should end in an infinite loop, like:

DO
CALL SLEEPQQ(0)
END DO

In this way, (and thanks to the two-threaded structure), the program will sit idle in the loop and all the events will happen in the "primary" thread (the one driving menus, your WndProc, KeyboardProc etc.), and the program can be closed only through the button. If you don't provide the infinite loop, the epilog code at END PROGRAM will by default try to issue the said message box and request the other thread to terminate as well (not sure how, maybe by sending WM_CLOSE or PostQuitMessage). But I suspect the hanging is due to a CallNextHookEx problem.

0 Kudos
NotThatItMatters
Beginner
2,141 Views

As far as getting keyboard messages, you are correct. I do get all input from the keyboard. So how might I capture Ctrl+C as opposed to C. Do I need to do something like the following:

INTEGER, SAVE :: iControl = 0
SELECT CASE (wParam)
CASE (VK_CONTROL)
IF (iEvent == WM_KEYDOWN) THEN
iControl = 1
ELSE
iControl = 0
END IF
CASE (IACHAR('C'))
IF (iControl == 1) THEN
! Do what you would do for Ctrl+C
END IF
! And similarly for other control keys
END SELECT

Now, as far as the hanging at the end goes, yes, I have been calling CallNextHookEx at the end of my KeyboardProc. And, no, it does not appear to be hanging today. I am still getting some strange response from the interface though. The App is closing itself down. I am guessing it is taking 5-10 seconds while it pauses, blinks, and then finally closes. Prior to using the hooks, it would close quickly.

In the larger scheme of things, the existing App we have closes itself nicely, without hanging, infinite loops, or other items. The real problem with the existing App lies in the X button and the Ctrl+C, both of which close the App without tidying up. Without the tidying up, the App produces unusable output.

Slippery

0 Kudos
Jugoslav_Dujic
Valued Contributor II
2,141 Views
Um. It appears we went astray... if the primary thing you want to do is to handle Ctrl+C and button, all you need to do is to place the following in the subclassed FrameWndProc:
CASE (WM_CLOSE)
 SELECT CASE(MessageBox(hFrame,"Save changes to Foo?"C, &
 "Close"C,MB_YESNOCANCEL))
 CASE (IDYES)
 CALL SaveDocument()
 FrameWndProc=CallWindowProc(lpfnOldFrameProc,hWnd,Msg,wParam,lParam)
 CASE (IDNO)
 FrameWndProc=CallWindowProc(lpfnOldFrameProc,hWnd,Msg,wParam,lParam)
 CASE (IDCANCEL)
 FrameWndProc=0
 END SELECT
This works fine for both Ctrl+C and ; you don't need a keyboard hook at all.

I still don't know what went wrong with the hook, but you can use it only to emulate "other" accelerators.

JIMB@GEMINISI.COM:

As far as getting keyboard messages, you are correct. I do get all input from the keyboard. So how might I capture Ctrl+C as opposed to C.



if (GetKeyState(VK_CONTROL) < 0) then

will tell you if the Ctrl is pressed.



0 Kudos
NotThatItMatters
Beginner
2,141 Views

I have eliminated the Hook code and replaced it with message pump code for my child window, which is the one evidently which begins with keyboard focus. Here is the code:

INTEGER (KIND = LRESULT) FUNCTION ChildWndProc(hwnd, uMsg, wParam, lParam)
!DEC$ATTRIBUTES STDCALL :: ChildWndProc
INTEGER (KIND = HANDLE), INTENT(IN) :: hwnd
INTEGER (KIND = UINT), INTENT(IN) :: uMsg
INTEGER (KIND = UINT_PTR), INTENT(IN) :: wParam
INTEGER (KIND = LONG_PTR), INTENT(IN) :: lParam
INTEGER (KIND = SHORT) iAlt, iControl, iShift
 Frame_Window_Message: SELECT CASE (uMsg)
CASE (WM_CHAR)
iControl = GetKeyState(VK_CONTROL)
iShift = GetKeyState(VK_SHIFT)
iAlt = GetKeyState(VK_MENU)
IF (IAND(iControl, KF_UP) == KF_UP .AND. IAND(iAlt, KF_UP) /= KF_UP .AND. IAND(iShift, KF_UP) /= KF_UP) THEN
! Control key is down
Character_Key: SELECT CASE (wParam)
CASE (Z'0001') ! Ctrl+A
CALL APPRESTART(.FALSE.)
CASE (Z'0003') ! Ctrl+C
CALL MERLINSTOP(.TRUE.)
CASE (Z'000F') ! Ctrl+O
CALL OVRRESTART(.FALSE.)
END SELECT Character_Key
END IF
CASE (WM_CLOSE)
CALL CLEAN_UP_FILES(...)
END SELECT Frame_Window_Message
ChildWndProc = CallWindowProc(lpOldChildWndProc, hwnd, uMsg, wParam, lParam)
END FUNCTION ChildWndProc

Ifany WM_CHAR messages get handled by this WindowProc, the application hangs. Also, the WM_CLOSE message isnever posted to this window. Should I have the WM_CLOSE message logic in a WindowProc for the Frame? How is the message pump interrupted by the WM_CHAR message handlers? Do I need to process all WM_CHAR messages here orwould the CallWindowProc call at the end handle some?

Slippery

0 Kudos
NotThatItMatters
Beginner
2,141 Views

One other item to note in the application hanging. On rare occasions, which I have not been able to reliably duplicate, the running of my App will yield an Intel Fortran error message: "unresolved contention for Intel Fortran RTL global resource". What might be causing this?

Slippery

0 Kudos
Reply