Custom Widgets which inherits from QTreeWidget crashes while destructing? - c++

Recently I've met some problems about QT.As we have a lot of custom widgets (inherit from QT's basic widgets such as QWidget,QTreeWidget...),they worked fine at runtime,BUT,IT IS EASY TO CRASH when exiting program or destructing them,I don't know IF THERE ANYTHING WRONG or inappropriate while implementing a widget which inherits from a basic widget.
Here is an example:
ChatTreeWidget.cpp:
#include "ChatTreeWidget.h"
#include <QPicture>
#include <QDebug>
ChatTreeWidget::ChatTreeWidget(QWidget *parent)
:QTreeWidget(parent)
{
setRootIsDecorated(false);
setIndentation(0);
setHeaderHidden(true);
setIconSize(QSize(30, 30));
setFocusPolicy(Qt::NoFocus);
setObjectName(QString("chatTreeWidget"));
setStyleSheet("#chatTreeWidget{border:0px;}"
"QTreeWidget::item{border-bottom:1px solid #d9d9d9;padding-left:0px;}"
"QTreeWidget::item:hover{background-color: #def0ff;}"
"QTreeWidget::item:selected{background-color:#c2e2fd;}");
connect(this, &QTreeWidget::itemExpanded, this, [=](QTreeWidgetItem *item)
{
auto indexvar = item->data(0, Qt::UserRole).toInt();
indexvar -= 10000;
if (indexvar > m_iconList.size() || indexvar < 0)
return;
auto lbl = m_iconList.at(indexvar);
lbl->setPixmap(QPixmap(":/Widgets/Resources/chattree/sanjiao1.png"));
});
connect(this, &QTreeWidget::itemCollapsed, this, [=](QTreeWidgetItem *item)
{
auto indexvar = item->data(0, Qt::UserRole).toInt();
indexvar -= 10000;
if (indexvar > m_iconList.size() || indexvar < 0)
return;
auto lbl = m_iconList.at(indexvar);
lbl->setPixmap(QPixmap(":/Widgets/Resources/chattree/sanjiao2.png"));
});
connect(this, &QTreeWidget::itemClicked, this, [=](QTreeWidgetItem *item, int column) {
auto indexvar = item->data(0, Qt::UserRole).toInt();
indexvar -= 10000;
if (indexvar > m_iconList.size() || indexvar < 0)
return;
if (item->isExpanded())
{
setItemExpanded(item, false);
}
else
{
setItemExpanded(item, true);
}
});
setVerticalScrollMode(QAbstractItemView::ScrollPerPixel);
}
ChatTreeWidget::~ChatTreeWidget()
{
qDebug() << "123123";
}
QTreeWidgetItem* ChatTreeWidget::addTreeWidgetTopItem(QString str)
{
return insertTreeWidgetTopItem(topLevelItemCount(), str);
}
QTreeWidgetItem* ChatTreeWidget::insertTreeWidgetTopItem(int index, QString str)
{
QWidget *widget = new QWidget(this);
QLabel *textLabel = new QLabel(widget);
QHBoxLayout *hlayout = new QHBoxLayout(widget);
QLabel *iconLabel = new QLabel(widget);
QSpacerItem *spacer = new QSpacerItem(20, 20, QSizePolicy::Expanding, QSizePolicy::Minimum);
textLabel->setText(str);
textLabel->setStyleSheet(u8"font-family: \"Microsoft YaHei\"");
hlayout->addWidget(textLabel);
hlayout->addSpacerItem(spacer);
iconLabel->setPixmap(QPixmap(":/Widgets/Resources/chattree/sanjiao2.png"));
hlayout->addWidget(iconLabel);
hlayout->setContentsMargins(5, 0, 10, 0);
widget->setLayout(hlayout);
m_iconList.append(iconLabel);
QTreeWidgetItem *rootItem = new QTreeWidgetItem();
rootItem->setData(0, Qt::UserRole, m_index);
rootItem->setSizeHint(0, QSize(30, 30));
m_index++;
this->insertTopLevelItem(index, rootItem);
setItemWidget(rootItem, 0, widget);
rootItem->setBackground(0, Qt::green);
setExpandsOnDoubleClick(false);
return rootItem;
}
void ChatTreeWidget::setRootItemStyleSheet(QTreeWidgetItem *item,QString css)
{
auto widgets = itemWidget(item, 0);
widgets->setStyleSheet(css);
}
void ChatTreeWidget::setRootItemStyleSheet(QString css)
{
for (qint32 i = 0; i < topLevelItemCount(); i++)
{
auto rootItem = topLevelItem(i);
auto widgets = itemWidget(rootItem,0);
widgets->setStyleSheet(css);
}
}
this code is trying to implement a custom-treewidget to display chat members,so it inherits the QTreeWidget (once I thought I should implement it with QTreeView,but as I'm stupid and lazy,I used QTreeWidget Directly),and uses a special style-sheet to change display style.and as it's a public widget,it offers some interfaces to add top-level items.there is an example image:
when I destruct this widget (I' sure that this widget is token from UI or layout) and even sometimes QT's automatic destructing will cause a crash.It happens very frequently but not everytime.
the two main ways of destroying the widget are:
1.Add it to some Ui,and let it managed by that UI.
2.delete by hand:
chatTreeWidget->setParent(0);
chatTreeWidget->disconnect();
chatTreeWidget->deleteLater();
both of them can cause a random crash.
Is there anything wrong? or it's just a bug?
Thanks for any kind of help!

Related

Recreate QMovie upon resizing the widget

What is the 'proper' way to recreate a QMovie widget when it gets resized?
class Gif : public QPushButton
{
Q_OBJECT
public:
QMovie* movie = nullptr;
QTimer *timer = new QTimer(this);
int widget_width = 0;
int widget_height = 0;
Gif(QWidget* parent = 0) : QPushButton(parent) { }
void paintEvent(QPaintEvent* p)
{
// Check if the widget has been resized, if so
// delete the QLabel/QMovie and recreate them.
if (widget_width)
{
if (widget_width != width())
{
QLabel* label = this->findChild<QLabel*>("label");
label->deleteLater();
movie->deleteLater();
movie = nullptr;
}
}
// Load the gif into the QMovie/QLabel.
if (!movie)
{
auto StyleSheet = styleSheet();
QVector<QString> matches;
QRegularExpression re(R"(image:\s*url\((.*)\);)");
QRegularExpressionMatch m = re.match(StyleSheet);
for (int i = 0; i <= m.lastCapturedIndex(); i++)
matches.append(m.captured(i));
movie = new QMovie(matches[1]);
if (!movie->isValid())
qDebug() << "failed to create the QMovie.";
QLabel* label = new QLabel(this);
label->setObjectName("label");
widget_width = width();
widget_height = height();
label->setGeometry(0, 0, widget_width, widget_height);
label->setMovie(movie);
label->show();
movie->setScaledSize(QSize().scaled(widget_width, widget_height, Qt::IgnoreAspectRatio));
movie->setSpeed(150);
movie->start();
}
// Pause for 2 seconds after getting into the
// last frame.
if (movie->currentFrameNumber() == (movie->frameCount() -1))
{
movie->stop();
connect(timer, SIGNAL(timeout()), this, SLOT(resume()));
timer->start(2000);
}
}
public slots:
void resume()
{
movie->start();
}
};
Is it 'safe' to call deleteLater() and then assign movie to nullptr? won't cause any mem leak / UB?
if (widget_width != width())
{
QLabel* label = this->findChild<QLabel*>("label");
label->deleteLater();
movie->deleteLater();
movie = nullptr;
}
Also, is there any alternative to QMovie for playing a gif in a gui? it uses a lot of CPU when the gif is medium/high size
Sorry I can not offer the answer to your main question.
With your second question, "a lot of CPU usage" when playing QMovie. I found a method to set the CacheMode of QMovie. It will help to reduce the CPU usage of QMovie playing gif.
movie.setCacheMode(QMovie.CacheMode.CacheAll)

How to drag and drop a specific QPixmap into a QGraphicsView?

On a subclassed QListWidget I have several items. Every QListWidget item (e.g. "ROS Init", "Images" etc) that is shown below is associated with a specific icon.
The problem I have is that I am trying to drag and drop the specific icon corresponding to that QListWidget item, but nothing happens.
Below the function responsible for the dragging:
void ListView::startDrag(Qt::DropActions supportedActions)
{
QMap<int, QString> icons;
icons.insert(IT_RosInit, "ROS Init");
icons.insert(IT_Images, "Images");
icons.insert(IT_Path, "Path");
icons.insert(IT_RosShutDown, "ROS Shutdown");
if (supportedActions & Qt::CopyAction)
{
const QList<QListWidgetItem *> &m_items(selectedItems());
if (m_items.isEmpty())
return;
QPixmap pixmapLaser("/home/images/laserscan.png");
QPixmap pixmapPCloud2("/home/images/pcloud2.png");
// etc ...
QStringList iconImages;
for(int i = 0; i < icons.count(); ++i)
{
for (const QString &tableType : iconImages) {
if (tableType == "ROS Init")
{
auto *data = mimeData(m_items);
auto *drag = new QDrag(this);
drag->setPixmap(pixmapLaser);
drag->setMimeData(data);
drag->setHotSpot(pixmapLaser.rect().center());
drag->exec(Qt::CopyAction);
}
else if(tableType == "Images")
{
auto *data2 = mimeData(m_items);
auto *drag2 = new QDrag(this);
drag2->setPixmap(pixmapPCloud2);
drag2->setMimeData(data2);
drag2->setHotSpot(pixmapPCloud2.rect().center());
drag2->exec(Qt::CopyAction);
}
}
}
}
else
{
QListWidget::startDrag(supportedActions);
}
}
After subclassing the QListWidget I just reimplemented the usual drag and drop function. All other function are working properly but the startDrag and in fact as I try to drag the proper QPixmap, I actually see nothing being dragged.
I consulted this source, useful, and also this other source which was useful but it didn't reimplement the startDrag but instead dropEvent which for me is not a problem because that is working well.
I also consulted this source and this other source but that also didn't help fixing the problem.
Thanks for shedding light on this matter for solving the problem
Solution
I would approach this problem in the following way:
Set the ItemIsDragEnabled flag of QListWidgetItem to enable the item for dragging:
item->setFlags(Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsDragEnabled);
Set the desired data for each item:
item->setData(Qt::UserRole, type);
where type is one of the enumerated values IT_RosInit, IT_Images, etc.
Enable the drag functionality of the ListWidget:
setDragEnabled(true);
setDragDropMode(QAbstractItemView::DragOnly);
Use the item settings to setup the QDrag object, created in startDrag.
Example
Here is an example I have prepared for you to demonstrate how the proposed solution could be implemented:
MainWindow.cpp
MainWindow::MainWindow(QWidget *parent) :
QWidget(parent)
{
auto *l = new QVBoxLayout(this);
auto *list = new ListView(this);
list->addItem(createItem(":/pix/images/laserscan.png", tr("RosInit"), IT_RosInit));
list->addItem(createItem(":/pix/images/icons/pcloud2.png", tr("Images"), IT_Images));
list->addItem(createItem(":/pix/images/icons/some_icon.png", tr("Path"), IT_Path));
list->addItem(createItem(":/pix/images/icons/another_icon.png", tr("RosShutDown"), IT_RosShutDown));
l->addWidget(list);
resize(300, 400);
setWindowTitle("IconDrag");
}
QListWidgetItem *MainWindow::createItem(const QString &pm, const QString &text, int type)
{
auto *item = new QListWidgetItem(QIcon(QPixmap(pm)), text);
item->setFlags(Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsDragEnabled);
item->setData(Qt::UserRole, type);
return item;
}
ListView.cpp
ListView::ListView(QWidget *parent) :
QListWidget(parent)
{
setDragEnabled(true);
setDragDropMode(QAbstractItemView::DragOnly);
setSelectionBehavior(QAbstractItemView::SelectRows);
setSelectionMode(QAbstractItemView::SingleSelection);
}
void ListView::startDrag(Qt::DropActions supportedActions)
{
if (supportedActions & Qt::CopyAction) {
const QList<QListWidgetItem *> &items(selectedItems());
if (items.isEmpty())
return;
const QPixmap &pm(items.first()->icon().pixmap(64));
auto *item = items.first();
auto *mimeData = new QMimeData();
auto *drag = new QDrag(this);
mimeData->setData("text/plain", item->data(Qt::UserRole).toByteArray());
drag->setPixmap(pm);
drag->setMimeData(mimeData);
drag->setHotSpot(pm.rect().center());
drag->exec(Qt::CopyAction);
}
}

How to properly drop a widget on a QGraphicsView right under the mouse?

Following my previous post I have been trying to solve a positioning problem withing a QGraphicsView.
After a widget is dragged from a QListWidget and dropped inside a QGraphicsView, it jumps everywhere.
The problem I have been trying to solve with also the help of another user is the following:
As soon as the widget is dragged inside the QGraphicsView and dropped. It just goes in random locations and have to pay attention to retrieve it. This behavior is not user friendly and sometimes it takes a bit to locate the widget.
Below the code:
scene.h
#ifndef SCENE_H
#define SCENE_H
#include <QGraphicsScene>
class Scene : public QGraphicsScene
{
public:
Scene(QObject *parent = nullptr);
protected:
void dragEnterEvent(QGraphicsSceneDragDropEvent *event);
void dragMoveEvent(QGraphicsSceneDragDropEvent *event);
void dropEvent(QGraphicsSceneDragDropEvent *event);
};
#endif // SCENE_H
scene.cpp
void Scene::dropEvent(QGraphicsSceneDragDropEvent *event)
{
QByteArray encoded =
event->mimeData()->data("application/x-qabstractitemmodeldatalist");
QDataStream stream(&encoded, QIODevice::ReadOnly);
QStringList rosTables;
QString newString;
while (!stream.atEnd()) {
int row, col;
QMap<int, QVariant> roleDataMap;
stream >> row >> col >> roleDataMap;
rosTables << roleDataMap[Qt::DisplayRole].toString();
}
for (const QString &tableType : rosTables) {
if (tableType == "Images") {
QPoint initPos(0, 0);
auto *wgt = new CustomTableWidget;
auto *proxyControl = addRect(0, 0, 0, 0, QPen(Qt::black),
QBrush(Qt::darkGreen));
auto *sizeGrip = new QSizeGrip(wgt);
auto *layout = new QHBoxLayout(wgt);
layout->setContentsMargins(0, 0, 0, 0);
layout->addWidget(sizeGrip, 0, Qt::AlignRight | Qt::AlignBottom);
connect(wgt, &CustomTableWidget::sizeChanged, [wgt, proxyControl](){
proxyControl->setRect(wgt->geometry().adjusted(-10, -10, 10, 10));
});
wgt->setColumnCount(2);
wgt->setRowCount(2);
for (int ridx = 0; ridx < wgt->rowCount(); ridx++) {
for (int cidx = 0; cidx < wgt->columnCount(); cidx++) {
auto *item = new QTableWidgetItem();
item->setText(QString("%1").arg(ridx));
wgt->setItem(ridx,cidx,item);
}
}
auto *const proxy = addWidget(wgt);
proxy->setPos(initPos.x(), initPos.y()
+ proxyControl->rect().height());
proxy->setParentItem(proxyControl);
proxyControl->setPos(initPos.x(), initPos.y());
proxyControl->setFlag(QGraphicsItem::ItemIsMovable, true);
proxyControl->setFlag(QGraphicsItem::ItemIsSelectable, true);
proxyControl->setRect(wgt->geometry().adjusted(-10, -10, 10, 10));
}
}
}
What it was tried so far to solve the problem:
Now the QGraphicsScene have been subclassed to re-write the mouse events, in particular and in this case attention was given to the dropEvent(QGraphicsSceneDragDropEvent *event) as that is the responsible function to "see the widget"
Several trials and errors were conducted and in the same function it was tried to add the following part:
for (const QString &tableType : rosTables) {
if (tableType == "Images") {
QPoint initPos(event->scenePos()); // <-- Tried this but no change
auto *wgt = new CustomTableWidget;
auto *proxyControl = addRect(0, 0, 0, 0, QPen(Qt::black),
QBrush(Qt::darkGreen));
auto *sizeGrip = new QSizeGrip(wgt);
auto *layout = new QHBoxLayout(wgt);
}
An additional thing tried was to provide the QPoint the event->scenePos().toPoint() as deemed proper for the goal, but unfortunately that didn't solve the problem either:
for (const QString &tableType : rosTables) {
if (tableType == "Images") {
QPoint initPos(event->scenePos().toPoint()); // <-- Tried this too but no change
auto *wgt = new CustomTableWidget;
auto *proxyControl = addRect(0, 0, 0, 0, QPen(Qt::black),
QBrush(Qt::darkGreen));
auto *sizeGrip = new QSizeGrip(wgt);
auto *layout = new QHBoxLayout(wgt);
}
If anyone has an idea of what the problem might be please provide guidance on how to solve it.
Change proxy->setPos(initPos.x(), initPos.y() + proxyControl->rect().height()); to proxy->setPos(10, 10);.
Set scene rect:
Scene::Scene(QObject *parent) :
QGraphicsScene(parent)
{
setBackgroundBrush(Qt::lightGray);
setSceneRect(0, 0, 1000, 1000);
}
Set view alignment, e.g. in MainWindow.cpp:
ui->graphicsView->setAlignment(Qt::AlignLeft | Qt::AlignTop);

Auto-resizing QStackedWidget

I have a class StackedWidget which inherits from QStackedWidget. The standard behaviour of QStackedWidget is that it adopts the size of its biggest element. What I want to do is force it to resize to its current element. I have already tried a couple of solutions found here and none of them work including e.g. setting size policies or calling hide() on all the widgets that go out of sight.
I think problem may reside in two possibilities:
1. Something is terribly wrong with StackedWidget. 2. The problem of resizing does not pertain directly to StackedWidget but to some layout or another widget that StackedWidget is nested in.
Concerning the first possibility, here I provide the StackedWidget class.
Please take a look at what my StackedWidget's declaration looks like:
class StackedWidget : public QStackedWidget
{
Q_OBJECT
private:
QComboBox* widgetChooser = nullptr;
public:
StackedWidget(QWidget* parent) : QStackedWidget(parent) {}
void setWidgetChooser(QComboBox* widgetChooser);
void addWidget(QWidget* widget);
private:
void makeConnection();
signals:
public slots:
void setCurrentIndex(const int& index);
};
Here goes the definition:
void StackedWidget::setWidgetChooser(QComboBox* widgetChooser)
{
if (widgetChooser == nullptr) throw RuntimeError("widgetChooser is nullptr");
this->widgetChooser = widgetChooser;
makeConnection();
}
void StackedWidget::addWidget(QWidget* widget)
{
widget->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
QStackedWidget::addWidget(widget);
}
void StackedWidget::makeConnection()
{
connect(widgetChooser, SIGNAL(currentIndexChanged(int)), this, SLOT(setCurrentIndex(int)));
}
void StackedWidget::setCurrentIndex(const int& index)
{
qDebug() << "Index changed to " << index;
QWidget* pWidget = widget(index);
Q_ASSERT(pWidget);
QStackedWidget::setCurrentWidget(pWidget);
pWidget->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
pWidget->adjustSize();
adjustSize();
}
Concering the second possibility, here goes how I use StackedWidget.
void SchemaWidget::TempBuildAlt(const SchemaElement* son, QVBoxLayout* parentLayout)
{
if (auto schemaAlternative = dynamic_cast<const SchemaAlternatives*>(son))
{
QComboBox* widgetBox = BuildWidgetChooserBox(schemaAlternative);
StackedWidget* stackedWidget = BuildStackedWidget(schemaAlternative);
stackedWidget->setWidgetChooser(widgetBox);
QHBoxLayout* boxLayout = new QHBoxLayout;
boxLayout->addWidget(new QLabel(schemaAlternative->Name, this));
boxLayout->addWidget(widgetBox);
QVBoxLayout* layout = new QVBoxLayout;
layout->addLayout(boxLayout);
layout->addWidget(stackedWidget);
parentLayout->addLayout(layout);
} else throw RuntimeError("Cannot cast son to SchemaAlternatives.");
}
QComboBox* SchemaWidget::BuildWidgetChooserBox(const SchemaAlternatives* schema)
{
QComboBox* alternativesBox = new QComboBox(this);
QStringListModel* listModel = new QStringListModel(this);
QStringList list;
for (int i = 1; i <= schema->Sons.High(); ++i)
{
if (const SchemaTree* son = dynamic_cast<const SchemaTree*>(schema->Sons(i).Get()))
{
list << son->Name;
}
else throw RuntimeError("Son cannot be cast to SchemaTree.");
}
listModel->setStringList(list);
alternativesBox->setModel(listModel);
return alternativesBox;
}
StackedWidget* SchemaWidget::BuildStackedWidget(const SchemaAlternatives* schema)
{
StackedWidget* stackedWidget = new StackedWidget(this);
for (int i = 1; i <= schema->Sons.High(); ++i)
{
if (const SchemaTree* alternativeSon = dynamic_cast<const SchemaTree*>(schema->Sons(i).Get()))
{
QWidget* widget = new QWidget(this);
QVBoxLayout* widgetLayout = new QVBoxLayout;
BuildWidget(alternativeSon, widgetLayout);
widget->setLayout(widgetLayout);
stackedWidget->addWidget(widget);
}
else throw RuntimeError("Son could not be cast to SchemaTree.");
}
return stackedWidget;
}
I want to say thanks in advance to anyone who will be willing to spend some time on my problem. I do appreciate it. Thanks. You're great.

how to get row number after comboBox item in QTableWidget in qt

I would like to get a number of a QTableWidget row after selecting some topic in comboBox how it is possible to get the row, thanks.
void MainWindow::metto_stringa(int i)
{
QWidget *w = qobject_cast<QWidget *>(sender()->parent());
if(w)
{
int row = ui->tableWidget->indexAt(w->pos()).row();
ui->lineEdit->setText(QString::number( row ));
}
// ui->lineEdit->setText(QString::number( i ));
}
else if(i == 3)
{
// ui->tableWidget->setCellWidget(ui->tableWidget->rowCount(), i, "");
QString s = "Normal";
QComboBox *combo = new QComboBox;
combo->addItem("Below normal");
combo->addItem("Normal");
combo->addItem("Above normal");
combo->addItem("High");
combo->addItem("Real time");
connect(combo,SIGNAL(currentIndexChanged(int)),this,
SLOT(metto_stringa(int)));
ui->tableWidget->setCellWidget(ui->tableWidget->rowCount()-1, i,combo);
/* ui->tableWidget->setCellWidget(i,4,combo);
QTableWidgetItem*item = new QTableWidgetItem(s);
item->setFlags(item->flags() ^ Qt::ItemIsEditable);
ui->tableWidget->setItem(ui->tableWidget->rowCount()-1, i,
item);*/
continue;
}
In this case you should not use the parent of the QComboBox, you must use the same sender()
void MainWindow::metto_stringa(int index)
{
QWidget *w = qobject_cast<QWidget *>(sender());
if(w)
{
int row = ui->tableWidget->indexAt(w->pos()).row();
ui->lineEdit->setText(QString::number(row));
}
}
In the question I answered before I commented that you must access the widget that you use in the setCellWidget() function, in the previous case the widget had the following form:
QWidget <--- QPushButton
parent() sender()
ie you owe to that widget so we take advantage of sender() and parent() in the previous case. In the current case QComboBox is added directly.