QTableView external drag and drop - c++

Is there a way to drag rows out of a QTableView?
I know how to internally move columns within a QTableView by configuring some properties on the view:
table_view_->horizontalHeader()->setSectionsMovable(true);
table_view_->horizontalHeader()->setDragEnabled(true);
table_view_->horizontalHeader()->setDragDropMode(QAbstractItemView::InternalMove);
Going through the documentation on QAbstractItemView::DragDropMode, I intuitively expected my following attempt to allow external dragging of vertical header items:
table_view_->verticalHeader()->setSectionsMovable(true);
table_view_->verticalHeader()->setDragEnabled(true);
table_view_->verticalHeader()->setDragDropMode(QAbstractItemView::DragOnly);
Still, I am limited to only internal move.
Given this article I derived my own model from QSqlRelationalTableModel and added the Qt::ItemIsDragEnabled flag to the flags function, for all vertical header indexes:
Qt::ItemFlags MyRelationalTableModel::flags(const QModelIndex &index) const
{
Qt::ItemFlags defaultFlags = QSqlRelationalTableModel::flags(index);
if(index.column() == -1 && index.row() > -1) {
qDebug() << "drag enabled.";
return Qt::ItemIsDragEnabled | defaultFlags;
} else {
qDebug() << "not drag enabled.";
}
return defaultFlags;
}
This didn't change anything and given the debug prints, flags wasn't even called for vertical header items.

Does adding acceptDrops(true) to the QTableView help at all?

Related

QT Multiple item selection from a list of files and folders

I'm developing a Qt C++ application in Unix and I've been trying to do something similar to what this image shows:
As you can see, there is a list of files and folders and a user can select multiple of them (if a folder is selected, all childs also get selected). I don't really care if the folder/file icons are shown.
I was able to create a list of QDir which stores all the files and folders paths given a root path. The problem is that I don't really know which widgets to use to design the selection panel.
By the way, the lis of QDir is a vector, but it can be easily modified to anything else.
Thanks!
You can try to make proxy model for QFileSystemModel, override flags() with Qt::ItemIsUserCheckable, override setData() and apply the model to QTreeView. Full example can be found at https://github.com/em2er/filesysmodel. This code is just a concept, i have not tested it thoroughly, but you can take some ideas from it. It will look smth like on the screenshot:
.
Also you can combine it with Merged Proxy Model to display multiple starting paths at one view.
You might want to consider the QTreeWidget, or it's a tad more advanced version - QTreeView and an appropriate data model.
As some users suggested, I ended up using QFileSystemModel. I'm gonna give a full description of how I implemented it, in case someone else comes up with this problem and needs a clear response.
First of all, a QFileSystemModel is a file tree without checkboxes, to add them, a new class which extends QFileSystemModel and at least 3 methods must be overriden.
class FileSelector : public QFileSystemModel
{
public:
FileSelector(const char *rootPath, QObject *parent = nullptr);
~FileSelector();
bool setData(const QModelIndex& index, const QVariant& value, int role);
Qt::ItemFlags flags(const QModelIndex& index) const;
QVariant data(const QModelIndex& index, int role) const;
private:
QObject *parent_;
/* checklist_ stores all the elements which have been marked as checked */
QSet<QPersistentModelIndex> checklist_;
};
When creating the model a flag, to indicate that it should have a checkable box, must be set. This is why we will use the flags function:
Qt::ItemFlags FileSelector::flags(const QModelIndex& index) const
{
return QFileSystemModel::flags(index) | Qt::ItemIsUserCheckable;
}
When a click is made in the checkbox, the method setData will be called, with the index of the element that was clicked (not the checkbox itself, but the :
bool FileSelector::setData(const QModelIndex& index, const QVariant& value, int role)
{
if (role == Qt::CheckStateRole && index.column() == 0) {
QModelIndexList list;
getAllChildren(index, list); // this function gets all children
// given the index and saves them into list (also saves index in the head)
if(value == Qt::Checked)
{
for(int i = 0; i < list.size(); i++)
{
checklist_.insert(list[i]);
// signals that a change has been made
emit dataChanged(list[i], list[i]);
}
}
else if(value == Qt::Unchecked)
{
for(int i = 0; i < list.size(); i++)
{
checklist_.remove(list[i]);
emit dataChanged(list[i], list[i]);
}
}
return true;
}
return QFileSystemModel::setData(index, value, role);
}
When dataChanged is signaled or you open a new path of the tree, the data function will be called. Here you have to make sure to only display the checkbox at the first column (next to the filename), and to retrieve the state of the checkbox, to mark it as checked/unchecked.
QVariant FileSelector::data(const QModelIndex& index, int role) const
{
if (role == Qt::CheckStateRole && index.column() == 0) {
if(checklist_.contains(index)) return Qt::Checked;
else return Qt::Unchecked;
}
return QFileSystemModel::data(index, role);
}
The only thing I was not able to accomplish was getting all childs, since the folders must be open to retrieve the childs. So a closed folder won't have any child until you open it.
Hope this can help someone who has the same problem as I did!

QTreeView: hierarchical context for multi-level columns

I'm looking for a better way to display multi-level hierarchical data in a tree where the meaning of each column changes depending on the level in the tree.
I am using QTreeView and QAbstractItemModel to display my model data.
Each model row has a different number of columns and different column names depending on its level in the hierarchy.
In order to give context to the data displayed in the tree, I need to have column headers for each level in the hierarchy.
The problem is that QTreeView only has 1 set of column headers.
Current method
At the moment I'm changing the headers each time the selected row changes.
I do this by connecting to the tree view's selectionModel, and emitting a signal with the new QModelIndex each time the selection changes
void Window::slotOnSelectionChanged(const QItemSelection& new_selection, const QItemSelection& old_selection)
{
QItemSelectionModel* selection_model = _view->selectionModel();
QModelIndex index = selection_model->currentIndex();
if (index.isValid())
emit selectedIndexChanged(index);
}
In my model I connect to this signal, and when its fires, store the selected row, and force a column header update
void Model::slotOnSelectedIndexChanged(QModelIndex index)
{
assert(index.isValid());
_selected_row = modelRow(index);
emit headerDataChanged(Qt::Horizontal, 0, _root->numColumns());
}
In the QAbstrateItemModel::headerData callback I then use selected_row to get the header for the currently selected row
QVariant Model::headerData(int i, Qt::Orientation orientation, int role) const
{
if (role == Qt::DisplayRole)
{
switch (orientation)
{
case Qt::Horizontal:
return QVariant(_selected_row->header(i));
...
Result
The result can be seen below - notice how the column headers change as the selected row changes.
Problem
It's not immediately obvious by just looking at the view what each datum is, and therefore the user is required to change rows in order to see what each column actually means.
What I'd like is to have some sort of embedded column header row, 1 per level in the hierarchy.
Something like this:
Questions
Is this possible?
If there is a better way to give context to the data in the tree, please do offer a suggestion.
At the suggestion of #Kuba Ober I added an extra row at position 0 in each hierarchy of the tree. It has no children.
The model is then configured to special case for index.row() == 0, knowing that this row is a header row rather than a data row.
eg: in Model::flags the header row is not editable
Qt::ItemFlags Model::flags(const QModelIndex& index) const
{
Qt::ItemFlags item_flags = Qt::ItemIsEnabled | Qt::ItemIsSelectable;
// header row is not editable
if (index.row() != 0)
item_flags |= Qt::ItemIsEditable;
return item_flags;
}
I now return empty strings for headerData as the headers are in the tree itself
QVariant Model::headerData(int i, Qt::Orientation orientation, int role) const
{
if (role == Qt::DisplayRole)
{
switch (orientation)
{
case Qt::Horizontal:
return QVariant(); // no column header, it's already in the tree
...
I also change the background color for the header so it stands out
QVariant Model::data(const QModelIndex& index, int role) const
{
switch (role)
{
case Qt::BackgroundColorRole:
if (index.row() == 0) // header row
return QColor(Qt::darkGray);
break;
...
The result is almost exactly what I was looking for

Editable checkbox only column in QTableView

I have column with a checkbox in a QTableView. The checkbox is generated by:
returning Qt::ItemIsUserCheckable in overridden flags member function
in overridden data() function I return a Qt::CheckState for role == Qt::CheckStateRole according to data
Works, see screenshot.
But beside the checkbox I have some editable textbox in the column. How can I get rid of this textbox (where I have entered "dsdsdsds" for demonstration? Clarification, the checkbox shall be editable, but nothing else.
As requested, I can only show simplified version
Qt::ItemFlags MyClass::flags(const QModelIndex &index) const {
Qt::ItemFlags f = QAbstractListModel::flags(index);
... return f if index is not target column ....
// for target column with checkbox
return (f | Qt::ItemIsEditable | Qt::ItemIsUserCheckable; )
}
QVariant MyClass::data(const QModelIndex &index, int role) const {
.. do something for other columns
.. for checkbox column
if (role != Qt::CheckStateRole) { return QVariant(); }
bool b = ... get value for checkbox column
Qt::CheckState cs = b ? Qt::Checked : Qt::Unchecked;
return QVariant(static_cast<int>(cs));
}
If I remove Qt::ItemIsEditable then the checkbox is read only too. I later found an SO answer with a similar approach.
Remark: No duplicate of A checkbox only column in QTableView
Replace the flag
Qt::ItemIsEditable
with the flag
Qt::ItemIsEnabled
The first one tells Qt to create an editor for the value present in the model, which seems to be a texteditor in your case.
If the value is of type bool then a dropdown list containing true and false would be shown instead.

QTableView - how to prevent selection change

I have QTableView with custom table model. User can select row in the table and in specific situations I want to prevent change of the current selection.
Reselection of previously selected row is not an ideal solution (signals about the change are emited).
So what is the easies solution?
Is there some option I do not see?
Do I need to subclass QTableView?
You can make a View not selectable with QAbstractItemView::setSelectionMode(QAbstractItemView::NoSelection)
And you can do it in a per item basis too, using Qt::ItemIsSelectable
Qt::ItemFlags QAbstractItemModel::flags(const QModelIndex & index) const [virtual]
Edit (comments):
You have a custom model, so you can set a current row member variable. then, override flags:
Qt::ItemFlags YourModel::flags(const QModelIndex & index) const
{
if( _current_row > 0 && index.row() != _current_row)
{
return QAbstractItemModel::flags() | ^Qt::ItemIsSelectable;
}
else
{
return QAbstractItemModel::flags() | Qt::ItemIsSelectable;
}
}
Of course, dindt tried, but you get the idea.
Updated so, if you set current_row to -1, all are selectable
You can reselect the previously selected row. In the signal handler, call blocksignals(true) before reselection and then call blocksignals(false) to allow signalling again.

qlistview with checkboxes using custom model

I have subclassed filesystemmodel to include checkboxes in listview , which is working fine. My problem is whenever I click an item the text of that item disappears and when i click another item the text of previously selected item becomes visible. Can anyone please tell me the reason behind it.
Here is the code i implemented.
Please tell me what i am missing here,
Thanks
#include "custommodel.h"
#include <iostream>
using namespace std;
CustomModel::CustomModel()
{
}
QVariant CustomModel::data(const QModelIndex& index, int role) const
{
QModelIndex parent=this->parent(index);
if(role == Qt::DecorationRole)
{
if(this->filePath(parent)=="")
{
return QIcon(":/Icons/HardDisk.png");
}
else if(this->isDir(index))
{
QDir dir(this->filePath(index));
QFileInfoList files = dir.entryInfoList(QDir::NoDotAndDotDot |
QDir::Files | QDir::Dirs);
for(int file = 0; file < files.count(); file++)
if(files.count()>0)
return QIcon(":/Icons/FullFolder.png");
if(files.count()==0)
return QIcon(":/Icons/EmptyFolder.png");
}
else{
QFileInfo fi( this->filePath(index));
QString ext = fi.suffix();
if(ext=="jpeg"||ext=="jpg"||ext=="png"||ext=="bmp")
return QIcon(filePath(index));
}
}
if (role == Qt::CheckStateRole && !(this->filePath(parent)==""))
return checklist.contains(index) ? Qt::Checked : Qt::Unchecked;
return QFileSystemModel::data(index, role);
}
Qt::ItemFlags CustomModel::flags(const QModelIndex& index) const
{
return QFileSystemModel::flags(index)| Qt::ItemIsUserCheckable;
}
bool CustomModel::setData(const QModelIndex& index, const QVariant& value, int role)
{
if (role == Qt::CheckStateRole) {
if (value == Qt::Checked)
checklist.insert(index);
else
checklist.remove(index);
emit dataChanged(index, index);
return true;
}
return QFileSystemModel::setData(index, value, role);
}
Not sure whether it's relevant, but I found the following note at:
http://doc.trolltech.com/4.6/qt.html#ItemFlag-enum
"Note that checkable items need to be given both a suitable set of flags and an initial state, indicating whether the item is checked or not. This is handled automatically for model/view components, but needs to be explicitly set for instances of QListWidgetItem, QTableWidgetItem, and QTreeWidgetItem."
As far as I can make out, your code looks correct -- but maybe try setting the ItemIsUserCheckable flag on the base QFileSystemModel class (in your custom constructor), and see if the inherited data() and setData() methods work with role=Qt::CheckStateRole. If you need to maintain a list of what's currently checked for some other reason, then go ahead and do so in your derived setData(), but still call QFileSystemModel::setData() as well.
Meanwhile, I'm looking for why my QTreeView doesn't update the timestamp when I modify a file (unless I quit and restart my app, kind of defeats the purpose!) ... looks like the dataChanged() signal isn't getting emitted.