I'm in need of a checkable QAction, that has besides the modes checked and unchecked the option of a partially check. This is basically already what QCheckBox provides, but unfortunately, QAction doesn't provide.
As a first attempt, I came up with the following approach by implementing a custom QWidgetAction.
TriState.h
#pragma once
#include <QWidgetAction>
#include <QCheckBox>
#include <QLabel>
#include <QFrame>
#include <QHBoxLayout>
class TriStateAction : public QWidgetAction {
Q_OBJECT
public:
TriStateAction(QWidget* parent=nullptr) : QWidgetAction(parent) {
mChkBox = new QCheckBox;
mChkBox->setTristate(true);
auto widget = new QFrame;
widget->setLayout(new QHBoxLayout);
widget->layout()->addWidget(mChkBox);
widget->layout()->addWidget(new QLabel("TriState"));
setDefaultWidget(widget);
connect(mChkBox, &QCheckBox::stateChanged, this, &QWidgetAction::changed);
}
void setCheckState(Qt::CheckState checkState) {
mChkBox->setCheckState(checkState);
}
Qt::CheckState checkState() const {
return mChkBox->checkState();
}
private:
QCheckBox* mChkBox{ nullptr };
};
With this a simple TestRunner:
main.cpp
#include <QApplication>
#include <QMenu>
#include <QAction>
#include "TriStateAction.h"
int main(int argc, char** args) {
QApplication app(argc, args);
auto label=new QLabel("Test");
label->setContextMenuPolicy(Qt::ContextMenuPolicy::CustomContextMenu);
label->connect(label, &QLabel::customContextMenuRequested, [&](const QPoint& point) {
QMenu menu(label);
auto globalPoint = label->mapToGlobal(point);
auto triStateAction = new TriStateAction();
auto normalAction = new QAction("Check");
normalAction->setCheckable(true);
normalAction->setChecked(true);
menu.addAction(triStateAction);
menu.addAction(normalAction);
menu.exec(globalPoint);
});
label->show();
app.exec();
}
Now, the context menu pops up and I can happily check, uncheck and partially check my TriState Action. But, unlike the ordinary QAction, the TriState will not close the menu upon interaction. How can this do?
Another issue is the different layout (visual representation) of my TriState Action. How can it be made more similar in comparison to the ordinary QAction? (Actually, this seems to be a very hard question.)
Let the action know its menu, adding this line in your main:
triStateAction->setMenu(&menu);
In TriStateAction class, add a slot to catch the check box stateChanged signal, and close the menu from there:
private slots:
void checkBoxStateChanged(int)
{
if (menu() != nullptr)
{
menu()->close();
}
}
Don't forget to connect the slot, in TriStateAction constructor:
connect(mChkBox, &QCheckBox::stateChanged, this, &TriStateAction::checkBoxStateChanged);
Related
I have a problem that, after closing a QDialog with web content, the QWebEngineProcess.exe process is not closing.
Here is a minimal example:
#include <QApplication>
#include <QWebEngineView>
#include <QWebEngineSettings>
#include <QDialog>
#include <QMainWindow>
#include <QLayout>
#include <QtGui>
#include <QPushButton>
class Dialog : public QDialog
{
public:
Dialog() : QDialog(nullptr)
{
resize(512, 512);
setAttribute(Qt::WA_DeleteOnClose);
auto verticalLayout = new QVBoxLayout(this);
verticalLayout->setSpacing(0);
verticalLayout->setContentsMargins(0, 0, 0, 0);
m_webView = new QWebEngineView(this);
verticalLayout->addWidget(m_webView);
}
void openPage(const QUrl& url)
{
m_webView->setUrl(url);
}
private:
QWebEngineView* m_webView;
};
class MainWindow : public QMainWindow
{
public:
MainWindow() : QMainWindow(nullptr)
{
resize(512, 512);
QPushButton* btn = new QPushButton("open web dialog", this);
connect(btn, &QPushButton::clicked, [this] ()
{
if (m_dialog == nullptr)
{
m_dialog = new Dialog();
m_dialog->openPage(QUrl("https://www.qt.io"));
m_dialog->show();
}
});
}
private:
QPointer<Dialog> m_dialog;
};
int main(int argc, char *argv[])
{
QCoreApplication::setOrganizationName("QtExamples");
QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
QApplication app(argc, argv);
MainWindow window;
window.show();
return app.exec();
}
I'm expecting that, after the dialog is closed, QWebEngineProcess.exe will be closed too, because I'm not using webengine anymore.
P.S. During opening WebPage, I have 2 QWebEngineProcess.exe. One is disappearing, but the second one left.
For any QObject instances (such as QDialog and QWebEngineView), the destructor of a parent object destroys all child objects. This means, for every object you new, it must either have a parent, or you must delete it manually, otherwise you are creating a leak and objects will remain open (not deleted).
You are creating a QWebEngineView object with the correct parent, but the parent (the Dialog) doesn't have a parent (you initialize the parent to nullptr). This means the Dialog would delete the QWebEngineView, except it never is deleted itself.
To fix it, either delete the Dialog (stored in m_dialog) manually in the MainWindow destructor (not recommended), or initialize it correctly with the MainWindow as its parent.
This may also be related to a bug in Qt 5.11 and 5.12. If this is the problem, the recommended solution is to add QCoreApplication::setAttribute(Qt::AA_UseOpenGLES); at the top of your main() function (before declaring the QApplication).
I have a ComboBox and set it to be edited.
QComboBox *myCombo = new QComboBox(this);
myCombo->setEditable(true);
myCombo->setStyleSheet("QComboBox::down-arrow{image: url(:/bulb.png);}");
myCombo->setCursor( QCursor( Qt::PointingHandCursor ) );
So now when i click onto the editing field, nothing happen. But what I need is, when I click onto the bulb (which is the down-arrow), something (like a table or a dialog....) should be appeared. How can I recognize this click event in this case? I looked at the list of signals for combo box but could not find any signal for that.
By overwriting the mousePressEvent() method you must use hitTestComplexControl() method to know that QStyle::SubControl has been pressed by issuing a signal if it is QStyle::SC_ComboBoxArrow.
#include <QtWidgets>
class ComboBox: public QComboBox
{
Q_OBJECT
public:
using QComboBox::QComboBox;
signals:
void clicked();
protected:
void mousePressEvent(QMouseEvent *event) override{
QComboBox::mousePressEvent(event);
QStyleOptionComboBox opt;
initStyleOption(&opt);
QStyle::SubControl sc = style()->hitTestComplexControl(QStyle::CC_ComboBox, &opt, event->pos(), this);
if(sc == QStyle::SC_ComboBoxArrow)
emit clicked();
}
};
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
ComboBox w;
w.setEditable(true);
w.setStyleSheet("QComboBox::down-arrow{image: url(:/bulb.png);}");
QObject::connect(&w, &ComboBox::clicked, [](){
qDebug()<<"clicked";
});
w.show();
return a.exec();
}
#include "main.moc"
Although showPopup() is a possible option this can be called directly without the down-arrow being pressed, for example by calling it directly: myCombo->showPopup() so it is not the correct option.
A possible solution is to subclass QComboBox and reimplement showPopup() virtual method:
.h:
#ifndef COMBOBOXDROPDOWN_H
#define COMBOBOXDROPDOWN_H
#include <QComboBox>
#include <QDebug>
class ComboBoxDropDown : public QComboBox
{
public:
ComboBoxDropDown(QWidget *parent = nullptr);
void showPopup() override;
};
#endif // COMBOBOXDROPDOWN_H
.cpp:
#include "comboboxdropdown.h"
ComboBoxDropDown::ComboBoxDropDown(QWidget *parent)
: QComboBox (parent)
{
}
void ComboBoxDropDown::showPopup()
{
//QComboBox::showPopup();
qDebug() << "Do something";
}
I made class inherited from QLabel. This class also have public slot, that should change label caption. I "call" this SLOT with clicked() SIGNAL of button.
So nothing happened when I press the button.
#include <QApplication>
#include <QLabel>
#include <QPushButton>
class Label : public QLabel
{
public:
Label(QString a) : QLabel(a){}
public slots:
void change()
{
this->setNum(2);
}
};
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
QPushButton* button = new QPushButton("Button");
Label* lbl = new Label("Label");
button->show();
lbl->show();
QObject::connect(button, SIGNAL(clicked(bool)), lbl, SLOT(change()));
return a.exec();
}
What should I do to change caption from slot?
In order for the signals and slots to be recognized, the classes must use the Q_OBJECT macro in the private part.
Another thing to do is to include "main.moc", for more information on this point read this.
#include <QApplication>
#include <QLabel>
#include <QPushButton>
class Label : public QLabel
{
Q_OBJECT
public:
Label(const QString &text, QWidget *parent = Q_NULLPTR, Qt::WindowFlags f = Qt::WindowFlags()) :
QLabel(text, parent, f){}
public slots:
void change()
{
setNum(2);
}
};
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
QPushButton* button = new QPushButton("Button");
Label* lbl = new Label("Label");
button->show();
lbl->show();
QObject::connect(button, SIGNAL(clicked()), lbl, SLOT(change()));
return a.exec();
}
#include "main.moc"
At the end of making these changes you must execute the following:
Press clean all in the Build menu.
then run qmake in the same menu.
And you just compose your project.
Add Q_OBJECT after
class Label : public QLabel
{
and then you should
either place your Label class declaration to a .h file or write #include "main.moc" after main function declaration.
try to get the return value from your connect call an check it for true or false.
Add Q_OBJECT Macro to the beginning of your derived class.
Add some debug output to your slot like
qDebug()<<"This is my slot.";
Maybe this would help to get a little further.
Best regards
I am trying to make a ruler in Qt. I am developing an application which requires a line edit in which the user can select any part of the string. When the selection is done, the start and end co-ordinates of the selection are passed to the backend.
The ruler which is shown with the line edit has a precision of one character or blank space in the string.
I have built a similar widget using Java and ExtJS some time ago. I am trying to simulate the same with Qt for quite some time now but not succeeding in doing so.
Please take a look at the image to understand what it looks like. I want to know whether it is possible or not in Qt. If it is possible what widget should I use to achieve it?
I am not 100% sure I understand your question correctly.
You can write a class inheriting from QLineEdit to emit signals when its selection has changed.
Below is a simple example of such a class: it takes the native signal selectionChanged and has two new signals which emit information about the changed text just to show what could be possible. I also include a simple dialog class to show how to use this (even if it's just a crude way).
lineedit.h
#ifndef MYLINEEDIT_H
#define MYLINEEDIT_H
#include <QLineEdit>
class LineEdit: public QLineEdit
{
Q_OBJECT
public:
LineEdit(QWidget *parent = 0): QLineEdit(parent)
{
connect(this, SIGNAL(selectionChanged()), this, SLOT(textSelectionChanged() ));
}
public slots:
void textSelectionChanged() {
emit selectionChanged(this->selectedText());
emit selectionChanged(this->selectionStart(), this->selectedText().length() );
}
signals:
void selectionChanged(const QString&);
void selectionChanged(int , int );
};
#endif
dialog.h
#ifndef MYDIALOG_H
#define MYDIALOG_H
#include <QDialog>
#include <QtCore>
class Dialog : public QDialog
{
Q_OBJECT
public:
Dialog(QWidget *parent = 0) : QDialog(parent) {}
public slots:
void textChanged(const QString &string) {
qDebug() << string;
}
void textChanged(int start, int length) {
qDebug() << "Start"<< start << ", length: " << length;
}
};
#endif
main.cc (for completeness)
#include <QApplication>
#include "dialog.h"
#include "lineedit.h"
#include <QHBoxLayout>
int main(int argc, char** argv)
{
QApplication app(argc, argv);
Dialog dialog;
QHBoxLayout *layout = new QHBoxLayout(&dialog);
LineEdit lineedit;
layout->addWidget(&lineedit);
QObject::connect(&lineedit,SIGNAL(selectionChanged(QString)), &dialog, SLOT(textChanged(QString)));
QObject::connect(&lineedit,SIGNAL(selectionChanged(int,int)), &dialog, SLOT(textChanged(int,int)));
dialog.show();
return app.exec();
}
Let me know if this helps.
I am having difficulty in my Qt program with connecting button signals to my slots. My code is:
Main.cpp
#include <QtGui/QApplication>
#include "MainWidget.h"
int main(int argc, char *argv[])
{
QApplication app(argc, argv);
MainWidget mainWidget;
mainWidget.show();
return app.exec();
}
MainWidget.h
#ifndef MAINWIDGET_H
#define MAINWIDGET_H
#include <QWidget>
class MainWidget : public QWidget
{
public:
MainWidget();
public slots:
void bAdvice_clicked();
void bWeather_clicked();
void bNextMeeting_clicked();
void bQuit_clicked();
};
#endif // MAINWIDGET_H
MainWidget.cpp
#include "MainWidget.h"
#include <QMessageBox>
#include <QPushButton>
#include <QTextEdit>
#include <QVBoxLayout>
MainWidget::MainWidget()
{
QLayout *layout = new QVBoxLayout();
this->setLayout(layout);
QTextEdit *message = new QTextEdit();
layout->addWidget(message);
QPushButton *bAdvice = new QPushButton("Advice");
connect(bAdvice, SIGNAL(clicked()), this, SLOT(bAdvice_clicked()));
layout->addWidget(bAdvice);
QPushButton *bWeather = new QPushButton("Weather");
connect(bWeather, SIGNAL(clicked()), this, SLOT(bWeather_clicked()));
layout->addWidget(bWeather);
QPushButton *bNextMeeting = new QPushButton("Next Meeting");
connect(bNextMeeting, SIGNAL(clicked()), this, SLOT(bNextMeeting_clicked()));
layout->addWidget(bNextMeeting);
QPushButton *bQuit = new QPushButton("Quit");
connect(bQuit, SIGNAL(clicked()), this, SLOT(bQuit_clicked()));
layout->addWidget(bQuit);
}
void MainWidget::bAdvice_clicked()
{
}
void MainWidget::bWeather_clicked()
{
}
void MainWidget::bNextMeeting_clicked()
{
QMessageBox::information(this, "Next Meeting", "Today", QMessageBox::Ok);
}
void MainWidget::bQuit_clicked()
{
this->close();
}
The program outputs the following:
Starting C:\Users\Sameer\Documents\PartAQuestion2\debug\PartAQuestion2.exe...
Object::connect: No such slot QWidget::bAdvice_clicked() in MainWidget.cpp:16
Object::connect: No such slot QWidget::bWeather_clicked() in MainWidget.cpp:20
Object::connect: No such slot QWidget::bNextMeeting_clicked() in MainWidget.cpp:24
Object::connect: No such slot QWidget::bQuit_clicked() in MainWidget.cpp:28
C:\Users\Sameer\Documents\PartAQuestion2\debug\PartAQuestion2.exe exited with code 0
The code seems right, no compiler warnings. Just this output at runtime. But it looks like I hooked the signals and slots up correctly.
Add Q_OBJECT to your class, like this:
class MainWidget : public QWidget
{
Q_OBJECT
You also have to run moc to generate some helper code. qmake does that automatically for your, but if you compile this yourself, you need to run moc.
When I started with Qt, I had this problem a lot. As I see it your slots are defined wrong. If you look at the signature for the signal (Qt Clicked Signal Docs), you will see that the argument list is (bool clicked = false).
The way Qt's signal & slots connect work at run time, is that it will only connect the signal and slot if they have the exact same signatures. If they don't match exactly, no connection.
so in MainWidget.h
public slots:
void bAdvice_clicked(bool);
In MainWidget.cpp
connect(bAdvice, SIGNAL(clicked(bool)), this, SLOT(bAdvice_clicked(bool)));
Things will start working for you.
Edited:
Compiled your code and all the slots were correctly called.
It was just the Q_OBJECT macro that was missing.