Native QPrintDialog does not process events during it's execution
-
Hello,
I've got a quite specific problem I had not found an answer to.
For the context, my app needs to log off the user who stays inactive for a certain amount of time. This usually works fine, with an exception to the native Windows QPrintDialog.The problem is, that during the execution of 'exec()' method, the dialog is impossible to be closed without interaction of the user - user must literally click one of the buttons on the dialog (cancel, accept, close, etc.) for it to close itself. No signals, close methods nor even calling the destructor of QPrintDialog works out. From what I've found out reading the sources, the reason for that is that the creation and destruction of Windows handle to the native dialog is locked within the scope of aforemetioned 'exec()' method.
I'm looking for ways to come around this issue. I've got some ideas on what I can do but would love to hear from more experienced Qt devs.
tl;dr
I want to call 'QPrintDialog::close()' method so it actually closes the dialog if the dialog is shown.Cheers!
-
What exactly do you mean by "my app needs to log off the user" - what happens then?
-
In short, all the windows are closed and their state is remembered if needed. It is done by closing the QMainWindow, which closes all it's children windows but the QPrintDialog.
-
@Siemaguysitsme
If it only the blockingexec()
which is at issue try non-blocking void QPrintDialog::open(QObject *receiver, const char *member)? But not sure whether it will help with being able to "kill" the dialog/handle, you could test. -
Unfortunatelly it does not work. While the Qt window object is destroyed when calling 'close()' on a dialog made with 'exec()' or 'open()' or even 'show()', the Windows handle to the dialog remains unchanged. Check the source code for the 'exec()' method at QT6.2.4: qtbase\src\printsupport\dialogs\qprintdialog_win.cpp:216. The Windows Print Dialog handle is created, managed and destroyed within the scope of this method up there. This is the reason for which the window can not be closed programmatically.
-
If you have looked at the source code then what you see is what you get! I doubt anyone else will know how to "change that behaviour" without changing the source you see!
I am not a Windows expert. You can make various native Win32 calls to find handles of open windows etc. (e.g. I think there is one to find by window title, but that might be for external programs rather than process-internal ones?). Can you use that to find the native Print Dialog and close/kill it by window handle? Assuming Qt would not then barf upon return.
Another possibility might be to handle the print dialog yourself with Windows calls instead of the
QPrintDialog
wrapper? Don't know how much you need the Qt wrapper for your print job. -
I have tried to rewrite the QPrintDialog but ended up just copying more and more QT files due to dependencies that these dialogs use. I mean the stuff like QPrintDialogPrivate and stuff. I'm quite very unsure of how would I get my hands on it. Can you try to give me some hints on that matter?
Regarding the your other recommendation - i definitely will try that out, but will give you a feedback on this tomorrow at the soonest. -
@Siemaguysitsme said in Native QPrintDialog does not process events during it's execution:
Can you try to give me some hints on that matter?
If you want to modify Qt then don't copy Qt source code files into your project. Instead, change Qt code and rebuild the Qt module you changed. But this is not trivial to do. You should rather look for a solution without changing Qt code. Is the closeEvent called?
-
Alright, it took me a bit more than I wanted but here is how it went:
- Created a
TTimer
withTimeout
slot to indicate a request for closing the window. - Subclassed
QPrintDialog
intoTPrintDialog
. - For QT5.7 I've connected the
TTimer::Timeout
signal toTPrintDialog::close
. This is not necessary in QT>=6, this is because for some reason theclose
method is not invoked when destroying the window in QT5.7. - Overridden the
TPrintDialog::closeEvent
- this is where the "hacking" begins - inside I find aHWND
handle to aPrintDlg
withGetWindow((HWND)this->winId(), GW_HWNDPREV)
, which gets the window that is before the this->winId() in Z axis on the screen, and then send aWM_CLOSE
message to the handle withPostMessage(handle, WM_CLOSE, 0, 0)
,
and set the handle to nullptr;
Feel free to lecture me if you see something concerning about that or something that could be done better :)
Here is a source code for the tests, I tried to make it as compact as possible:
app.hpp
#pragma once #include <QMainWindow> #include <QTime> #include <QPrintDialog> class TAutoLogoff : public QObject { Q_OBJECT public: explicit TAutoLogoff(QObject* parent); ~TAutoLogoff(); virtual void timerEvent(QTimerEvent* e) override; void StopTimer(); public: signals: void Timeout(); private: ULONGLONG m_interval; ULONGLONG m_begin_tick; int m_timer_id; }; class TMainWindow : public QMainWindow { Q_OBJECT public: explicit TMainWindow(QWidget* parent = nullptr); ~TMainWindow(); void Process(); public slots: void OnTimerExit(); private: TAutoLogoff* m_auto_logoff; }; class TPrintDialog : public QPrintDialog { Q_OBJECT public: explicit TPrintDialog(QWidget* parent = nullptr); ~TPrintDialog(); virtual void closeEvent(QCloseEvent* e) override; };
app.cpp
#include "app.hpp" #include <QApplication> #include <QTimer> #include <windows.h> static TAutoLogoff* g_auto_logoff = nullptr; TAutoLogoff::TAutoLogoff(QObject* parent) : QObject{ parent } , m_begin_tick{ GetTickCount64() } , m_interval{ 2500 } , m_timer_id{ -1 } { m_timer_id = QObject::startTimer(m_interval, Qt::CoarseTimer); } TAutoLogoff::~TAutoLogoff() { qDebug() << "TAutoLogoff::~TAutoLogoff"; } void TAutoLogoff::timerEvent(QTimerEvent* e) { if (m_begin_tick + m_interval < GetTickCount64()) { StopTimer(); emit Timeout(); } } void TAutoLogoff::StopTimer() { if (m_timer_id != -1) { QObject::killTimer(m_timer_id); m_timer_id = QObject::startTimer(m_interval, Qt::CoarseTimer); } } TMainWindow::TMainWindow(QWidget* parent) : QMainWindow{ parent } , m_auto_logoff{ new TAutoLogoff{ this } } { g_auto_logoff = m_auto_logoff; connect(m_auto_logoff, &TAutoLogoff::Timeout, this, &TMainWindow::OnTimerExit, Qt::QueuedConnection); QTimer::singleShot(0, this, &TMainWindow::Process); } TMainWindow::~TMainWindow() { qDebug() << "TMainWindow::~TMainWindow"; g_auto_logoff = nullptr; } void TMainWindow::Process() { TPrintDialog pd{ this }; if (pd.exec() != QPrintDialog::Accepted) return; } void TMainWindow::OnTimerExit() { qDebug() << "TMainWindow::OnTimerExit"; close(); } TPrintDialog::TPrintDialog(QWidget* parent) : QPrintDialog{ parent } { connect(g_auto_logoff, &TAutoLogoff::Timeout, this, &TPrintDialog::close); } TPrintDialog::~TPrintDialog() { qDebug() << "TPrintDialog::~TPrintDialog"; } void TPrintDialog::closeEvent(QCloseEvent* e) { qDebug() << "TPrintDialog::closeEvent"; auto* handle = GetWindow((HWND)this->winId(), GW_HWNDPREV); if (handle != nullptr) { PostMessage(handle, WM_CLOSE, 0, 0); handle = nullptr; } QPrintDialog::closeEvent(e); }
main.cpp
#include <QApplication> #include "app.hpp" int main(int argc, char *argv[]) { QApplication app{ argc, argv }; TMainWindow window{}; window.show(); return app.exec(); }
- Created a
-