Updating QTableView with more rows dynamically - qt

I have a QTableView, which uses a model derived from QAbstractTableModel.
The model starts out with a number of rows and displays the data properly.
The model also has a timer attached to it, which upon expiring, gets the number of rows and columns, constructs an index for it, and emits dataChanged signal, to update the dynamic contents of the table.
The problem is when the number of rows it is supposed to display, changes.
In that case, even though I obtain a new index with the changed no. of rows, and update the table, it still doesn't display any new rows.
I've identified why, perhaps. Let's say I have 2 rows in the beginning, and next, 5 rows are supposed to be displayed. In the timer expiration logic, I've asked it to construct a new index, with new row count… but dataChanged() signal, will only change the data for the rows which had already been inserted in the first. The new rows are not displayed.
This is my code for the model.
TableLayout::TableLayout()
: QAbstractTableModel()
{
rowCount();
columnCount();
timer = new QTimer(this);
timer->setInterval(3000);
timer->start();
connect(timer, SIGNAL(timeout()), this, SLOT(timerHit()));
//ItemList[0].SetFields("Blah" ,"Blah" , "Blah" , "Blah" , "Blah", "Blah", true );
//ItemList[1].SetFields("Blah1" ,"Blah1" ,"Blah1" ,"Blah1" ,"Blah1", "Blah1", true );
}
void TableLayout::timerHit()
{
int row = this->rowCount();
qDebug() << "RowCOunt::" << row;
qDebug() << " Now IT IS : " << row;
int col = this->columnCount();
qDebug() << "ColumnCOunt::" << col;
QModelIndex Ind = createIndex( 0, 0);
QModelIndex Ind1 = createIndex( row, col);
data(Ind1);
emit dataChanged(Ind, Ind1);
}
int TableLayout::rowCount(const QModelIndex& /*parent*/) const
{
RtdbReader* rtdbReader = RtdbReader::instance();
if (isConnected)
return rtdbReader->Get_Number_of_Items();
else return 2;
}
int TableLayout::columnCount(const QModelIndex& /*parent*/) const
{
return 6;
}
QVariant TableLayout::data(const QModelIndex& index, int role) const
{
int row = index.row();
int col = index.column();
//return row;
int row_count, col_count = 0;
if ( role == Qt::DisplayRole) {
if ( col == 1)
return ItemList[row]->GetExplanation();
if ( col == 2) {
qDebug() << " Now printing Sig name for row" << index.row();
return ItemList[row]->GetCond_Sig_Name();
}
if ( col == 3)
return ItemList[row]->GetDescription();
if ( col == 5)
return ItemList[row]->GetLogic();
if ( col == 4)
return ItemList[row]->Get_State_Text();
} else if ( role == Qt::DecorationRole && col == 0 ) {
bool col_to_display = ItemList[row]->GetValueColour();
qDebug() << "colour in view:" << col_to_display;
if ( col_to_display ) {
QString path = "Green.png";
//QPixmap p( s_type1Icon );
// return s_type1Icon;
QIcon icon ( path );
return icon;
} else {
QString path = "Yellow.png";
//QPixmap p( s_type1Icon );
// return s_type1Icon;
QIcon icon ( path );
return icon;
}
}
if (role == Qt::TextAlignmentRole )
return Qt::AlignCenter | Qt::AlignVCenter;
/* else if ( role == Qt::BackgroundRole)
{
QBrush yellowBackground(Qt::yellow);
return yellowBackground;
}*/
return QVariant();
}
Please suggest how I can change the view so new rows are added.
Thanks.

Just because you create a QModelIndex to the element at row, col, doesn't mean you created data for that element (and also not for the elements between the current end and that element).
You have to use setData(...) to add new data to the table, and then emit dataChanged(...) for the view to display it.

Related

How to get keypressed event for table view

This is my code of DropMimeData for the Tree Model.
The code works as expected , the user can drag and drop a treeitem from one location in the table view to another location in the view..
I need to add a condition in mimeData function based on Cntrl KeyPressed.
1) How can i get to know if the cntrl key is pressed in the function.
bool TreeModel::dropMimeData(const QMimeData *mimeData, Qt::DropAction
action, int row, int column, const QModelIndex &parent)
{
if (!mimeData->hasFormat(s_treeNodeMimeType)) {
return false;
}
QByteArray data = mimeData->data(s_treeNodeMimeType);
QDataStream stream(&data, QIODevice::ReadOnly);
qint64 senderPid;
stream >> senderPid;
if (senderPid != QCoreApplication::applicationPid()) {
return false;
}
TreeItem *parentNode = getItem(parent);
int count;
stream >> count;
if (row == -1) {
if (parent.isValid())
row = 0;
else
row = rowCount(parent);
}
for (int i = 0; i < count; ++i) {
qlonglong nodePtr;
stream >> nodePtr;
TreeItem *node = reinterpret_cast<TreeItem *>(nodePtr);
if (node->row() < row && parentNode == node->parent())
--row;
TreeItem *nodeNew = new TreeItem(node->GetContainer(), parentNode);
nodeNew->setContainer(node->GetContainer());
parentNode->insertChild(row, nodeNew);
endInsertRows();
++row;
// if( ctrl key is pressed ) while dragging and dropping item the Cntrl key is pressed
// removeItem(node);
}
return true;
}
Try this.
if (QGuiApplication::keyboardModifiers() != Qt::ControlModifier)
removeItem(node);

Qt: How to implement simple internal drag&drop for reordering items in QListView using a custom model

I have a QList of custom structs and i'm using custom model class (subclass of QAbstractListModel) to display those structs in 1-dimensional QListView. I have overriden the methodsrowCount, flags and data to construct a display string from the struct elements.
Now i would like to enable internal drag&drop to be able to reorder the items in the list by dragging them and dropping them between some other items, but this task seems unbeliavably complicated. What exactly do i need to override and what parameters do i need to set? I tried a lot of things, i tried
view->setDragEnabled( true );
view->setAcceptDrops( true );
view->setDragDropMode( QAbstractItemView::InternalMove );
view->setDefaultDropAction( Qt::MoveAction );
I tried
Qt::DropActions supportedDropActions() const override {
return Qt::MoveAction;
}
Qt::ItemFlags flags( const QModelIndex & index ) const override{
return QAbstractItemModel::flags( index ) | Qt::ItemIsDragEnabled;
}
I tried implementing insertRows and removeRows, but it still doesn't work.
I haven't found a single example of a code doing exactly that. The official documentation goes very deeply into how view/model pattern works and how to make drag&drops from external apps or from other widgets, but i don't want any of that. I only want simple internal drag&drop for manual reordering of the items in that one list view.
Can someone please help me? Or i'll get nuts from this.
EDIT: adding insertRows/removeRows implementation on request:
bool insertRows( int row, int count, const QModelIndex & parent ) override
{
QAbstractListModel::beginInsertRows( parent, row, row + count - 1 );
for (int i = 0; i < count; i++)
AObjectListModel<Object>::objectList.insert( row, Object() );
QAbstractListModel::endInsertRows();
return true;
}
bool removeRows( int row, int count, const QModelIndex & parent ) override
{
if (row < 0 || row + count > AObjectListModel<Object>::objectList.size())
return false;
QAbstractListModel::beginRemoveRows( parent, row, row + count - 1 );
for (int i = 0; i < count; i++)
AObjectListModel<Object>::objectList.removeAt( row );
QAbstractListModel::endRemoveRows();
return true;
}
objectList is QList where Object is template parameter.
In addition to the Romha's great answer, i would like to supplement few more details about how it works and what's confusing on it.
The official documentation says the QAbstractItemModel has default implementations of mimeTypes, mimeData and dropMimeData which should work for internal move and copy operations as long as you correctly implement data, setData, insertRows and removeRows.
And from certain point of view, they were right. It does work without overriding mimeData and dropMimeData, but only when your underlying data structure contains only single strings, those that are returned from data and received in setData as DisplayRole. When you have a list of compound objects (like i have) with multiple elements, only one of which is used for the DisplayRole, for example
struct Elem {
QString name;
int i;
bool b;
}
QVariant data( const QModelIndex & index, int role ) const override
{
return objectList[ index.row() ].name;
}
bool setData( const QModelIndex & index, const QVariant & value, int role ) override
{
objectList[ index.row() ].name = value.toString();
}
then the default implementations will actually do this
QVariant data = data( oldIndex, Qt::DisplayRole );
insertRows( newIndex, 1 )
setData( newIndex, data, Qt::DisplayRole )
removeRows( oldIndex, 1 )
and therefore only correctly move the names and leave the rest of the struct as is. Which makes sense now, but the system is so complicated that i didn't realize it before.
Therefore custom mimeData and dropMimeData are required to move the whole content of the structs
When you want to reorganize items in a custom model, you have to implement all needed actions:
- how to insert and remove a row
- how to get and set data
- how to serialize items (build the mimedata)
- how to unserialize items
An example with a custom model with a QStringList as data source:
The minimal implementation of the model should be:
class CustomModel: public QAbstractListModel
{
public:
CustomModel()
{
internalData = QString("abcdefghij").split("");
}
int rowCount(const QModelIndex &parent) const
{
return internalData.length();
}
QVariant data(const QModelIndex &index, int role) const
{
if (!index.isValid() || index.parent().isValid())
return QVariant();
if (role != Qt::DisplayRole)
return QVariant();
return internalData.at(index.row());
}
private:
QStringList internalData;
};
We have to add the way to insert/remove rows and set the data:
bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::DisplayRole)
{
if (role != Qt::DisplayRole)
return false;
internalData[index.row()] = value.toString();
return true;
}
bool insertRows(int row, int count, const QModelIndex &parent)
{
if (parent.isValid())
return false;
for (int i = 0; i != count; ++i)
internalData.insert(row + i, "");
return true;
}
bool removeRows(int row, int count, const QModelIndex &parent)
{
if (parent.isValid())
return false;
beginRemoveRows(parent, row, row + count - 1);
for (int i = 0; i != count; ++i)
internalData.removeAt(row);
endRemoveRows();
return true;
}
For the drag and drop part:
First, we need to define a mime type to define the way we will deserialize the data:
QStringList mimeTypes() const
{
QStringList types;
types << CustomModel::MimeType;
return types;
}
Where CustomModel::MimeType is a constant string like "application/my.custom.model"
The method canDropMimeData will be used to check if the dropped data are legit or not. So, we can discard external data:
bool canDropMimeData(const QMimeData *data,
Qt::DropAction action, int /*row*/, int /*column*/, const QModelIndex& /*parent*/)
{
if ( action != Qt::MoveAction || !data->hasFormat(CustomModel::MimeType))
return false;
return true;
}
Then, we can create our mime data based on the internal data:
QMimeData* mimeData(const QModelIndexList &indexes) const
{
QMimeData* mimeData = new QMimeData;
QByteArray encodedData;
QDataStream stream(&encodedData, QIODevice::WriteOnly);
for (const QModelIndex &index : indexes) {
if (index.isValid()) {
QString text = data(index, Qt::DisplayRole).toString();
stream << text;
}
}
mimeData->setData(CustomModel::MimeType, encodedData);
return mimeData;
}
Now, we have to handle the dropped data. We have to deserialize the mime data, insert a new row to set the data at the right place (for a Qt::MoveAction, the old row will be automaticaly removed. That why we had to implement removeRows):
bool dropMimeData(const QMimeData *data,
Qt::DropAction action, int row, int column, const QModelIndex &parent)
{
if (!canDropMimeData(data, action, row, column, parent))
return false;
if (action == Qt::IgnoreAction)
return true;
else if (action != Qt::MoveAction)
return false;
QByteArray encodedData = data->data("application/my.custom.model");
QDataStream stream(&encodedData, QIODevice::ReadOnly);
QStringList newItems;
int rows = 0;
while (!stream.atEnd()) {
QString text;
stream >> text;
newItems << text;
++rows;
}
insertRows(row, rows, QModelIndex());
for (const QString &text : qAsConst(newItems))
{
QModelIndex idx = index(row, 0, QModelIndex());
setData(idx, text);
row++;
}
return true;
}
If you want more info on the drag and drop system in Qt, take a look at the documentation.
Here is a evidenced example for you ,but in Python:
import sys
from PySide6 import QtCore, QtGui, QtWidgets
from PySide6.QtCore import (Qt, QStringListModel, QModelIndex,
QMimeData, QByteArray, QDataStream, QIODevice)
from PySide6.QtWidgets import (QApplication, QMainWindow, QListView, QAbstractItemView, QPushButton, QVBoxLayout, QWidget)
class DragDropListModel(QStringListModel):
def __init__(self, parent=None):
super(DragDropListModel, self).__init__(parent)
# self.myMimeTypes = 'application/vnd.text.list' # 可行
# self.myMimeTypes = "text/plain" # 可行
self.myMimeTypes = 'application/json' # 可行
def supportedDropActions(self):
# return Qt.CopyAction | Qt.MoveAction # 拖动时复制并移动相关项目
return Qt.MoveAction # 拖动时移动相关项目
def flags(self, index):
defaultFlags = QStringListModel.flags(self, index)
if index.isValid():
return Qt.ItemIsDragEnabled | Qt.ItemIsDropEnabled | defaultFlags
else:
return Qt.ItemIsDropEnabled | defaultFlags
def mimeTypes(self):
return [self.myMimeTypes]
# 直接将indexes里面对应的数据取出来,然后打包进了QMimeData()对象,并返回
def mimeData(self, indexes):
mmData = QMimeData()
encodedData = QByteArray()
stream = QDataStream(encodedData, QIODevice.WriteOnly)
for index in indexes:
if index.isValid():
text = self.data(index, Qt.DisplayRole)
stream << text # 测试,也行
# stream.writeQString(str(text)) # 原始, 可行
mmData.setData(self.myMimeTypes, encodedData)
return mmData
def canDropMimeData(self, data, action, row, column, parent):
if data.hasFormat(self.myMimeTypes) is False:
return False
if column > 0:
return False
return True
def dropMimeData(self, data, action, row, column, parent):
if self.canDropMimeData(data, action, row, column, parent) is False:
return False
if action == Qt.IgnoreAction:
return True
beginRow = -1
if row != -1: # 表示
print("case 1: ROW IS NOT -1, meaning inserting in between, above or below an existing node")
beginRow = row
elif parent.isValid():
print("case 2: PARENT IS VALID, inserting ONTO something since row was not -1, "
"beginRow becomes 0 because we want to "
"insert it at the beginning of this parents children")
beginRow = parent.row()
else:
print("case 3: PARENT IS INVALID, inserting to root, "
"can change to 0 if you want it to appear at the top")
beginRow = self.rowCount(QModelIndex())
print(f"row={row}, beginRow={beginRow}")
encodedData = data.data(self.myMimeTypes)
stream = QDataStream(encodedData, QIODevice.ReadOnly)
newItems = []
rows = 0
while stream.atEnd() is False:
text = stream.readQString()
newItems.append(str(text))
rows += 1
self.insertRows(beginRow, rows, QModelIndex()) # 先插入多行
for text in newItems: # 然后给每一行设置数值
idx = self.index(beginRow, 0, QModelIndex())
self.setData(idx, text)
beginRow += 1
return True
class DemoDragDrop(QWidget):
def __init__(self, parent=None):
super(DemoDragDrop, self).__init__(parent)
# 设置窗口标题
self.setWindowTitle('drag&drop in PySide6')
# 设置窗口大小
self.resize(480, 320)
self.initUi()
def initUi(self):
self.vLayout = QVBoxLayout(self)
self.listView = QListView(self)
self.listView.setSelectionMode(QAbstractItemView.ExtendedSelection)
self.listView.setDragEnabled(True)
self.listView.setAcceptDrops(True)
self.listView.setDropIndicatorShown(True)
self.ddm = DragDropListModel() # 该行和下面4行的效果类似
# self.listView.setDragDropMode(QAbstractItemView.InternalMove)
# self.listView.setDefaultDropAction(Qt.MoveAction)
# self.listView.setDragDropOverwriteMode(False)
# self.ddm = QStringListModel()
self.ddm.setStringList(['Item 1', 'Item 2', 'Item 3', 'Item 4'])
self.listView.setModel(self.ddm)
self.printButton = QPushButton("Print")
self.vLayout.addWidget(self.listView)
self.vLayout.addWidget(self.printButton)
self.printButton.clicked.connect(self.printModel)
def printModel(self): # 验证移动view中项目后,背后model中数据也发生了移动
print(self.ddm.data(self.listView.currentIndex()))
if __name__ == '__main__':
app = QApplication(sys.argv)
app.setStyle('fusion')
window = DemoDragDrop()
window.show()
sys.exit(app.exec_())

Sorting Big Table

I'm using QTableView with reimplemented QAbstractTableModel and QSortFilterProxyModel.
In this tabel some file sizes were displayed. data(...) method for Size column return a long long value wich is formated in QStyleditemDelegate like 2,38 Gb
For exmple if in table 8000 items were populated sorting will took about 2-3 sec.
How can I improve this?
edit1
QVariant FileViewModel::data( const QModelIndex &index, int role /*= Qt::DisplayRole */ ) const
{
qDebug() << "FileViewModel::data" << index.row() << index.column();
int column = index.column();
if (role == Qt::DisplayRole && dataSource.storrage.is_valid())
{
int row = index.row();
internal_file_entry file = dataSource.storrage.internal_at(row);
switch (column)
{
case 0:
return QString::fromStdString(dataSource.storrage.file_path(file));
case 1:
return dataSource.storrage.file_size(file);
case 2:
return dataSource.progresses[row]*100.0f;
case 3:
return dataSource.priorities[row];
}
}
}
creating and applying model
void AppMainView::setupFileTabel()
{
fileViewModel = new FileViewModel(this);
QSortFilterProxyModel* proxymodel = new QSortFilterProxyModel(this);
proxymodel->setSourceModel(fileViewModel);
fileTableView->setModel(proxymodel);
fileTableView->setShowGrid(false);
fileTableView->setContextMenuPolicy(Qt::CustomContextMenu);
fileTableView->setSelectionBehavior(QAbstractItemView::SelectRows);
fileTableView->setItemDelegateForColumn(1,new FileSizeItemDelegate(this));
fileTableView->setItemDelegateForColumn(2,new ProgressItemDelegate(this));
fileTableView->setItemDelegateForColumn(3,new PriorityItemDelegate(this));
fileTableView->setSortingEnabled(true);
fileTableView->setColumnWidth(0,300);
fileTableView->setColumnWidth(1,65);
fileTableView->setColumnWidth(2,65);
fileTableView->setColumnWidth(3,70);
setupFileTabelContextMenu();
}

How to show icons use QSqlRelationalTableModel and QTableView in every row

Now,I am try to use QSqlRelationalTableModel and QTableView to show my data in database,according to the properity "fileType" such as "doc,txt,exe,sln" to show a icon in the first column.
dirModel=new QSqlRelationalTableModel(this);
dirModel->setTable("ecm_doc");
dirModel->setFilter(QString("creatoruserid='%1' and parentid='%2'").arg(userid).arg(parentid));
dirModel->select();
dirView=new QTableView(this);
dirView->setItemDelegate(new DirDelegate(this));
dirView->setModel(dirModel);
dirView->setSelectionMode(QAbstractItemView::SingleSelection);
showIcon();
void DirTree::showIcon()
{
int rowCount = dirModel->rowCount();
for(int row = 0; row < rowCount; row++)
{
//here is a test.
QModelIndex index = dirModel->index(row, 1);
QIcon folderIcon(style()->standardPixmap(QStyle::SP_DirClosedIcon));
dirModel->setData(index, folderIcon, Qt::DecorationRole);
}
}
Help me,online wait:)
You should subclass from QSqlRelationalTableModel and reimplement the data() function in order to specify the icon:
QVariant MyModel::data(const QModelIndex &index, int role) const
{
// I assume that 1 is the db table column with the filetype property
if (index.column() == 1 && role == Qt::DecorationRole)
{
QSqlRecord r = modelRecord(index.row());
QString fileType = r.field(1).value().toString();
QIcon icon;
if (fileType == "exe")
icon = QIcon(":/PathToIcon/exe.png");
else if (fileType == "sln")
icon = QIcon(":/PathToIcon/sln.png");
...
return QVariant(icon);
}
return QSqlRelationalTableModel::data(index, role);
}

Qt - QTreeView and custom model with checkbox columns

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.

Resources