Dragging from an inherited QTableView - qt

I have in my app a table view called "partsview" which is descended from a QTableView, this is on the main form. The relevant header code looks like:
class partsiew : public QTableView
{
Q_OBJECT
public:
explicit partsview(QWidget *parent = 0);
public:
QStringList mimeTypes() const;
QMimeData *mimeData(const QModelIndexList &indexes) const;
};
I have also added the following into the constructor of "partsview":
this->setSelectionMode(QAbstractItemView::ExtendedSelection);
this->setDragEnabled(true);
this->setAcceptDrops(true);
this->setDropIndicatorShown(true);
though the last two are probably not needed.
When dragging, I can pick up a row and drag it to a target - a QTreeView - and I get the appropriate cursor and even a dropMimeData event is fired on the treeview.
However, the row and column values in the dropMimeData() method are always -1 and the two methods in the "partsview" are not called.
I'm guessing that the first error may have something to do with the second. Have I declared the mimeType() and mimeData() methods correctly for them to be called during a drag operation.
I have been following this page in the documentation QT4.6 - Using Model/View Classes

Here is the link for the most recent documentation on it:
http://doc.qt.io/qt-5/model-view-programming.html#using-drag-and-drop-with-item-views
I recently worked on some code for doing an internal move of cells on a single table, but while I was researching it, I found some other useful links.
http://qt-project.org/forums/viewthread/14197
I used the code in the above link and translated it to use QTableView instead of QTableWidget:
void MyTableView::dropEvent(QDropEvent *event)
{
// TODO: switch to using EyeTrackerModel::dropMimeData instead
QPoint old_coordinates = QPoint(-1,-1);
int dropAction = event->dropAction();
if(currentIndex().isValid()) //Check if user is not accessing empty cell
{
old_coordinates = QPoint( currentIndex().row(), currentIndex().column() );
}
QTableView::dropEvent(event);
qDebug() << "Detected drop event...";
event->acceptProposedAction();
if( this->indexAt(event->pos()).isValid() && old_coordinates != QPoint(-1, -1))
{
qDebug() << "Drop Event Accepted.";
qDebug() << "Source: " << old_coordinates.x() << old_coordinates.y()
<< "Destination: " << this->indexAt(event->pos()).row()
<< this->indexAt(event->pos()).column()
<< "Type: " << dropAction;
emit itemDropped( old_coordinates.x(), old_coordinates.y(),
this->indexAt(event->pos()).row(),
this->indexAt(event->pos()).column(),
dropAction);
}
// resize rows and columns right after a move!
this->doAdjustSize();
}
Even though the following answer is for Python, it still helped me when checking for what I could be missing.
QT: internal drag and drop of rows in QTableView, that changes order of rows in QTableModel
Here are some code snippets from my recent project related to drag and drop. I think you have most of them already, but I included them below for completeness.
QAbstractTableModel::dropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent)
Qt::ItemFlags QAbstractTableModel::flags(const QModelIndex &index) const
{
Q_UNUSED(index)
// if(index.isValid())
return Qt::ItemIsDragEnabled | Qt::ItemIsDropEnabled | Qt::ItemIsEditable | Qt::ItemIsEnabled | Qt::ItemIsSelectable;
// else
// return Qt::ItemIsDropEnabled | Qt::ItemIsEditable | Qt::ItemIsEnabled | Qt::ItemIsSelectable;
}
QAbstractTableView(QWidget *parent) :
QTableView(parent)
{
setAcceptDrops(true);
setDefaultDropAction(Qt::MoveAction);
}
Hope that helps.

Related

Impementation of QListView with MouseDoubleClick Event

I am intended to implement an QListView, which will be showing a frame when I will double press my mousebutton on each delegate. With my very basic programming skill I cann't do it. Here below is my code:
void MainWindow::eventFilter(QObject *obj, QEvent *event)
{
if (obj == ui->listView->viewport() && event->type() == QEvent::MouseButtonDblClick)
{
int row = getListViewRow();
qDebug() << "Double Clicked on Row: " << row << endl;
mFrame->setGeometry(700,500,150,150);
mFrame->show(); }}
Inside my constructor I also have added this below line:
qApp->installEventFilter(this);
So please correct me to achieve the goal. Thanks.
The solution is quite simple: If you install an event-filter, you need to install it on the object whos events you want to filter:
ui->listView->viewport()->installEventFilter(this);

Qt5: How to change background color of a row in a table and make it automatically move when sorting?

I have a QTableView which is populated using a QSqlTableModel.
In this table I need to be able to set the background color (let's say yellow) of some rows (using mouse clicks) in order to do some post-processing on these "yellow-selected" rows.
I found a solution that will seem kinda cumbersome... i.e. I used a proxy model and I used the Qt::Background role to give the row(s) the desired background color (yellow if "selected" and white if not).
It works acceptably (I've noticed some delays - no big deal), yet there is a problem with my implementation: when I sort the table (clicking on some column header), the "yellow-selected" rows DO NOT change their positions according to sorting operation!... The yellow rows just keep the initial positions...
So, is there a way to do this in a simple manner? Maybe using proxy model was not the best approach?!? How / What can I do to have it respond correctly to sorting operation?
I am novice so please it would be great if you can provide some code also, as I am the kind of person who learns better from examples :)
I did try 2-3 days to fix this problem but I didn't manage to do it. Internet / googling for help was of no help so far, unfortunately.
I am using Qt 5 under Windows 7.
#include "keylistgenerator.h"
#include "ui_keylistgenerator.h"
#include "dataBase/database.h"
KeyListGenerator::KeyListGenerator(QWidget * parent) :
QDialog(parent),
ui(new Ui::KeyListGenerator)
{
ui->setupUi(this);
dbConnection = DataBase::instance()->openDataBaseConnection();
model = new QSqlTableModel(this, QSqlDatabase::database(dbConnection));
proxy = new ProxyModel(this);
// unleash the power of proxy :)
proxy->setSourceModel(model);
model->setTable("MachineStatus");
model->select();
// display
ui->tableView->setModel(proxy);
ui->tableView->setEditTriggers(QAbstractItemView::NoEditTriggers);
ui->tableView->setSelectionMode(QAbstractItemView::NoSelection);
connect(ui->tableView, SIGNAL(clicked(QModelIndex)), this, SLOT(toggleSelectCurrentRow(QModelIndex)));
connect(ui->selectAllKeys, SIGNAL(clicked()), this, SLOT(setUpdateAllKeys()));
connect(ui->cancelAllKeys, SIGNAL(clicked()), this, SLOT(cancelUpdateAllKeys()));
}
KeyListGenerator::~KeyListGenerator()
{
delete ui;
}
void KeyListGenerator::toggleSelectCurrentRow(QModelIndex index)
{
proxy->toggleRowSelection(index.row());
}
void KeyListGenerator::generateKeysListFile()
{
// TODO...
}
void KeyListGenerator::setUpdateAllKeys()
{
for(int i = 0; i < proxy->rowCount(); ++i)
{
proxy->setRowSelection(i);
}
}
void KeyListGenerator::cancelUpdateAllKeys()
{
for(int i = 0; i < proxy->rowCount(); ++i)
{
proxy->setRowSelection(i, false);
}
}
Ok, and here is my proxy model :
#include <QBrush>
#include "myproxymodel.h"
ProxyModel::ProxyModel(QObject * parent)
: QAbstractProxyModel(parent)
{
}
ProxyModel::~ProxyModel()
{
}
int ProxyModel::rowCount(const QModelIndex & parent) const
{
if(!parent.isValid())
return sourceModel()->rowCount(QModelIndex());
return 0;
}
int ProxyModel::columnCount(const QModelIndex & parent) const
{
if(!parent.isValid())
return sourceModel()->columnCount();
return 0;
}
QModelIndex ProxyModel::index(int row, int column, const QModelIndex & parent) const
{
if(!parent.isValid())
return createIndex(row, column);
return QModelIndex();
}
QModelIndex ProxyModel::parent(const QModelIndex & child) const
{
return QModelIndex();
}
QModelIndex ProxyModel::mapToSource(const QModelIndex & proxyIndex) const
{
if(!proxyIndex.isValid())
return QModelIndex();
return sourceModel()->index(proxyIndex.row(), proxyIndex.column());
}
QModelIndex ProxyModel::mapFromSource(const QModelIndex & sourceIndex) const
{
if(!sourceIndex.isValid())
return QModelIndex();
return index(sourceIndex.row(), sourceIndex.column());
}
QVariant ProxyModel::data(const QModelIndex & index, int role) const
{
if(role == Qt::BackgroundRole)
{
Qt::GlobalColor color = (map.value(index.row()) == true) ? Qt::yellow : Qt::white;
QBrush background(color);
return background;
}
return sourceModel()->data(mapToSource(index), role);
}
void ProxyModel::toggleRowSelection(int row)
{
if(row < sourceModel()->rowCount())
{
// toggle status into ProxyModel for that row
bool status = map.value(row) ^ 1;
map.insert(row, status);
QModelIndex first = createIndex(row, 0);
QModelIndex last = createIndex(row, sourceModel()->columnCount() - 1);
emit dataChanged(first, last);
}
}
void ProxyModel::setRowSelection(int row, bool selected)
{
if(row < sourceModel()->rowCount())
{
// store selected status into ProxyModel for that row
map.insert(row, selected);
QModelIndex first = createIndex(row, 0);
QModelIndex last = createIndex(row, sourceModel()->columnCount() - 1);
emit dataChanged(first, last);
}
}
And here is like it looks now...
Your proposed solution is along the right track, with some problems.
The sorting is done by the model's QSQLTableModel::sort() in a way that doesn't preserve the indices. Namely, sort() resets the model, and that implies that the indices are not valid anymore. Your proxy fails to take that into account. Your map is invalid as soon as sort() is called by the view.
To work around that issue, you need to track the selection by the primary key of the involved row.
It's also simpler to derive the proxy from QIdentityProxyModel - you'd then only need to override the data() const member.
More details of using primary keys for selection indices are given in this answer, along with a full, working example.
Things get vastly simpler if you can deal with a model that happens to provide indices that refer to the underlying data source in spite of the sorting. QStringListModel is just such a model.
#include <QApplication>
#include <QStringListModel>
#include <QTableView>
int main(int argc, char *argv[])
{
QApplication app(argc, argv);
auto data = QStringList() << "Foo" << "Baz" << "Bar" << "Car";
QStringListModel model(data);
QTableView view;
view.setModel(&model);
view.setEditTriggers(QAbstractItemView::NoEditTriggers);
view.setSelectionMode(QAbstractItemView::MultiSelection);
view.setSortingEnabled(true);
app.setStyleSheet("QTableView { selection-background-color: yellow; selection-color: black; }");
view.show();
return app.exec();
}
You need persistence in the model. And you just happen to use a QSqlTableModel... So to provide persistence you need to modify the model (aka MachineStatus table) to include the "color" property.
As you said yourself
in order to do some post-processing on these "yellow-selected" rows
You are actually doing some post-processing on items with a specific property, and the fact that you color them means you are modifying that underlying property called "will be post processed".
In Concrete terms, it means ditching the proxy, subclassing the QSqlTableModel as described by QT QSqlTableModel - background color in a given data column. Additionally, you can also hide the color column.

A checkbox only column in QTableView

I have a table in Sqlite database which I display using QTableview and QSqlQueryModel. The first column needs to have a header which is a checkbox and all the items in the column need to be checkboxes too. I have implemented the first column header as a checkbox and it works perfectly.
Since the checkboxes in the column need to be centered, I used a delegate to paint it. I have painted checkboxes using the following code, but they cannot be checked or unchecked. I do not know how to implement that.
static QRect CheckBoxRect(const QStyleOptionViewItem &view_item_style_options) {
QStyleOptionButton check_box_style_option;
QRect check_box_rect = QApplication::style()->subElementRect(
QStyle::SE_CheckBoxIndicator,
&check_box_style_option);
QPoint check_box_point(view_item_style_options.rect.x() +
view_item_style_options.rect.width() / 2 -
check_box_rect.width() / 2,
view_item_style_options.rect.y() +
view_item_style_options.rect.height() / 2 -
check_box_rect.height() / 2);
return QRect(check_box_point, check_box_rect.size());
}
CheckBoxDelegate::CheckBoxDelegate(QObject *parent) :
QStyledItemDelegate(parent)
{
}
void CheckBoxDelegate::paint(QPainter *painter,
const QStyleOptionViewItem &option,
const QModelIndex &index) const {
bool checked = index.model()->data(index, Qt::DisplayRole).toBool();
QStyleOptionButton check_box_style_option;
check_box_style_option.state |= QStyle::State_Enabled;
if (checked) {
check_box_style_option.state |= QStyle::State_On;
} else {
check_box_style_option.state |= QStyle::State_Off;
}
check_box_style_option.rect = CheckBoxRect(option);
QApplication::style()->drawControl(QStyle::CE_CheckBox,
&check_box_style_option,
painter);
}
The following code shows how I use the QSqlQueryModel for QTableView to load the table from the database.
//Load the tableview with the database table
QSqlQueryModel model = new QSqlQueryModel();
//Initializaton of the query
QSqlQuery *query = new QSqlQuery(dbm->db);
query->prepare("SELECT * FROM UserData");
if(query->exec())
{
model->setQuery(*query);
ui->tableView->setModel(model);
//The header delegate to paint a checkbox on the header
HeaderDelegate *myHeader = new HeaderDelegate(Qt::Horizontal, ui->tableView);
ui->tableView->setHorizontalHeader(myHeader);
int RowCount = model->rowCount();
qDebug() << RowCount;
CheckBoxDelegate *myCheckBoxDelegate = new CheckBoxDelegate();
ui->tableView->setItemDelegateForColumn(0,myCheckBoxDelegate);
ui->tableView->horizontalHeader()->setClickable(true);
ui->tableView->setSortingEnabled(true);
}
Could you please tell me how to go about it? Any help is appreciated.
I have found this solution that does not uses delegates or anything like that. You will still have a problem centering the checkbox. That is up to you.
The next code snippet will make a column filled with checkboxes:
yourSqlQueryModel = new QSqlQueryModel();
yourTableView = new QtableView();
yourSqlQueryModel ->setQuery(yourQuery);
yourSqlQueryModel ->insertColumn(0);//Insert column for checkboxes
ui->yourTableView ->setModel(yourSqlQueryModel );
ui->yourTableView ->resizeColumnsToContents();
int p;
for(p = 0;p<yourSqlQueryModel ->rowCount();p++)
{
ui->yourTableView ->setIndexWidget(yourSqlQueryModel ->index(p,0),new QCheckBox());
}
Please read carefully, the most important here is the setIndexWidget method, which allows you to insert the widget into the created column.
The easiest way for displaying checkable items is using QStandardItemModel as QStandardItem can be set checkable. It is good for prototyping. The drawback however is, you had to fill the model manually.

QStandardItemModel with QListView, external drop action does not work

I'm using Qt 4.2.
I've a QMainWindow with a QListView inside which uses a QStandardItemModel for showing some items that I get from .desktop files.
Now I'm trying to implement a drop action over the app so for ex: I can run firefox when a .html file is droped over the firefox item.
So this is what I've done:
-for the listView:
viewport()->setAcceptDrops(true);
setAcceptDrops(true);
setDragEnabled(true);
setDropIndicatorShown(true);
setDragDropMode(QListView::DragDrop);
-for the standardItemModel:
Qt::DropActions supportedDropActions() const {
return Qt::CopyAction | Qt::MoveAction;
}
Qt::ItemFlags flags(const QModelIndex &index) const {
return Qt::ItemIsSelectable | Qt::ItemIsDragEnabled |
Qt::ItemIsDropEnabled | Qt::ItemIsEnabled;
}
QStringList mimeTypes() const {
QStringList types;
types<<"text/uri-list";
return types;
}
bool dropMimeData(const QMimeData *data, Qt::DropAction action,
int row, int column, const QModelIndex &parent) {
qDebug() << "dropMimeData";
}
After that, I never get the dropMimeData debug message when I drop some file over the app.
You also need to reimplement dragEnterEvent() and dropEvent() - they are virtual functions in QListView.
From the documentation:
Dropping
To be able to receive media dropped on a widget, call
setAcceptDrops(true) for the widget, and reimplement the
dragEnterEvent() and dropEvent() event handler functions.
For example,
the following code enables drop events in the constructor of a QWidget
subclass, making it possible to usefully implement drop event
handlers:
void Window::dragEnterEvent(QDragEnterEvent *event)
{
if (event->mimeData()->hasFormat("text/plain"))
event->acceptProposedAction();
}
void Window::dropEvent(QDropEvent *event)
{
textBrowser->setPlainText(event->mimeData()->text());
mimeTypeCombo->clear();
mimeTypeCombo->addItems(event->mimeData()->formats());
event->acceptProposedAction();
}

Qt, QStandarItemModel: Delegation items on custom QComboBox filling its contents from instantiator of model

I need to implement a table in Qt that shows a QComboBox on each row on a particular column.
Based on this question: QStandardItem + QComboBox I succesfully managed to create a QItemDelegate. In that example the QComboBox contents are defined statically on ComboBoxDelegate class, but in my case I need to define the QComboBox contents within the function where the QStandardItemModel is created.
The model is defined inside a MainWindow class method:
void MainWindow::fooHandler() {
QStandardItemModel* mymodel = new QStandardItemModel;
ui->tablePoint->setModel(mymodel);
ComboBoxDelegate* delegate=new ComboBoxDelegate;
ui->tablePoint->setItemDelegateForColumn(2,delegate);
QStringList Pets;
Pets.append("cat");
Pets.append("dog");
Pets.append("parrot");
// So far this is how I tried to store data under `Qt::UserRole` in "mymodel":
QModelIndex idx = mymodel->index(0, 2, QModelIndex());
mymodel->setData(idx,QVariant::fromValue(Pets), Qt::UserRole);
//Now i fill the table with some values...
QList< QStandardItem * > items;
items.clear();
items << new QStandardItem("col0");
items << new QStandardItem("col1");
items << new QStandardItem("parrot");
items << new QStandardItem("col3");
mymodel->appendRow(items);
items.clear();
items << new QStandardItem("col0");
items << new QStandardItem("col1");
items << new QStandardItem("cat");
items << new QStandardItem("col3");
mymodel->appendRow(items);
}
Then I should be able to recover the ComboBox contents from the delegate class:
void ComboBoxDelegate::setEditorData(QWidget *editor,
const QModelIndex &index) const
{
QString value = index.model()->data(index, Qt::EditRole).toString();
QComboBox *cBox = static_cast<QComboBox*>(editor);
if(index.column()==2) {
QModelIndex idx = index.model()->index(0, 2, QModelIndex());
cBox->addItem( index.model()->data(idx,Qt::UserRole).toStringList().at(0) );
cBox->addItem( index.model()->data(idx,Qt::UserRole).toStringList().at(1) );
cBox->addItem( index.model()->data(idx,Qt::UserRole).toStringList().at(2) );
}
cBox->setCurrentIndex(cBox->findText(value));
}
The project compiles well but when I click on a cell to change the QComboBox value the program crashes and I got an "Invalid parameter passed to C run time function."
My problem was that I was trying to use mymodel->setdata() before I append rows to the model.
So If at first I should do:
QList< QStandardItem * > items;
items.clear();
items << new QStandardItem("col0");
items << new QStandardItem("col1");
items << new QStandardItem("parrot");
items << new QStandardItem("col3");
mymodel->appendRow(items);
and ONLY then...
QModelIndex idx = mymodel->index(0, 2, QModelIndex());
mymodel->setData(idx,QVariant::fromValue(Pets), Qt::UserRole);
This solved the issue.
Thank you all.

Resources