PDA

View Full Version : On "there is no reliable way to activate a window"



ber_44
6th June 2007, 12:14
I have the following code:


Qt::WindowFlags flags = Qt::Window;
flags |= Qt::FramelessWindowHint;
flags |= Qt::WindowStaysOnTopHint;
setWindowFlags(flags);

//...

show();
raise();
activateWindow();
imageLabel->setFocus(Qt::ActiveWindowFocusReason);

Now it does not always works on Windows, because as they said:


since windows 2000 there is no reliable way
to activate a window. You can flash the title bar, though. And Microsoft
calls that a security feature ;-)

So what happens is that when I pop up the window, it receives the keyboard focus mostly, but sometimes it doesn't, in which case the user will start to hit the keyboard like crazy since the foreground application will not receive anything (Qt::WindowStaysOnTopHint prevents him from seeing what is in the active, background window).
So I should display a red label or something if the window is not receiving keyboard events, saying that press Alt+Tab or Click before you type. However, I don't know how to detect if it is not active.

high_flyer
6th June 2007, 12:19
you mean isActiveWindow() (http://doc.trolltech.com/4.2/qwidget.html#isActiveWindow-prop)?

ber_44
6th June 2007, 12:43
you mean isActiveWindow() (http://doc.trolltech.com/4.2/qwidget.html#isActiveWindow-prop)?

Theoretically, but


show();
raise();
activateWindow();
imageLabel->setFocus(Qt::ActiveWindowFocusReason);
if (!isActiveWindow())
abort();

doesn't detect whether activateWindow() failed or not. (The docs also admits that it can fail on Windows and X11.)

high_flyer
6th June 2007, 12:59
hmm...
Try detecting the changed window state then with QWindowStateChangeEvent

Eldritch
6th June 2007, 16:25
Most UI elements already have something to indicate if they have key focus... a blinking cursor, 'selection rectangle' around button text, highlight (rollover) colors, etc. Is your application different from those?

I've expended large amounts of time and effort on these kinds of problems in the development of a commercial package. Managing Z-plane, activation and focus in an envrionment with arbitrary windows from arbitrary threads (and in some cases, processes!) isn't trivial. It looks like you're doing 'the right thing' in your code. Even in the case of a 'topmost' window, the title bar should show 'active' or not.

But, don't forget the 'focus-follows-mouse' scenario! :P Then, you can't simply rely on activation -- I *hate* that. And of course, in X, your user might be running who-knows-what flavor-of-the-month buggy as bat-sh*t window manager with random features and adherance to quasi-"standards" over a 300 baud modem and blame you (or Qt) for failing to operate perfectly. Go figure. Me, bitter about that kind of thing? Where'd you get that idea!? ;)

BTW, the reasons for the policy in Windows w.r.t. window / focus 'stealing' are actually sound -- and if you dig around in MSDN you can find ways to circumvent the policy. Consider this scenario:


Bob is violating company policy, and decides to type an email to his girlfriend while he's supposed to be monitoring a manufacturing process that makes chocolate chip cookies.

While he's typing, an alarm from the cookie dough monitoring system pops up that has an 'OK' button on it. The dialog reports: "Dang! We're out of chocolate chips! Re-fill the chocolate chip hopper and press 'OK' to resume making cookie dough."

Well, that dialog pops in right when Bob hits the <spacebar> between two words in the email to his girlfriend. He thinks he saw something funny on the screen, but doesn't think anything of it.

A few days later, you buy a bag of chocolate chip cookies from the Vendo while you wait for your über Qt app to compile, walk back to your desk and open it. %^*&@!#$ cookies don't have any chocolate chips in them! WTF?!

OK, it's a little hokey, and you'd *think* that this kind of thing wouldn't happen. It has, and it does. Hell, it happened to me all the time when Lotus Notes would pop up some meeting reminder while I was coding, and I'd promise to attend some worthless random meeting and have to go clean up the mess later. Certainly, more serious Bad Things than these examples have happened, and probably are the reason for the change. It can sure be annoying, I agree, but the reasoning behind it is certainly not BS.

high_flyer
6th June 2007, 16:49
Very nice post! :)
I enjoyed reading it.

Regarding the coockies scenario -
In KDE, I use KDESvn, and it has the annoying feature to popup a message box for each action it does.
So if you are commiting a tree, using the keyabord is usless while the commiting is going on, and it can take some time for large projects!
I had exactly that with an e.mail - I thought I'll write an email whie this is commiting, and I pressed a space or enter just when such a progress dialog was popped - recommit...

So this is VERY real.

ber_44
6th June 2007, 20:16
Let's not deviate to speaking about unwanted popups. I just want to know how to detect if a top-level window is active or not.
I'm having a frameless window, so the title bar does not exist and cannot show anything.
The "'selection rectangle' around button text" sounds good, but it's too inconspicous, and besides that I only have pixmaps on my window.


Try detecting the changed window state then with QWindowStateChangeEvent
I'm not sure how to use it. Here is my little bogus code now. Can you help fixing it?


show();
raise();
activateWindow();
QEvent *e = new QEvent(QEvent::WindowActivate);
if (!event(e))
abort(); //activateWindow() failed
}

bool ImageViewer::event(QEvent *e) {
if (static_cast<QWindowStateChangeEvent*>(e)->oldState() == Qt::WindowActive)
return true;
else
return false;
}



BTW, the reasons for the policy in Windows w.r.t. window / focus 'stealing' are actually sound -- and if you dig around in MSDN you can find ways to circumvent the policy.

Sorry, I didn't find anything.

ber_44
6th June 2007, 23:30
I just had the idea to put activateWindow(); in a loop. Probably that would work "sooner or later" (though I still want to find a way of detecting whether the window is active or not).

Eldritch
7th June 2007, 18:48
Have you tried QWidget::isActiveWindow() ?

Qt's implementation in Win32 uses GetActiveWindow()... and so the return value of that function may not tell you what you really want to know.

In Win32, GetActiveWindow() returns the 'active' window on the calling thread. That is distinctly separate from GetForegroundWindow(), which will tell you the 'active' window in the entire system. The distinction between entire system and calling thread is extremely important.

There are corresponding "Set" functions for these in the Win32 API, and the documentation for those should lead you to more info about the activation policies -- especially SetForegroundWindow().

Other platforms *may* have similar functions. Carbon sure does... I'm rusty in X.

ber_44
9th June 2007, 09:05
GetForegroundWindow(), which will tell you the 'active' window in the entire system.
Ok, now can I do it without "Qt/MFC Migration Framework"? If yes, how?

high_flyer
11th June 2007, 10:39
I'm not sure how to use it. Here is my little bogus code now. Can you help fixing it?
Try this:


void ImageViewer::customEvent(QEvent *e) {
if (static_cast<QWindowStateChangeEvent*>(e)->oldState() == Qt::WindowActive)
{
doSomething();
//e->accepted(); //if apropriate
return true;
}
else {
e->ignore();
return false;
}
}

ber_44
9th July 2007, 04:36
As Eldritch suggested, it cannot be done with Qt.
Would it be safe to just recompile Qt with the line
HWND active = GetActiveWindow();
changed in kernel\qwidget.cpp to
HWND active = GetForegroundWindow();

ber_44
9th July 2007, 04:49
I guess it wouldn't be safe, since Qt calles it internally, but I could reimplement the function with a different name just with this little change.

Eldritch
10th July 2007, 00:15
Sure, you can guard your Win32 call if you need to worry about platform-specific code (e.g. #if _WIN32 or some such).

You can check the result of GetForegroundWindow() against your QWidget::winId() value. If they are the same, your window is active, else something else is.

You could perhaps make this even simpler, without any Win32 code.
You can piggy-back off QWindowStateChangeEvent as high_flyer originally suggested by overriding the QWidget::changeEvent method.

You can then check your current state (via windowState()) and make your red rectangle or whatnot to show whether your window is active.



void MyWidget::changeEvent(QEvent *event)
{
if(event->type() == QEvent::WindowStateChanged)
{
if(windowState & Qt::WindowActive)
setMyActiveIndicatorToGreen();
else
setMyActiveIndicatorToRed();
}
MyBaseWidgetClass::changeEvent(); // so whetever else should happen still does
}


I didn't test it, but it *should* work if all you really want to do is *report* whether you're active, and not force yourself to be active. You could also put in a prompt, like <click here to edit> to draw the user into doing the right thing.

Hope this helps.