Updating view based on model update on Qt - qt

I am having trouble updating my qtableview when the data wrapped by the model the view uses changes. I am not talking about appending/adding data here, I am specifically talking about modifying existing data. View is only updated when I scroll or select cell(s). In other words, it is only updated when repainting is triggered (this is my guess actually). So, this made me think that calling update() on widget when the data is modified would suffice, but this was not the case.
I am adding data to the model programmatically, user cannot modify or add data in the model.
Here is the minimal, reproducible example:
mymodel.h:
#pragma once
#include <QAbstractTableModel>
#include <QVector>
#include "myobject.h"
class MyModel : public QAbstractTableModel
{
Q_OBJECT
private:
QVector<MyObject> my_objects_; /// underlying data structure for the model.
public:
MyModel(QObject * parent = {});
int rowCount(const QModelIndex &) const override;
int columnCount(const QModelIndex &) const override;
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::EditRole) const override;
void add(const MyObject& my_object);
void edit(const MyObject& my_object);
};
mymodel.cpp:
#include "mymodel.h"
#include <QDebug>
#include <iostream>
MyModel::MyModel(QObject * parent)
: QAbstractTableModel{parent}
{
}
int MyModel::rowCount(const QModelIndex &) const
{
return my_objects_.count();
}
int MyModel::columnCount(const QModelIndex &) const {
return 4;
}
QVariant MyModel::data(const QModelIndex &index, int role) const {
const auto row = index.row();
if(row == -1) {
return {};
}
const auto my_object = my_objects_[row];
if(role == Qt::DisplayRole) {
switch (index.column()) {
case 0: return my_object.id;
case 1: return my_object.a;
case 2: return my_object.b;
case 3: return my_object.c;
default: return {};
};
}
return {};
}
QVariant MyModel::headerData(int section, Qt::Orientation orientation, int role) const {
if (orientation != Qt::Horizontal || role != Qt::DisplayRole) {
return {};
}
switch (section) {
case 0: return "id";
case 1: return "a";
case 2: return "b";
case 3: return "c";
default: return {};
}
}
void MyModel::add(const MyObject &my_object)
{
beginInsertRows({}, my_objects_.count(), my_objects_.count());
my_objects_.push_back(my_object);
endInsertRows();
}
void MyModel::edit(const MyObject& my_object) {
const auto id = my_object.id;
for(auto& my_object_ : my_objects_) {
if(my_object_.id == id) {
my_object_.a = my_object.a;
my_object_.b = my_object.b;
my_object_.c = my_object.c;
/// should I use dataChanged signal here? If so, how?
break;
}
}
}
myobject.h:
#pragma once
#include <QString>
struct MyObject {
int id;
int a;
double b;
QString c;
};
mainwindow.h:
#pragma once
#include <QMainWindow>
#include "mymodel.h"
QT_BEGIN_NAMESPACE
namespace Ui { class MainWindow; }
QT_END_NAMESPACE
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
MainWindow(QWidget *parent = nullptr);
~MainWindow();
private slots:
void onEditClicked();
private:
Ui::MainWindow *ui;
MyModel my_model_;
};
mainwindow.cpp:
#include "mainwindow.h"
#include "./ui_mainwindow.h"
const static QVector<MyObject> g_my_objects{
{0, 1, 1.1, "object1"},
{1, 11, 1.2, "object2"},
{2, 12, 1.3, "object3"},
{3, 13, 1.4, "object4"},
{4, 14, 1.5, "object5"},
{5, 15, 1.6, "object6"},
{6, 16, 1.7, "object7"},
{7, 17, 1.8, "object8"}
};
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
, ui(new Ui::MainWindow)
{
ui->setupUi(this);
ui->tableView->horizontalHeader()->setSectionResizeMode(QHeaderView::Stretch);
ui->tableView->setModel(&my_model_);
for(const auto& g_my_object : g_my_objects) {
my_model_.add(g_my_object);
}
}
MainWindow::~MainWindow()
{
delete ui;
}
void MainWindow::onEditClicked()
{
qDebug() << __PRETTY_FUNCTION__;
my_model_.edit({2, 22222, 2222.222, "myobject22222"});
update(); /// seems to be doing nothing.
}
main.cpp:
#include "mainwindow.h"
#include <QApplication>
#include <QVector>
#include "myobject.h"
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
MainWindow w;
w.show();
return a.exec();
}
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>800</width>
<height>600</height>
</rect>
</property>
<property name="windowTitle">
<string>MainWindow</string>
</property>
<widget class="QWidget" name="centralwidget">
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QPushButton" name="pushButton">
<property name="text">
<string>Edit</string>
</property>
</widget>
</item>
<item>
<widget class="QTableView" name="tableView"/>
</item>
</layout>
</widget>
<widget class="QMenuBar" name="menubar">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>800</width>
<height>23</height>
</rect>
</property>
</widget>
<widget class="QStatusBar" name="statusbar"/>
</widget>
<resources/>
<connections>
<connection>
<sender>pushButton</sender>
<signal>clicked()</signal>
<receiver>MainWindow</receiver>
<slot>onEditClicked()</slot>
<hints>
<hint type="sourcelabel">
<x>604</x>
<y>41</y>
</hint>
<hint type="destinationlabel">
<x>951</x>
<y>20</y>
</hint>
</hints>
</connection>
</connections>
<slots>
<slot>onEditClicked()</slot>
</slots>
</ui>
CMakeLists.txt:
cmake_minimum_required(VERSION 3.5)
project(model_view_example VERSION 0.1 LANGUAGES CXX)
set(CMAKE_INCLUDE_CURRENT_DIR ON)
set(CMAKE_AUTOUIC ON)
set(CMAKE_AUTOMOC ON)
set(CMAKE_AUTORCC ON)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
find_package(QT NAMES Qt6 Qt5 REQUIRED COMPONENTS Widgets)
find_package(Qt${QT_VERSION_MAJOR} REQUIRED COMPONENTS Widgets)
set(PROJECT_SOURCES
main.cpp
mainwindow.cpp
mymodel.cpp
mainwindow.h
mainwindow.ui
)
if(${QT_VERSION_MAJOR} GREATER_EQUAL 6)
qt_add_executable(model_view_example
MANUAL_FINALIZATION
${PROJECT_SOURCES}
)
# Define target properties for Android with Qt 6 as:
# set_property(TARGET model_view_example APPEND PROPERTY QT_ANDROID_PACKAGE_SOURCE_DIR
# ${CMAKE_CURRENT_SOURCE_DIR}/android)
# For more information, see https://doc.qt.io/qt-6/qt-add-executable.html#target-creation
else()
if(ANDROID)
add_library(model_view_example SHARED
${PROJECT_SOURCES}
)
# Define properties for Android with Qt 5 after find_package() calls as:
# set(ANDROID_PACKAGE_SOURCE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/android")
else()
add_executable(model_view_example
${PROJECT_SOURCES}
)
endif()
endif()
target_link_libraries(model_view_example PRIVATE Qt${QT_VERSION_MAJOR}::Widgets)
set_target_properties(model_view_example PROPERTIES
MACOSX_BUNDLE_GUI_IDENTIFIER my.example.com
MACOSX_BUNDLE_BUNDLE_VERSION ${PROJECT_VERSION}
MACOSX_BUNDLE_SHORT_VERSION_STRING ${PROJECT_VERSION_MAJOR}.${PROJECT_VERSION_MINOR}
MACOSX_BUNDLE TRUE
WIN32_EXECUTABLE TRUE
)
if(QT_VERSION_MAJOR EQUAL 6)
qt_finalize_executable(model_view_example)
endif()
So, why the view is not updated when I call update()? Is there something wrong about my implementation?
Here, I clicked on edit button, but the view only got updated with its new value when I clicked on a cell:
I feel like Qt MVC examples do not really map well to how I do it here. I do not know how I can implement this logic in a more Qt-ish way if possible.
Here is the github repo for the minimal project:
https://github.com/ozcanay/qt_model_view_example

You should emit the signal dataChanged whenever the data in an existing item changes. The QTableView should update automatically.
See:
QAbstractItemModel::dataChanged
Model subclassing reference

I got help from Qt forum and solved it.

Related

Increase in memory usage for QObject::connect call

We are facing issue related to increase in memory usage due to
QObject::connect call in our application. This is causing an application crash
after continuous execution for 16~17 Hrs. Here we are trying to understand the
reason for increase in memory usage due to QObject::connect call and if there
is any specific way to handle the same.
To simulate the same I created a sample application, which is performing the following (Please refer to
the app screenshot for better understanding):
On press of "Start" button, the application will start creating
objects pointers of a class at an interval of 50 ms in a separate
thread. Each object pointer of the class will create a buffer of
1000 bytes internally. And the new pointer will be stored in a list
(which is a member variable of the object creation thread). On every
new object creation the same will be notified to the MainWindow (the
ui thread), with the created object pointer.
On notified by the object creation thread, the MainWindow can
perform following 2 actions depending on the "Use Connect" check box
status:
a. Initialize a local member variable to the new object
created.
b. disconnect/connect to the destroyed() signal of the new
object created to a slot, if "Use Connect" check box is checked.
Observation:
We ran the sample application for 5 minutes and collected the result for
both "with connect" and "without connect" method call.
collected 5 samples for both the condition.
The memory usage for "Without connect" call comes around 8.5 Mb
(almost constant for all 5 samples collected)
The memory usage for "With connect call" comes around 9.7 Mb
(almost constant for all 5 samples collected)
Above result suggests that, when connect call happens memory usage is
increasing. And this is what is aso observed in out actual project.
We are trying to understand the reason for additional memory usage in case of
"With connect" call and need a solution/suggestion for the same.
Kindly let us know your input on the same.
Main.cpp
#include <QApplication>
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
MainWindow w;
w.show();
return a.exec();
}
HeavyClass.cpp
#include "HeavyClass.h"
#include <QDateTime>
const unsigned int CHeaveyClass::kArraySize = 1000;
CHeaveyClass::CHeaveyClass(QObject *parent)
: QObject(parent)
, m_pForMemAllocaion(nullptr)
{
m_pForMemAllocaion = new char[kArraySize];
if (m_pForMemAllocaion)
{
memset(m_pForMemAllocaion, 0, kArraySize);
sprintf_s(m_pForMemAllocaion, kArraySize, "I am created at Bikash %s", QDateTime::currentDateTime().toString().toStdString().c_str());
}
}
CHeaveyClass::~CHeaveyClass()
{
if (m_pForMemAllocaion)
{
delete m_pForMemAllocaion;
m_pForMemAllocaion = nullptr;
}
}
HeaveyClass.h
#ifndef CHEAVYCLASS_H
#define CHEAVYCLASS_H
#include <QObject>
class CHeaveyClass : public QObject
{
Q_OBJECT
public:
explicit CHeaveyClass(QObject *parent = nullptr);
~CHeaveyClass();
signals:
private:
char* m_pForMemAllocaion;
const static unsigned int kArraySize;
};
#endif // CHEAVYCLASS_H
MainWindow.cpp
#include "mainwindow.h"
#include "ui_mainwindow.h"
#include "MyThread.h"
#include "HeavyClass.h"
#include <QTimer>
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
, ui(new Ui::MainWindow)
, m_pHeaveyClass(nullptr)
, m_pTimer(nullptr)
{
ui->setupUi(this);
ui->clearMemoryButton->setEnabled(false);
m_pMyThread = new CMyThread(nullptr, this);
if (m_pMyThread)
{
connect(m_pMyThread, SIGNAL(listSizeChanged(unsigned int)), this, SLOT(OnListSizeChanged(unsigned int)));
}
m_pTimer = new QTimer();
if (m_pTimer)
{
connect(m_pTimer, SIGNAL(timeout()), this, SLOT(OnTimerTimeOut()));
}
}
MainWindow::~MainWindow()
{
if (m_pMyThread)
{
disconnect(m_pMyThread, SIGNAL(listSizeChanged(unsigned int)), this, SLOT(OnListSizeChanged(unsigned int)));
delete m_pMyThread;
m_pMyThread = nullptr;
}
if (m_pTimer)
{
if (m_pTimer->isActive())
{
m_pTimer->stop();
}
disconnect(m_pTimer, SIGNAL(timeout()), this, SLOT(OnTimerTimeOut()));
delete m_pTimer;
m_pTimer = nullptr;
}
delete ui;
}
void MainWindow::on_startButton_clicked()
{
if (0 == ui->startButton->text().compare("start", Qt::CaseInsensitive))
{
ui->startButton->setText("Stop");
ui->clearMemoryButton->setEnabled(false);
if (m_pMyThread)
{
m_pMyThread->start();
}
if (m_pTimer)
{
m_tStartTime = QTime::currentTime();
m_pTimer->start(1000);
}
}
else
{
ui->startButton->setText("Start");
ui->timeLabel->clear();
ui->ranFor->clear();
if (m_pMyThread)
{
m_pMyThread->StopThread();
}
if (m_pTimer)
{
m_pTimer->stop();
}
}
}
void MainWindow::OnListSizeChanged(unsigned int size)
{
ui->clearMemoryButton->setEnabled(size > 0 ? true : false);
}
void MainWindow::setPrefferedHeavyObject(CHeaveyClass *ptr)
{
Q_UNUSED(ptr)
if (ui->useConnect->checkState() == Qt::Checked)
disconnect(m_pHeaveyClass,SIGNAL(destroyed(QObject*)));
m_pHeaveyClass = ptr;
if (ui->useConnect->checkState() == Qt::Checked)
connect(m_pHeaveyClass, SIGNAL(destroyed(QObject*)), this, SLOT(OnPrefferedHeavyObjectDeleted(QObject*)));
}
void MainWindow::on_clearMemoryButton_clicked()
{
m_pMyThread->ClearMemory();
}
void MainWindow::OnPrefferedHeavyObjectDeleted(QObject *ptr)
{
Q_UNUSED(ptr)
m_pHeaveyClass = nullptr;
}
void MainWindow::OnTimerTimeOut()
{
QTime currentTime = QTime::currentTime();
ui->timeLabel->setText("Current Time : " + currentTime.toString());
int timeDiffInSecs = m_tStartTime.secsTo(currentTime);
ui->ranFor->setText("Ran for : " + QString::number(timeDiffInSecs) + " secs");
if (timeDiffInSecs == 300)
{
on_startButton_clicked();
}
}
MainWindow.h
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include <QMainWindow>
#include "IMainWindow.h"
#include <QTime>
class CHeaveyClass;
class CMyThread;
class QTimer;
QT_BEGIN_NAMESPACE
namespace Ui { class MainWindow; }
QT_END_NAMESPACE
class MainWindow
: public QMainWindow
, public IMainWindow
{
Q_OBJECT
public:
MainWindow(QWidget *parent = nullptr);
~MainWindow();
private slots:
void on_startButton_clicked();
void OnListSizeChanged(unsigned int size);
void on_clearMemoryButton_clicked();
void OnPrefferedHeavyObjectDeleted(QObject* ptr);
void OnTimerTimeOut();
// IMainWindow interface
protected:
void setPrefferedHeavyObject(CHeaveyClass *ptr);
private:
Ui::MainWindow *ui;
CHeaveyClass* m_pHeaveyClass;
CMyThread* m_pMyThread;
QTimer* m_pTimer;
QTime m_tStartTime;
};
#endif // MAINWINDOW_H
MyThread.cpp
#include "MyThread.h"
#include "HeavyClass.h"
#include <QDebug>
#include "IMainWindow.h"
CMyThread::CMyThread(QObject *pParent, IMainWindow* pMainWindowInterface)
: QThread(pParent)
, m_pMainWindow(pMainWindowInterface)
{
m_lstHeavyClass.clear();
}
CMyThread::~CMyThread()
{
StopThread();
while (isRunning());
ClearMemory();
qDebug() << "Destroying the thread.";
}
void CMyThread::StopThread()
{
m_bContinueExecution = false;
}
void CMyThread::ClearMemory()
{
if (false == isRunning())
{
while (m_lstHeavyClass.size() != 0)
{
CHeaveyClass* p = m_lstHeavyClass.last();
m_lstHeavyClass.removeLast();
delete p;
p = nullptr;
listSizeChanged(m_lstHeavyClass.size());
}
qDebug() << "All created heavy class ptrs are deleted.";
}
else
{
qDebug() << "Thread is still active. Stop the thread first.";
}
}
void CMyThread::run()
{
m_bContinueExecution = true;
while (m_bContinueExecution)
{
CHeaveyClass * pHeavyClass = new CHeaveyClass();
if (pHeavyClass)
{
m_lstHeavyClass.append(pHeavyClass);
m_pMainWindow->setPrefferedHeavyObject(pHeavyClass);
listSizeChanged(m_lstHeavyClass.size());
msleep(50);
}
}
qDebug() << "Stopping thread execution.";
}
MyThread.h
#ifndef CMYTHREAD_H
#define CMYTHREAD_H
#include <QThread>
class IMainWindow;
class CHeaveyClass;
class CMyThread : public QThread
{
Q_OBJECT
public:
explicit CMyThread(QObject* pParent = nullptr, IMainWindow* pMainWindowInterface = nullptr);
~CMyThread();
void StopThread();
void ClearMemory();
// QThread interface
signals:
void listSizeChanged(unsigned int size);
protected:
void run();
private:
bool m_bContinueExecution;
IMainWindow* m_pMainWindow;
QList<CHeaveyClass*> m_lstHeavyClass;
};
#endif // CMYTHREAD_H
IMainWindow.h
#ifndef IMAINWINDOW_H
#define IMAINWINDOW_H
class CHeaveyClass;
class IMainWindow {
public:
virtual void setPrefferedHeavyObject(CHeaveyClass* ptr) = 0;
};
#endif // IMAINWINDOW_H
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>400</height>
</rect>
</property>
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="windowTitle">
<string>MainWindow</string>
</property>
<widget class="QWidget" name="centralwidget">
<layout class="QVBoxLayout" name="verticalLayout_3">
<item>
<layout class="QVBoxLayout" name="verticalLayout_2">
<item>
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QPushButton" name="startButton">
<property name="text">
<string>Start</string>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="useConnect">
<property name="text">
<string>Use Connect</string>
</property>
</widget>
</item>
</layout>
</item>
<item>
<widget class="QPushButton" name="clearMemoryButton">
<property name="text">
<string>Clear Memory</string>
</property>
</widget>
</item>
<item>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QLabel" name="timeLabel">
<property name="text">
<string/>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="ranFor">
<property name="text">
<string/>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</item>
</layout>
</widget>
<widget class="QMenuBar" name="menubar">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>400</width>
<height>26</height>
</rect>
</property>
</widget>
<widget class="QStatusBar" name="statusbar"/>
</widget>
<resources/>
<connections/>
</ui>

QPushButton icon aligned left with text centered

In my Qt 5.7.1 application I've some buttons, I want to align button's icons to left and centre text, but there is no option in designer to do this.
I can align icon and text left by adding to button stylesheet this code:
text-align:left;
But it's not what I want to achieve.
So, could you please tell me, If there is any option to align icon to left, and keep text aligned center? Thank you for help.
Less code way without breaking UI style
pushButton->setIcon(QApplication::style()->standardIcon(QStyle::SP_MessageBoxQuestion));
pushButton->setStyleSheet("text-align:left;");
pushButton->setLayout(new QGridLayout);
QLabel* textLabel = new QLabel("Hello world!");
textLabel->setAlignment(Qt::AlignRight | Qt::AlignVCenter); // or center
textLabel->setAttribute(Qt::WA_TransparentForMouseEvents, true);
pushButton->layout()->addWidget(textLabel);
Remember to send setText signals to textLabel instead of pushButton
To get this level of control you will need to write some code to override the style for your platform. This is best done using a QProxyStyle. In this case we are looking for when the style is asked to draw a CE_PushButtonLabel (the label includes the icon, and they are hard coded in Qt to be aligned together).
You need to implement a QProxyStyle and override the drawControl() method. The code for the bulk of it is copied directly from the Qt source code for the default drawcontrol method (in qcommonstyle.cpp) - so although it looks lengthy, it is mostly doing what Qt already does. I put extra /****/ markers around the sections I modified. This won't show up in Qt Designer, but will work at runtime.
Final result (shown on mac, should match platform you're on)
main.cpp:
QApplication a(argc, argv);
a.setStyle(new MyStyle);
...
mystyle.h
class MyStyle : public QProxyStyle
{
public:
virtual void drawControl(ControlElement element, const QStyleOption *opt,
QPainter *p, const QWidget *widget = Q_NULLPTR) const;
};
mystyle.cpp
// Copied from Qt source code..
static QWindow *qt_getWindow(const QWidget *widget)
{
return widget ? widget->window()->windowHandle() : 0;
}
void MyStyle::drawControl(ControlElement element, const QStyleOption *opt, QPainter *p, const QWidget *widget) const
{
if(element==CE_PushButtonLabel)
{
if (const QStyleOptionButton *button = qstyleoption_cast<const QStyleOptionButton *>(opt))
{
QRect textRect = button->rect;
uint tf = Qt::AlignVCenter | Qt::TextShowMnemonic;
if (!proxy()->styleHint(SH_UnderlineShortcut, button, widget))
tf |= Qt::TextHideMnemonic;
if (!button->icon.isNull()) {
QRect iconRect;
QIcon::Mode mode = button->state & State_Enabled ? QIcon::Normal : QIcon::Disabled;
if (mode == QIcon::Normal && button->state & State_HasFocus)
mode = QIcon::Active;
QIcon::State state = QIcon::Off;
if (button->state & State_On)
state = QIcon::On;
QPixmap pixmap = button->icon.pixmap(qt_getWindow(widget), button->iconSize, mode, state);
int pixmapWidth = pixmap.width() / pixmap.devicePixelRatio();
int pixmapHeight = pixmap.height() / pixmap.devicePixelRatio();
int labelWidth = pixmapWidth;
int labelHeight = pixmapHeight;
int iconSpacing = 4;//### 4 is currently hardcoded in QPushButton::sizeHint()
int textWidth = button->fontMetrics.boundingRect(opt->rect, tf, button->text).width();
if (!button->text.isEmpty())
labelWidth += (textWidth + iconSpacing);
/*************************************************************/
// Make the icon rectangle always be 10px in from the left edge
/*************************************************************/
iconRect = QRect(10,
textRect.y() + (textRect.height() - labelHeight) / 2,
pixmapWidth, pixmapHeight);
iconRect = visualRect(button->direction, textRect, iconRect);
/***********************************/
// Always horizontal align the text
/***********************************/
tf |= Qt::AlignHCenter;
if (button->state & (State_On | State_Sunken))
iconRect.translate(proxy()->pixelMetric(PM_ButtonShiftHorizontal, opt, widget),
proxy()->pixelMetric(PM_ButtonShiftVertical, opt, widget));
p->drawPixmap(iconRect, pixmap);
} else {
tf |= Qt::AlignHCenter;
}
if (button->state & (State_On | State_Sunken))
textRect.translate(proxy()->pixelMetric(PM_ButtonShiftHorizontal, opt, widget),
proxy()->pixelMetric(PM_ButtonShiftVertical, opt, widget));
if (button->features & QStyleOptionButton::HasMenu) {
int indicatorSize = proxy()->pixelMetric(PM_MenuButtonIndicator, button, widget);
if (button->direction == Qt::LeftToRight)
textRect = textRect.adjusted(0, 0, -indicatorSize, 0);
else
textRect = textRect.adjusted(indicatorSize, 0, 0, 0);
}
proxy()->drawItemText(p, textRect, tf, button->palette, (button->state & State_Enabled),
button->text, QPalette::ButtonText);
}
return;
}
// For all other controls, draw the default
QProxyStyle::drawControl(element, opt, p, widget);
}
Simply specialize QPushButton and override paintEvent and sizeHint, as proposed by cbuchart here. Then use it as a regular QPushButton.
MyButton declaration and implementation:
mybutton.h:
#pragma once
#include <QPushButton>
class MyButton : public QPushButton
{
public:
explicit MyButton(QWidget* parent = nullptr);
virtual ~MyButton();
void setPixmap(const QPixmap& pixmap);
virtual QSize sizeHint() const override;
protected:
virtual void paintEvent(QPaintEvent* e) override;
private:
QPixmap m_pixmap;
};
mybutton.cpp:
#include "mybutton.h"
#include <QPainter>
MyButton::MyButton(QWidget* parent) : QPushButton(parent)
{
}
MyButton::~MyButton()
{
}
QSize MyButton::sizeHint() const
{
const auto parentHint = QPushButton::sizeHint();
// add margins here if needed
return QSize(parentHint.width() + m_pixmap.width(), std::max(parentHint.height(), m_pixmap.height()));
}
void MyButton::setPixmap(const QPixmap& pixmap)
{
m_pixmap = pixmap;
}
void MyButton::paintEvent(QPaintEvent* e)
{
QPushButton::paintEvent(e);
if (!m_pixmap.isNull())
{
const int y = (height() - m_pixmap.height()) / 2; // add margin if needed
QPainter painter(this);
painter.drawPixmap(5, y, m_pixmap); // hardcoded horizontal margin
}
}
Exemple of usage:
Here is an example where "Promote widget" feature in Qt Designer was used to create MyButton from .ui files:
mainframe.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">
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="MyButton" name="button1">
<property name="text">
<string>Button</string>
</property>
</widget>
</item>
<item>
<widget class="MyButton" name="button2">
<property name="text">
<string>Other button</string>
</property>
</widget>
</item>
</layout>
</widget>
<widget class="QMenuBar" name="menuBar">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>400</width>
<height>20</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"/>
<customwidgets>
<customwidget>
<class>MyButton</class>
<extends>QPushButton</extends>
<header>mybutton.h</header>
</customwidget>
</customwidgets>
<resources/>
<connections/>
</ui>
mainwindow.h:
#pragma once
#include <QMainWindow>
namespace Ui {
class MainWindow;
}
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
explicit MainWindow(QWidget *parent = 0);
~MainWindow();
private:
Ui::MainWindow *ui;
};
mainwindow.cpp:
#include "mainwindow.h"
#include "ui_mainwindow.h"
#include <QStyle>
MainWindow::MainWindow(QWidget *parent) :
QMainWindow(parent),
ui(new Ui::MainWindow)
{
ui->setupUi(this);
QStyle* style = qApp->style();
// set buttons pixmaps:
ui->button1->setPixmap( style->standardPixmap(QStyle::SP_ComputerIcon) );
ui->button2->setPixmap( style->standardPixmap(QStyle::SP_TrashIcon) );
}
MainWindow::~MainWindow()
{
delete 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();
}
Results in:
Compared to docsteer answer, this solution makes it possible to apply the new style to only some buttons of your project. And the code is also smaller.
Compared to IGHOR answer, you can still use the button as a regular QPushButton (using QPushButton::setText), you don't need to keep a reference to a mockup QLabel to change button's text.

OpenFileDialog in a dynamic QDialog

I am trying to build a form with several LineEdits and I need an OpenFileDialog to select a Path and write it into one of the LineEdits.
The problem is, this form is generated "dynamically".
All of this needs to happen inside a function call, in which the Dialog and all LineEdits are created, so i can't just add a Button, link the clicked Signal to a new Slot, open the File Dialog in the Slot and write the result into the LineEdit.
Here is my Code from inside the function:
QDialog dialog(this);
QFormLayout form(&dialog);
QList<QLineEdit *> fields;
QLineEdit *lineEdit = new QLineEdit(&dialog);
QString label = QString("Name");
form.addRow(label, lineEdit);
fields << lineEdit;
lineEdit = new QLineEdit(&dialog);
label = QString("Time");
form.addRow(label, lineEdit);
fields << lineEdit;
lineEdit = new QLineEdit(&dialog);
label = QString("Number #1");
form.addRow(label, lineEdit);
fields << lineEdit;
lineEdit = new QLineEdit(&dialog);
label = QString("Number #2");
form.addRow(label, lineEdit);
fields << lineEdit;
lineEdit = new QLineEdit(&dialog);
label = QString("Number #3");
form.addRow(label, lineEdit);
fields << lineEdit;
//--------------Here should be the path selection
lineEdit = new QLineEdit(&dialog);
label = QString("Path");
form.addRow(label, lineEdit);
fields << lineEdit;
//-----------------------------------------------
QDialogButtonBox buttonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel,Qt::Horizontal, &dialog);
form.addRow(&buttonBox);
QObject::connect(&buttonBox, SIGNAL(accepted()), &dialog, SLOT(accept()));
QObject::connect(&buttonBox, SIGNAL(rejected()), &dialog, SLOT(reject()));
if (dialog.exec() == QDialog::Accepted) {
//...Do stuff with the Text from the Line Edits
Is there a way to do this without making things global, so a Slot could access it?
Because I really like the idea of this form completely staying inside the function call.
If I understood the issue correctly, I think you need to redesign the application.
You need some variables in your class and update the dialog when the user changes the QLineEdit path. But to do this, you need the dialog as a member variable.
The next code is just an example, but it's a good way of showing you a way of solving this. Of course, there are many different options to do it.
Note #1: I don't know when you launch the dialog, so I've created a very basic ui with a button to display it.
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>160</x>
<y>120</y>
<width>75</width>
<height>23</height>
</rect>
</property>
<property name="text">
<string>PushButton</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();
}
mainwindow.h
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include <QMainWindow>
#include <QLineEdit>
namespace Ui {
class MainWindow;
}
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
explicit MainWindow(QWidget *parent = 0);
~MainWindow();
private slots:
void updateValues(const QString & text);
void on_pushButton_clicked();
private:
Ui::MainWindow *ui;
QLineEdit* path;
QLineEdit* name;
QLineEdit* time;
QLineEdit* number;
QDialog* dialog;
};
#endif // MAINWINDOW_H
mainwindow.cpp
#include "mainwindow.h"
#include "ui_mainwindow.h"
#include <QDialog>
#include <QLineEdit>
#include <QDialogButtonBox>
#include <QFormLayout>
MainWindow::MainWindow(QWidget *parent) :
QMainWindow(parent),
ui(new Ui::MainWindow)
{
ui->setupUi(this);
dialog = new QDialog(this);
}
MainWindow::~MainWindow()
{
delete ui;
}
void MainWindow::on_pushButton_clicked()
{
QFormLayout form(dialog);
name = new QLineEdit(dialog);
QString label = QString("Name");
form.addRow(label, name);
time = new QLineEdit(dialog);
label = QString("Time");
form.addRow(label, time);
number = new QLineEdit(dialog);
label = QString("Number #1");
form.addRow(label, number);
path = new QLineEdit(dialog);
label = QString("Path");
form.addRow(label, path);
connect(path, &QLineEdit::textChanged,
this, &MainWindow::updateValues );
QDialogButtonBox buttonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel,Qt::Horizontal, dialog);
form.addRow(&buttonBox);
QObject::connect(&buttonBox, SIGNAL(accepted()), dialog, SLOT(accept()));
QObject::connect(&buttonBox, SIGNAL(rejected()), dialog, SLOT(reject()));
dialog->exec();
}
void MainWindow::updateValues(const QString & text)
{
name->setText(text);
time->setText(text);
number->setText(text);
}

Qt - QTableView and QSqlQueryModel items disappear

I'm implementing a QtableView with the data from a QSqlQueryModeL.
What happens is, if I select a item in the view, the item after a second disappears and the items in the other rows disappear as well.
my code:
ui->medicstableView->setHorizontalScrollBarPolicy(Qt::ScrollBarAsNeeded);
ui->medicstableView->setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded);
ui->medicstableView->setStyleSheet(QString::fromUtf8("background-color: rgb(227, 226, 226);"));
//ui->medicstableView->setVisible(true);
//ui->medicstableView->setEditTriggers(QAbstractItemView::NoEditTriggers);
doctors_table = new QSqlQueryModel(this);
QSqlDatabase mydb;
QString Squery;
QString conn = mydb.connectionName();
if (conn.isEmpty()) {
mydb = QSqlDatabase::addDatabase("QMYSQL");
}
else
{
mydb.close();
QSqlDatabase::removeDatabase(conn);
mydb = QSqlDatabase::addDatabase("QMYSQL");
}
mydb.setHostName(myhost);
mydb.setUserName(myuser);
mydb.setPassword(mypass);
mydb.setDatabaseName(mybase);
try
{
if(mydb.open()==true)
{
Squery=QString("SELECT id_medico,medico FROM Registos_Medicos");
doctors_table->setQuery(Squery);
doctors_table->setHeaderData(0, Qt::Horizontal, QObject::tr("ID"));
doctors_table->setHeaderData(1, Qt::Horizontal, QObject::tr("Nome do médico"));
ui->medicstableView->setModel(doctors_table);
mydb.close();
//ui->statusbar->addAction();
ui->medicstableView->resizeColumnsToContents ();
ui->medicstableView->resizeRowsToContents ();
ui->medicstableView->setColumnWidth(1,255);
ui->medicstableView->show();
The QTableView is created in the Qt Designer with the name "medicstableView"
Thanks in advance.
ok let's try with this. It works to me:
//pro file:
QT += core gui sql
TARGET = test1
TEMPLATE = app
SOURCES += main.cpp\
widget.cpp \
database.cpp
HEADERS += widget.h \
database.h
FORMS += widget.ui
//database.h:
#ifndef DATABASE_H
#define DATABASE_H
#include <QSqlDatabase>
class Database
{
public:
Database();
~Database();
QSqlDatabase db;
bool connection();
void createTables();
};
#endif // DATABASE_H
//database.cpp:
#include "database.h"
#include <QtGui>
#include <QSqlQuery>
Database::Database()
{
db = QSqlDatabase::addDatabase("QSQLITE");
db.setDatabaseName(":memory:");
}
Database::~Database()
{
db.close();
}
bool Database::connection()
{
if (!db.open()) {
QMessageBox::critical(0, qApp->tr("Cannot open database"),
qApp->tr("Unable to establish a database connection.\n"
"This example needs SQLite support. Please read "
"the Qt SQL driver documentation for information how "
"to build it.\n\n"
"Click Cancel to exit."), QMessageBox::Ok);
return false;
}
return true;
}
void Database::createTables()
{
QSqlQuery q;
q.exec("CREATE TABLE test(id integer primary key,name varchar(20))");
q.exec("INSERT INTO test(name) VALUES('foo')");
q.exec("INSERT INTO test(name) VALUES('fie')");
q.exec("INSERT INTO test(name) VALUES('bar')");
}
//widget.h:
#ifndef WIDGET_H
#define WIDGET_H
#include <QWidget>
class QSqlQueryModel;
class Database;
namespace Ui {
class Widget;
}
class Widget : public QWidget
{
Q_OBJECT
public:
explicit Widget(QWidget *parent = 0);
~Widget();
private:
QSqlQueryModel *model;
Database *m_db;
Ui::Widget *ui;
};
#endif // WIDGET_H
//widget.cpp:
#include "widget.h"
#include "ui_widget.h"
#include "database.h"
#include <QSqlQueryModel>
Widget::Widget(QWidget *parent) :
QWidget(parent),
ui(new Ui::Widget)
{
ui->setupUi(this);
m_db = new Database();
m_db->connection();
m_db->createTables();
model = new QSqlQueryModel();
model->setQuery("SELECT * from test");
ui->tableView->setModel(model);
}
Widget::~Widget()
{
delete ui;
}
//widget.ui:
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>Widget</class>
<widget class="QWidget" name="Widget">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>274</width>
<height>210</height>
</rect>
</property>
<property name="windowTitle">
<string>Widget</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QTableView" name="tableView"/>
</item>
</layout>
</widget>
<layoutdefault spacing="6" margin="11"/>
<resources/>
<connections/>
</ui>
i guess your mistake is close db connection. Db connection must be closed on application closure

How can I call the "pushButton" in main.cpp?

mainwindow.h:
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include <QMainWindow>
namespace Ui {
class MainWindow;
}
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
explicit MainWindow(QWidget *parent = 0);
~MainWindow();
private:
Ui::MainWindow *ui;
};
#endif // MAINWINDOW_H
mainwindow.cpp
#include "mainwindow.h"
#include "ui_mainwindow.h"
MainWindow::MainWindow(QWidget *parent) :
QMainWindow(parent),
ui(new Ui::MainWindow)
{
ui->setupUi(this);
}
MainWindow::~MainWindow()
{
delete ui;
}
main.cpp
#include <QtGui/QApplication>
#include "mainwindow.h"
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
MainWindow w;
w.show();
QObject::connect(pushButton, SIGNAL(clicked()),
&a, SLOT(quit()));
return a.exec();
}
All the codes are given above. In a general Qt GUI program, I put a pushButton on the UI Form and tried to use it in main.cpp. But below error was given:
main.cpp:10: Error:'pushButton' was not declared in this scope
Could you please give me a solution? How can I call it in main.cpp?
Thanks!
Complement1:
mainwindow.cpp
#include "mainwindow.h"
#include "ui_mainwindow.h"
MainWindow::MainWindow(QWidget *parent) :
QMainWindow(parent),
ui(new Ui::MainWindow)
{
ui->setupUi(this);
QObject::connect(ui->pushButton, SIGNAL(clicked()),
QCoreApplication::instance(), SLOT(close()));
}
MainWindow::~MainWindow()
{
delete ui;
}
If I do it this way, then the program can run but cannot close the whole application. I guess that is because the QCoreApplication::instance() returns null due to in the constructor phase the QApplication doesn't exist, right?
Complement2:
mainwindow.cpp
void MainWindow::on_pushButton_clicked()
{
close();
}
One solution is to add new slot of the pushButton in mainwindow.cpp, like above.
But still I hope to know how to do it my way (main part of this post)?
Complement3:
Alberto's code works fine by using QWidget as below way.
ui->setupUi(this);
connect(ui->pushButton,SIGNAL(clicked()),this,SLOT(close()));
Your first approach can't work because, as compiler says, you didn't defined a QPushButton in main.cpp scope. So compiler can't find it.
Right approach is your last one. You develop a .ui file in which you add a QPushButton and then in c++ code that handles your .ui file(in your example a QMainWindow subclass) you connect it with a slot. But your connect call is wrong. This is my code:
//widget.h:
#ifndef WIDGET_H
#define WIDGET_H
#include <QWidget>
namespace Ui {
class Widget;
}
class Widget : public QWidget
{
Q_OBJECT
public:
explicit Widget(QWidget *parent = 0);
~Widget();
private:
Ui::Widget *ui;
};
#endif // WIDGET_H
//widget.cpp:
#include "widget.h"
#include "ui_widget.h"
#include <QtGui>
Widget::~Widget()
{
delete ui;
}
Widget::Widget(QWidget *parent) :
QWidget(parent),
ui(new Ui::Widget)
{
ui->setupUi(this);
connect(ui->pushButton,SIGNAL(clicked()),this,SLOT(close()));
}
//widget.ui:
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>Widget</class>
<widget class="QWidget" name="Widget">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>93</width>
<height>41</height>
</rect>
</property>
<property name="windowTitle">
<string>Widget</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QPushButton" name="pushButton">
<property name="text">
<string>PushButton</string>
</property>
</widget>
</item>
</layout>
</widget>
<layoutdefault spacing="6" margin="11"/>
<resources/>
<connections/>
</ui>
//main.cpp:
#include <QtGui/QApplication>
#include "widget.h"
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
Widget w;
w.show();
return a.exec();
}
with: connect(ui->pushButton,SIGNAL(clicked()),this,SLOT(close()));
which we can simplify in this way: connect(1,2,3,4);
when 2 is emitted because user does something on 1, do 4 on 3.
translated: when user click on pushButton(defined into ui file), do close on this(QMainWindow subclass). And if you take a look at your main.cpp code, MainWindow is the main widget you show to the user. So you're closing the whole app.

Resources