I have following problem:
when I call update() on QListView, its paintEvent() is not triggered unless some other event occurs over the widget (mouse move, got focus....)
I am using Qt 4.8.3, and unless this is definitely bug in the version, I would prefer not upgrading (as from my experience upgrades bears more trouble than benefits).
The question:
How to make QListView (and Q...View) to update next time main loop gets control?
Some background as what I am solving if it helps:
Meant as single threaded application.
At the bottom is some independent (no Qt) model, which is hierarchical, and consumers request subitems. Items at the bottom of hierarchy may be modified.
On modification, consumer requests W(ritable)Item. At that point, Model parts influenced by the change reports "modified" via observer approach. So observers are notified at the start of the change (model returns writable object, has no control or idea when change ends).
Consumer is expected to finish modification before returning from function/method which started modification.
Modifying methods/functions are expected to be called from main thread, so next time main thread tinkers with GUI, model is in consistent state, and consumers can refresh.
QModels are done to provide data from model below in Qt accessible format.
Next are QWidgets (lists/textboxes/labels) visualising data to user,
these are modified to support Desync() method, which mark visualised data to be out of sync, and overridden paintEvent, which checks for inSync state. For simple QWidgets like labels, on synchronization, callback is called, which just fills in the data. For Q...View, I assumed to force models to emit modelReset, so list reloads number of rows and content of visible ones.
On the top is class collecting it all together under its region, which is hooked to observers, and on reported changes Desync relevant widgets.
All methods changing anything are connected via signal/slot Qt thingie to buttons/comboboxes/other GUI elements, so I assume it all runs under main thread.
Idea of change:
GUI induced event, main thread starts processing consumer's change method
consumer obtains necessary items for change
consumer obtains writable items
real model reports modified to observers
observers mark (Desync) relevant QWidgets as out of sync
QWidgets are marked as out of sync, and scheduled to update, yet do not attempt to access anything, as we run under main thread
consumer performs change, during which real model might be even inconsistent
consumer returns control to whatever called it
main loop performs updates, which are overridden to sync widgets
* What I observed: *
update() results in paintEvent for most widgets that do not have model at all (label/ textbox...)
update() does not result in paintEvent for QListView
repaint() does not help (was just wild attempt)
moving mouse over widget results in paintEvent, and QWidget synchronizes
trying to visible(false); update(); visible(true); repaints immediately
that is wrong, as QWidget synchronizes before consumer performs the change
switching windows (i.e. visual studio) or back results in paintEvent being called
Simplified sources where to get the behavior:
myList.h
#ifndef __myList_h__
#define __myList_h__
#include <qlistview.h>
class myList : public QListView
{
bool inSync;
void sync();
protected:
virtual void paintEvent(QPaintEvent * event) override;
public:
myList(QWidget * parent);
void Desync();
virtual ~myList();
};
#endif
myList.cpp
#include "myList.h"
#include "myModel.h"
void myList::sync()
{
if (inSync)
return;
inSync = true; //< set early, to prevent loops
((myModel*)model())->ResetModel();
}
void myList::paintEvent(QPaintEvent * event)
{
sync();
QListView::paintEvent(event);
}
myList::myList(QWidget * parent) : QListView(parent), inSync(false)
{}
void myList::Desync()
{
inSync = false;
update();
}
myList::~myList()
{}
myModel.h
#ifndef __myModel_h__
#define __myModel_h__
#include <QAbstractListModel>
class myModel : public QAbstractListModel
{
Q_OBJECT;
int & externalComplexData;
public:
myModel(int & externalComplexData);
virtual int rowCount(QModelIndex const & parent = QModelIndex()) const override;
virtual QVariant data(QModelIndex const & index, int role) const override;
void ResetModel();
virtual ~myModel();
};
#endif
myModel.cpp
#include "myModel.h"
myModel::myModel(int & externalComplexData) : externalComplexData(externalComplexData)
{}
int myModel::rowCount(QModelIndex const & parent) const
{
return 1;
}
QVariant myModel::data(QModelIndex const & index, int role) const
{
if (role != Qt::DisplayRole)
return QVariant();
return QString::number(externalComplexData);
}
void myModel::ResetModel()
{
reset();
}
myModel::~myModel()
{}
tmp.h
#ifndef __Tmp_H__
#define __Tmp_H__
#include <QtGui/QMainWindow>
#include "ui_tmp.h"
class tmp : public QMainWindow
{
Q_OBJECT
public:
tmp(QWidget *parent = 0, Qt::WFlags flags = 0);
~tmp();
private:
Ui::tmpClass ui;
private slots:
void clicked();
};
#endif
tmp.cpp
#include "tmp.h"
#include "myModel.h"
int localComplexData = 0;
tmp::tmp(QWidget *parent, Qt::WFlags flags)
: QMainWindow(parent, flags)
{
ui.setupUi(this);
ui.lst->setModel(new myModel(localComplexData));
connect(ui.btn, SIGNAL(clicked()), this, SLOT(clicked()));
}
void tmp::clicked()
{
ui.lst->Desync();
++localComplexData;
}
tmp::~tmp()
{}
Behavior:
Clicking button updates external model, yet the list is not synchronized.
When moving mouse over the list, it synchronizes.
Expected Behavior:
Registering programmer's wish to update(), and result in paintEvent next time main loop gets the control (or even few loops later).
You did it wrong.
Do not touch QListView you don't have to. Just fix data model (Qt) and rest will work out of box.
Your model myModel should simply invoke proper methods when data are changing. This model should observe source of real data.
When something will happen to the data:
data are changed - emit signal dataChanged
data are added or removed call beginInsertRows and endInsertRows or other respective versions
If you do this properly nothing else is needed.
Related
I'm using a QTableWidget and need some custom handling of the editing, so I set a QStyledItemDelegate on it.
When the user finishes editing, the signal closeEditor() is emitted, which I connect to to process the entered data. This signal is both emitted when Enter/Return is pressed, and when the user clicks somewhere else (outside the QTableWidgetItem to be edited).
My question is: Is is possible to differntiate if the user presses Return/Enter or clicks somewhere else? I'd like to handle the "click outside" just like an ESC press (no data is changed and the original value of the QTableWidgetItem is restored). By now, both cases change the data.
QAbstractItemDelegate::EndEditHint (which is emitted with closeEditor()) does not give me that information.
Thanks for all help!
Edit:
To process data before it's written back to the model, one can implement setModelData(), but still, there seems to be no way if this function was called by pressing Enter/Return or by clicking somewhere else …
I recently made a sample to become (more) familiar with QStyledItemDelegate concerning inline editing of cells:
In this sample, I did the update of underlying data model overloading QStyledItemDelegate::setModelData().
After having read this question, I just tested what happens if input is finished (or strictly speaking: aborted) by ESC. This is what I observed:
The underlying data is kept unchanged.
Digging deeper (i.e. setting a break-point into my overloaded setModelData()), I observed setModelData() is even not called in this case.
May be, the OPs issue can be solved easily by a little re-design of his delegate.
Finally, my derived class (declaration) I used to achieve a "full-featured" delegate to edit table cells inline:
class ValueNameDelegate: public QStyledItemDelegate {
// methods:
public:
/// #name Construction & Destruction
//#{
/// constructor.
ValueNameDelegate();
/// destructor.
virtual ~ValueNameDelegate() = default;
// disabled:
ValueNameDelegate(const ValueNameDelegate&) = delete;
ValueNameDelegate& operator=(const ValueNameDelegate&) = delete;
//#}
protected:
/// #name Overloaded Event Handlers
//#{
// inserts editor in table (by setParent(pQParent)) and
// returns the editor widget to edit cell.
virtual QWidget* createEditor(
QWidget *pQParent, const QStyleOptionViewItem &qOption,
const QModelIndex &qMIndex) const override;
// removes editor from table (by setParent(nullptr)).
virtual void destroyEditor(
QWidget *pQEditor, const QModelIndex &qMIndex) const override;
// reads data from table model and updates editor.
virtual void setEditorData(
QWidget *pQEditor, const QModelIndex &qMIndex) const override;
// reads data from editor and updates table model.
virtual void setModelData(
QWidget *pQEditor, QAbstractItemModel *pQModel,
const QModelIndex &qMIndex) const override;
//#}
};
Note:
In my case, there is only one editor widget created before-hand. It is re-used for every cell editing (instead of creating for each edit a new one).
This is a little bit different from the Qt Spin Box Delegate Example but works as expected.
I cannot imagine how this shouldn't make any difference concerning the OPs problem.
According to feed-back, OP wants to handle the focus-lose event like abort of input. This is in opposition how this is handled by default in QLineEdit.
So, my solution is to provide an overloaded version of QLineEdit. Instead of changing the behavior of QLineEdit, it simply tracks in a member bool _confirmed whether Enter was pressed. The most tricky part was to identify another suitable event or signal to reset the member. Finally, I decided that focusOutEvent() is just called at the right time and hence suitable for this task.
testQLineEdit-Finished.cc:
#include <QtWidgets>
class LineEdit: public QLineEdit {
private:
// flag: true ... last finished editing was confirmed
bool _confirmed;
public:
// Construction & Destruction
explicit LineEdit(
const QString &contents = QString(), QWidget *pQParent = nullptr):
QLineEdit(contents, pQParent),
_confirmed(false)
{
QObject::connect(this, &QLineEdit::returnPressed,
[this](){ onSigReturnPressed(); });
}
LineEdit(QWidget *pQParent): LineEdit(QString(), pQParent) { }
virtual ~LineEdit() = default;
LineEdit(const LineEdit&) = delete;
LineEdit& operator=(const LineEdit&) = delete;
public:
// returns whether last finished editing was confirmed.
bool isConfirmed() { return _confirmed; }
protected:
virtual void focusOutEvent(QFocusEvent *pQEvent) override
{
_confirmed = false;
QLineEdit::focusOutEvent(pQEvent);
}
private:
void onSigReturnPressed() { _confirmed = true; }
};
int main(int argc, char **argv)
{
qDebug() << "Qt Version:" << QT_VERSION_STR;
QApplication app(argc, argv);
// setup GUI
LineEdit qEdit(QString::fromUtf8("Hello World"));
qEdit.show();
// install signal handlers
QObject::connect(&qEdit, &LineEdit::editingFinished,
[&]() {
qDebug() << "Edit confirmed:" << qEdit.isConfirmed();
});
// runtime loop
return app.exec();
}
The output of Edit confirmed: true was achieved by pressing Enter, the false lines by clicking around.
This is done rather simple and might be done more sophisticated. However, it shows the principle achieved with a rather low number of code lines.
I need to know when the verticalScrollBar of my QTableWidget is being shown.
I am currently using the following instruction:
Header:
#ifndef MYCLASS_H
#define MYCLASS_H
#include <QDebug>
#include <QWidget>
#include <QScrollBar>
namespace Ui {
class MyClass;
}
class MyClass: public QWidget
{
Q_OBJECT
public:
explicit MyClass(QWidget *parent = 0);
~MyClass();
private:
void populateTable(QVector<QString> content);
private:
Ui::MyClass *ui;
};
#endif // MYCLASS_H
Populate table function:
void MyClass::populateTable(QVector<QString> content)
{
while( ui->myTableWidget->rowCount() > 0 )
{
ui->myTableWidget->removeRow(0);
}
QTableWidgetItem* item;
for (int row = 0; row < content.length(); ++row)
{
ui->myTableWidget->insertRow(row);
item = new QTableWidgetItem( QString::number(row) );
item->setTextAlignment(Qt::AlignCenter);
ui->myTableWidget->setItem(row, 0, item);
item = new QTableWidgetItem( content.at(row) );
item->setTextAlignment(Qt::AlignCenter);
ui->myTableWidget->setItem(row, 1, item);
}
qDebug() << "This : " << this->isVisible();
qDebug() << "QTableWidget : " << ui->myTableWidget->isVisible();
qDebug() << "VerticalScrollBar : " << ui->myTableWidget->verticalScrollBar()->isVisible(); // <-HERE
}
Output:
// Called from the constructor
This : false
QTableWidget : false
VerticalScrollBar : false
// Called by a button pressed
This : true
QTableWidget : true
VerticalScrollBar : true
This : true
QTableWidget : true
VerticalScrollBar : false
But it returns a wrong value. When the ScrollBar is visible it returns false and when it is not visible it returns true. Note: myTableWidget (QTableWidget) is always visible.
Is there any other way that I can do this?
I'm using Qt version 5.3.2
In general the code you are using is supposed to work - checked on Qt 5.3.0.
However, you must be sure that when you are making the call the QTableWidget itself is visible.
For example if you make the call inside the MainWindow constructor you will certainly get a false answer. Only after the form is shown the call to isVisible() on particular scrollbar would return the correct value.
EDIT:
With your code pasted I was able to reproduce the issue. I needed to go through the Qt code a bit to see whats going on. Basically it turns out that for QTableView which is parent class of QTableWidget scroll bar values are updated via updateGeometries (do not confuse it with the regular updateGeometry the one I'm mentioning is protected). Internally this method is called either directly or the event is processed through the event loop. In short, it depends on whether you add columns or rows.
In your example, if you insertColumn instead of insertRow (and switch the arguments in setItem) after checking the visibility of horizontalScrollBar you will get the proper result right away.
I could confirm this by subclassing the QTableWidget and overriding event method. It shows that when adding columns following events are executed: MetaCall (invoke call) and LayoutRequest. On the other hand, when adding rows first event passed is Timer.
I'm not Qt implementer so I'm not sure what is the purpose the difference. However, this info helps solving your problem in a more elegant way.
You can implement MyTableWidget which overrides the event method.
class MyTableWidget: public QTableWidget
{
Q_OBJECT
public:
bool event(QEvent *e) override
{
const bool result = QTableWidget::event(e);
if (e->type() == QEvent::LayoutRequest)
{
// call what you need here
// or emit layoutUpdated(); and connect some slots which perform
// processing dependent on scrollbar visibility
}
return result;
}
signals:
void layoutUpdated();
}
However, such event might get called in other situations not only when the view needs to be updated due to model data updates.
Another solution would be to avoid overriding the event method but creating your own method to trigger the required updates. For example:
void MyTableWidget::updateLayout()
{
QEvent ev{QEvent::LayoutRequest};
QTableWidget::updateGeometries();
QTableWidget::event(&ev);
}
This would call directly updateGeometries which recalculates scrollbar min/max values and perform a direct event method call for LayoutRequest (without processing through eventloop). Which if I'm correct indirectly updates scrollbar visibility.
Calling this method before checking the visibility should also fix your problem.
ui->myTableWidget->updateLayout();
qDebug() << "VerticalScrollBar : " << ui->myTableWidget->verticalScrollBar()->isVisible();
// prints "VerticalScrollBar : true false"
I wanted to get notified via a signal, when a QScrollBar gets visible. I'm writing this answer here, since this is the first google result I got and no result had a proper answer.
First of all, there is no signal for changed visibility, so we will use valueChanged.
Now you can add a function that checks if the scrollbar isVisible() or not.
Problem: It won't work (and someone with more knowledge in Qt could probably explain why, I sadly cannot).
The trick is, to use a QTimer (Python code):
self.horizontalScrollBar().valueChanged.connect(lambda x: QTimer.singleShot(0, some_func))
The Situation
My company has a QML-based application which displays some content using a custom OpenGL-based render plugin (MyGame). This plugin has a few critical needs:
To be able to effect changes in the renderer in response to QML-based signals.
(e.g. change the position of an object rendered by the game)
To only process these changes at a specific spot in MyGame's redraw loop.
(This is very important; MyGame is very sensitive about when changes are allowed.)
To have the plugin redraw at 60Hz (at least).
The Problem
The code we have right now honors (1) and (2), but fails (3); the plugin does not get visually updated consistently. (The updates are erratic, at an estimated 5-10Hz.) I believe that the plugin we have created—based on QQuickFramebufferObject—is not taking proper advantage of how Qt/QML intended the scene graph to be updated.
How can I re-structure my plugin so that I get all three of the above?
The Code
Overview:
The plugin creates a QQuickFramebufferObject (MyPlugin) and a QQuickFramebufferObject::Renderer (MyRenderer).
When MyRenderer::render() is called it calls MyGame::Redraw() itself, and then calls update().
MyGame::Redraw() does what it needs to, and at the right spot where changes can be accepted, emits a timeToMakeChanges QML signal on MyPlugin.
QML listens for the onTimeToMakeChanges signal and invokes methods on the plugin that affect MyGame.
To workaround the problem of low-frequency visual updates, I've found that if I overlay a QML Canvas over my plugin and redraw the canvas frequently using a Timer, my plugin starts to get visually updated at what appears to be around 60Hz. Clearly this is a gross hack.
Following is a summary of the code setup. Please forgive missing/incorrect code; I'm trying to distill thousands of lines of glue code down to the essentials for this question.
MyPlugin.h
#include <QOpenGLFramebufferObject>
#include <QQuickFramebufferObject>
class MyPlugin : public QQuickFramebufferObject {
Q_OBJECT
public:
MyPlugin();
virtual ~MyPlugin();
virtual QQuickFramebufferObject::Renderer* createRenderer() const;
signals:
void timeToMakeChanges();
public slots:
void makeChanges(QVariant inValue);
void HandleWindowChanged(QQuickWindow *inWindow);
private:
MyGame* GetGame() { ... }
};
MyPlugin.cpp
#include "MyPlugin.h"
#include <MyGame.h>
// ******************************************************************
class MyRenderer:
public QObject,
public QQuickFramebufferObject::Renderer,
protected QOpenGLFunctions
{
Q_OBJECT
public:
virtual void render();
private:
static void RequestGameChanges();
};
void MyRenderer::render() {
if ( !m_Initialized ) {
QOpenGLFramebufferObject *theFbo = this->framebufferObject();
InitializeGl( theFbo ); // Not shown
m_MyGame = &MyGame::Create();
m_MyGame->RegisterCallback(
reinterpret_cast<qml_Function>(MyRenderer::RequestGameChanges)
);
m_Initialized = true;
}
m_MyGame->RestoreState();
m_MyGame->Redraw();
m_MyGame->SaveState();
m_PluginItem->window()->resetOpenGLState();
// Tell QML that we want to render again as soon as possible
update();
}
// This gets invoked in the middle of m_MyGame->Redraw()
void MyRenderer::RequestGameChanges() {
emit m_PluginItem->timeToMakeChanges();
}
// ******************************************************************
MyPlugin::MyPlugin() {
setMirrorVertically(true);
connect(
this, SIGNAL(windowChanged(QQuickWindow*)),
this, SLOT(HandleWindowChanged(QQuickWindow*))
);
}
void MyPlugin::HandleWindowChanged(QQuickWindow *inWindow) {
inWindow->setClearBeforeRendering(false);
}
void MyPlugin::makeChanges(QVariant inValue) {
MyGame *theGame = GetGame();
// Send the requested changes to theGame
}
QQuickFramebufferObject::Renderer* MyPlugin::createRenderer() const {
m_Renderer = new MyRenderer( *this );
}
MyApp.qml
import MyPlugin 1.0
Window {
MyPlugin {
property var queuedUpChanges: ([])
onSomeOtherSignal: queueUpChangesToMake();
onTimeToMakeChanges: makeChanges( queuedUpChanges );
}
Canvas { id:hack }
Timer {
interval:10; running:true; repeat:true
onTriggered: hack.changeWhatYouShow();
}
}
Bonus Points
The main question is "How do I modify my code so that I get 60Hz updates?" However, as seen in the QML, the setup above requires me to queue up all changes in QML so that they are able to be applied during the right spot in the MyGame::Render().
Ideally, I'd prefer to write QML without timeToMakeChanges, like:
MyPlugin {
onSomeOtherSignal: makeChanges( ... );
}
If there's a way to accomplish this (other than queuing up the changes in C++ instead)—perhaps something related to synchronize() I'd love to know about it.
I'd make a timer in QML that calls the makeChanges regularly. But store all the state in MyPlugin. Then, in Renderer::synchronize(), copy from MyPlugin to MyRenderer, so it can be used by the MyGame.
(although, I wouldn't do any gamelogic-related calculations in QML ever in the first place)
I have created a QThread class to run a function that is in another class, but this another class has a pointer to a QWidget (QwtPlot), and I am receiving this message in the application output:
QCoreApplication::sendPostedEvents: Cannot send posted events for objects in another thread
I already read in another topics that QThreads doesn't work with QWidgets (the UI widgets must be in the main thread), but the output in my application seems to be correct.
Can anyone explain to me why this message appears? And what can happen if I let the code as is?
Note: sorry, I can't post the code.
Thanks in advance
I already read in another topics that QThreads doesn't work with QWidgets [...]
but the output in my application seems to be correct.
It's not correct, otherwise you wouldn't ask, right?
A QWidget must be in the main thread. And most likely it is. But you're invoking its methods from another thread, and the methods you invoke are not thread safe. Don't do that. There are other ways of invoking methods safely across threads. Use them instead. For example, assuming that you wish to call QWidget::resize, you could use postToThread from this answer:
QWidget* widget;
QSize size;
Q_ASSERT_X(widget->thread() == qApp->thread(), "widget",
"The widget must live in the main thread.");
postToThread([=]{ widget->resize(size); }, widget);
If you want to be more verbose, or have to maintain a Qt 4 code base, you could do this instead:
class TSWidgetAdapter : public QObject {
Q_OBJECT
QWidget * widget() const { return qobject_cast<QWidget*>(parent()); }
Q_SLOT void resize_d(const QSize & size) { widget()->resize(size); }
public:
explicit TSWidgetAdapter(QWidget * parent) : QObject(parent) {
Q_ASSERT_X(parent->thread() == qApp->thread(), "TSWidgetAdapter()",
"The widget must live in the main thread.");
connect(this, SIGNAL(resize(QSize)), this, SLOT(resize_d(QSize)));
}
Q_SIGNAL void resize(const QSize & size);
};
QWidget* widget;
QSize size;
TSWidgetAdapter widget_ts(widget);
widget_ts.resize(size);
The _d slots are called in the thread of the widget. That's the beauty of automatic connections: you can call the signal in any thread, and the slot will be called in the thread of the target object only. Since the adapter is a child of the widget, it is in the widget's thread - that's enforced by Qt.
I need show files from QFileSystemModel in QTreeView and customize that tree to show one more column with QCheckBox, so user can pick 0..N files from that QTreeView.
I read doc from Qt to understand model/view architecture and i am now in my code at point, where i have custom delegate CustomItemDelegatefor specific column, but actually i don't know how to create QCheckBox in paint method of my custom delegate (to be more specific i know how, but this is 99% bad way).
customitemdelegate.h
#ifndef CUSTOMITEMDELEGATE_H
#define CUSTOMITEMDELEGATE_H
#include <QStyledItemDelegate>
class CustomItemDelegate : public QStyledItemDelegate
{
Q_OBJECT
public:
explicit CustomItemDelegate(QObject *parent = 0);
void paint(QPainter * painter, const QStyleOptionViewItem & option, const QModelIndex & index) const;
signals:
public slots:
};
#endif // CUSTOMITEMDELEGATE_H
customitemdelegate.cpp
#include "customitemdelegate.h"
#include <QCheckBox>
#include <iostream>
#include <QTreeView>
using namespace std;
CustomItemDelegate::CustomItemDelegate(QObject *parent) :
QStyledItemDelegate(parent)
{
}
void CustomItemDelegate::paint(QPainter * painter, const QStyleOptionViewItem & option, const QModelIndex & index) const {
((QTreeView *)parent())->setIndexWidget(index, new QCheckBox());
}
You don't create a QCheckbox, you paint one using the current style. Look at the docs regarding QStyle, specifically for drawControl(..). There's also a customised example I wrote for a question on SO which you can get an idea from.
Mouse handling has to be handled by the view (because the control doesn't actually exist), and for most styles that will include mouse-over updating.
It is a bit of a pain (things may have gotten easier in v5.0+, I last did this in v4.8), but it's well worth it. Creating 'real' QCheckBoxs is inefficient (in your example it will cause a massive memory leak), and becomes noticeably slow for large datasets. Whereas painting a 'fake' one only when required (i.e. visible) is very fast.