QlistView setCurrentIndex() not working - qt

I am facing a problem with a QListView component.
I created a simple form with a listview and a tableview.
Then I put this code, both widgets populate with the data model as I want:
QSqlQueryModel * modela = new QSqlQueryModel();
QSqlQueryModel * modelb = new QSqlQueryModel();
[...]
ui->listView->setModel(modela);
ui->tableView->setModel(modelb);
[...]
void MyWindow::on_listView_clicked(const QModelIndex &index)
{
ui->tableView->setCurrentIndex(ui->listView->currentIndex());
}
void MyWindow::on_tableView_clicked(const QModelIndex &index)
{
ui->listView->setCurrentIndex(ui->tableView->currentIndex());
// FAILS, does not react...
}
The first slot (when I click any item in the listview widget) works as expected, it automatically selects the corresponding item in the tableview widget, but the second case does not work, it just does not select any item in the listview...
What I want is that whatever item the user clicks in the tableview gets selected in the listview.
Is it possible?
I tried hard, looking for examples and the official qt documentation, but I don't find the right way to do (also tried to connect with signal/slots, but I don't know how to exactly connect both widgets).
Thanks in advance.

QModelIndex is an integral part of a certain QAbstractItemModel. It means that you can't use an index from model A to select an item in a view of model B.
QModelIndex is not just a couple of x,y. It also keeps a pointer to a model which created it.
So if you need to select the same row as selected in the first view, you need to extract a row from the first index, then get a right index in the second model and use it to select an item in the second view:
void selectTheSameRow(const QModelIndex& indexFromModelA)
{
int row = indexFromModelA.row();
QModelIndex indexFromModelB = modelB->index(row, 0);
viewB->setCurrentIndex(indexFromModelB);
}

Related

How to share a model's item between a QML ListView and a Grid

I would like to know the best way to achieve the following thing in QML:
I have a ListView with droppable elements and a Grid initialy filled with DropArea. The ListView uses a model derived from QAbstractItemModel. I would like to drop an element on the grid and interact with it (rename it for instance). For now, any modifications in the ListView update the model, but how modifications of the element in the grid could update the model ?
There can be multiple items dropped in the grid corresponding to a subset of ListView's model. I do not know how can I achieve this. The Package can not be used because the Grid is not a GridView and Items must be moved/set at specific positions. So I tried to:
create a ListView displayed on the dropped item, using the same model as the source ListView used to drag items,
set the same rootIndex, then the same index
I am close to a solution but I think it is not the best way to do this.
Any clue ?
Thanks
I would like to have different visual representation of the same model
item in a ListView and in a component in a Grid. So, a modification of
the item in the ListView should update the item in the Grid and
vice-versa.
If your model's data is not QObject derived with NOTIFY and properties, notifications of changes can only be made through the model.
And since you won't be using the model for the grid, that means your model has to use underlying QObject instances. A generic model object might come in handy.
When you have that you only need to reference the underlying QObject model item and use bindings to set up your list view delegate. Then when you drag and drop, you only need to pass a reference to the QObject to that other visual representation which you will be creating inside the grid.
Lastly, take care not to be left with dangling references when you remove items from the model, as that will most likely hard-crash your application. Use the onDestruction attached signal to clear elements from the grid as the model item objects are destroyed.
I finally found a solution by create a C++ type which inherits from QObject an which can be embedded in a QML object. This type has read/write properties and is initialized with the same model as the ListView. The interesting methods are:
/* writing to a property **from QML** goes here */
void ModelItem::setName(const QString& name)
{
setModelData(GroupMemberModel::Nom, name);
}
/* then here */
bool ModelItem::setModelData(GroupMemberModel::Role role, const QVariant& value)
{
return m_model->setData(m_modelIndex, value, role);
}
/* any changes in the model fall here (signals/slots mecanism)*/
void ModelItem::dataChanged(const QModelIndex& topLeft, const QModelIndex& bottomRight, const QVector<int>& roles)
{
if(m_modelIndex.row() < topLeft.row() || m_modelIndex.row() > bottomRight.row())
return;
if(m_modelIndex.column() < topLeft.column() || m_modelIndex.column() > bottomRight.column())
return;
//Index is modified, emit signal
foreach(int role, roles) {
emitDataChanged(role);
}
}
/* **notify QML** by emit signal on property */
void ModelItem::emitDataChanged(int role) const
{
if(role < (Qt::UserRole+1))
role+=Qt::UserRole+1;
switch(role)
{
case GroupMemberModel::Nom:
emit nameChanged();
break;
default:
qDebug() << "ModelItem::dataChanged, unknown role";
break;
}
}
This works as needed and is simplier than what I thought.

How to implement mixed selection model for QTableView

I have custom table view that shows content of custom abstract model.
I need to implement mixed selection for these view.
When user clicks on the first column the whole row should be selected (AbstractItemView::SelectRow).
When user clicks on cell in other column just the particular cell should be selected (AbstractItemView::SelectItems).
What need to do to achieve such behaviour?
Just do this:
void MainWindow::on_tableView_clicked(const QModelIndex &index)
{
//if(!index.column()) more elegant
if(index.column() == 0)
ui->tableView->selectRow(index.row());
}
Catch clicked() signal and check is it first column. If so, then selectRow() with current row (index.row())
I use here QTableView but QAbstractItemView has clicked signal too.

How to get list of visible QModelIndex in QAbstractItemView

Is there any way to get a list of currently visible items in QAbstractItemView? And, if it possible, to receive any notifications about changing of this list.
Upd:
I'm asking exactly about QAbstractItemView or QTreeView with non-plain structure, not QTableView.
Upd2:
I'm implementing tree view model with checkboxes. I want next behavior (same for checking/uncheking):
If one of checkbox is checked - then all childs must be checked
If all child checkboxes are checked - then parent check box should be checked too. And same for parent of parent, and so on...
Check state are monitored/modified by external data source, so I need a mechanism to update all changed children/parents. dataChanged signal is not enough for me because it is very expansive to build a list of all changed QModelIndex for updating. And it is not necessary at all, because all fresh data will be picked from QAbstractItemModel::data.
I found next dirty hack to update all items: emit dataChanged( QModelIndex(), QModelIndex() ); but it's undocumented for invalid indexes.
So, I need a way to force all visible items redraw they content with fresh data.
You can get the topleft and bottom right cell by calling:
tableview->indexAt(tableview->rect().topLeft())
tableview->indexAt(tableview->rect().bottomRight())
To get notification of change, reimplement the virtual function of qabstractscrollarea
scrollContentsBy
This function is called when the view port is scrolled.
call QTableView::scrollContentsBy and then do whatever you need to.
For QTreeView, the list of visible items can be traversed like this:
QTreeView& tv (yourTreeView);
// Get model index for first visible item
QModelIndex modelIndex = tv.indexAt(tv.rect().topLeft());
while (modelIndex.isValid())
{
// do something with the item indexed by modelIndex
...
// This navigates to the next visible item
modelIndex = tv.indexBelow(modelIndex);
}
I think that there are no cases where list of visible items is requred. In case of correct model implementation all items are updated automatically.
Hard part of implementation - force children and parents to update. I wrote following code:
bool TreeModel::setData( const QModelIndex &index, const QVariant &value, int role )
case Qt::CheckStateRole:
{
TreeItemList updateRangeList; // Filled with items, in which all childred must be updated
TreeItemList updateSingleList; // Filled with items, which must be updated
item->setCheckState( value.toBool(), updateRangeList, updateSingleList ); // All magic there
foreach ( TreeAbstractItem *i, updateRangeList )
{
const int nRows = i->rowCount();
QModelIndex topLeft = indexForItem( i->m_childs[0] );
QModelIndex bottomRight = indexForItem( i->m_childs[nRows - 1] );
emit dataChanged( topLeft, bottomRight );
}
foreach ( TreeAbstractItem *i, updateSingleList )
{
QModelIndex updateIndex = indexForItem( i );
emit dataChanged( updateIndex, updateIndex );
}
}
i always update whole QAbstractTableModel with:
emit dataChanged(index(0, 0), index(rowCount(), columnCount()-1)); // update whole view
Method 1
i, j = table.indexAt(table.rect().topLeft()).row(), table.indexAt(table.rect().bottomLeft()).row() - 1
Method 2
i, j = table.rowAt(0), table.rowAt(table.height()) - 1

Interact with editor widgets' data in QTableView

I'm using an editable QTableView + QStandardItemModel.
While editing a cell in a table view, I'd like to do something according to the new input data in this specific cell when committing the new data into the table view.
To do this, I need the new input data and the current model index (or column & row number).
I tried some slots such as
virtual void closeEditor (QWidget * editor,
QAbstractItemDelegate::EndEditHint hint)
and
virtual void commitData ( QWidget * editor ).
commitData seems to be what I need, however, the parameter is only the editor and I cannot figure out how to obtain the text in this editor widget.
I looked QTextEdit but it's not a inherited class of QWidget.
I wonder if there's any way to obtain the data (text) and axis (column, row) of an editor widget?
I suggest to implement your own item delegate, inheriting QStandardItemDelegate (or QAbstractItemDelegate). There you can override
void setModelData ( QWidget * editor, QAbstractItemModel * model, const QModelIndex & index ) const
Simply do you your custom processing and then call QStandardItemDelegate::setModelData(...) to make sure that your model is updated with the newly edited data.
The itemChanged(QStandardItem*) signal is emitted by a QStandardItemModel whenever an item's data changes.
From the given QStandardItem, you can retrieve the row and column directly. To get the displayed text, pass Qt::DisplayRole to the item's data() method.

Some cells in QtableView are not repainted automatically after a clicking on a table cell

I'm using a QTableView in the implementation of an interactive board game. Images are to be displayed in the cells of the table. I'm using a QStyledItemDelegate with a paint function to draw the images inside the table cells.
As the images should be displayed only in certain cells of the table and updated when a user clicks on a table cell, an double int array is used which is of the same dimensions as the table. Depending on the values the the array, the painter should draw images in specific cells of the table. Initially there are only 4 images inside 4 cells of the table and as the user clicks on a cell in the table, the array is updated which should consequently mean that whats drawn and displayed inside the cells of rthe table should be changed.
Normally the user clicks on an empty (white) cell which is updated successfully and the specific image is shown in the cell. However, if there are other cells which contain an image and should be updated, the update is not shown, although the double int array is updated. I also saw a weird thing, that is when I click on the cells in which their display should have been updated, the update happens. This of-course occurs regardless of how I update when someone clicks on a cell.
I tried to first erase whats inside the cell before redrawing, but its still not working. Does the delegate runs continuously in a thread and the painter function is called with the index of each cell in the table? I do not get how an update on a cell containing an Image does not update automatically although the painter should have redrawn the cell's area and it occurs only after the a click on the cell has been made. Or its cus a new painter is called to the painter's function each time?!
Well, here is my implementation of the painter's function of the delegate:
void Sphere::paint(QPainter *painter, const QStyleOptionViewItem &option,
const QModelIndex &index) const
{
if(tb1[index.row()][index.column()] == 1)
{
QImage Q1("Red Sphere.jpg");
QRectF source(0.0, 0.0, 72.0, 70.0);
painter->eraseRect(option.rect);
if (option.state & QStyle::State_Selected)
painter->fillRect(option.rect, option.palette.highlight());
painter->drawImage(option.rect, Q1, source);
}
else if(tb1[index.row()][index.column()] == 2)
{
QImage Q1("Blue Sphere.jpg");
QRectF source(0.0, 0.0, 72.0, 70.0);
painter->eraseRect(option.rect);
if (option.state & QStyle::State_Selected)
painter->fillRect(option.rect, option.palette.highlight());
painter->drawImage(option.rect, Q1, source);
}
else
{
painter->eraseRect(option.rect);
QStyledItemDelegate::paint(painter, option, index);
}
}
I can give you any more info if u needed to solve my problem. Thanks in advance.
I also saw a weird thing, that is when I click on the cells in which their display should have been updated, the update happens.
I think you have to determine witch cells are affected by the change made by the user in the current cell and force those cells to update(). When you think of it, your delegate can check if the content of the tb1 change to automatically decide to repaint the cell it belong to.
EDIT
A simple way to do that, could be to connect a slot to the clicked ( const QModelIndex & index ) then in here determine what change and call the update ( const QModelIndex & index ) method ...
According to QT Documentation
After painting, you should ensure that
the painter is returned to its the
state it was supplied in when this
function was called. For example, it
may be useful to call QPainter::save()
before painting and
QPainter::restore() afterwards.
I think you are missing save() and restore() methods of QPainter in your function.

Resources