rodrigocfd/windigo

Can't get WM_KEYDOWN event when button focused

Hoto-Cocoa opened this issue · 8 comments

Hi. I trying to create a app that has some buttons with keyboard shortcuts.

I can't get WM_KEYDOWN event when button focused. As windigo makes first child as focused this meaning I can't use keyboard shortcuts at any time. (WindowsRawMain.go#92L)

I tried comment out that line and works normally but not after clicking or focusing any button.

Here is some reproducible code:

package main

import (
	"fmt"

	"github.com/rodrigocfd/windigo/ui"
	"github.com/rodrigocfd/windigo/ui/wm"
)

func main() {
	window := ui.NewWindowMain(
		ui.WindowMainOpts(),
	)

	ui.NewButton(window,
		ui.ButtonOpts().
			Text("Button"),
	)

	window.On().WmKeyDown(func(p wm.Key) {
		fmt.Println(p.ScanCode())
	})

	window.RunAsMain()
}

After removing button, WM_KEYDOWN works perfectly.

How can I resolve this?

This will never work. Once you have any control in the window, it will take the focus and process the keydown messages itself, taking that power away from the window.

Now the solution depends of what you're trying to do. If you want to handle keyboard shortcuts, you must create an accelerator table; if you want to handle arrow keys (or something like that), this are more complicated, you'll have to create a window hook or subclass all controls.

My keyboard shortcut has two types: one char type (such as "d", "i"), word type (like Chrome's "thisisunsafe"). Can you recommend method to implement this? I tried subclass to get WM_KEYDOWN event on buttons, but I don't know that How can I use window.RunUiThread().

My keyboard shortcut has two types: one char type (such as "d", "i"), word type (like Chrome's "thisisunsafe"). Can you recommend method to implement this? I tried subclass to get WM_KEYDOWN event on buttons, but I don't know that

Well, that's hard to implement. I've never done anything like that, but it probably boils down to having a FIFO-like structure to store the pressed keys, which keeps being emptied after a specific timelapse. And to capture the keys, you'd have to create a window hook or maybe write the window loop yourself.

How can I use window.RunUiThread().

Just call it from another thread. It exists to prevent deadlock when updating from another thread.

Now I created window hook, It works as expected when not using goroutine. However If I use goroutine and use RunUiThread, Just nothing happened. Here are what I tried.

Working code:

kbHook := KeyboardHook{}

kbHook.Install()

defer kbHook.Uninstall()

window.RunAsMain()

Not working 1:

kbHook := KeyboardHook{}

kbHook.Install()

defer kbHook.Uninstall()

go window.RunAsMain()

Not working 2:

kbHook := KeyboardHook{}

window.RunUiThread(func() {
    kbHook.Install()
})

defer kbHook.Uninstall()

go window.RunAsMain()

I tried changing order of code, run RunUiThread with goroutine, but no luck.

What's wrong with my code?

Thanks for assistant me and sorry for was opening question issue.

This is wrong:

go window.RunAsMain()

By doing this you are launching a separated goroutine and calling a GUI function. If the goroutine is switched to another processor thread, you're likely to deadlock your window, and it would freeze and crash.

You should call RunAsMain() only, and just only as shown in the README example.

This is wrong too, for the same reasons:

window.RunUiThread(func() {
    kbHook.Install()
})

If you want to install the hook as soon as the window shows up, you should call it within WM_CREATE:

me.wnd.On().WmCreate(func(_ wm.Create) int {

    // do stuff after the window is created

    return 0
})

...or WM_INITDIALOG, if you're using a dialog resource:

me.wnd.On().WmInitDialog(func(_ wm.InitDialog) bool {

    // do stuff after the dialog resource is created

    return true
})

You are saying window.RunAsMain() should not called with goroutine, right? My code has GUI and HTTP server at same time, then should I run HTTP server as goroutine, and GUI as main thread? or this is not intented?

Anyway I will fix my code then, Thanks for the let me know.

You are saying window.RunAsMain() should not called with goroutine, right?

Exactly.

My code has GUI and HTTP server at same time, then should I run HTTP server as goroutine, and GUI as main thread? or this is not intented?

The GUI must run in the main thread, always. The HTTP server should be launched in a separated goroutine. For example, if the server is launched immediately after the program runs, your code will look like this:

me.wnd.On().WmCreate(func(_ wm.Create) int {

    go launchHttpServer()

    return 0
})

Huge thanks for the support. My application was seems to have deadlock, but now seems to resolve. My window hook works perfectly, now I can get keydown event even button focused.

Thanks a lot again! Now my issue is resolved, I'm closing this issue. Please let me know If you want to say something.