I'm subclassing a QFrame widget created using the Qt Designer, this frame contains some QPushButtons as child.
class Frame : public QFrame
{
Q_OBJECT
public:
Frame(QWidget* parent = 0) : QFrame(parent)
{
QList<QPushButton*> list = this->findChildren<QPushButton*>(); // <- at this point list is empty.
};
bool event(QEvent *event)
{
if (event->type() == QEvent::ShowToParent)
{
QList<QPushButton*> list = this->findChildren<QPushButton*>(); // <- now list is populated.
//...
}
return QWidget::event(event);
}
}
When I call this->findChildren<QPushButton*>() from the subclass constructor, the QList is empty, probably because the buttons have not been added to it yet.
My question is: there's any event that is fired only once that indicates that a widget is 'ready'?
I mean, the widget has finished adding everything.
I'm also aware that at this point:
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
{
ui.setupUi(this);
QList<QPushButton*> list = ui.frame->findChildren<QPushButton*>();
}
The widget is 'ready', but I would like to identify it from inside of the subclass.
I did some tests with the event QEvent::ShowToParent but I'm not sure if this event is called only once during the entire application execution.
Related
When I enable a child of my QGroupBox after unchecking the group box, that child is enabled. But if I do the same to a grandchild widget, that widget remains disabled. I would expect all children of an unchecked group box to act the same as all children of a disabled parent widget (with respect to being enabled).
Gist of the code used to create the image above.
What should I do to ensure my child automatically remains disabled, even if I call child.setEnabled(true) after the group box is unchecked?
I am using Qt 5.9.1.
This appears to be a known bug.
At the moment, you need to know if the parent group box is checked to call setEnabled on a child widget:
child.setEnabled(groupBox.isChecked() && otherCondition);
That happens because a QGroupBox itself is not disabled when the checkbox is off, and thus normal widget enablement propagation doesn't apply to this behavior. This workaround is IMHO sensible. The only possible workaround via public APIs would be to add a viewport child widget in the groupbox, and make the everything a child of that viewport:
class GroupBoxViewport : public QWidget {
Q_OBJECT
void updateGeometry() {
if (parent())
setGeometry(parentWidget()->contentsRect());
}
void newParent() {
if (parent()) {
parent()->installEventFilter(this);
updateGeometry();
}
}
protected:
bool eventFilter(QObject *obj, QEvent *ev) override {
if (obj == parent() && ev->type() == QEvent::Resize)
updateGeometry();
return QWidget::eventFilter(obj, ev);
}
bool event(QEvent *ev) override {
if (ev->type() == QEvent::ParentAboutToChange) {
if (parent())
parent()->uninstallEventFilter(this);
} else if (ev->type() == QEvent::ParentChange)
newParent();
return QWidget::event(ev);
}
public:
QWidget(QWidget *parent = {}) : QWidget(parent) {
newParent();
}
};
Then, set the layout on and add all the children to the viewport:
int main(int argc, char *argv[]) {
QApplication app(argc, argv);
QGroupBox box("The Group");
GroupBoxViewPort viewport(&box);
QVBoxLayout layout(&viewport);
QLabel label("A, uh, member of the group");
layout.addwidget(&label);
box.show();
return app.exec();
}
I have a qt application with different widgets. All widgets should use the same data, so I declare a fixed list of classes for this data (different data from different events). The active data got selected with a QComboBox.
This data get passed as an reference to one widget, which contains a QtPlot with a QAbstractTableModel to visualize the data.
mainwindow.h:
MyClass currentData;
QList<MyClass> dataList;
plotWidgetPointer* plotWidget;
mainwindow.cpp:
MainWindow::MainWindow( QWidget* parent ) : QMainWindow( parent )
{
plotWidgetPointer = new plotWidget( currentData, this );
}
void MaindWindow::selectionChange(int index)
{
if( index != -1)
{
currentData = dataList.at( index );
}
}
plotWidget.h:
public:
explicit plotWidget(MyClass& myClass, QWidget* parent = 0);
plotWidget.cpp:
plotWidget::plotWidget(MyClass& myClass, QWidget* parent) : QWidget(parent)
{
}
After starting the programm and switching the data over the combobox the application crashes. Does anybody know why?
Thank you.
Something is fundamentally wrong with my eventFilter, as it lets every single event through, while I want to stop everything. I've read lots of documentation on QEvent, eventFilter() and so on, but obviously I'm missing something big. Essentially, I'm trying to create my own modal-functionality for my popup-window class based on QDialog. I want to implement my own since the built-in setModal(true) includes a lot of features, e.g. playing QApplication::Beep(), that I want to exclude. Basically, I want to discard all events going to the QWidget (window) that created my popup. What I have so far is,
// popupdialog.h
#ifndef POPUPDIALOG_H
#define POPUPDIALOG_H
#include <QDialog>
#include <QString>
namespace Ui {class PopupDialog;}
class PopupDialog : public QDialog
{
Q_OBJECT
public:
explicit PopupDialog(QWidget *window=0, QString messageText="");
~PopupDialog();
private:
Ui::PopupDialog *ui;
QString messageText;
QWidget window; // the window that caused/created the popup
void mouseReleaseEvent(QMouseEvent*); // popup closes when clicked on
bool eventFilter(QObject *, QEvent*);
};
...
// popupdialog.cpp
#include "popupdialog.h"
#include "ui_popupdialog.h"
PopupDialog::PopupDialog(QWidget *window, QString messageText) :
QDialog(NULL), // parentless
ui(new Ui::PopupDialog),
messageText(messageText),
window(window)
{
ui->setupUi(this);
setAttribute(Qt::WA_DeleteOnClose, true); // Prevents memory leak
setWindowFlags(Qt::Window | Qt::FramelessWindowHint);
ui->message_text_display->setText(messageText);
window->installEventFilter(this);
//this->installEventFilter(window); // tried this also, just to be sure ..
}
PopupDialog::~PopupDialog()
{
window->removeEventFilter(this);
delete ui;
}
// popup closes when clicked on
void PopupDialog::mouseReleaseEvent(QMouseEvent *e)
{
close();
}
Here's the problem, the filter doesn't work. Note that if I write a std::cout
inside the if(...), I see that it does trigger whenever events are sent to window, it just doesn't stop them.
bool PopupDialog::eventFilter(QObject *obj, QEvent *e)
{
if( obj == window )
return true; //should discard the signal (?)
else
return false; // I tried setting this to 'true' also without success
}
When the user interacts with the main program, a PopupDialog can be created like this:
PopupDialog *popup_msg = new PopupDialog(ptr_to_source_window, "some text message");
popup_msg->show();
// I understand that naming the source 'window' might be a little confusing.
// I apologise for that. The source can in fact be any 'QWidget'.
Everything else works as expected. Only the event filter fails. I want the filter to remove events sent to the window that created the popup; like mouse clicking and key pressing, until the popup is closed. I'm expecting to be extremely embarrassed when someone points out a trivial fix in my code.
You have to ignore all events that arrive in the widget tree of the window. Therefore, you need to install the eventFilter application-wide and check, if the object you are filtering on is a descendant of window. In other words: Replace
window->installEventFilter(this);
by
QCoreApplication::instance()->installEventFilter(this);
and implement the event filter function this way:
bool PopupDialog::eventFilter(QObject *obj, QEvent *e)
{
if ( !dynamic_cast<QInputEvent*>( event ) )
return false;
while ( obj != NULL )
{
if( obj == window )
return true;
obj = obj->parent();
}
return false;
}
I tried it, tested it and it worked for me.
Note: Using event filters in Qt is a bit messy in my experience, since it is not quite transparent what is happening. Expect bugs to pop up from time to time. You may consider disabling the main window instead, if you and your clients don't have a problem with the grayed-out main window as a consequence.
After the massive amount of responses, feedback, suggestions and time ivested in extensive research I've finally found what I believe to be the optimal, and safest solution. I wish to express my sincere gratidtude to everyone for their aid to what Kuba Ober describes as "(...) not as simple of a problem as you think".
We want to filter out all certain events from a widget, including its children. This is difficult, because events may be caught in the childrens default eventfilters and responded to, before they are caught and filtered by the the parent's custom filter for which the programmer implements. The following code solves this problem by installing the filter on all children upon their creation. This example assumes the use of Qt Creator UI-forms and is based on the following blog post: How to install eventfilters for all children.
// The widget class (based on QMainWindow, but could be anything) for
// which you want to install the event filter on, includings its children
class WidgetClassToBeFiltered : public QMainWindow
{
Q_OBJECT
public:
explicit WidgetClassToBeFiltered(QWidget *parent = 0);
~WidgetClassToBeFiltered();
private:
bool eventFilter(QObject*, QEvent*);
Ui::WidgetClassToBeFiltered *ui;
};
...
WidgetClassToBeFiltered::WidgetClassToBeFiltered(QWidget *parent) :
QMainWindow(parent), // Base Class constructor
ui(new Ui::WidgetClassToBeFiltered)
{
installEventFilter(this); // install filter BEFORE setupUI.
ui->setupUi(this);
}
...
bool WidgetClassToBeFiltered::eventFilter(QObject *obj, QEvent* e)
{
if( e->type() == QEvent::ChildAdded ) // install eventfilter on children
{
QChildEvent *ce = static_cast<QChildEvent*>(e);
ce->child()->installEventFilter(this);
}
else if( e->type() == QEvent::ChildRemoved ) // remove eventfilter from children
{
QChildEvent *ce = static_cast<QChildEvent*>(e);
ce->child()->removeEventFilter(this);
}
else if( (e->type() == QEvent::MouseButtonRelease) ) // e.g. filter out Mouse Buttons Relases
{
// do whatever ..
return true; // filter these events out
}
return QWidget::eventFilter( obj, e ); // apply default filter
}
Note that this works, because the eventfilter installs itself on added children! Hence, it should also work without the use of UI-forms.
Refer this code to filter out specific event:-
class MainWindow : public QMainWindow
{
public:
MainWindow();
protected:
bool eventFilter(QObject *obj, QEvent *ev);
private:
QTextEdit *textEdit;
};
MainWindow::MainWindow()
{
textEdit = new QTextEdit;
setCentralWidget(textEdit);
textEdit->installEventFilter(this);
}
bool MainWindow::eventFilter(QObject *obj, QEvent *event)
{
if (obj == textEdit) {
if (event->type() == QEvent::KeyPress) {
QKeyEvent *keyEvent = static_cast<QKeyEvent*>(event);
qDebug() << "Ate key press" << keyEvent->key();
return true;
} else {
return false;
}
} else {
// pass the event on to the parent class
return QMainWindow::eventFilter(obj, event);
}
}
If you want to set more specific event filter on multiple widgets you can refer following code:
class KeyPressEater : public QObject
{
Q_OBJECT
protected:
bool eventFilter(QObject *obj, QEvent *event);
};
bool KeyPressEater::eventFilter(QObject *obj, QEvent *event)
{
if (event->type() == QEvent::KeyPress) {
QKeyEvent *keyEvent = static_cast<QKeyEvent *>(event);
qDebug("Ate key press %d", keyEvent->key());
return true;
} else {
// standard event processing
return QObject::eventFilter(obj, event);
}
}
KeyPressEater *keyPressEater = new KeyPressEater(this);
QPushButton *pushButton = new QPushButton(this);
pushButton->installEventFilter(keyPressEater);
I want to create a combobox inside a message box and return the selected value to be used later.
I can do the same on the window itself but not sure how to do that inside a combobox.
MainWindow::MainWindow(QWidget *parent) :
QMainWindow(parent),
ui(new Ui::MainWindow)
{
ui->setupUi(this);
ui->comboBox->addItem("Red");
ui->comboBox->addItem("Blue");
ui->comboBox->addItem("Green");
ui->comboBox->addItem("Yellow");
ui->comboBox->addItem("Pink");
ui->comboBox->addItem("Purple");
}
MainWindow::~MainWindow()
{
delete ui;
}
void MainWindow::on_pushButton_clicked()
{
QMessageBox::about(this,"Choose color of rectangle", ui->comboBox->currentText() );
}
If I understand you correct you would like to show a combobox in a separate dialog window for the user to select some option.
One of the ways to do that, would be to subclass QDialog. If a combo field and a button to accept is sufficient the class could look as below:
class CustomDialog : public QDialog
{
public:
CustomDialog(const QStringList& items)
{
setLayout(new QHBoxLayout());
box = new QComboBox;
box->addItems(items);
layout()->addWidget(box);
QPushButton* ok = new QPushButton("ok");
layout()->addWidget(ok);
connect(ok, &QPushButton::clicked, this, [this]()
{
accept();
});
}
QComboBox* comboBox() { return box; }
private:
QComboBox* box;
};
To use the class object you can call exec to display it modally. Then you can verify whether the user accepted the choice by pressing the ok button and take proper action.
QStringList itemList({"item1", "item2", "item3"});
CustomDialog dialog(itemList);
if (dialog.exec() == QDialog::Accepted)
{
// take proper action here
qDebug() << dialog.comboBox()->currentText();
}
Similar approach is implemented in the QMessageBox class where a number of options can be specified to alter the displayed contents (for example button configuration or check box existance).
EDIT:
To use the sample code in your own project you should put the latter section I posted into your on_pushButton_clicked() slot. Substitute the itemList with your color names list. Then put the CustomDialog class to a separate file which you include in main and you should be good to go.
I have a parent widget inside which I have to place a custom widget (say a QFrame). Inside that custom widget, I have to place a number of child widgets (derived from QPushButton). I want the child widgets to have a certain background under normal circumstances, and another one when hovered upon. This is my code:
//parent widget code, where the QFrame derived widget is initialized
QFrameDerivedWidget *qFrameWidget = new QFrameDerivedWidget(this, someString);
This is the QFrameDerivedWidget header file:
//QFrameDerivedWidget header file
class QFrameDerivedWidget: public QFrame
{
Q_OBJECT
public:
QFrameDerivedWidget(QWidget *aParent,
std::string someValue);
bool eventFilter(QObject *obj, QEvent *event);
}
This is the QFrameDerivedWidget implementation file, the ChildWidget class is defined and declared inline:
class ChildWidget: public QPushButton
{
Q_Object
public:
ChildWidget(std::string aText, QWidget *aParent);
};
ChildWidget::ChildWidget(std::string aText, QWidget *aParent):
QPushButton(QString::fromStdString(aText),aParent)
{
this->setFixedHeight(30);
this->setMouseTracking(true);
this->setCursor(Qt::PointingHandCursor);
/* ---other custom styling--- */
}
bool QFrameDerivedWidget::eventFilter(QObject *obj, QEvent *event)
{
// this never prints out anything, even though it should for any mouseenter, mouseleave, click, etc event on it
qDebug() << obj->metaObject()->className() << endl;
if (obj->metaObject()->className() == "ChildWidget")
{
//including this line throws a 'missing QObject missing macro error' as well
ChildWidget *option = qobject_cast<ChildWidget* >(obj);
if (event->type() == QEvent::Enter)
{
option->setStyleSheet("---");
}
if (event->type() == QEvent::Leave)
{
option->setStyleSheet("---");
}
return QWidget::eventFilter(obj, event);
}
else
{
// pass the event on to the parent class
return QWidget::eventFilter(obj, event);
}
}
QFrameDerivedWidget::QFrameDerivedWidget(QWidget *aParent,
std::string someVal): fParent(aParent)
{
initUI();
}
QFrameDerivedWidget::initUI()
{
this->setParent(fParent);
this->setAttribute(Qt::WA_Hover);
this->setMouseTracking(true);
QWidget *dd = new QWidget(this);
QVBoxLayout *layout = new QVBoxLayout();
dd->setLayout(layout);
for (int i = 0; i < fValues.size(); i++)
{
ChildWidget *button = new ChildWidget(fValues[i],dd);
button->addEventFilter(this);
layout->addWidget(button);
}
}
The idea is, whenever I hover over the QFrameDerivedWidget and enter any ChildWidget, its background color should change. Additionally, I have set a qDebug() statement inside the eventFilter. It is currently not working, the ChildWidget buttons are not visible, but they are there, for the cursor turns pointer when I hover over where they are supposed to be.
What am I doing wrong, and how do I make it work?
You forget to add Q_OBJECT macro in ChildWidget declaration
You need to track mouse (setMouseTracking(true))
You need to set setAttribute(Qt::WA_Hover) to your widget
Be sure that you really need to return true; in your event filter, instead of returning QWidget::eventFilter(obj, event);. You don't need to filter out hover events.