Sunday, July 12, 2009

wxWidgets - focus problems using multiple top level windows or frames

[Updated Jul 14, 2009 - this turned out to be a code bug on my part, at least partially. Further details in the comment section.]

As I'm sure you all know, I've been putting together a graphical/GUI client for the MUD Alter Aeon for the last few years. About 9 months ago, I switched to using the wxWidgets toolkit instead of QT, for reasons of executable size and licensing.

This switch has cost me on the order of 3 full months of development time trying to work around bugs in the wxWidgets ports on various platforms. I hit another one of these porting bugs/issues today; fortunately it only cost me about four hours, and amazingly enough, I found a workaround/answer that didn't involve rewriting the component from scratch.

Long story short: if you have multiple wxFrames or top-level windows in an MSVC build, the default is that you can't focus on any but the first one. My exact scenario:

1) App creates main window frame.

2) Main window frame later on dynamically creates a handful of new popup-style frames.

3) All of these new frames are immediately defocused and placed behind the main window frame. Attempts to raise or focus them are completely and utterly ignored. The FLOAT_ON_PARENT window style does nothing.

Oddly enough, minimizing the main window sometimes allowed the children to gain focus or 'disconnect' from the main window so the focus worked properly. It wasn't reliable, and it was never clear to me exactly why or how, but it never did what I ultimately wanted anyway.

There's no documentation on why any of this should happen, at least nowhere I could find. It all worked fine in the Linux port! After multiple hours of screwing around with it and reading unrelated documentation, I finally tried something I found in an obscure post, and it worked.

The solution:

1) Don't bother to hook the focus event. It doesn't do shit anyway.

2) Hook the Activate event in your new frame. You've probably never seen this before. Neither had I. I still don't know what exactly it's supposed to do.

3) In your Activate event handler, SetFocus(), then Skip() and pass the event down. The SetFocus() brings your new frame to the top and allows it to take focus in the future.

That's it. It's all of like three lines of code; I hope it doesn't waste nearly as much of your time as it did of mine.

[Note - I understand that the wx guys are doing this basically for free, and that they're up against a nasty set of ports to get everything working properly. Still, it's cost me a lot of time, and if I had known better I would probably have just paid for a proper QT license and found some other way to reduce the file size.]

2 comments:

VZ said...

[full disclosure: I'm a wx developer]

I suspect you must have done something strange with the frames parent pointers as what you describe definitely shouldn't happen by default. And FWIW I'm not sure if your workaround doesn't break something else.

In any case, if you can reproduce this behaviour in a sample (this is the usual requirement for reporting a bug in wx), please do and we'd try to look it.

Good luck!

Dennis Towne said...

You weren't right, but you weren't wrong either. This turned out to be one of the subwindows of the new frame transferring the focus elsewhere.

Why arbitrary subwindows in a new frame would decide to get focus instead of the frame itself I have no idea, but transferring focus to the parent instead of an external entity is closer to the behavior I wanted anyway.

Closing, my fault.