I have an editable model, which inherits QAbstractTableModel. Also have a custom delegate to go with it. This is my first editable model, and I think I'm missing something. I'm pretty much following the examples found at Nokia. My model tells the delegate that the data is editable via flags(). When I do this, it draws a QSpinBox in the cell.
Underlying model is a simple std::map. The key is days, the value is a rate.
Generally, what is painted in any editable cell, is a QCheckBox, but is ghosted out, then the data. If I double-click on the value, I am shown the editor, which happens to be a custom widget based on QDoubleSpinbox.
Qt::ItemFlags my_model_t::flags( const QModelIndex& index ) const
{
if ( !index.isValid() ) {
return Qt::NoItemFlags;
}
if ( index.column() == col_rates ) {
return QAbstractItemModel::flags( index ) | Qt::ItemIsEditable;
}
return QAbstractItemModel::flags( index );
}
QVariant my_model_t::data( const QModelIndex& index, int role ) const
{
if ( !index.isValid() ) {
return QVariant();
}
if ( role == Qt::DisplayRole || Qt::EditRole ) {
if ( static_cast<int>( rates.size() ) <= index.row() ) {
return QVariant();
}
int day = vec[index.row()];
if ( index.column() == col_days ) {
return day;
} else if ( index.column() == col_rates ) {
std::map<int, double>::const_iterator it = rates.find( day );
if ( it != rates.end() ) {
return (*it).second;
}
}
}
return QVariant();
}
QWidget* my_delegate_t::createEditor( QWidget* parent, const QStyleOptionViewItem& /*option*/, const QModelIndex& index ) const
{
gui_spinbox* editor = new gui_spinbox( parent );
if ( index.column() == col_rate ) {
const my_model_t* model = static_cast<my_model_t*>( index.model() );
}
return editor;
}
void my_delegate_t::setEditorData( QWidget* editor, const QModelIndex& index ) const
{
double value = index.model()->data( index, Qt::EditRole ).toDouble();
gui_spinbox* spin_box = static_cast<gui_spinbox*>( editor );
if ( spin_box ) {
spin_box->setValue( value );
}
}
void my_delegate_t::setModelData( QWidget* editor, QAbstractItemModel* model, const QModelIndex& index ) const
{
gui_spinbox* spin_box = static_cast<gui_spinbox*>( editor );
if ( spin_box ) {
double value = spin_box->value();
model->setData( index, value, Qt::EditRole );
}
}
void my_delegate_t::updateEditorGemoetry( QWidget* editor, const QStyleOptionViewItem& option, const QModelIndex& /*index*/ ) const
{
editor->setGeometry( option.rect );
}
I'm not pretty sure about your problem. Do you want to paint a QCheckBox?
In your createEditor method, you created an editor use QSpinbox but in the Title you said it was unwanted.
If you want to be shown an editor based on an QCheckBox when you double clicked an item you just create a checkBox in the createEditor method and use checkBox in setEditorData and setModelData.
Related
This is an example on Qt Website
In this case the matching characters are not bold, and other characters are bold. I want to do the other way around: the matching characters should be bold and others are not bold. How can I do it?
MyLineEdit.cpp
MyLineEdit::MyLineEdit( QWidget *p_parent ) : QWidget( p_parent )
{
............
m_completer = new Completer();
m_lineEdit = new QLineEdit;
m_lineEdit->setObjectName( "lineEdit" );
connect( m_lineEdit, &LineEdit::textChanged, this, &MyLineEdit::setTextForFilter );
}
void MyLineEdit::setTextForFilter( const QString &p_text )
{
m_completer->setTextForFilter( p_text );
}
Completer.cpp
Completer::Completer( QWidget *p_parent ) : QWidget( p_parent )
{
.........
QGridLayout *tableViewLayout = new QGridLayout( this );
m_caption = new Label;
tableViewLayout->addWidget( m_caption );
m_table = new QTreeView();
m_table->setItemDelegate( new CompleterDelegate() );
m_sourceModel = new CompleterModel( this );
m_proxyModel = new CompleterProxyModel( this );
m_proxyModel->setSourceModel( m_sourceModel );
m_table->setModel( m_proxyModel );
tableViewLayout->addWidget( m_table );
}
void Completer::setTextForFilter( const QString &p_text )
{
QRegExp regExp( p_text, Qt::CaseInsensitive, QRegExp::RegExp );
m_proxyModel->setFilterRegExp( regExp );
}
CompleterModel.cpp
QVariant CompleterModel::data( const QModelIndex &p_index, int p_role ) const
{
int row = p_index.row();
int col = p_index.column();
switch ( p_role )
{
case Qt::DisplayRole:
if ( col < m_completerData.rowData().at( row ).columnCount() )
{
return m_completerData.rowData().at( row ).columnData( col ).first;
}
break;
case Qt::TextAlignmentRole:
return Qt::AlignLeft;
case Qt::UserRole:
return QVariant( static_cast<int>( m_completerData.rowData().at( row ).type() ) );
}
return QVariant();
}
CompleterProxyModel.cpp
bool CompleterProxyModel::filterAcceptsRow( int p_sourceRow, const QModelIndex &p_sourceParent ) const
{
QModelIndex index0 = sourceModel()->index( p_sourceRow, 0, p_sourceParent );
QModelIndex index1 = sourceModel()->index( p_sourceRow, 1, p_sourceParent );
return ( sourceModel()->data( index0 ).toString().contains( filterRegExp() ) || sourceModel()->data( index1 ).toString().contains( filterRegExp() ) );
}
You are going to need a custom delegate and do your own rendering. Using rich text is unfortunately not supported out of the box.
I am looking for a way to highlight rows of a QTreeView without reimplementing a QTreeView subclass.
I have seen similar questions:
highlight-specific-substrings-in-a-qtreeview
how-to-make-item-view-render-rich-html-text-in-qt
set-bold-rows-in-a-qtreeview
But they all use delegate. The reason is that I am creating a search text in widgets tool which browse all widgets and find and highlights text in them. Hence, I cannot use delegate.
Is there any possible solution?
Painting a semi-transparent item above it?
Here is the code of the tool which stores the widget and the way to search for text in them.
class GUI_EXPORT QgsSearchHighlightOptionWidget : public QObject
{
Q_OBJECT
public:
/**
* Constructor
* \param widget the widget used to search text into
*/
explicit QgsSearchHighlightOptionWidget( QWidget *widget = nullptr );
/**
* Returns if it valid: if the widget type is handled and if the widget is not still available
*/
bool isValid() { return mWidget && mValid; }
/**
* search for a text pattern and highlight the widget if the text is found
* \returns true if the text pattern is found
*/
bool searchHighlight( const QString &searchText );
/**
* reset the style to the original state
*/
void reset();
/**
* return the widget
*/
QWidget *widget() { return mWidget; }
bool eventFilter( QObject *obj, QEvent *event ) override;
private slots:
void widgetDestroyed();
private:
QPointer< QWidget > mWidget;
QString mStyleSheet;
bool mValid = true;
bool mChangedStyle = false;
std::function < bool( QString )> mTextFound = []( QString searchText ) {Q_UNUSED( searchText ); return false;};
bool mInstalledFilter = false;
};
QgsSearchHighlightOptionWidget::QgsSearchHighlightOptionWidget( QWidget *widget )
: QObject( widget )
, mWidget( widget )
{
if ( qobject_cast<QLabel *>( widget ) )
{
mStyleSheet = QStringLiteral( "QLabel { background-color: yellow; color: blue;}" );
mTextFound = [ = ]( QString searchText ) {return qobject_cast<QLabel *>( mWidget )->text().contains( searchText, Qt::CaseInsensitive );};
}
else if ( qobject_cast<QCheckBox *>( widget ) )
{
mStyleSheet = QStringLiteral( "QCheckBox { background-color: yellow; color: blue;}" );
mTextFound = [ = ]( QString searchText ) {return qobject_cast<QCheckBox *>( mWidget )->text().contains( searchText, Qt::CaseInsensitive );};
}
else if ( qobject_cast<QAbstractButton *>( widget ) )
{
mStyleSheet = QStringLiteral( "QAbstractButton { background-color: yellow; color: blue;}" );
mTextFound = [ = ]( QString searchText ) {return qobject_cast<QAbstractButton *>( mWidget )->text().contains( searchText, Qt::CaseInsensitive );};
}
else if ( qobject_cast<QGroupBox *>( widget ) )
{
mStyleSheet = QStringLiteral( "QGroupBox::title { background-color: yellow; color: blue;}" );
mTextFound = [ = ]( QString searchText ) {return qobject_cast<QGroupBox *>( mWidget )->title().contains( searchText, Qt::CaseInsensitive );};
}
else if ( qobject_cast<QTreeView *>( widget ) )
{
// TODO - style individual matching items
mTextFound = [ = ]( QString searchText )
{
QTreeView *tree = qobject_cast<QTreeView *>( mWidget );
if ( !tree )
return false;
QModelIndexList hits = tree->model()->match( tree->model()->index( 0, 0 ), Qt::DisplayRole, searchText, 1, Qt::MatchContains | Qt::MatchRecursive );
return !hits.isEmpty();
};
}
else
{
mValid = false;
}
if ( mValid )
{
mStyleSheet.prepend( "/*!search!*/" ).append( "/*!search!*/" );
QgsDebugMsgLevel( mStyleSheet, 4 );
connect( mWidget, &QWidget::destroyed, this, &QgsSearchHighlightOptionWidget::widgetDestroyed );
}
}
bool QgsSearchHighlightOptionWidget::searchHighlight( const QString &searchText )
{
bool found = false;
if ( !mWidget )
return found;
if ( !searchText.isEmpty() )
{
found = mTextFound( searchText );
}
if ( found && !mChangedStyle )
{
if ( !mWidget->isVisible() )
{
// show the widget to get initial stylesheet in case it's modified
QgsDebugMsg( QString( "installing event filter on: %1 (%2)" )
.arg( mWidget->objectName() )
.arg( qobject_cast<QLabel *>( mWidget ) ? qobject_cast<QLabel *>( mWidget )->text() : QString() ) );
mWidget->installEventFilter( this );
mInstalledFilter = true;
}
else
{
mWidget->setStyleSheet( mWidget->styleSheet() + mStyleSheet );
mChangedStyle = true;
}
}
return found;
}
bool QgsSearchHighlightOptionWidget::eventFilter( QObject *obj, QEvent *event )
{
if ( mInstalledFilter && event->type() == QEvent::Show && obj == mWidget )
{
mWidget->removeEventFilter( this );
mInstalledFilter = false;
// instead of catching the event and calling show again
// it might be better to use a timer to change the style
// after the widget is shown
#if 1
mWidget->show();
mWidget->setStyleSheet( mWidget->styleSheet() + mStyleSheet );
return true;
#else
QTimer::singleShot( 500, this, [ = ]
{
mWidget->setStyleSheet( mWidget->styleSheet() + mStyleSheet );
mChangedStyle = true;
} );
#endif
}
return QObject::eventFilter( obj, event );
}
void QgsSearchHighlightOptionWidget::reset()
{
if ( mWidget && mValid )
{
if ( mChangedStyle )
{
QString ss = mWidget->styleSheet();
ss.remove( mStyleSheet );
mWidget->setStyleSheet( ss );
mChangedStyle = false;
}
else if ( mInstalledFilter )
{
mWidget->removeEventFilter( this );
mInstalledFilter = false;
}
}
}
void QgsSearchHighlightOptionWidget::widgetDestroyed()
{
mWidget = nullptr;
mValid = false;
}
And here is the code to actually register the widgets from the dialog:
void QgsOptionsDialogBase::registerTextSearchWidgets()
{
mRegisteredSearchWidgets.clear();
for ( int i = 0; i < mOptStackedWidget->count(); i++ )
{
Q_FOREACH ( QWidget *w, mOptStackedWidget->widget( i )->findChildren<QWidget *>() )
{
QgsSearchHighlightOptionWidget *shw = new QgsSearchHighlightOptionWidget( w );
if ( shw->isValid() )
{
QgsDebugMsgLevel( QString( "Registering: %1" ).arg( w->objectName() ), 4 );
mRegisteredSearchWidgets.append( qMakePair( shw, i ) );
}
else
{
delete shw;
}
}
}
}
Reading the docs, I don't know how to do it directly.
Here are some suggestions. Hopefully one works for you:
1) Could you use QTreeWidget instead of QTreeView ?
With this, it should be easy.
Use the item functions and e.g. setBackground() on the item.
2) How about you filter tree instead of highlighting? Using setRowHidden()?
3) If you haven't already, you could also try using QTreeView::keyboardSearch() and see what this does.
4) you could use select* and add a next/previous button to your search box. I.e. you hop through the tree selecting the current search result.
I use a custom widget for an item delegate.
This widget is composed of a combobox and a tool button, see below for the source.
Now when I use this widget in an item delegate, pressing on the tool button has no effect if the combobox has not the focus.
For a demo, see this video: http://youtu.be/o5AgjC4cCqY
Any idea how to handle this?
Thanks a lot!
Source of the widget:
QgsFieldExpressionWidget::QgsFieldExpressionWidget( QWidget *parent )
: QWidget( parent )
{
QHBoxLayout* layout = new QHBoxLayout( this );
layout->setContentsMargins( 0, 0, 0, 0 );
mCombo = new QComboBox( this );
mCombo->setEditable( true );
mCombo->setSizePolicy( QSizePolicy::MinimumExpanding, QSizePolicy::Minimum );
mButton = new QToolButton( this );
mButton->setSizePolicy( QSizePolicy::Minimum, QSizePolicy::Minimum );
mButton->setIcon( QgsApplication::getThemeIcon( "/mIconExpressionEditorOpen.svg" ) );
layout->addWidget( mCombo );
layout->addWidget( mButton );
}
Source of the delegate:
QgsComposerColumnSourceDelegate::QgsComposerColumnSourceDelegate( QgsVectorLayer* vlayer, QObject* parent ) : QItemDelegate( parent ),
mVectorLayer( vlayer )
{
}
QWidget* QgsComposerColumnSourceDelegate::createEditor( QWidget* parent, const QStyleOptionViewItem& option, const QModelIndex& index ) const
{
Q_UNUSED( option );
Q_UNUSED( index );
QgsFieldExpressionWidget *fieldExpression = new QgsFieldExpressionWidget( parent );
fieldExpression->setLayer( mVectorLayer );
connect( fieldExpression, SIGNAL( fieldChanged( QString ) ), this, SLOT( commitAndCloseEditor() ) );
return fieldExpression;
}
void QgsComposerColumnSourceDelegate::setEditorData( QWidget* editor, const QModelIndex& index ) const
{
QString field = index.model()->data( index, Qt::EditRole ).toString();
//set the value for the field combobox
QgsFieldExpressionWidget *fieldExpression = static_cast<QgsFieldExpressionWidget*>( editor );
fieldExpression->setField( field );
}
void QgsComposerColumnSourceDelegate::setModelData( QWidget* editor, QAbstractItemModel* model, const QModelIndex& index ) const
{
QgsFieldExpressionWidget *fieldExpression = static_cast<QgsFieldExpressionWidget*>( editor );
QString field = fieldExpression->currentField();
model->setData( index, field, Qt::EditRole );
}
void QgsComposerColumnSourceDelegate::updateEditorGeometry( QWidget* editor, const QStyleOptionViewItem& option, const QModelIndex& index ) const
{
Q_UNUSED( index );
editor->setGeometry( option.rect );
}
Add the following line to your QgsFieldExpressionWidget constructor anywhere after the QComboBox is created:
setFocusProxy(mCombo);
I wanted to have a tree view which shows the item name, the item description, and two related Boolean values in respective columns. I started by modifying the Editable Tree Mode example, so there's a TreeModel that keeps track of a group of TreeItems, each of which not only has a list of child TreeItems, but also a list of QVariants which stores a set of values that can later be displayed in columns in the QTreeView.
I managed to add two more columns for two Boolean values. I also searched through the net on how to add checkboxes for QTreeView and QAbstractItemModel. I managed to have the checkboxes on the two Boolean columns working okay, as well as the rest of the tree hierarchy. Yet all the items in each column renders a checkbox and a line of text now.
Here's the parts where I've modified from the example, mainly within TreeModel.
treemodel.cpp:
bool TreeModel::isBooleanColumn( const QModelIndex &index ) const
{
bool bRet = false;
if ( !index.isValid() )
{
}
else
{
bRet = ( index.column() == COLUMN_BOL1 ) || ( index.column() == COLUMN_ BOL2 );
}
return bRet;
}
Qt::ItemFlags TreeModel::flags(const QModelIndex &index) const
{
if (!index.isValid())
return 0;
if ( isBooleanColumn( index ) )
{
return Qt::ItemIsEnabled | Qt::ItemIsUserCheckable;
}
else
{
return Qt::ItemIsEditable | Qt::ItemIsEnabled | Qt::ItemIsSelectable;
}
}
QVariant TreeModel::data(const QModelIndex &index, int role) const
{
if (!index.isValid())
return QVariant();
if (role != Qt::DisplayRole && role != Qt::EditRole && role != Qt::CheckStateRole )
return QVariant();
TreeItem *item = getItem(index);
if ( role == Qt::CheckStateRole && isBooleanColumn( index ) )
{
Qt::CheckState eChkState = ( item->data( index.column() ).toBool() ) ? Qt::Checked : Qt::Unchecked;
return eChkState;
}
return item->data(index.column());
}
bool TreeModel::setData(const QModelIndex &index, const QVariant &value,
int role)
{
if (role != Qt::EditRole && role != Qt::CheckStateRole )
return false;
TreeItem *item = getItem(index);
bool result;
if ( role == Qt::CheckStateRole && isBooleanColumn( index ) )
{
Qt::CheckState eChecked = static_cast< Qt::CheckState >( value.toInt() );
bool bNewValue = eChecked == Qt::Checked;
result = item->setData( index.column(), bNewValue );
}
else
{
result = item->setData(index.column(), value);
}
if (result)
emit dataChanged(index, index);
return result;
}
mainwindow.cpp:
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
{
…
QStringList headers;
headers << tr("Title") << tr("Description") << tr("Hide") << tr("Lock");
QFile file(":/default.txt");
file.open(QIODevice::ReadOnly);
TreeModel *model = new TreeModel(headers, file.readAll());
file.close();
…
}
The checkboxes under the non-Boolean columns don't respond to user input, and the text under the Boolean columns aren't editable. So functionality-wise there's nothing wrong, but it's still bothersome as far as UI goes.
I'm moving onto having QTreeWidget do the same thing. Meanwhile, I'm couldn't help but wonder if there's something else I'm missing here. I heard one solution is to have a custom delegate; is that the only option?
If there's anyone who can point out what else I need to do, or provide a similar example, I will greatly appreciate it.
I think the problem is in the Data method. You should return QVariant() When the role is CheckStateRole but the column is not boolean.
I had this problem. It occurred in TreeModel::parent() method due to passing child.column() value to createIndex() method. It should be 0 instead. So, instead of
createIndex(parentItem->childNumber(), child.column(), parentItem);
should be
createIndex(parentItem->childNumber(), 0, parentItem);
The reason this is happening is related to a "bug" in peoples implementation of the models data method.
In the example below, only column 2 should show a checkbox.
Problem code:
if role == Qt.CheckStateRole:
if index.column() == 2:
if item.checked:
return Qt.Checked
else:
return Qt.Unchecked
Correct code:
if role == Qt.CheckStateRole:
if index.column() == 2:
if item.checked:
return Qt.Checked
else:
return Qt.Unchecked
return None
In the problematic code, table cells that should not have a checkbox decorator were getting missed and processed by a catch all role handler farther down in the code.
Problem: I need to draw small pixmap to QHeaderView section like presented on this figure (pixmap located on the right corner of the section, marked with red square):
As I understand, there is two ways to do that:
reimplement QHeaderView's paintSection() method.
create an delegate from QStyledItemDelegate class and reimplement paint() method.
If I tried (1) variant with this code below, filter pixmap are not shown at all:
void DecorativeHeaderView::paintSection( QPainter* painter, const QRect& rect, int logicalIndex ) const
{
if( !rect.isValid() )
{
return;
}
// get the state of the section
QStyleOptionHeader option;
initStyleOption( &option );
// setup the style options structure
option.rect = rect;
option.section = logicalIndex;
option.iconAlignment = Qt::AlignVCenter | Qt::AlignHCenter;
QVariant variant = model()->headerData( logicalIndex, orientation(), Qt::DecorationRole );
option.icon = qvariant_cast< QIcon >( variant );
if( option.icon.isNull() )
{
option.icon = qvariant_cast< QPixmap >( variant );
}
// draw the section
if( !option.icon.isNull() )
{
style()->drawControl( QStyle::CE_Header, &option, painter, this );
}
else
{
QHeaderView::paintSection( painter, rect, logicalIndex );
// HERE is where I'm trying to draw my filter picture!!!
if( logicalIndex == filteredLogicalIndex_ )
{
QPixmap pixmap( ":/spreadsheet/images/spreadsheet/filter_icon_table.png" );
int x = rect.right() - pixmap.width();
int y = rect.top() + ( rect.height() - pixmap.height() ) / 2;
painter->drawPixmap( QPoint( x, y ), pixmap );
}
}
}
The (2) variant is this:
class HeaderDelegate : public QStyledItemDelegate
{
Q_OBJECT
Q_DISABLE_COPY( HeaderDelegate )
public:
HeaderDelegate( QObject* parent = 0 ) : QStyledItemDelegate( parent ) {}
virtual ~HeaderDelegate() {}
virtual void paint( QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index ) const;
}; // HeaderDelegate
void HeaderDelegate::paint( QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index ) const
{
// THIS method never starts!!!
if( index.column() == 2 )
{
QPixmap pixmap( ":/spreadsheet/images/spreadsheet/filter_icon_table.png" );
int x = option.rect.right() - pixmap.width();
int y = option.rect.top() + ( option.rect.height() - pixmap.height() ) / 2;
painter->save();
painter->drawPixmap( QPoint( x, y ), pixmap );
painter->restore();
}
QStyledItemDelegate::paint( painter, option, index );
}
DecorativeHeaderView::DecorativeHeaderView( Qt::Orientation orientation, QWidget* parent /* = 0 */ )
: QHeaderView( orientation, parent )
, filteredLogicalIndex_( -1 )
{
setItemDelegate( new HeaderDelegate( this ) );
}
Delegate created, but function did not start the paint() method!
Any help?
Thanks!
It Seems like it isn't possible at this time.
From Qt documentation:
Note: Each header renders the data for each section itself, and does not rely on a delegate. As a result, calling a header's setItemDelegate() function will have no effect.
How to have dynamic pixmap in a QHeaderView item
But you may override QHeaderView::paintSection method.
subclassing-QHeaderView
class HeaderView : public QHeaderView {
Q_OBJECT
public:
HeaderView(Qt::Orientation orientation, QWidget * parent = 0)
: QHeaderView(orientation, parent), p("333222.jpeg") {
}
int sectionSizeHint ( int /*logicalIndex*/ ) const { return p.width(); }
protected:
void paintSection(QPainter * painter, const QRect & rect, int /*logicalIndex*/) const {
painter->drawPixmap(rect, p);
}
private:
QPixmap p;
};