I am developing a GUI application in Qt, and I have some difficulties embedding a custom widget in my ui. From Qt's documentation I can see it is possible to promote such a widget. However, I am still a little confused about how this should be done.
My widget QTreeWidget is heavily inspired by Qt's torrent example, where I want to embed this in my application:
So I have for my FilesView class (not included the src code, because it is trivial):
#include <QTreeWidget>
#include <QUrl>
#include <QFile>
#include <QDragMoveEvent>
#include <QDropEvent>
// FilesView extends QTreeWidget to allow drag and drop.
class FilesView : public QTreeWidget
{
Q_OBJECT
public:
FilesView(QWidget *parent = 0);
signals:
void fileDropped(const QString &fileName);
protected:
void dragMoveEvent(QDragMoveEvent *event);
void dropEvent(QDropEvent *event);
};
To this is a TorrentViewDelegate class (comment the progressbar for testing purposes)
#include <QItemDelegate>
#include <QMainWindow>
#include <QApplication>
// TorrentViewDelegate is used to draw the progress bars.
class TorrentViewDelegate : public QItemDelegate
{
Q_OBJECT
public:
inline TorrentViewDelegate(QMainWindow *mainWindow) : QItemDelegate(mainWindow) {}
inline void paint(QPainter *painter, const QStyleOptionViewItem &option,
const QModelIndex &index ) const
{
if (index.column() != 2) {
QItemDelegate::paint(painter, option, index);
return;
}
// Set up a QStyleOptionProgressBar to precisely mimic the
// environment of a progress bar.
QStyleOptionProgressBar progressBarOption;
progressBarOption.state = QStyle::State_Enabled;
progressBarOption.direction = QApplication::layoutDirection();
progressBarOption.rect = option.rect;
progressBarOption.fontMetrics = QApplication::fontMetrics();
progressBarOption.minimum = 0;
progressBarOption.maximum = 100;
progressBarOption.textAlignment = Qt::AlignCenter;
progressBarOption.textVisible = true;
// Set the progress and text values of the style option.
//int progress = qobject_cast<MainWindow *>(parent())->clientForRow(index.row())->progress();
int progress = 40;
progressBarOption.progress = progress < 0 ? 0 : progress;
progressBarOption.text = QString().sprintf("%d%%", progressBarOption.progress);
// Draw the progress bar onto the view.
QApplication::style()->drawControl(QStyle::CE_ProgressBar, &progressBarOption, painter);
}
};
In the example the embed the widget in MainWindow as:
filesView = new FilesView(this);
filesView->setItemDelegate(new TorrentViewDelegate(this));
filesView->setHeaderLabels(headers);
filesView->setSelectionBehavior(QAbstractItemView::SelectRows);
filesView->setAlternatingRowColors(true);
filesView->setRootIsDecorated(false);
ui->verticalLayout_Filebox->addWidget(filesView);
How can I do this from the Qt designer?
Place an empty widget where you want to have your FilesView
Right click on it and select Promote to
Set the promoted class name to FilesView press Add and then Promote
You cannot set the delegate from QtDesigner
For more info have a look here:
http://qt-project.org/doc/qt-4.8/designer-using-custom-widgets.html
The second option you have is to create a plugin for your widget which will allow you to set its properties through designer. If you are not going to use your widget multiple times I do not suggest it. For more details check the following link:
http://qt-project.org/doc/qt-4.8/designer-creating-custom-widgets.html
Related
I work with QT-Creator 4.9.1 based on Qt 5.12.3 and I am making a gui for a touch terminal. I have a stacked widget with multiple LineEdit widgets inside on different pages. The problem i have, is that my text from the keyboard should be shown inside the LineEdit of my MainWindow.
Question:
How can I determine which LineEdit called my touchkeyboard and how can i insert the pressed key inside my LineEdit in the MainWindow when my touchkeyboard dialog is modal?
Touch-Keyboard Dialog:
Example for one Stackwidget Page:
when creating QLineEdit you need set ID , Like that
#include <QLineEdit>
class MyLineEdit : public QLineEdit
{
Q_OBJECT
public:
MyLineEdit(int id = 0, QWidget* parent = nullptr);
int id() const;
private:
int m_id;
};
MyLineEdit::MyLineEdit(int id, QWidget *parent)
:QLineEdit (parent)
,m_id(id)
{
}
int MyLineEdit::id() const
{
return m_id;
}
after that in the slot you can find out through id which one QLineEdit gave the signal
connect(myLineEdit, SIGNAL(textChanged(const QString &)), this, SLOT(customSlot(const QString &)));
or also use lambda expression
connect(myLineEdit, &QLineEdit::textChanged,[this](const QString & txt){
// Touch-Keyboard Dialog
});
I have a simple widget which uses an image as background and contains some child widgets. When I create it without a parent (as a dialogue) everything is perfect. But if I create it as a child of some other widget, I can't see the background.
Can I use QWidget::setPalette to set the background for a child widget?
If not, how would you accomplish this?
#include <QWidget>
#include <QPixmap>
#include <QPalette>
#include <QLabel>
class Panel : public QWidget
{
Q_OBJECT
public:
explicit Panel(QWidget *parent = 0) QWidget(parent)
{
bgnd_ = new QPixmap(":/path/to/image.png");
PaintBackground();
QLabel* lbl = new QLabel("SomeChild",this);
}
private:
void PaintBackground()
{
QPixmap bgnd = bgnd_->scaled(this->size(), Qt::IgnoreAspectRatio);
QPalette palette;
palette.setBrush(QPalette::Background, bgnd);
this->setPalette(palette);
}
protected:
void resizeEvent(QResizeEvent *event)
{
QWidget::resizeEvent(event);
PaintBackground();
}
private:
QPixmap* bgnd_;
};
If I create this widget as an independent object with no parent, then it will render fine. I see the background and the child widget. If I create this widget as a child of another widget, then I see the lowest-level child, but the background is empty.
#include <QMainWindow>
#include "panel.h"
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
explicit MainWindow(QWidget *parent = 0) :
QMainWindow(parent)
{
Panel* solo = new Panel();
solo->show();
Panel* child = new Panel(this);
}
};
The above class instantiates the widget in 2 ways: solo gives me a dialogue with everything looking perfect. child lets me see Panel's child widgets, but the background is white.
Troubleshooting details
I thought this could be a bug in Qt as described here so I tried filtering out ThemeChange events by reimplementing the following in both Panel and MainWindow.
bool event(QEvent *event) override
{
if (event->type() != QEvent::ThemeChange)
{
return QWidget::event(event);
}
return true;
}
That didn't help.
Instead of painting my background with a QPalette in resizeEvent I found that the answer was to paint it with QPainter in paintEvent.
#include <QWidget>
#include <QPixmap>
#include <QPainter>
#include <QLabel>
class Panel : public QWidget
{
Q_OBJECT
public:
explicit Panel(QWidget *parent = 0) : QWidget(parent)
{
bgnd_ = new QPixmap(":/path/to/image.png");
QLabel* lbl = new QLabel("Hello",this);
}
protected:
void paintEvent( QPaintEvent* e )
{
QPainter painter( this );
painter.drawPixmap( 0, 0, bgnd_->scaled(size(), Qt::IgnoreAspectRatio));
QWidget::paintEvent( e );
}
private:
QPixmap* bgnd_;
};
I wrote a minimal working example of the problem and I believe it might be a Qt bug. But just in case I wanted to ask.
Here are My classes:
mydialog.h
#include <QDialog>
#include <QVBoxLayout>
#include <QLabel>
class MyDialog : public QDialog
{
public:
MyDialog(QWidget *parent = 0);
};
mydialog.cpp
#include "mydialog.h"
MyDialog::MyDialog(QWidget *parent):QDialog(parent)
{
QLabel *label = new QLabel("Some random dialog",this);
QVBoxLayout *layout = new QVBoxLayout();
layout->addWidget(label);
this->setLayout(layout);
}
myitem.h
#include <QGraphicsTextItem>
#include <QPainter>
#include <QDebug>
#include "mydialog.h"
class MyItem : public QGraphicsItem
{
public:
MyItem();
void paint(QPainter * painter, const QStyleOptionGraphicsItem * option, QWidget * widget = 0);
QRectF boundingRect() const {return boundingBox;}
void setMyDialog(MyDialog *d){ dialog = d; }
private:
QRectF boundingBox;
MyDialog *dialog;
protected:
void mousePressEvent(QGraphicsSceneMouseEvent *e);
};
myitem.cpp
MyItem::MyItem()
{
boundingBox = QRectF(0,0,200,100);
}
void MyItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget){
painter->setBrush(QBrush(Qt::red));
painter->drawRect(boundingBox);
}
void MyItem::mousePressEvent(QGraphicsSceneMouseEvent *e){
//dialog->exec(); // BUG
//dialog->open(); // BUG
dialog->show(); // WORKS!
}
test.h
#include "myitem.h"
namespace Ui {
class Test;
}
class Test : public QMainWindow
{
Q_OBJECT
public:
explicit Test(QWidget *parent = 0);
~Test();
protected:
void resizeEvent(QResizeEvent *e);
private:
Ui::Test *ui;
MyDialog *diag;
};
And test.cpp
Test::Test(QWidget *parent) :
QMainWindow(parent),
ui(new Ui::Test)
{
ui->setupUi(this);
ui->graphicsView->setScene(new QGraphicsScene(this));
diag = new MyDialog(this);
}
void Test::resizeEvent(QResizeEvent *e){
ui->graphicsView->setSceneRect(0,0,ui->graphicsView->width(),ui->graphicsView->height());
ui->graphicsView->scene()->clear();
MyItem *item = new MyItem();
item->setMyDialog(diag);
ui->graphicsView->scene()->addItem(item);
}
Test::~Test()
{
delete ui;
}
So here is what happens (tested on Qt 5.7 and Qt 5.6). If the dialog is opened with either exec or open then, after it is closed ALL further mouse clicks ANYWHERE on the screen will open up the dialog again, making it impossible to interact with anything else drawn in there. This happens ONLY after it is opened for the first time. If i resize the screen, the item is recreated and I can click normally again. If I again click on the red box, then again all further clicks anywhere on the screen open up the dialog
However if the Dialog is opened by show, then it works as expected, only showing again if I click on the red rectangle.
Now the obvious problem is that exec make the dialog block execution until it is closed, but show doesn't. I can program around this using signals, but my question is why? and Is this a bug?
It seems that MyItem's reimplementation of mousePressEvent needs some behavior provided by default implementation. Here is the code, works fine in my machine:
void MyItem::mousePressEvent(QGraphicsSceneMouseEvent *event){
dialog->exec(); // WORKS
//dialog->open(); // WORKS
//dialog->show(); // WORKS
QGraphicsItem::mousePressEvent(event);
}
I am saving an image of a QQuickWidget with several QML children but all I have is a blank image.
C++ side:
QQuickWidget* content..
content->setSource(QUrl("qml:/main.qml"));
QPixmap *pm = content->grab(QRect(QPoint(0,0),QSize(-1,-1));
pm->save("someFilename.png", 0, 100);
QML side:
Rectangle{ width: 5; height: 5; color: "yellow"; objectname: "rootobj"}
In the QML I wish to dynamically add children and be able to show them in the image. I have tried QQuickWindow grabWindow method with a connection to a slot and it works but it captures only the window visible area and I need to capture the whole QML.
I believe this is not rocket science just that I am not getting it somewhere. Thanks for your replies!
Addendum:
Ok, I do not think its the issue of before/after rendering since I can see all the qml children before I call the picture grabber. So sorry for not being precise.
c++ side:
QQuickWidget* content..
content->setSource(QUrl("qml:/main.qml"));
//do all my dynamic qml children adding
After I can visually see all my qml:
QPixmap *pm = content->grab(QRect(QPoint(0,0),QSize(-1,-1));
pm->save(....
Unless I am wrong, I dont think its rendering issue. Thank you!
Issue is like Mido said. You can solve it like follows.
Create a class Viewer:
viewer.h
class Viewer : public QQuickView{
Q_OBJECT
public:
explicit Viewer(QWindow *parent = 0);
Viewer(bool showBar);
virtual ~Viewer();
void setMainQmlFile(const QString file);
void addImportPath(const QString path);
public slots:
void beforeRendering();
void afterRendering()
}
Viewer.cpp
#include "viewer.h"
Viewer::Viewer(QWindow *parent)
: QQuickView(parent)
{
setWidth(800);
setHeight(480);
connect(this, SIGNAL(beforeRendering()), this, SLOT(beforeRendering()));
connect(this, SIGNAL(afterRendering()), this, SLOT(afterRendering()));
}
void Viewer::setMainQmlFile(const QString file)
{
setSource(QUrl::fromLocalFile(file));
}
void Viewer::addImportPath(const QString path)
{
engine()->addImportPath(path);
}
void Viewer::beforeRendering()
{
//
}
void Viewer::afterRendering()
{
//grab window
QImage img = this->grabWindow();
img.save(path);
//or your code
}
main.cpp
Viewer *viewer = new Viewer;
//
///
//
viewer->setMainQmlFile(QStringLiteral("qml/main.qml"));
viewer->show();
I think your issue is that the capture screen is done before the rendering of the QML object.
In order to make it work you should connect the grab of the signal after rendering signal:
connect(this, SIGNAL(beforeRendering()), this, SLOT(sltBeforeRendering()));
connect(this, SIGNAL(afterRendering()), this, SLOT(sltAfterRendering()));
do the grab in sltAfterRendering slot.
To grab screen I use the grabWindow() function and I call it from QML.
It depends on the behaviour that you want from your software.
Try this:
grabber.h
#ifndef GRABBER_H
#define GRABBER_H
#include <QObject>
#include <QImage>
#include <QQuickView>
class Grabber : public QObject
{
Q_OBJECT
public:
explicit Grabber(QObject *parent = 0);
Grabber(QQuickView *view);
~Grabber();
Q_INVOKABLE void capture(QString const &path) const;
signals:
public slots:
private:
QQuickView* view_;
};
#endif // GRABBER_H
grabber.cpp
#include "grabber.h"
Grabber::Grabber(QObject *parent) :
QObject(parent)
{
}
Grabber::Grabber(QQuickView* view) :
view_(view)
{
}
Grabber::~Grabber()
{
if(view_ != NULL)
{
delete view_;
view_ = NULL;
}
}
void Grabber::capture(QString const &path) const
{
QImage img = view_->grabWindow();
img.save(path);
}
main.cpp
#include <QtGui/QGuiApplication>
#include "qtquick2applicationviewer.h"
#include <QQmlContext>
#include <QQmlEngine>
#include "grabber.h"
int main(int argc, char *argv[])
{
QGuiApplication app(argc, argv);
QtQuick2ApplicationViewer *viewer = new QtQuick2ApplicationViewer;
Grabber * grab = new Grabber(viewer);
viewer->setHeight(480);
viewer->setWidth(800);
viewer->rootContext()->setContextProperty("grab", grab);
viewer->setMainQmlFile(QStringLiteral("qml/main.qml"));
viewer->showExpanded();
return app.exec();
}
Call it from QML with:
grab.capture(path + "imageName.png")
I have built an app in Qt that contains two buttons: an exit button and an import button. When the import button is pushed, a list of buttons is shown in a scrollarea on the screen (the file loggers.csv contains the data 1;2;3;4;5;).
It all works fine, but when I push the exit button (which of course should close everything), the app is not stopped properly (the stop button of Qt is still active, and the play button isn't). When I run the debugger and push the exit button it gives an error: Invalid address specified to RtlFreeHeap( 0ADF0000, 0028FE40 ). Can anybody help me?
main
#include <QtGui/QApplication>
#include "mainwindow.h"
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
MainWindow w;
w.showFullScreen();
return a.exec();
}
Mainwindow.h
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include <QMainWindow>
#include <QtGui>
#include "logger.h"
namespace Ui {
class MainWindow;
}
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
explicit MainWindow(QWidget *parent = 0);
~MainWindow();
QPushButton exit_btn;
QPushButton import_btn;
private slots:
void createMenus();
void exit();
void import();
private:
int window_width;
int window_height;
int numLoggers;
int numSelected;
QVector<Logger*> loggers;
QScrollArea * scroll_area;
QVBoxLayout scrollLayout;
QWidget viewport;
Ui::MainWindow *ui;
};
#endif // MAINWINDOW_H
Mainwindow.cpp:
#include "mainwindow.h"
#include "ui_mainwindow.h"
#include "QtGui"
MainWindow::MainWindow(QWidget *parent) :
QMainWindow(parent),
ui(new Ui::MainWindow)
{
ui->setupUi(this);
window_width = QApplication::desktop()->width();
window_height = QApplication::desktop()->height();
createMenus();
connect(&exit_btn,SIGNAL(clicked()),this,SLOT(exit()));
connect(&import_btn,SIGNAL(clicked()),this,SLOT(import()));
}
MainWindow::~MainWindow()
{
delete ui;
}
void MainWindow::createMenus()
{
import_btn.setParent(ui->centralWidget);
import_btn.setGeometry(400,300,100,100);
import_btn.setText("IMPORT");
exit_btn.setText("EXIT");
exit_btn.setParent(ui->centralWidget);
exit_btn.setGeometry(window_width-50,12,32,32);
viewport.setLayout(&scrollLayout);
viewport.resize(0,0);
scroll_area = new QScrollArea(ui->centralWidget);
scroll_area->setGeometry(0,66,317,window_height-116);
scroll_area->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
scroll_area->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
scroll_area->setWidget(&viewport);
scroll_area->setGeometry(0,97,317,window_height-228);
scrollLayout.setMargin(0);
scrollLayout.setSpacing(0);
}
void MainWindow::exit()
{
close();
qApp->quit();
}
void MainWindow::import()
{
numSelected=0;
QFile f("Loggers3.csv");
if (f.open(QIODevice::ReadOnly))
{
numLoggers=0;
QString data;
data = f.readAll();
QStringList vals = data.split(';');
while(vals.size()>=1)
{
Logger * logger = new Logger;
logger->setNumber(vals[0].toInt());
vals.removeAt(0);
loggers<<logger;
numLoggers++;
}
f.close();
for(int i=0; i<numLoggers;i++)
{
loggers[i]->createButtons();
scrollLayout.addWidget(loggers[i]->button);
}
viewport.resize(367,numLoggers*60);
}
}
logger.h
#ifndef LOGGER_H
#define LOGGER_H
#include <QtGui>
class Logger : public QWidget
{
Q_OBJECT
public:
explicit Logger(QWidget *parent = 0);
~Logger();
int number;
QLabel num;
QToolButton * button;
bool checked;
signals:
public slots:
void setNumber(int number);
void createButtons();
};
#endif // LOGGER_H
logger.cpp
#include "logger.h"
#include <QtGui>
Logger::Logger(QWidget *parent) :
QWidget(parent)
{
button = new QToolButton;
button->setCheckable(true);
button->setMinimumSize(317,60);
button->setStyleSheet("QToolButton{background-image: url(images/btn_bg); border:none}");
}
Logger::~Logger()
{
}
void Logger::setNumber(int logNumber)
{
number=logNumber;
}
void Logger::createButtons()
{
QLayout * layout = new QHBoxLayout;
QSpacerItem *spacer = new QSpacerItem(120, 31, QSizePolicy::Maximum, SizePolicy::Maximum);
num.setStyleSheet("color: white; font: bold 16px");
num.setText(QString::number(number));
layout->addWidget(&num);
layout->addItem(spacer);
button->setLayout(layout);
}
I'm not entirely certain about what you are trying to achieve... but your problem lies with these two lines:
viewport.setLayout(&scrollLayout);
viewport.resize(0,0);
In the documentation for the QWidget class it states that:
If there already is a layout manager installed on this widget, QWidget
won't let you install another. You must first delete the existing layout manager (returned by layout()) before you can call setLayout() with the new layout.
This is where your problem lies. Don't believe me, add this check before those two lines of code.
if(layout()){
qDebug() << "Another layout exists";
}
Source: QVBoxLayout Class Reference
The QVBoxLayout class lines up widgets vertically.
This class is used to construct vertical box layout objects. See QBoxLayout for details.
The simplest use of the class is like this:
QWidget *window = new QWidget;
QPushButton *button1 = new QPushButton("One");
QPushButton *button2 = new QPushButton("Two");
QPushButton *button3 = new QPushButton("Three");
QPushButton *button4 = new QPushButton("Four");
QPushButton *button5 = new QPushButton("Five");
QVBoxLayout *layout = new QVBoxLayout;
layout->addWidget(button1);
layout->addWidget(button2);
layout->addWidget(button3);
layout->addWidget(button4);
layout->addWidget(button5);
window->setLayout(layout);
window->show();
First, we create the widgets we want in the layout. Then, we create the QVBoxLayout object and add the widgets into the layout. Finally, we call QWidget::setLayout() to install the QVBoxLayout object onto the widget. At that point, the widgets in the layout are reparented to have window as their parent.
Critical source of error in your project:
Widgets should be constructed on the heap because they will be deleted automatically when their parents are deleted. You have a custom widget class that you instantiate on the heap. The members should also go on the heap. Also, you should consider using the parent /child hierarchy in your GUI code to ensure proper memory management and proper deletion.
In my experience, if your program stops in RtlFreeHeap it is a good sign of memory corruption.
When calling
import_btn.setParent(ui->centralWidget);
centralWidget takes ownership of import_btn. That means, when centralWidget is deleted (which happens as part of delete ui;in your MainWindow's destructor), it will call delete on your member variable!
This leads to the reported memory corruption.
You need to allocate your QPushButton's dynamically, not as a plain member variable. So make them QPushButton*.
Here's how I did it from mainwindow.cpp, thanks to and this question: How to create a correct exit button in qt
QPushButton * quit_btn = new QPushButton(this);
quit_btn->setGeometry(540,440,93,27);
quit_btn->setText("Exit");
QObject::connect(quit_btn,SIGNAL(clicked()),qApp,SLOT(quit()));
Works flawlessly :D