PDA

View Full Version : prevent Window Moving?



maybnxtseasn
9th March 2012, 06:34
Could anyone point me in the right direction for disabling the user from MOVING my windows using it's title bar? ( i want a title bar to be visible or else i would have hid it :P )

high_flyer
9th March 2012, 12:17
Catch the move event, and reset the position.

Spitfire
9th March 2012, 12:45
It will not work as events comming from non client area are just informational and the move done by move() or setGeometry() in reply to moveEvent() will happen after the mouse button is released on the title bar.

Atually it's hard problem and I don't believe it can be done without hacking into winapi or doing tricks like covering title bar with transparent widget and filtering out mouse events.

high_flyer
9th March 2012, 14:04
What is an "informational" event?
I will not negate what you wrote, since it was long ago since I had to deal with move events.
I will test it, and reply again.
But I think you are wrong - or at least, that one should be able to configure if move events should get in before the mouse button is released if what you say is correct.

A work around could be done by ignoring mouseMoveEvents.

The docs says nothing about "only after mouse release":

If the widget is a window, the position is that of the widget on the desktop, including its frame.

When changing the position, the widget, if visible, receives a move event (moveEvent()) immediately. If the widget is not currently visible, it is guaranteed to receive an event before it is shown.

By default, this property contains a position that refers to the origin.

Spitfire
9th March 2012, 14:59
You're talking about QEvent::Move, I'm talking about WM_MOVE.
WM_MOVE (http://msdn.microsoft.com/en-us/library/windows/desktop/ms632631%28v=vs.85%29.aspx)is a windows event that is dispatched to the window after it has already new position just to inform it that it has new position. You can't stop it.



Sent after a window has been moved.

A window receives this message through its WindowProc function.


The same for non-client-area events, these are for you to know that something happend, but you have no control over it.
For example: you can ignore QEvent::MouseMove event, but this one is not delivered when working with title bar, instead you receive QEvent::NonClientAreaMouseMove event which can't be ignored.

The documentation you've quoted is about QObject::move(), but when moving window using title bar that's not what happens, instead windows os calls MoveWindow().


BOOL WINAPI MoveWindow(
__in HWND hWnd,
__in int X,
__in int Y,
__in int nWidth,
__in int nHeight,
__in BOOL bRepaint
);
It happens before Qt knows about it and as far as I know there's nothing you can do in this case (at least not using qt events).

In this case Qt Documentation is not the way to go. Take look at win api docs (http://msdn.microsoft.com/en-us/library/windows/desktop/ff468919%28v=vs.85%29.aspx).

In QETWidget::translateMouseEvent() in qapplication_win.cpp, around line 3245 (qt 4.6.3) you even have a comment saying:


res = QApplicationPrivate::sendMouseEvent(widget, &e, alienWidget, this, &qt_button_down,
qt_last_mouse_receiver);

// non client area events are only informational, you cannot "handle" them
res = res && e.isAccepted() && !nonClientAreaEvent;


If you want to confirm/disprove what I said, here's a staring point for you:


#include <QDebug>
#include <QEvent>
#include <windows.h>

MainWindow::MainWindow( QWidget* p )
:
QMainWindow( p )
{
}

void MainWindow::moveEvent( QMoveEvent * e )
{
qDebug() << "moveEvent()";
this->move( 50, 50 );
}

bool MainWindow::event( QEvent* e )
{
switch( e->type() )
{
case QEvent::Move:
qDebug() << "QEvent::Move";
break;
case QEvent::NonClientAreaMouseButtonPress:
qDebug() << "QEvent::NonClientAreaMouseButtonPress";
break;
case QEvent::NonClientAreaMouseMove:
qDebug() << "QEvent::NonClientAreaMouseMove";
break;
default:
break;
}

return QMainWindow::event( e );
}

bool MainWindow::winEvent( MSG* m, long* r)
{
if( m->message == WM_MOVE )
{
qDebug() << "WM_MOVE";
}

return QMainWindow::winEvent( m, r );
}

If anyone can prove me wrong I'm more than happy to stand corrected.

Edit:
Corrected wrong name of one event (ment MouseMove not Move).

high_flyer
9th March 2012, 15:10
@spitfire:
I think you and I (at least on some points) misunderstand each other.

The same for non-client-area events, these are for you to know that something happend, but you have no control over it.
But I didn't want control over it, just to know when the window is moved.
Your example code at the end is more or less what I meant.

At any rate, I don't see why even getting below Qt in this issue is necessary? - as what ever you code here should work on all platforms Qt supports.
So working with Qt events should be enough.
Since Qt must at some point move the window - at THAT point you can (should) intervene - when Qt already knows about the event - and AFAIK that is where you can intercept the move event (now it could be that in this case its QEvent::NonClientAreaMouseMove event (as I said, it has been a while since I had to deal with this under Qt)

I will test it when I get a chance and get back to you :-)

Spitfire
9th March 2012, 15:26
Missunderstanding is possible, It happens to me quite often :)

I'm guessing that you assume that every qt window is always moved using QObject::move(), which generates the moveEvent().
That's largery true, except when OS is moving the window using native api (can happen in few scenarios). QObject::move() is never called and only thing that Qt does, it uses the windows event to update it's internal data about window position.

Anyway, try it, if you find anything how to stop the window from being moved via titlebar using Qt (as per OP's question) let me know, it may come handy :)

high_flyer
9th March 2012, 15:31
Anyway, try it, if you find anything how to stop the window from being moved via titlebar using Qt (as per OP's question) let me know, it may come handy
Will do.

Just one thing - its not QObject::move() but QWidget::move() - since QObject has no visual representation.

EDIT:

That's largery true, except when OS is moving the window using native api
That is something I don't understand.
Since Qt is drawing the window, how can the OS move it without Qt drawing it. (thus knowing about it).

Spitfire
9th March 2012, 16:23
My bad, was too quick writing QObject instead of QWidget.

True, Qt is drawing the window, but in response to an event.

There are two ways that event can be generated, by Qt, in which case paintEvent() is generated, or by the OS, in which case WM_PAINT generated by OS is translated by Qt to paintEvent().
In first case Qt knows why the paint event was generated, in other case it just does what the OS told it to do.

Look at it this way, your son (Qt) may brush his teeth in the evening before going to bed, but it may also have to brush the teeth straight after dinner because you (OS) have told him to.
(I'm very bad at analogies! Sorry!) :)

When OS moves some window using MoveWindow() (http://msdn.microsoft.com/en-us/library/windows/desktop/ms633534%28v=vs.85%29.aspx) it also tels that window to repaint itself via UpdateWindow() (http://msdn.microsoft.com/en-us/library/windows/desktop/dd145167%28v=vs.85%29.aspx) (and WM_PAINT (http://msdn.microsoft.com/en-us/library/windows/desktop/dd145213%28v=vs.85%29.aspx) in result).
You have to remember that Qt app sits on top of OS and OS can do everything to the app.

Read the WinApi doc for MoveWindow() (http://msdn.microsoft.com/en-us/library/windows/desktop/ms633534%28v=vs.85%29.aspx) and UpdateWindow() (http://msdn.microsoft.com/en-us/library/windows/desktop/dd145167%28v=vs.85%29.aspx). That should clear things up.

I hope my explenation makes now some more sense.

high_flyer
9th March 2012, 17:10
There are two ways that event can be generated, by Qt, in which case paintEvent() is generated, or by the OS, in which case WM_PAINT generated by OS is translated by Qt to paintEvent().
In first case Qt knows why the paint event was generated, in other case it just does what the OS told it to do.
True.
But just as you wrote - both cases end up by Qt doing the actual work - or to your analogy - in both cases the son is the one doing the teeth brushing :-)
What was the trigger for it is not important.


When OS moves some window using MoveWindow() it also tels that window to repaint itself via UpdateWindow() (and WM_PAINT in result).
You have to remember that Qt app sits on top of OS and OS can do everything to the app.
Ok, this actually may be the bit I was looking for.
If this situation allows Qt to actually be moving the windows without triggering a move event, then I guess that is the point you where making - is it?
But it seems to me this should be still possible to overcome in the code - so my offered solution is just not full - one needs to catch both cases not just the first one.
I guess you will say to that - that the other case is tricky to catch before it happened - is that so?

I'll have a look later, and let you know.

Spitfire
9th March 2012, 18:05
I know I'm terrible at explaining things... :)

To put it in simplest words - Qt is not moving the window, window is moved by OS and OS kindly informs Qt of what it did.
Whatever you see in Qt (WM_MOVE, QEvent::move, QEvent::NonClientAreaMouseMove) - it's too late (in this case) for any action - that's what I ment by 'informative'.
OS is telling you that window has been moved, not will be moved, so there's nothing to prevent any more.
After that it also asks Qt to kindly repaint the window so it won't look bad.
Internaly Qt converts OS events to QEvent so application can act upon those events if required.

So it's not true that in both cases Qt does the work.
When you use move() to change window position the flow is something like that:
QWidget::move()->QWidget::moveEvent()
Qt sets new geometry and emits QEvent::move.

When OS uses WindowMove() (like in this case) the flow is something like that:
WindowMove()->QETWidget::translateMouseEvent()->QWidget::moveEvent()
Window was already moved, qt gets informed that it was moved, updates internals and emits QEvent::move so components depending on the window position can get updated.

In second case Qt was only informed that the position of the window was changed. Qt did nothing except to acknowledge by updating its internals.

It may be possible to overcome the move, but as far as I know it's impossible to do it using Qt only.
In this case Qt is being pushed around by OS so you have to go down to the native api level to do something with it.

Anyway, I'm off for the weekend, let me know what you conclued when you have time,
Cheers!

high_flyer
9th March 2012, 18:16
OS is telling you that window has been moved, not will be moved, so there's nothing to prevent any more.
After that it also asks Qt to kindly repaint the window so it won't look bad.
See the bold quote.
That is the point though.
As long as Qt did not repaint the window, you will still see it in the old position - even though the OS has moved it logically - on the screen it still didn't move.
If you intervene at this time - tell Qt to move the window back to the old position - before it painted it in the new - OS triggered position, the window will *appear* not to have moved.


It may be possible to overcome the move, but as far as I know it's impossible to do it using Qt only.
In this case Qt is being pushed around by OS so you have to go down to the native api level to do something with it.
Well, I will test that.
It might be true that one will have to catch WM_MOVE or similar, but that is not much, to have #ifdef with variants for other windowing systems - but that is something I still have to prove :-)

to be continued...

high_flyer
10th March 2012, 03:06
Ok, I have tested it.
And the conclusion is somewhere in the middle :-)
My approach does work - but indeed only when using the windows events - before they get translated to Qt events - so you are perfectly correct about that spitfire.
BUT -
The while holding the windows and moving it, it will flicker between its would be position and its original position.
At the beginning I thought, well ok, that makes sense, since the paint events are still getting through.
However event when intercepting the WM_PAINT events it made no change at all.
I even intercepted the mouse events - but that didn't change anything either.
So I can't quite explain why the flicker occurs.

So to the OP:
If you don't mind a bit of flickering when the user tries to move the window, then use the below code.
Note however, this is not portable, and you will have to implement similar code per windowing system.
If you can't accept the flickering, then you other option is to use a non decorated window, and draw the decoration (titlebar) your self.
This will work (I know since I have implemented that in the past) but you will have to invest quite a bit time in getting your title bar behaving and looking like the original one.



bool MyWindow::winEvent( MSG* m, long* r )
{
switch( m->message)
{
case WM_MOVE:
{
static QPoint oldPos = pos();
move(oldPos);
return true;
}
break;
}


return QMainWindow::winEvent( m, r );
}

Spitfire
12th March 2012, 17:40
Ok, in case anyone want's to do it in proper but non cross-platform way, this is how:



// main.cpp
#include "mainwindow.h"

#include "windows.h"

HHOOK g_hMouseHook;

LRESULT CALLBACK MouseProc( int nCode, WPARAM wParam, LPARAM lParam )
{
if( nCode < 0 )
{
return CallNextHookEx( g_hMouseHook, nCode, wParam, lParam );
}

LPMOUSEHOOKSTRUCT mhs = (LPMOUSEHOOKSTRUCT) lParam;
if( wParam == WM_NCLBUTTONDOWN && mhs->wHitTestCode == HTCAPTION )
{
return true;
}

return CallNextHookEx( g_hMouseHook, nCode, wParam, lParam );
}

int main(int argc, char *argv[])
{
g_hMouseHook = SetWindowsHookEx( WH_MOUSE, MouseProc, GetModuleHandle( NULL ), GetCurrentThreadId() );

if (!g_hMouseHook)
{
qDebug() << GetLastError();
}

QApplication a(argc, argv);
MainWindow w;
w.show();

return a.exec();
}
This piece of code will consume any Left Mouse Button Press event on the title bar (but not the buttons on the bar) preventing it from being moved by grabbing the bar.
Drawback is that double-click won't maximise the window, but I think it's enough to point you in the right direction.