Retrieve QComboBox index from QCompletion popup - c++

I have a QComboBox with a QCompleter. The QCompleter displays suggestions in a popup list and I'm drawing the elements in a custom way.
The problem is: I can't get the index in the original combobox even if I did store it into the UserData of the item.
Complete code follows:
class MyItemDelegate: public QItemDelegate {
public:
MyItemDelegate(QWidget *parent) :
QItemDelegate(parent) {}
void paint(QPainter *painter, const QStyleOptionViewItem &option,
const QModelIndex &index ) const Q_DECL_OVERRIDE
{
if (index.row() == 0) { // If this is the first item in the POPUP VIEW
auto comboIndex = index.data(Qt::UserRole).toInt();
qDebug() << "Combobox index is " << comboIndex; // WRONG!!
}
... custom styling code omitted since off-topic ...
}
};
MyWindow::MyWindow(QStringList words, QMainWindow *parent) :
QDialog((QWidget*)parent),
ui(new Ui::MyWindow) {
ui->setupUi(this);
int comboBoxIndex = 0;
for(auto& word : words) {
ui->myComboBox->addItem(word, comboBoxIndex); // This is stored in UserData
++comboBoxIndex;
}
completer = new QCompleter(words, this);
// Use a TreeView for the popup of the completer
QTreeView *treeView = new QTreeView(this);
completer->setPopup(treeView);
treeView->setItemDelegate(new MyItemDelegate(this));
}
No matter what element I have first in the QCompletion popup list, the qDebug() line always returns 0 as index.
Isn't index.data() referring to the original item data of the combobox?

As i see, your problem is that you retrieve comboIndex from model where you didn't set UserData. You set UserData for items of internal QComboBox model (see QComboBox), but when you try to retrieve this data, you do that in code of MyItemDelegate, which you set for QTreeView for QCompleter. That's why index in paint method of delegate is index of QCompleter internal model, not index of QComboBox internal model. And that's why comboIndex is always 0.
To solve your problem you can add UserData in QCompleter internal model. For example, you can create your own model and set this model for QCompleter with method setModel.

Related

How to show selected text different from list text in QComboBox?

I have a QComboBox with a popup list (a QAbstractItemView) showing different items (QStandardItems). Now, I want the item in the list to show a different text than if the item is selected.
Background:
I am creating a word-processor like style chooser where one can choose, say, "1.1 Heading 2" from the list, indicating the numbering and the style name, but when an item is chosen the combobox should only show the style name, say "Heading 2".
I thought the following question was exactly about what I was asking for but apparently an answer was chosen that does not work (apparently even according to the person asking the question): Can a QComboBox display a different value than whats in it's list?
Solution
Since QComboBox uses a list view to display the values, probably the "Qt'iest" way to achieve the desired effect, is to use a custom delegate and modify the text within its paint method, using a hash map (QHash) to get the corresponding string.
Example
Here is a simple example I have prepared for you to demonstrate how the proposed solution could be implemented:
Delegate.h this is where the magic is happening
#include <QStyledItemDelegate>
#include <QApplication>
class Delegate : public QStyledItemDelegate
{
Q_OBJECT
public:
explicit Delegate(QObject *parent = nullptr) :
QStyledItemDelegate(parent) {}
void setHash(const QHash<int, QString> &hash) {
m_hash = hash;
}
void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const override {
if (!index.isValid())
return;
QStyleOptionViewItem opt = option;
initStyleOption(&opt, index);
opt.text = m_hash.value(index.row());
QApplication::style()->drawControl(QStyle::CE_ItemViewItem, &opt, painter);
}
private:
QHash<int, QString> m_hash;
};
MainWindow.h only for demo purposes
#include <QWidget>
#include <QBoxLayout>
#include <QComboBox>
#include <QStandardItemModel>
#include "Delegate.h"
class MainWindow : public QWidget
{
Q_OBJECT
public:
MainWindow(QWidget *parent = nullptr)
: QWidget(parent)
{
auto *l = new QVBoxLayout(this);
auto *cmbBox = new QComboBox(this);
auto *model = new QStandardItemModel(this);
auto *delegate = new Delegate(this);
QHash<int, QString> hash;
for (int n = 0; n < 5; n++) {
// For demo purposes I am using "it#" and "item #"
// Feel free to set those strings to whatever you need
model->appendRow(new QStandardItem(tr("it%1").arg(QString::number(n))));
hash.insert(n, tr("item %1").arg(QString::number(n)));
}
delegate->setHash(hash);
cmbBox->setModel(model);
cmbBox->setItemDelegate(delegate);
l->addWidget(cmbBox);
resize(600, 480);
}
};
Result
The example produces the following result:
The easiest way to do is to set the ComboBox as editable and then when the current item changes, you change the text to whatever you want. Example:
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
, ui(new Ui::MainWindow)
{
ui->setupUi(this);
QStringList a = {"Red", "Green", "Blue"};
aModel.setStringList(a);
ui->comboBox->setModel(&aModel);
ui->comboBox->setEditable(true);
}
void MainWindow::on_comboBox_currentIndexChanged(const QString &arg1)
{
if (arg1 == "Green") {
ui->comboBox->setCurrentText("Green on");
} else if (arg1 == "Red") {
ui->comboBox->setCurrentText("Red on");
}
}
ui->comboBox->setCurrentText("Green on"); will only change the text when the item is selected, when you reopen the combobox, the text will be reverted back to original. This is somewhat similar to my answer here.
Another way to do this would be to inherit the QComboBox class and then reimplement the mousePressEvent to change the model whenever the mouse is pressed, and switch it back after releasing. This will probably be more difficult to get right or may not even work as I have not tried it myself

Slot called twice qt

I have an editable list view inside a dock widget. I wanted to keep the track of the data before the user edits and the data after the user edits. The complete concerning code is:
void MainWindow :: createDock()
{
//initialize dockWidget
QDockWidget *dock = new QDockWidget("Tags", this);
dock->setAllowedAreas(Qt::LeftDockWidgetArea | Qt::RightDockWidgetArea);
dock->setFeatures(QDockWidget::DockWidgetFloatable | QDockWidget::DockWidgetMovable);
//widget to store all widgets placed inside dock because dock cannot set layout but can set widget
QWidget *tags = new QWidget(dock);
//initiazlize treeViewModel
listViewModel = new QSqlTableModel(this);
listViewModel->setTable("tags");
listViewModel->select();
listViewModel->setHeaderData(0, Qt::Horizontal, "Tags");
//set the model for treeView
listView = new QListView(dock);
listView->setModel(listViewModel);
connect(listView, &QListView::doubleClicked, this, &MainWindow::onListViewDoubleClicked, Qt::UniqueConnection);
connect(listViewModel, &QSqlTableModel::dataChanged, this, &MainWindow::onLVDataChanged, Qt::UniqueConnection);
//add treeView to the dock
QVBoxLayout *layout = new QVBoxLayout;
layout->addWidget(listView);
tags->setLayout(layout);
//add the dock widget to the main window and show it
dock->setWidget(tags);
this->addDockWidget(Qt::LeftDockWidgetArea, dock);
//dock->show();
}
void MainWindow :: onLVDataChanged(const QModelIndex& index, const QModelIndex& index2, const QVector<int> & roles)
{
QMetaMethod metaMethod = sender()->metaObject()->method(senderSignalIndex());
QMessageBox::information(this, "", metaMethod.name());
afterUpdate = index.data().toString();
//do somethings
beforeUpdate = "";
afterUpdate = "";
}
void MainWindow :: onListViewDoubleClicked(const QModelIndex &index)
{
QMetaMethod metaMethod = sender()->metaObject()->method(senderSignalIndex());
QMessageBox::information(this, "", metaMethod.name());
beforeUpdate = index.data().toString();
}
I do this:
I double click an item so as to edit it. The onDoubleClick() is called only once (seen becuase of QMessageBox). I add a space to the data present (in my case it was "fiction", i changed it to "fiction "). But, after I press enter, dataChanged() is called twice (again seen through QMessageBox).
I don't emit the signal explicitly. It is emitted only by model.
The problem is caused by the editing strategy, by default it is QSqlTableModel::OnRowChange, this expects the row to be changed emitting a signal to update the item and another to update the entire row, that can be easily seen if we use the following:
void MainWindow::onListViewDoubleClicked(const QModelIndex &index)
{
qDebug()<<__FUNCTION__<<index;
}
void MainWindow::onLVDataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight, const QVector<int> &roles)
{
qDebug()<<__FUNCTION__<<topLeft<<bottomRight<<roles<<topLeft.data();
}
Output:
onListViewDoubleClicked QModelIndex(1,0,0x0,QSqlTableModel(0x562940043670))
onLVDataChanged QModelIndex(1,0,0x0,QSqlTableModel(0x562940043670)) QModelIndex(1,0,0x0,QSqlTableModel(0x562940043670)) QVector() QVariant(QString, "tag2 ")
onLVDataChanged QModelIndex(1,0,0x0,QSqlTableModel(0x562940043670)) QModelIndex(1,2,0x0,QSqlTableModel(0x562940043670)) QVector() QVariant(QString, "tag2 ")
The solution is to change the editing strategy to QSqlTableModel::OnManualSubmit:
...
listViewModel = new QSqlTableModel(this);
listViewModel->setTable("tags");
listViewModel->setEditStrategy(QSqlTableModel::OnManualSubmit); // <--
listViewModel->select();
listViewModel->setHeaderData(0, Qt::Horizontal, "Tags");
...

How to set ItemDelegate to only apply to parent column in QTreeView

I am inserting QComboboxes into the first column of a QTreeView as follows.
view->setItemDelegateForColumn(0, new ComboBoxDelegate(view));
The nodes in the 0th column have children, who (if i'm not mistaken) are also part of column "0". Therefore the comboboxes also appear there. How can I prevent the Comboboxes from appearing in the child branch?
What I have now:
>Combobox1
Combobox2
How I want it to look: (Where "text" depends on index of combobox)
>Combobox1
Text
Here are some of the functions that create the combobox:
ComboBoxDelegate::ComboBoxDelegate(QObject *parent): QItemDelegate(parent){
}
QWidget *ComboBoxDelegate::createEditor(QWidget *parent, const QStyleOptionViewItem &option, const QModelIndex &index) const{
QComboBox *editor = new QComboBox(parent);
editor->addItem("Run");
editor->addItem("Run with SM");
editor->addItem("Kinetic Run");
}
return editor;
}
What you should do is user the QModelIndex &index parameter to get the row, and then say something like:
if (!index.parent().isValid()) {
//draw combobox
}
else {
//don't draw
}

Signal when value is changed ( before return) of a field of a EnhTableWidget

I have a question concerning signals of a field of a EnhTableWidget:
when I click into a cell of that table -->
..currentCellChanged(int,int,int,int) is emitted
when I click return in a cell of a table -->
..cellChanged(int,int) is emitted
I need to start a calculation-method when the value of a cell is changed, but before return is pressed.Is there a signal for that, something like
when I change the value of a field ( no return yet !) of that table --> ..?? is emitted
Create a customized delegate that handles changes emitted by the cell editor:
MyDelegate::MyDelegate(QObject *parent) : QStyledItemDelegate(parent)
{
}
QWidget* MyDelegate::createEditor(QWidget* parent,const QStyleOptionViewItem &option,const QModelIndex &index) const
{
// Assume you want a QLineEdit editor for the QTableWidget cell
QLineEdit* editor = new QLineEdit(parent);
// Get notified when editor changes
QObject::connect(editor, &QLineEdit::textEdited, this, [=](const QString &newValue) {
qDebug() << "Cell has changed without pressing return: " << newValue;
}
return editor;
}
The itemChanged signal is emitted
void QTableWidget::itemChanged(QTableWidgetItem * item)
Also you could try to catch the dataChanged signal, which is inherited from the QAbstractItemView class
void QAbstractItemView::dataChanged(const QModelIndex & topLeft, const QModelIndex & bottomRight, const QVector<int> & roles = QVector<int> ())
Or you could subclass QTableWidget and reimplement keyPressEvent or use event filter with a custom keyPressHandler if you don't want to subclass:
tableWidget->installEventFilter(keyPressHandler);

QItemDelegate with custom widgets

I'm having problems with my QTableView and QItemDelegate classes. For one column my delegate creates a simple combo box and everything works just fine. For my 2nd column I need a widget that has two combo boxes in a single widget.
I've written the following code in my QItemDelegate, just to be clear this only shows code for my 2nd column, the one that doesn't work. The other simple Combo-box isn't shown as it works fine:
QWidget *UserDefinedUnitsDelegate::createEditor(QWidget *parent,const QStyleOptionViewItem & option ,const QModelIndex & index ) const
{
//set up a simple widget with a layout
QWidget* pWidget = new QWidget(parent);
QHBoxLayout* hLayout = new QHBoxLayout(pWidget);
pWidget->setLayout(hLayout);
//add two combo boxes to the layout
QComboBox* comboEditor = new QComboBox(pWidget);
QComboBox* comboEditor2 = new QComboBox(pWidget);
//now add both editors to this
hLayout->addWidget(comboEditor);
hLayout->addWidget(comboEditor2);
return pWidget;
}
Now this displays just fine but when I edit it and click elsewhere it doesn't stop editing. Can anyone offer any pointers?
Edit: So i need to call CommitData() and closeEditor() at some point. Can anyone offer pointers on where to call these?
Thanks.
You can keep the editor widget as a member of class and emit commitData when the current index of one of the comboboxes has changed. So you can connect currentIndexChanged(int) to a slot and emit commitData from there:
QWidget *UserDefinedUnitsDelegate::createEditor(QWidget *parent,const QStyleOptionViewItem & option ,const QModelIndex & index ) const
{
//set up a simple widget with a layout
pWidget = new QWidget(parent);
QHBoxLayout* hLayout = new QHBoxLayout(pWidget);
pWidget->setLayout(hLayout);
//add two combo boxes to the layout
QComboBox* comboEditor = new QComboBox(pWidget);
QComboBox* comboEditor2 = new QComboBox(pWidget);
connect(comboEditor,SIGNAL(currentIndexChanged(int)),this,SLOT(setData(int)));
connect(comboEditor2,SIGNAL(currentIndexChanged(int)),this,SLOT(setData(int)));
//now add both editors to this
hLayout->addWidget(comboEditor);
hLayout->addWidget(comboEditor2);
return pWidget;
}
void UserDefinedUnitsDelegate::setData(int val)
{
emit commitData(pWidget);
}