How to show icons use QSqlRelationalTableModel and QTableView in every row - qt

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

Related

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_())

Qt signal emit in addressbook

I am learning the Model-View in Qt by the AddressBook example. https://doc.qt.io/qt-5/qtwidgets-itemviews-addressbook-example.html
And I find something interesting. The code construct a TableModel class besed on QAbstractTableModel. In the override setData function, it emit the dataChanged signal. But, there is no signal emit in removeRows/insertRows. Then, how can these function update the View.
bool TableModel::removeRows(int position, int rows, const QModelIndex &index)
{
Q_UNUSED(index);
beginRemoveRows(QModelIndex(), position, position + rows - 1);
for (int row = 0; row < rows; ++row) {
listOfPairs.removeAt(position);
}
endRemoveRows();
return true;
}
bool TableModel::setData(const QModelIndex &index, const QVariant &value, int role)
{
if (index.isValid() && role == Qt::EditRole) {
int row = index.row();
QPair<QString, QString> p = listOfPairs.value(row);
if (index.column() == 0)
p.first = value.toString();
else if (index.column() == 1)
p.second = value.toString();
else
return false;
listOfPairs.replace(row, p);
emit(dataChanged(index, index));
return true;
}
return false;
}
Make note of function calls beginRemoveRows() and endRemoveRows() in the function removeRows() of your posted code.
The beginRemoveRows() function, emits a signal rowsAboutToBeRemoved(). This is how connected views can know about the deletion and the underlying connected views must handle before data is removed.
Look the note in below documentation:
https://doc.qt.io/qt-5/qabstractitemmodel.html#beginRemoveRows

Drawing a check mark in Qt

I am working with simple QTreeView. Each row of tree is a specific class inherited from one class EditorRow.
EditorRow has these functions:
virtual QVariant data(ColumnIndexEnum index, int role = Qt::DisplayRole) const = 0;
virtual void setData(const QVariant& data, ColumnIndexEnum index, int role = Qt::UserRole);
virtual QWidget* getEditor(QWidget* parent) const;
Each row has its specific widget, which is shown in the right column when selecting that row. When the row is not selected data function returns appropriate value for each row(f.e. the value which was chosen in the QComboBox).
But in case of row, which's widget is QCheckBox, I need to draw a checked(or unchecked) checkbox when the row is not selected.
I have tried to use Decoration role like this:
if(Qt::DecorationRole == role)
{
if(ValueColumn == index)
{
QStyle* style = QApplication::style();
QStyleOptionButton opt;
opt.state |= QStyle::State_Enabled;
if(isChecked())
opt.state = QStyle::State_On;
else
opt.state = QStyle::State_Off;
const int indicatorWidth = style->pixelMetric(QStyle::PM_IndicatorWidth, &opt);
const int indicatorHeight = style->pixelMetric(QStyle::PM_IndicatorHeight, &opt);
const int listViewIconSize = indicatorWidth;
const int pixmapWidth = indicatorWidth;
const int pixmapHeight = qMax(indicatorHeight, listViewIconSize);
opt.rect = QRect(0, 0, indicatorWidth, indicatorHeight);
QPixmap pixmap = QPixmap(pixmapWidth, pixmapHeight);
pixmap.fill(Qt::transparent);
{
QPainter painter(&pixmap);
QCheckBox cb;
cb.setLayoutDirection(Qt::RightToLeft);
style->drawPrimitive(QStyle::PE_IndicatorCheckBox, &opt, &painter, &cb);
}
return QIcon(pixmap);
}
}
It actually works, but the icon is shown always, even when the row is selected.
I think it is because of DecorationRole.
Do You have any ideas how to handle this problem ?
Thank You.

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

using QList<QStringList> to populate a QTableView. When something gets changed in the View, how do I get it back in the data?

I have a matrix of data, I simply stored it as a QList of QStringLists, all containing an equal number of QStrings. In this way, the data looks almost like a spreadsheet.
I use a QTableView to present this data to the user:
void DialogwitQTableView::setData(QList<QStringList> s)
{
/* Create the data model */
// 1. give it some headers
QStandardItemModel model = new QStandardItemModel(s.count(),25,this); //x Rows and 25 Columns
model->setHorizontalHeaderItem(0, new QStandardItem(QString("Column 1")));
model->setHorizontalHeaderItem(1, new QStandardItem(QString("Column 2")));
// ...
model->setHorizontalHeaderItem(24, new QStandardItem(QString("Column 25")));
// 2. populate the model with the data
for(int i = 0; i < s.count() ; i++)
{
for(int j = 0; j < s[i].count() ; j++)
model->setItem(i,j,new QStandardItem(QString(s[i][j])));
}
ui->NameOfTheTableView->setModel(model);
}
Now, if the user wants to change some of the data, he will just doubleclick in the QTableView in the Dialogbox and edits what he wants.
How do I get that edit also in the data? How can I adapt the QStringList with that new information?
If I search for documentation, I mostly find QTableViews linked to databases, but I don't see how this will work with a simple datastructure in memory. If I go to QtDesigner and click on "go to slots" for the TableView, I also do not see a slot called "datachanged" or anything similar.
Any thoughts? I feel pretty stuck and I am probably overviewing something, any tip is very welcome.
Looking at the doco, a QTableView inherits 6 signals from QAbstractItemView
http://doc.qt.digia.com/qt/qabstractitemview.html#signals
This class has all sorts of functionality for capturing edits, and edit triggers.
Once you can catch when the data is changed you can recommit it back to your model if you are using an MVC view. I am sure there are a lot of examples.
Hope that helps.
I think that for more complicated cases it's always best to use the abstract classes, more specifically QAbstractTableModel in this case.
Looking at this file, I just replaced Contact with StringList and changed the getters and setters. Check it out:
https://doc.qt.io/qt-5/qtwidgets-itemviews-addressbook-tablemodel-cpp.html
TableModel::TableModel(QObject *parent) :
QAbstractTableModel(parent)
{
}
TableModel::TableModel(QList<QStringList> stringLists, QObject *parent) :
QAbstractTableModel(parent),
stringLists(stringLists)
{
}
int TableModel::rowCount(const QModelIndex &parent) const
{
Q_UNUSED(parent);
return stringLists.size();
}
int TableModel::columnCount(const QModelIndex &parent) const
{
Q_UNUSED(parent);
return 2; // put the amount of columns here
}
QVariant TableModel::data(const QModelIndex &index, int role) const
{
if (!index.isValid()) return QVariant();
if (index.row() >= stringLists.size() || index.row() < 0) return QVariant();
if (role == Qt::DisplayRole) {
const auto &strings = stringLists.at(index.row());
if (index.column() == 0)
return strings[0];
else if (index.column() == 1)
return strings[1];
}
return QVariant();
}
QVariant TableModel::headerData(int section, Qt::Orientation orientation, int role) const
{
if (role != Qt::DisplayRole)
return QVariant();
if (orientation == Qt::Horizontal) {
switch (section) {
case 0:
return tr("String 1");
case 1:
return tr("String 2");
default:
return QVariant();
}
}
return QVariant();
}
bool TableModel::insertRows(int position, int rows, const QModelIndex &index)
{
Q_UNUSED(index);
beginInsertRows(QModelIndex(), position, position + rows - 1);
for (int row = 0; row < rows; ++row)
stringLists.insert(position, { QString(), QString() });
endInsertRows();
return true;
}
bool TableModel::removeRows(int position, int rows, const QModelIndex &index)
{
Q_UNUSED(index);
beginRemoveRows(QModelIndex(), position, position + rows - 1);
for (int row = 0; row < rows; ++row)
stringLists.removeAt(position);
endRemoveRows();
return true;
}
bool TableModel::setData(const QModelIndex &index, const QVariant &value, int role)
{
if (index.isValid() && role == Qt::EditRole) {
int row = index.row();
auto strings = stringLists.at(row);
if (index.column() == 0)
strings[0] = value.toString();
else if (index.column() == 1)
contact[1] = value.toString();
else
return false;
stringLists.replace(row, contact);
emit dataChanged(index, index, {role});
return true;
}
return false;
}
Qt::ItemFlags TableModel::flags(const QModelIndex &index) const
{
if (!index.isValid()) return Qt::ItemIsEnabled;
return QAbstractTableModel::flags(index) | Qt::ItemIsEditable;
}
QList<QStringList> TableModel::getStringLists() const
{
return stringLists;
}
I also highly recommend that you read this:
https://doc.qt.io/qt-5/modelview.html
Hope it helps.
If I search for documentation, I mostly find QTableViews linked to
databases, but I don't see how this will work with a simple
datastructure in memory.
QTableView is a part of Qt's Model/View framework. There are bunch of examples of model-views.
How do I get that edit also in the data? How can I adapt the
QStringList with that new information?
At least these solutions exists:
You can grab all data from QStandardItemModel via item method.
Connect to QStandardItemModel::itemChanged signal.
You can make your own model via subclassing (and I suggest to base on QAbstractTableModel) and implement several methods (data, setData + several utility methods).

Resources