How to get a signal when window manager is done resizing window in qt QML? - c++

I'm trying to achieve a similar effect as iTunes' miniPlayer when resizing happens on macOS. That is, detecting when a resizing of the the window has been completed, THEN changing the height to a certain value. Here is a visual example :
The problem is that no signal in a QML window exists to notify me when the window manager is done resizing (that is, the user released the handle). Hence, if I don't have a signal and apply my height change as soon as width or height is changed during resizing, the window will flicker (double resize happens) as long as the user didn't release the handle.
Thanks for any input or help!

You could implement your own resize handle pretty easily, using a MouseArea and handling the final resize calculation using onReleased (here forcing the height to be 75% of the width on release):
Window {
id: window
flags: Qt.FramelessWindowHint
visible: true
height: 300
width: 400
MouseArea {
id: resize
anchors {
right: parent.right
bottom: parent.bottom
}
width: 15
height: 15
cursorShape: Qt.SizeFDiagCursor
property point clickPos: "1,1"
onPressed: {
resize.clickPos = Qt.point(mouse.x,mouse.y)
}
onPositionChanged: {
var delta = Qt.point(mouse.x-resize.clickPos.x, mouse.y-resize.clickPos.y)
window.width += delta.x;
window.height += delta.y;
}
onReleased: {
window.height = .75 * window.width
}
Rectangle {
id: resizeHint
color: "red"
anchors.fill: resize
}
}
}

QML provided some NOTIFY signals when property values are supposed to be updated. So you can use Window.width's and Window.height's:
Window {
id: window
onWidthChanged: {
// Will be executed after window.width value changes.
}
onHeightChanged: {
// Will be executed after window.height value changes.
}
// Other window-related stuff
}

Here is an idea (that I did not try yet, but still):
Implement an event handler reacting to changes of the global cursor position. The cursor position inside the window is not applicable as the cursor is outside the window when grabbing a resize handle. Not sure if and how such an event handler is possible, but at least accessing the screen-global cursor position is possible in Qt.
In the event handler, check if the vertical cursor position change compared to the last call of the handler is the same as the window height change.
If yes, assume that the user still holds the window resizing handle, so don't adapt window height.
If no, assume that the user released the window resizing handle and started to move the cursor around freely. Adapt the window height now.
You'll probably have to overcome several issues, such as (1) allowing for a certain divergence between the window height and cursor y position changes, as window height changes might be less frequent and trail the cursor movement somewhat, (2) engaging the cursor position event handler only during a window resize to limit system load. But if it works, it's a native solution, not implementing own resizing handles.

It seems you want QML to send two different signal types, one to mark the start and finish of resizing, and one to mark size changes during resizing. The event sequence would then be something like this:
window.resizeStarted() // hypothetical new event
window.widthChanged()
window.heightChanged()
window.widthChanged()
window.heightChanged()
...
window.resizeEnded() // hypothetical new event
That is apparently impossible in Qt out of the box, but you should be able to implement it yourself with this approach, originally meant to not repaint the window at all while resizing:
Filter the relevant events out until the mouse button has been released. Specifically, "eat" the resize event while the mouse button is held down, and then synthesize the final resize event once the mouse is released. You can do it all in an event filter attached to the window / widget object that displays your QML interface. (source)
The process would be very similar to the one in the quote:
Extend the QML Window type with custom signals:
//MyWindow.qml
Window {
signal resizeStarted()
signal resizeEnded()
}
Create an event filter on the QML window that "eats" all window resize events. When it encounters the first one, it sends resizeStarted(). Then it forwards the window resize events. When it encounters a mouse release event while resizing, it sends resizeEnded() after the last widthChanged() / heightChanged() event.
Implement a corresponding signal handler onResizeEnded in QML to react, here to adapt the height of your application window to a certain fixed value.
Seems quite a promising route to me, but to note, I haven't tried it in code so far.

Related

C++ how to display text in child window at real time

This application creates a child window (which is the white box) when I right click anywhere, and destroys the child window after another right click. I have implemented the mechanics to expand and shrink the red rectangle through Direct 2D. I would like the child window to display the width and height of the rectangle at real time, as I make changes to it. I do not interact with the child window at all: it doesn't need an "x" button for closing and stuff; it just prints out a couple lines of data.
Here is what I have in my main window procedure:
case WM_RBUTTONUP:
{
DemoApp *pDemoApp = reinterpret_cast<DemoApp *>(static_cast<LONG_PTR>(
::GetWindowLongPtrW(hwnd, GWLP_USERDATA)));
pDemoApp->showTextBox = !pDemoApp->showTextBox; //showTextBox is a boolean
if (pDemoApp->showTextBox) {
POINTS cursor = MAKEPOINTS(lParam);
pDemoApp->child_hwnd = CreateWindowEx(
WS_EX_TOPMOST,
"LISTBOX",
"I dont need a title here",
WS_CHILDWINDOW,
cursor.x,
cursor.y,
100,
200,
pDemoApp->main_hwnd,
NULL,
HINST_THISCOMPONENT,
pDemoApp
);
ShowWindow(pDemoApp->child_hwnd, SW_SHOWNORMAL);
UpdateWindow(pDemoApp->child_hwnd);
}
else {
DestroyWindow(pDemoApp->child_hwnd);
}
}
break;
How may I go from here? I would like to know:
Is using Direct Write to draw text in the child window my only option? I see the dashed lines in the white box so I assume there must be a way to display plain text.
I used LISTBOX here, which is a predefined windows class name. How do I set a procedure for it? What else predefined class name can better suit my need? Or do I have to register a custom one;
I would like to drag the child window around, how can I set it up so that the system handles dragging for me.
Would popping a dialog box to display text be better than popping a child window?
Thanks.
Since you are using a Win32 LISTBOX control as the child window, then you have a couple of options for displaying the rectangle's dimensions in it:
give the ListBox the LBS_HASSTRINGS style, and add 1-2 items to it. Then, any time you change the rectangle, send LB_DELETESTRING and LB_ADDSTRING messages to the ListBox's HWND to display the updated dimensions as needed. A ListBox does not have a way to update an existing item, so to change an existing item's text, you have to remove the item and then re-add it with the new text.
give the ListBox an LBS_OWNERDRAW... style, and add 1-2 blank item(s) in it. Then have the ListBox's parent window handle WM_MEASUREITEM and WM_DRAWITEM notifications from the ListBox to display the rectangle's current dimensions in the item(s) as needed. Whenever the rectangle is changed, call InvalidateRect() on the ListBox's HWND to trigger a redraw.

Sending mouse events to a QtQuick window in a virtual reality OpenGL scene

I am currently working on a virtual reality project that builds on OpenGL. Because I also needed some form of user interface, I thought it would be a good idea to integrate QtQuick windows into the scene. Drawing the window to a texture works without problems (I used this example) but I struggle to send mouse events so that my controllers can interact with it.
Here is a quick example video on YouTube. In this example, the animation of the embedded GIF should stop whenever I hover over it. This works in a normal QML application but not when I manually send a MouseMove event.
The mouse position within the window is known (the red line in the video indicates an intersection) and I am currently sending the event through
QQuickWindow::sendEvent(QQuickItem* item, QEvent*)
where item is the root Rectangle in the qml source:
import QtQuick 2.3
import QtQuick.Controls 2.0
Rectangle {
color: mouseArea.containsMouse ? "red" : "white"
width: 600
height: 400
MouseArea {
id: mouseArea
anchors.fill: parent
hoverEnabled: true
AnimatedImage {
anchors.fill: parent
paused: mouseArea.containsMouse
source: "test.gif"
}
}
}
The event, which I send every time an intersection between the ray and the window is found, is done with:
QMouseEvent* mouseMoveEvent = new QMouseEvent(
QEvent::MouseMove, // wrong event?
cursorPosition, cursorPosition, // Note: cursorPosition := the calculated cursor coordinates within the window
Qt::MouseButton::NoButton,
Qt::MouseButtons(), // is this right?
Qt::KeyboardModifier::NoModifier);
window->sendEvent(rootItem, mouseMoveEvent); // Note: window is my QQuickWindow, rootItem is the root Rectangle
I am not an expert in QML (rarely used it) and would appreciate it if someone has suggestions on how to solve this. My guess is, that I am neither using the right event nor correctly sending them.
I found a solution myself. Instead of using QQuickWindow::sendEvent(..) I had to simply use QApplication()::instance()->sendEvent(..).

Change QWidget Parent During Mouse Event

I'm trying to create a detachable type style widget, like in the way Chrome tabs are detachable (class is called Tab). I have everything working, except for a bug where sometimes (maybe 50% of the time), the Tab object never gets the mouse release event, and stops getting mouse move events.
Essentially, the detaching system works by allowing drags in the mouse press/move/release functions, just like normal. The mouseMoveEvent checks the total distance moved from the start, and if over a certain amount, will start the "detaching" process. The detaching process involves setting the parent widget to 0 (top level widget, undecorated window), so the Tab object is pretty much floating above everything, under the mouse, and continues to be dragged along with it until released.
I ran through all the QEvent items being delivered, and I found that when this issue occurs, the QEvent::MouseMove items (and all mouse events after this) are being sent to the TabBar (the Tab object's original parent). This occurs directly after calling setParent(0) on the Tab.
Basic mouse handling overview:
void Tab::mousePressEvent(*) {
[set up some boolean, start positions, etc]
}
void Tab::mouseMoveEvent(*) {
[track the updated position]
if (positionChange > STATIC_AMOUNT)
detachTab();
}
void Tab::mouseReleaseEvent(*) {
[return the Tab to its original position, and set the parent back to the TabBar]
}
void Tab::detachTab() {
QPoint mappedPos = mapToGlobal(0, 0);
setParent(0); //The loss of MouseMove events occurs when this returns.
move(mappedPos);
show();
raise();
}
Here are the events that the Tab object receives (first row is QEvent type, second is the name)
[Tab::detachTab() started]
[setParent(0) started]
QEvent::Hide
QEvent::Leave
qApp QEvent::MouseMove [ TabBar ] <-- now the TabBar is soaking up the mouse events
QEvent::HideToParent
QEvent::ParentAboutToChange
QEvent::ParentChange
[setParent(0) returned]
....
Summed up: my draggable QWidget loses QEvent::MouseMove and QEvent::MouseButtonRelease events after having its parent set to 0.
Any advice would be really appreciated!
A bit tricky workaround. I didn't test it, it's just an idea.
When your mouse hovers draggable part of a widget you may create topmost widget (let's call it Shade) with Qt::FramelessWindowHint (and possible with Qt::WA_TranslucentBackground). You may manipulate with Shade apperance via reimplementing paintEvent. For example - draw content of original widget, or draw some transparent preview, etc.
Then you may resize a Shade during dragging, to show user that widget will be detached. You will not loose mouse capture.
When user release mouse - you remember position of Shade, destroy it and detach+move original widget.
Feel free to ask, if you want more details.
Here is similar question.
So you suppose to use QDocWidget and enforce stacking of this widgets using tabifyDockWidget.

Qt user resize event ends (stops)

I have a QWidget and i need to do some actions (refresh a picture in widget) when resize event ends. How can i catch this action?
I need to catch moment when user ENDs all his resize actions by releasing mouse button. It is not a good practice in my application to refresh image every pixel resized. It should calls only when mouse released and resize actions ends.
I am just tried to reimplement QMouseReleaseEvent to catch it, but it do not works when user presses on the border of widget to resize it. It means does not working in our situation.
Then i was tried to create my own QSizeGrip and insert it on the bottom of my widget, but reimplemented event QMouseReleaseEvent again did not work in it. Event did not generates any time user released mouse. I do not know why.
Anybody can help me with that problem?
Thanks in advance.
The timeout method is a decent idea, but if the user is resizing and then pauses for longer than the timer's interval then you end up not getting the true "user is done resizing the window" event. Setting the interval longer makes that situation less likely, but by doing that, you end up having a long delay between the time the user finished resizing and the time your function gets called. In my search for a solution, I found quite a few people solving it using the timer method, so apparently it's reliable enough for some use cases, but I think it's a bit hacky.
I like mhstnsc's idea, so after implementing it, I decided to add some code here that might be of use to someone trying to do something similar.
You could easily adapt it to catch the "user is done moving the window" event by making a m_bUserIsMoving flag and overriding "void MainWindow::moveEvent(QMoveEvent* pEvent)". I'm using it to save a config file whenever the user finishes resizing or moving the window, so that the last position will always be saved even if the app is killed in an unclean manner.
// constructor
MainWindow::MainWindow(QWidget* pParent, Qt::WindowFlags flags) : QMainWindow(pParent, flags)
{
m_bUserIsResizing = false;
qApp->installEventFilter(this);
}
// this will be called when any event in the application occurs
bool MainWindow::eventFilter(QObject* pObj, QEvent* pEvent)
{
// We need to check for both types of mouse release, because it can vary on which type happens when resizing.
if ((pEvent->type() == QEvent::MouseButtonRelease) || (pEvent->type() == QEvent::NonClientAreaMouseButtonRelease)) {
QMouseEvent* pMouseEvent = dynamic_cast<QMouseEvent*>(pEvent);
if ((pMouseEvent->button() == Qt::MouseButton::LeftButton) && m_bUserIsResizing) {
printf("Gotcha!\n");
m_bUserIsResizing = false; // reset user resizing flag
}
}
return QObject::eventFilter(pObj, pEvent); // pass it on without eating it
}
// override from QWidget that triggers whenever the user resizes the window
void MainWindow::resizeEvent(QResizeEvent* pEvent) { m_bUserIsResizing = true; }
It's slightly more complicated than the timer, but more robust.
I've do it in this way:
inherit my class from QWidget
define private variable int timerId = 0
overload QWidget::resizeEvent and QObject::timerEvent
void MapLoader::resizeEvent(QResizeEvent *){
if (timerId){
killTimer(timerId);
timerId = 0;
}
timerId = startTimer(5000/*delay beetween ends of resize and your action*/);
}
void MapLoader::timerEvent(QTimerEvent *te){
/*your actions here*/
killTimer(te->timerId());
timerId = 0;
}
Mouse events on windows decoration are managed by the underlying window system, this is why you can't catch them as you tried.
I had the same issue once, the solution I chose was to (re)start a singleshot QTimer on each resize event, and only process the update after the timer interval elapsed. Not very sexy but I did not find any other workaround..
Another way would be to install an event filter for the app and get all the events of the application, trap mouse press and mouse release and do not update the window in between .
"Installing an event filter on QCoreApplication::instance(). Such an event filter is able to process all events for all widgets, so it's just as powerful as reimplementing notify(); furthermore, it's possible to have more than one application-global event filter. Global event filters even see mouse events for disabled widgets. Note that application event filters are only called for objects that live in the main thread."
My Qt app uses image windows and does complex layered rebuilds, which can take some time even on a very fast machine. So not having the window redraw with every change in the window frame size was important to me so the response to the window frame resize would not be laggy.
So I solved it this way:
In my image window, I have enabled mouse tracking:
setMouseTracking(true);
Then, in the window class, I have a boolean, puntme; this is set when a resize event is caught:
bool puntme;
Then, in the mousemove event:
void imgWindow::mouseMoveEvent(QMouseEvent* event)
{
if (puntme)
{
puntme = false;
needRebuild = true;
update();
}
...
Basically, what this does is as soon as the user moves the mouse over the window -- which is a pretty natural thing for them to do if they were just resizing it -- then the window redraws with the new size. It doesn't happen during the resize, because Qt isn't forwarding move moves then.
Instead, during the resize, I just scale up an already existing bitmap, which gives a crude approximation of the change in scale with necessarily handling the actual newly more-or-less available resolution.
Worst case, user resizes, moves away from the window, and leaves the crudely scaled bitmap in place until the come back to it, at which point it will duly update to the actual new displayed bitmap == scale/size conditions.
There's no perfect way - what is really needed here is for Qt to provide (user has stopped resizing window" message, but in lieu of that, this has been working well for me.

QT mouse event handling problem

Greetings all,
As seen in the picture
I have an extended QWidget object (which draws the cell images and some countour data) inside a QScrollBar.
User can zoom in/out the Image (QWidget size is changed according to the zoomed size of the QImage ) using mouse wheel.
I process the events (mouseMoveEvent(),wheelEvent()..etc) by implementing the listener methods in QWidget.
My problem is ,I can only perform zooming (and other events) when the mouse pointer is over the QWidget.
If the mouse point is over the QScrollBar (the gray area in the image) ,those events are consumed by the QScroolBar.
Any tips,
[Edit] Sorry I was refering to QScrollArea , not QScrollBar.
thanks,
umanga
I'm uncertain if you want the scroll wheel to only ever be used for zooming the image or if you want the scroll wheel to control zooming when the image is smaller than the scroll area viewport and then use the scroll wheel to do scrolling when the image is larger than the scroll area viewport. In either case, you should be able to customize how the wheel is handled with the following:
Since I've not actually tried this one, I'm not sure if it will work. The hope is that if you install an event filter and set ignore on the event, the event will still be propagated back to your image widget. This will allow you to leave your current mouse handling in the image widget intact.
bool YourImageWidget::eventFilter(QObject *obj, QEvent *event)
{
if((obj == scrollAreaPointer) && (event->type() == QEvent::Wheel))
{
if(!scrollAreaShouldHandleWheel)
{
event->ignore();
}
}
return false; // always pass the event back to the scroll area
}
The scrollAreaShouldHandleWheel flag is a boolean you would set from your image widget based on whether or not you want the scroll area to handle the wheel events.
Somewhere in your code you would install your image widget as an event filter for the scrollarea.
scrollArea->installEventFilter(imageWidget);
If this doesn't work, you can always use this filter to make sure your widget gets the event and handle it directly and then return true so that the scroll area won't be able to receive the event.
I recommend you use QGraphicsScene and QGraphicsView. The graphics framework already provides a lot of useful features (including viewport transformation). And the QGraphicsView is a scroll area.
have you done grabMouse() for Qwidget i.e for the one which you display image?