2

There are two windows in my Qt project: parant and child. Child window is modal, so all clicks on parent window are ignored by system. I need to react on mouse clicks on internal part of parant window (I understand that it is strange requirement, but I can't persuade customer to do not ask it).

So what do we have: parent window is disabled (because child window is opened and modal). And I need to catch mouse click on this blocked (disabled) window.

I see two ways to do it:

  1. Simulate modality of child window. It is my current temporary solution: I've commented out line setWindowModality(Qt::WindowModal); in the code of child window (i.e. child window is not modal now), so I can catch mouse clicks on parent window. And I've set eventFilter() for parent window to ignore the most of actions. This solution works, but it looks wrong and rough.

  2. Find way to catch mouse events on disabled window. Unfortunatelly I can't catch it by eventFilter() (because input of parent window is blocked). Do you see other approaches?

Or do you see other ways to do it?

Ilya
  • 4,583
  • 4
  • 26
  • 51
  • 1
    AFAIK this is not really possible if the window is modal as this is handled by the OS-window manager and not QT. If a child window is modal all events are routed to it from the OS. You could probably catch all events in the child, check the mouse coordinates and then redirect to the parent but I am not sure if the main window could process any slots in such a state. – Bowdzone Nov 07 '14 at 12:48
  • I believe you should be able to use `winEventFilter` (`nativeEventFilter` in `Qt5`) to catch all events of an app. – Ezee Nov 07 '14 at 12:51
  • @Bowdzone, thank you for good idea! I'll try to check if modal window receives any messages after mouse clicks on parent window. – Ilya Nov 07 '14 at 12:51
  • @Ezee I thought so too and when I tried this `if(msg->message == WM_LBUTTONDOWN)` didn't work when window is modal. Maybe I don't right but it seems that system eat this event, so nativeEvent can't catch it – Jablonski Nov 07 '14 at 13:18
  • @Chernobyl, thank you for this result of your experiment! – Ilya Nov 07 '14 at 13:19
  • I think that installing mouse hook on windows will work(you will be able to catch click and coordinates) but I think also that hook in this case is worst solution, so I didn't test this and another dirty trick will be better than hook – Jablonski Nov 07 '14 at 13:25
  • @Chernobyl if `nativeEvent` doesn't receive the message then it will be inpossible to catch it somewhere else in the app. But what if you call `EnableWindow` with `winId` of the mainWindow from the modal dialog? it will make the window enabled for OS... – Ezee Nov 07 '14 at 13:27
  • 1
    @Ilya I think it can be done easier in a pure Qt style...see my answer. – Iuliu Nov 07 '14 at 13:42
  • 1
    @Ezee I am not advanced winapi developer, but `if(!EnableWindow((HWND)parent->winId(), TRUE))qDebug() < – Jablonski Nov 07 '14 at 13:58

1 Answers1

2

This can be done by installing an event filter on the parent window inside the child window.

Here is an example project I did to demonstrate it:

untitled3.pro:

QT       += core gui

greaterThan(QT_MAJOR_VERSION, 4): QT += widgets

TARGET = untitled3
TEMPLATE = app


SOURCES += main.cpp\
        mainwindow.cpp \
    form.cpp

HEADERS  += mainwindow.h \
    form.h

FORMS    += mainwindow.ui \
    form.ui

form.h:

#ifndef FORM_H
#define FORM_H

#include <QDialog>

namespace Ui {
class Form;
}

class Form : public QDialog
{
    Q_OBJECT

public:
    explicit Form(QWidget *parent = 0);
    ~Form();

protected:
    bool eventFilter(QObject *obj, QEvent *event);

private:
    Ui::Form *ui;
};

#endif // FORM_H

form.cpp:

#include "form.h"
#include "ui_form.h"

Form::Form(QWidget *parent) :
    QDialog(parent),
    ui(new Ui::Form)
{
    ui->setupUi(this);

    parentWidget()->installEventFilter(this);
}

Form::~Form()
{
    delete ui;
}

bool Form::eventFilter(QObject *obj, QEvent *event)
{
    return QObject::eventFilter(obj, event);
}

mainwindow.h:

#ifndef MAINWINDOW_H
#define MAINWINDOW_H

#include <QMainWindow>

class Form;

namespace Ui {
class MainWindow;
}

class MainWindow : public QMainWindow
{
    Q_OBJECT

public:
    explicit MainWindow(QWidget *parent = 0);
    ~MainWindow();

private:
    Ui::MainWindow *ui;
    Form *f;
};

#endif // MAINWINDOW_H

mainwindow.cpp:

#include "mainwindow.h"
#include "ui_mainwindow.h"
#include "form.h"

MainWindow::MainWindow(QWidget *parent) :
    QMainWindow(parent),
    ui(new Ui::MainWindow)
{
    ui->setupUi(this);
    f = new Form(this);


    connect(ui->pushButton, SIGNAL(clicked()), f, SLOT(show()));
}

MainWindow::~MainWindow()
{
    delete ui;
}

form.ui:

<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
 <class>Form</class>
 <widget class="QDialog" name="Form">
  <property name="geometry">
   <rect>
    <x>0</x>
    <y>0</y>
    <width>400</width>
    <height>300</height>
   </rect>
  </property>
  <property name="windowTitle">
   <string>Dialog</string>
  </property>
  <widget class="QDialogButtonBox" name="buttonBox">
   <property name="geometry">
    <rect>
     <x>30</x>
     <y>240</y>
     <width>341</width>
     <height>32</height>
    </rect>
   </property>
   <property name="orientation">
    <enum>Qt::Horizontal</enum>
   </property>
   <property name="standardButtons">
    <set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
   </property>
  </widget>
  <widget class="QLabel" name="label">
   <property name="geometry">
    <rect>
     <x>190</x>
     <y>40</y>
     <width>46</width>
     <height>13</height>
    </rect>
   </property>
   <property name="text">
    <string>zz</string>
   </property>
  </widget>
 </widget>
 <resources/>
 <connections>
  <connection>
   <sender>buttonBox</sender>
   <signal>accepted()</signal>
   <receiver>Form</receiver>
   <slot>accept()</slot>
   <hints>
    <hint type="sourcelabel">
     <x>248</x>
     <y>254</y>
    </hint>
    <hint type="destinationlabel">
     <x>157</x>
     <y>274</y>
    </hint>
   </hints>
  </connection>
  <connection>
   <sender>buttonBox</sender>
   <signal>rejected()</signal>
   <receiver>Form</receiver>
   <slot>reject()</slot>
   <hints>
    <hint type="sourcelabel">
     <x>316</x>
     <y>260</y>
    </hint>
    <hint type="destinationlabel">
     <x>286</x>
     <y>274</y>
    </hint>
   </hints>
  </connection>
 </connections>
</ui>

mainwindow.ui:

<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
 <class>MainWindow</class>
 <widget class="QMainWindow" name="MainWindow">
  <property name="geometry">
   <rect>
    <x>0</x>
    <y>0</y>
    <width>400</width>
    <height>300</height>
   </rect>
  </property>
  <property name="windowTitle">
   <string>MainWindow</string>
  </property>
  <widget class="QWidget" name="centralWidget">
   <widget class="QPushButton" name="pushButton">
    <property name="geometry">
     <rect>
      <x>200</x>
      <y>40</y>
      <width>75</width>
      <height>23</height>
     </rect>
    </property>
    <property name="text">
     <string>Push</string>
    </property>
   </widget>
  </widget>
  <widget class="QMenuBar" name="menuBar">
   <property name="geometry">
    <rect>
     <x>0</x>
     <y>0</y>
     <width>400</width>
     <height>21</height>
    </rect>
   </property>
  </widget>
  <widget class="QToolBar" name="mainToolBar">
   <attribute name="toolBarArea">
    <enum>TopToolBarArea</enum>
   </attribute>
   <attribute name="toolBarBreak">
    <bool>false</bool>
   </attribute>
  </widget>
  <widget class="QStatusBar" name="statusBar"/>
 </widget>
 <layoutdefault spacing="6" margin="11"/>
 <resources/>
 <connections/>
</ui>

main.cpp:

#include "mainwindow.h"
#include <QApplication>

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);
    MainWindow w;
    w.show();

    return a.exec();
}

The result is that the child widget loses focus but always stays on top of the parent widget. Let me know if it is what you are looking for.

Iuliu
  • 4,001
  • 19
  • 31
  • Thank you for this example! It works on my side. But as I understand, it is my current solution (i.e. I don't set modality flag on child window, so I can catch all events of parent; also I am simulating modality by ignoring the most of events). Please correct me if I am wrong! – Ilya Nov 07 '14 at 13:57
  • @Ilya I thought that you were setting the child window modal and *after* that you were trying to catch the events. I think I missed that part. But why can't you use this approach? – Iuliu Nov 07 '14 at 14:01
  • @luliu, yes, I'd prefer to set child window modal and after that be able to catch mouse events of parent. Now I am simulating modality to do it. I can use this approach, but it looks dirty. So I am looking for right solution for this case. – Ilya Nov 07 '14 at 14:04
  • @Ilya I understand. Although it looks dirty, I think it's the cleanest solution you can find considering the fact that Qt doesn't have a flag for this kind of behavior. What you can do is derive `QWidget` and use the derived class in order to mask this functionality behind a flag. If there is a better solution than this, I'm looking forward in seeing it. – Iuliu Nov 07 '14 at 14:09