How to set ItemDelegate to only apply to parent column in QTreeView - c++

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
}

Related

QTreeWidget doesn't display QCombobox delegate

I have the delegate class and method paint in it. I would like to show the comboboxes in the QTreeWidget columns and do for it
ui->treeWidget->setItemDelegateForColumn(2, box);
where box is the object of my delegate
The paint method is
void ComboboxDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const
{
QStyleOptionComboBox box;
QString text = values[1];
box.currentText = text;
box.rect = option.rect;
QApplication::style()->drawComplexControl(QStyle::CC_ComboBox, &box, painter, 0);
QApplication::style()->drawControl(QStyle::CE_ComboBoxLabel, &box, painter, 0);
}
values is the QMap with two variables - "Hello" and "Goodbye"
But instead of drawing comboboxes treewidget shows just "Hello" strings in second column
Why?
How can i fix it?
If you have qss files and want to customize your QComboBox delegate you should add some combobox in paint() method and replace that:
QApplication::style()->drawComplexControl(QStyle::CC_ComboBox, &box, painter, 0);
to:
QComboBox tmp;
QApplication::style()->drawComplexControl(QStyle::CC_ComboBox, &box, painter, &tmp);

Retrieve QComboBox index from QCompletion popup

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.

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

Qt: start editing of cell after one click

By default the cell in QTableView starts being edited after double click. How to change this behavior. I need it to start editing after one click.
I have set combo-box delegate to the cell. When clicking the cell it only selects it. When double clicking on the cell the QComboBox editor is activated but not expanded. I want it to expand after just one click as if I added QComboBox by setCellWidget function of QTableWidget. I need the same effect by using model-view-delegate.
You can just set edit trigger use this function setEditTriggers
C++
yourView->setEditTriggers(QAbstractItemView::AllEditTriggers)
Python:
yourView.setEditTriggers(QAbstractItemView.AllEditTriggers)
enum QAbstractItemView::EditTrigger
flags QAbstractItemView::EditTriggers
This enum describes actions which will initiate item editing.
Constant Value Description
QAbstractItemView::NoEditTriggers 0 No editing possible.
QAbstractItemView::CurrentChanged 1 Editing start whenever current item changes.
QAbstractItemView::DoubleClicked 2 Editing starts when an item is double clicked.
QAbstractItemView::SelectedClicked 4 Editing starts when clicking on an already selected item.
QAbstractItemView::EditKeyPressed 8 Editing starts when the platform edit key has been pressed over an item.
QAbstractItemView::AnyKeyPressed 16 Editing starts when any key is pressed over an item.
QAbstractItemView::AllEditTriggers 31 Editing starts for all above actions.
The EditTriggers type is a typedef for QFlags. It stores an OR combination of EditTrigger values.
Edit after one click
You can reimplement mousePressEvent in view you are using
void YourView::mousePressEvent(QMouseEvent *event)
{
if (event->button() == Qt::LeftButton) {
QModelIndex index = indexAt(event->pos());
if (index.column() == 0) { // column you want to use for one click
edit(index);
}
}
QTreeView::mousePressEvent(event);
}
Expanded QCombobox when edit
You should imlement setEditorData in your subclass of QItemDelegate and at the end call showPopup.
But it has some unexpected behaviour. QComboBox disappears when mouse leave its area. But for me it is advantage.
I can select different item with single click and release.
void IconDelegate::setEditorData(QWidget *editor, const QModelIndex &index) const
{
Q_UNUSED(index);
QComboBox *comboBox = qobject_cast<QComboBox*>(editor);
// Add data
comboBox->addItem(QIcon(":/icons/information16.png"), "info");
comboBox->addItem(QIcon(":/icons/warning16.png"), "warning");
comboBox->addItem(QIcon(":/icons/send16.png"), "send");
comboBox->addItem(QIcon(":/icons/select16.png"), "select");
comboBox->showPopup(); // <<<< Show popup here
}
Together it works fast way. Click and hold to choose item and commit data on release ( Just one click and release )
If you want click to show expanded qcombobox and next click to choose/hide, I do not know solution for now.
Based on the idea provided by Jason, I came up with this solution.
To launch the editor on single click, I connected QAbstractItemView::clicked(const QModelIndex &index) signal of my view, to QAbstractItemView::edit(const QModelIndex &index) slot of that same view.
If you use Qt4, you need to create a slot in your delegate. Pass your combobox as an argument to this slot. In this slot you call QComboBox::showPopup. So it will look like this:
void MyDelegate::popUpComboBox(QComboBox *cb)
{
cb->showPopup();
}
But first we need to register the QComboBox* type. You can call this in the constructor of your delegate:
qRegisterMetaType<QComboBox*>("QComboBox*");
The reason we need this slot, is because we can't show the pop up straight away in MyDelegate::createEditor, because the position and the rect of the list view are unknown. So what we do is in MyDelegate::createEditor, we call this slot with a queued connection:
QComboBox *cb = new QComboBox(parent);
// populate your combobox...
QMetaObject::invokeMethod(const_cast<MyDelegate*>(this), "popUpComboBox", Qt::QueuedConnection, Q_ARG(QComboBox*, cb));
This will show the list view of the combobox correctly when the editor is activated.
Now if you are using Qt5, the slot is not needed. All you do is call QComboBox::showPopup with a queued connection from MyDelegate::createEditor. The easiest way to do this is with a QTimer:
QTimer::singleShot(0, cb, &QComboBox::showPopup);
For some extra information, this is how you can paint the combobox so it is shown all the time, not only when the editor is shown:
void MyDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const
{
if(index.column() == 1) // show combobox only in the second column
{
QStyleOptionComboBox box;
box.state = option.state;
box.rect = option.rect;
box.currentText = index.data(Qt::EditRole).toString();
QApplication::style()->drawComplexControl(QStyle::CC_ComboBox, &box, painter, 0);
QApplication::style()->drawControl(QStyle::CE_ComboBoxLabel, &box, painter, 0);
return;
}
QStyledItemDelegate::paint(painter, option, index);
}
This solution works perfeclty for me. Single click on a cell, and the combo pops up.
class GFQtComboEnumItemDelegate : public QStyledItemDelegate
{
void setEditorData(QWidget *editor, const QModelIndex &index) const
{
QComboBox* pE = qobject_cast<QComboBox*>(editor);
... // init the combo here
if(m_must_open_box)
{
m_must_open_box = false;
pE->showPopup();
}
}
bool editorEvent(QEvent *event, QAbstractItemModel *model, const QStyleOptionViewItem &option, const QModelIndex &index)
{
if (event->type() == QEvent::MouseButtonRelease)
{
QMouseEvent* pME = static_cast<QMouseEvent*>(event);
if(pME->button() == Qt::LeftButton)
{
QAbstractItemView* pView = qobject_cast<QAbstractItemView*>( const_cast<QWidget*>(option.widget) );
if(pView != nullptr)
{
emit pView->setCurrentIndex(index);
m_must_open_box = true;
emit pView->edit(index);
}
return true;
}
}
return QStyledItemDelegate::editorEvent(event, model, option, index);
}
mutable bool m_must_open_box;
};
If you override QStyledItemDelegate::createEditor() then you can expand the combo box after it is created.

How to open an URL in a QTableView

What is the best way to present a clickable URL in a QTableView (or QTreeView, QListView, etc...)
Given a QStandardItemModel where some of the columns contain text with URLs I'd like them to become clickable and then handle the click by using QDesktopServices::openURL()
I was hoping there would be some easy way to leverage QLabel's textInteraction flags and to cram them into the table. I can't believe there's not an easier way to handle this. I really hope I'm missing something.
You'll need to create a delegate to do the painting. The code should look something like this:
void RenderLinkDelegate::paint(
QPainter *painter,
const QStyleOptionViewItem &option,
const QModelIndex &index
) const
{
QString text = index.data(Qt::DisplayRole).toString();
if (text.isEmpty())
return;
painter->save();
// I only wanted it for mouse over, but you'll probably want to remove
// this condition
if (option.state & QStyle::State_MouseOver)
{
QFont font = option.font;
font.setUnderline(true);
painter->setFont(font);
painter->setPen(option.palette.link().color());
}
painter->drawText(option.rect, Qt::AlignLeft | Qt::AlignVCenter, text);
painter->restore();
}
Well, you can use delegates to render rich text in a qtableview with custom delegates reimplementing the paint method such as:
void CHtmlDelegate::paint(QPainter *painter,
const QStyleOptionViewItem &option,
const QModelIndex &index) const
{
QStyleOptionViewItemV4 opt(option);
QLabel *label = new QLabel;
label->setText(index.data().toString());
label->setTextFormat(Qt::RichText);
label->setGeometry(option.rect);
label->setStyleSheet("QLabel { background-color : transparent; }");
painter->translate(option.rect.topLeft());
label->render(painter);
painter->translate(-option.rect.topLeft());
}
However, it will not make hyperlinks clickable.
To do so, you can use the following hack. Reimplement the setModel method of your table/list view and use setIndexWidget.
void MyView::setModel(QAbstractItemModel *m)
{
if (!m)
return;
QTableView::setModel(m);
const int rows = model()->rowCount();
for (int i = 0; i < rows; ++i)
{
QModelIndex idx = model()->index(i, 1);
QLabel *label = new QLabel;
label->setTextFormat(Qt::RichText);
label->setText(model()->data(idx, CTableModel::HtmlRole).toString());
label->setOpenExternalLinks(true);
setIndexWidget(idx, label);
}
}
In the example above, I replace column 1 with qlabels. Note that you need to void the display role in the model to avoid overlapping data.
Anyway, I would be interested in a better solution based on delegates.
Sadly, its not that easy to render a QLabel with setOpenExternalLinks() when using a QTableView (as opposed to using a QTableWidget). There are no magic two lines of code you can call and have the job done.
use a delegate
set the delegate to the column of your table
use QTextDocument combined with setHTML() to render a html link
this means your model needs to provide an HTML fragment containing a href
calculate the geometry of the link and provide event handlers for to intercept the mouse
change the cursor when it is over the link
execute the action when the link is clicked
what a mess :( i want painter->setWidgetToCell()
--
void LabelColumnItemDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const
{
if( option.state & QStyle::State_Selected )
painter->fillRect( option.rect, option.palette.highlight() );
painter->save();
QTextDocument document; // <---- RichText
document.setTextWidth(option.rect.width());
QVariant value = index.data(Qt::DisplayRole);
if (value.isValid() && !value.isNull())
{
document.setHtml(value.toString()); // <---- make sure model contains html
painter->translate(option.rect.topLeft());
document.drawContents(painter);
}
painter->restore();
}