How to know when the VerticalScrollBar is showing? - c++

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))

Related

QTableView does not detect doubleClick [duplicate]

My application starts with an empty table, and I want to imlement different methods to add items. One should be by double-clicking the table's unused area (or "background") that is not occupied by any cells. When a cell is double-clicked, I want the default behavior.
I've found way to do this by re-implementing QAbstractScrollArea::mouseDoubleClickEvent() method in my TestTable class:
#include <QMouseEvent>
#include <QTableView>
class TestTable : public QTableView
{
Q_OBJECT
signals:
void backgroundDoubleClickEvent(void);
protected:
void mouseDoubleClickEvent (QMouseEvent* e)
{
if (indexAt(e->pos()).isValid())
{
QTableView::mouseDoubleClickEvent(e);
}
else
{
e->accept();
emit backgroundDoubleClickEvent();
}
}
};
This works, but is there a more elegant way of doing this without subclassing QTableView?
I'm not aware of any limitations of my current implementation. Are there obvious caveats?
If you don't want to subclass QTableView, try installEventFilter

Qt widget destructor indirectly calls widget method via connected signal, and crashes

I made a widget (QDataflowCanvas) based on QGraphicsView, where I connect the signal QGraphicsScene::selectionChanged() to the slot MainWindow::onSelectionChanged of my main window:
void MainWindow::onSelectionChanged()
{
// canvas is ptr to QDataflowCanvas, subclass of QGraphicsView
auto selNodes = canvas->selectedNodes();
auto selConns = canvas->selectedConnections();
...
}
The problem happens when I close my MainWindow and there is some item selected in the QGraphicsView.
I don't think I need to provide the complete code (although it can be found here), as I already isolated the cause of the crash.
This is what will happen (in order of causality):
destructor of MainWindow is called
destructor of QDataflowCanvas is called
destructor of QGraphicsView is called
destructor of QGraphicsScene is called, which triggers the removal of all items (with clear())
destructor of a QGraphicsItem is called
that will trigger a selectionChange event
the MainWindow::onSelectionChanged slot is called
method QDataflowCanvas::selectedNodes() is called, but the object is destroyed
crash!
which can be seen more in detail from the stack trace of the crash:
I found this workaround: if I disconnect the signal in MainWindow::~MainWindow, it will of course not crash:
MainWindow::~MainWindow()
{
QObject::disconnect(canvas->scene(), &QGraphicsScene::selectionChanged, this, &MainWindow::onSelectionChanged);
}
But this seems a rather atypical practice: I never found myself having to manually severe signal-slot connections because the program would otherwise crash.
There must be a more proper solution to this.
First of all, the name of your project is a mistake. The Q-prefixed namespace is taken. You should not be having any Q-prefixed classes in any project that uses Qt. You should rename the project to DataflowCanvas, for example.
There are three solutions:
Hold all children by value, order the children according to their dependencies. The QWidgetPrivate::deleteChildren called from QDataFlowCanvas will be a no-op, or at least it won't be touching objects you care about.
Use the old connect syntax when connecting to the MainWindow::onSelectionChanged slot. Note that when your slot was invoked, the mainwindow object was of the QWidget dynamic type, not MainWindow type. The connections made using the old connect syntax respect the dynamic type of the object, and a connection made to a slot of given class will guarantee that the object is of that class dynamically, i.e. at runtime.
Clear the selection in the destructor - then no futher selection changes will be handled.
The first solution makes everything explicit and is the one I'd use:
class DataFlowCanvas : public QGraphicsView {
...
private:
QDataflowModel *model_;
QDataflowTextCompletion *completion_;
QSet<QDataflowNode*> ownedNodes_;
QSet<QDataflowConnection*> ownedConnections_;
QMap<QDataflowModelNode*, QDataflowNode*> nodes_;
QMap<QDataflowModelConnection*, QDataflowConnection*> connections_;
bool showIOletsTooltips_;
bool showObjectHoverFeedback_;
bool showConnectionHoverFeedback_;
qreal gridSize_;
bool drawGrid_;
QGraphicsSecene scene_;
};
The scene is destructed before any other fields. Problem solved. You should hold everything else by value as well. E.g. completion_, etc. The pointer indirections are not useful.
The second solution highlights an unfortunate Qt bug. To wit -- in the code below, the old connect syntax will never invoke Derived2::aSlot2, because at the time the slot is invoked, the object isn't of the Derived2 type anymore:
#include <QtCore>
int ctr1, ctr2;
struct Derived1 : QObject {
Q_SLOT void aSlot1() { ctr1++; qDebug() << __FUNCTION__; }
Q_SIGNAL void aSignal();
~Derived1() { Q_EMIT aSignal(); }
Q_OBJECT
};
struct Derived2 : Derived1 {
Q_SLOT void aSlot2() { ctr2++; qDebug() << __FUNCTION__ << qobject_cast<Derived2*>(this); }
Q_OBJECT
};
int main() {
{
Derived2 d;
QObject::connect(&d, &Derived2::aSignal, &d, &Derived2::aSlot2);
QObject::connect(&d, SIGNAL(aSignal()), &d, SLOT(aSlot2()));
QObject::connect(&d, SIGNAL(aSignal()), &d, SLOT(aSlot1()));
}
Q_ASSERT(ctr1 == 1);
Q_ASSERT(ctr2 == 1);
}
#include "main.moc"
The output clearly demonstrates the problem:
aSlot2 QObject(0x0) <-- aSlot2 called but `this` is of `Derived1*` type!
aSlot1
I was taking it so simple :) What about just checking canvas pointer:
void MainWindow::onSelectionChanged()
{
if (!qobject_cast<QGraphicsScene*>(canvas))
return;
auto selNodes = canvas->selectedNodes();
auto selConns = canvas->selectedConnections();
...
}
I used qobject_cast to check if pointer canvas still exists. You can check in other (better) way. The code works.

QStyledItemDelegate: differentiate the cause for closeEditor() or setModelData()

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.

No double / Only one QMessageBox at same time

In my application, I have the following situation:
In a verify-method for inputs of a QTabWidget, I'm coming to determine an error for a sub-widget's input.
In cause of that I set this sub-widget as the active widget
In showEvent of this widget I'm firing an QTimer::singleShot to a method onShowEventFinished() in which I'm going to show a QMessageBox
After activating the the new sub-widget, I want to show at first the error message from my verify (step 1).
So then the message from verify shows up, but then in cause of my singleShot in step 3 the other one show up over it. I want to show the second one at the earliest, when the first one is closed.
(It's important that the first message shows up at the earliest when the regarding sub widget ist showing.)
Example code:
class MySubwidget : public QWidget
{
// omitted (ctor, etc.)
protected:
void showEvent( QShowEvent* e )
{
QShowEvent( e );
QTimer::singleShot( 200, this, SLOT(onShowEventFinished()) );
};
private slots:
void onShowEventFinished()
{
bool showEntryHint = false;
// omitted (some stuff to determine to show an entry hint or not)
if( showEntryHint )
{
QMessageBox t_MessageBox( this );
// omitted (set up the message box
t_MessageBox.exec();
}
};
};
I've tried different ways to solve this with QMutex, QWaitCondition or QSemaphore in a derived class from QMessageBox but this doesn't work because the execution of the message box is in the same thread.
This means, when the first message box is started with QMessageBox::exec() than QApplication::processEvents (called by QMessageBox) caused the invokement of my slot and called QMessageBox::exec() twice (for the second hint, while the first exec() is alive until the first message box is closed).
My currently workaround is now to have a subclass where I'm calling QApplication::processEvents() as long as another instance of MyMessageBox is shown:
class MyMessageBox : public QMessageBox
{
public:
// omitted (ctor, etc.)
int exec()
{
while( MessageBoxShowingCount > 0 )
QApplication::processEvents();
return QMessageBox::exec();
};
protected:
void showEvent( QShowEvent* e )
{
MessageBoxShowingCount++;
QMessageBox::showEvent(e);
};
void hideEvent( QHideEvent* e )
{
QMessageBox::hideEvent(e);
MessageBoxShowingCount--;
};
static int MessageBoxShowingCount = 0;
};
(For this solution I have replaced all my QMessageBox-instances by an MyMessageBox-instance.)
What you seem to need is an application-wide message manager. The QMessageBox then becomes that class's implementation detail and is not used directly anymore. When you want to show a message, you'd then use your MessageManager class.

QListView update - does not trigger update

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.