QStyledItemDelegate: differentiate the cause for closeEditor() or setModelData() - c++

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.

Related

How to combine a QLineEdit with a QPushButton having no space in between?

I need to make an element like this:
It's a combination of a line edit and a button with the same height and no space between them.
I have tried in Qt Designer but the height of them is not the same and there is always a small space between these 2 elements.
How can I solve this problem?
Maybe you could customize a QComboBox, something like this:
#include <QComboBox>
#include <QLineEdit>
class CustomBox : public QComboBox
{
Q_OBJECT
public:
CustomBox(QWidget * parent = nullptr) : QComboBox(parent)
{
setEditable(true);
QString ss =
"QComboBox::drop-down {border: none;}"
"QComboBox::down-arrow { image: url(/data/whatever.png); }"
"QComboBox::down-arrow:pressed { image: url(/data/whatever-pressed.png); }";
setStyleSheet(ss);
connect(lineEdit(), &QLineEdit::editingFinished, [this](){
QString t = lineEdit()->text();
clear();
lineEdit()->setText(t);
});
}
void setText(const QString & t) { lineEdit()->setText(t); }
QString text() const { return lineEdit()->text(); }
protected:
void showPopup() override
{
emit buttonClicked();
}
signals:
void buttonClicked();
};
The combo box is set as editable, so you can use an underlying QLineEdit, which is, more or less, under full control through the protected lineEdit member function. As you can see, I connected its editingFinished signal to a lambda to avoid adding items to the combo each time (this happens when the user press Enter in an editable combo box).
The styling is quite simple, given that you have a couple of icons for the button.
I exposed the line edit text getter/setter, and added a signal for the button click, which gets emitted from the showPopup protected method (called when the user press the button).
Just an idea.

How to know when the VerticalScrollBar is showing?

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

QStyledItemDelegate partially select text of default QLineEdit editor

I have a subclass of QStyledItemDelegate which at the moment does not reimplement any functions (for simplicity of the question).
With default QStyledItemDelegate implementation, when the user begins to edit text in a QTableView, the delegate draws a QLineEdit with the text from the model, and selects all of it (highlights all for editing).
The text represents file names such as "document.pdf". The user is allowed to edit this entire text, however, I only want to initially highlight the base name portion ("document") and not the suffix ("pdf"). How can I do this? (I don't need the logic of how to do this, I need to know how to get the QStyledItemDelegate to highlight a portion of text)
I've tried:
in setEditorData() used QLineEdit::setSelection() to highlight some text. This has no effect.
in paint() attempted to paint based on what other respondents have recommended to similar questions, but not success. I have little experience with QPainter. Here is an example: Adjusting the selection behaviour of QStandardItem with QStyledItemDelegate
Please help, and thanks in advance. A code snippet with say selecting the first 3 characters of text would be greatly appreciated.
As noted in my comments to the question, the problem with subclassing QStyledItemDelegate and trying to set any default selection in setEditorData like this:
void setEditorData(QWidget* editor, const QModelIndex &index)const{
QStyledItemDelegate::setEditorData(editor, index);
if(index.column() == 0){ //the column with file names in it
//try to cast the default editor to QLineEdit
QLineEdit* le= qobject_cast<QLineEdit*>(editor);
if(le){
//set default selection in the line edit
int lastDotIndex= le->text().lastIndexOf(".");
le->setSelection(0,lastDotIndex);
}
}
}
is that (in Qt code) after the view calls our setEditorData here, it tries to call selectAll() here when the editor widget is a QLineEdit. That means that whatever selection we provide in setEditorData will be changed afterwards.
The only solution I could come up with, was to provide our selection in a queued manner. So that, our selection is set when execution is back into the event loop. Here is working example:
#include <QApplication>
#include <QtWidgets>
class FileNameDelegate : public QStyledItemDelegate{
public:
explicit FileNameDelegate(QObject* parent= nullptr)
:QStyledItemDelegate(parent){}
~FileNameDelegate(){}
void setEditorData(QWidget* editor, const QModelIndex &index)const{
QStyledItemDelegate::setEditorData(editor, index);
//the column with file names in it
if(index.column() == 0){
//try to cast the default editor to QLineEdit
QLineEdit* le= qobject_cast<QLineEdit*>(editor);
if(le){
QObject src;
//the lambda function is executed using a queued connection
connect(&src, &QObject::destroyed, le, [le](){
//set default selection in the line edit
int lastDotIndex= le->text().lastIndexOf(".");
le->setSelection(0,lastDotIndex);
}, Qt::QueuedConnection);
}
}
}
};
//Demo program
int main(int argc, char** argv){
QApplication a(argc, argv);
QStandardItemModel model;
QList<QStandardItem*> row;
QStandardItem item("document.pdf");
row.append(&item);
model.appendRow(row);
FileNameDelegate delegate;
QTableView tableView;
tableView.setModel(&model);
tableView.setItemDelegate(&delegate);
tableView.show();
return a.exec();
}
This may sound like a hack, but I decided to write this until someone has a better approach to the problem.

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.

Get previous value of QComboBox, which is in a QTableWidget, when the value is changed

Say I have a QTableWidget and in each row there is a QComboBox and a QSpinBox. Consider that I store their values is a QMap<QString /*Combo box val*/,int /*spin box val*/> theMap;
When comboBoxes value or spin boxes value is being changed I want to update theMap. So I should know what was the former value of the combo box in order to replace with the new value of the comboBox and also take care of the value of the spin box.
How can I do this?
P.S. I have decided to create a slot that when you click on a table, it stores the current value of the combo box of that row. But this works only when you press on row caption. In other places (clicking on a combobox or on a spinbox) itemSelectionChanged() signal of QTableWidget does not work.
So in general my problem is to store the value of the combo box of selected row, and the I will get ComboBox or SpinBox change even and will process theMap easily.
How about creating your own, derived QComboBox class, something along the lines of:
class MyComboBox : public QComboBox
{
Q_OBJECT
private:
QString _oldText;
public:
MyComboBox(QWidget *parent=0) : QComboBox(parent), _oldText()
{
connect(this,SIGNAL(editTextChanged(const QString&)), this,
SLOT(myTextChangedSlot(const QString&)));
connect(this,SIGNAL(currentIndexChanged(const QString&)), this,
SLOT(myTextChangedSlot(const QString&)));
}
private slots:
myTextChangedSlot(const QString &newText)
{
emit myTextChangedSignal(_oldText, newText);
_oldText = newText;
}
signals:
myTextChangedSignal(const QString &oldText, const QString &newText);
};
And then just connect to myTextChangedSignal instead, which now additionally provides the old combo box text.
I hope that helps.
A bit late but I had the same problem and solved in this way:
class CComboBox : public QComboBox
{
Q_OBJECT
public:
CComboBox(QWidget *parent = 0) : QComboBox(parent) {}
QString GetPreviousText() { return m_PreviousText; }
protected:
void mousePressEvent(QMouseEvent *e)
{
m_PreviousText = this->currentText();
QComboBox::mousePressEvent(e);
}
private:
QString m_PreviousText;
};
My suggestion is to implement a model, which would help you make a clean separation between the data, and the UI editing the data. Your model would then get notified that a given model index (row and column) changed to the new data, and you could change whatever other data you needed to at that point.
I was just having a similar issue, but for me i needed the previous index for something very trivial so defining and implementing a whole class for it was unjustified.
So what I did instead was keep an argument called say 'previousIndex' and updated it's value only after I had done everything I needed with it