I have a QTableView with my own implemented QAbstractItemModel, in which I can drag and drop multiple items inside. My problem is that when dragging the items and while trying to drop them in a destination cell, it is not so obvious for the user what the result is going to be. For example, I have the following,but I would prefer sth like the default widows displaying, which makes all 3 items like one item:
my QT Table
vs
windows dragging n dropping folders
After eyllanesc's suggestion for QPixmap, I found the correct solution to my problem, so that I can keep the mime data coming from my model. I have re-implemented startDrag(Qt::DropActions supportedActions) in my QTreeView class, so that when multiple objects are moved, one icon will be displayed along with the number of items moved. Now looks like this:
void MyTreeView::startDrag(Qt::DropActions supportedActions)
{
QModelIndexList indexes = selectedIndexes();
if (indexes.size() == 1)
return QAbstractItemView::startDrag(supportedActions);
if (indexes.count() > 0)
{
QMimeData *data = model()->mimeData(indexes);
if (!data)
return;
QRect rect;
rect.adjust(horizontalOffset(), verticalOffset(), 0, 0);
QDrag *drag = new QDrag(this);
ActionTreeItem* pItem = static_cast<ActionTreeItem*>(indexes[0].internalPointer());
if (pItem != NULL)
{
QPixmap pixmap = myIcon.pixmap(myIcon.actualSize(QSize(32, 32)));
QPainter *paint = new QPainter(&pixmap);
paint->setPen(Qt::black);
paint->setBrush(QBrush(Qt::white));
QRect numberRect(18, 18, 13, 13);
paint->drawRect(numberRect);
paint->drawText(numberRect, Qt::AlignHCenter | Qt::AlignVCenter, QString("%1").arg(indexes.count()));
drag->setPixmap(pixmap);
}
drag->setMimeData(data);
Qt::DropAction defaultDropAction = Qt::MoveAction;
drag->exec(supportedActions, defaultDropAction);
}
}
Taking this tutorial as a reference, the mousePressEvent method is overwritten, and a new QPixmap is placed in QDrag:
void mousePressEvent(QMouseEvent *event){
if (event->button() == Qt::LeftButton){
QDrag *drag = new QDrag(this);
drag->setMimeData(new QMimeData());
drag->setPixmap(QPixmap("image.png"));
drag->exec();
}
QTableView::mousePressEvent(event);
}
Output:
Related
I have a QMenuBar with for example two QMenu items.
How can I only make the "Floors" item be blue, for example? I know how to change it for ALL the items with:
QMenuBar::item {
background: ...;
}
But I can't find a way to color a specific item. I tried to use setProperty on Qmenu, I tried with setPalette,... I just find nothing working. Is there a way to set a specific QMenuBar::item property in C++ code?
I finally found something.
Create your own object, for example WidgetMenuBar, inherited from QMenuBar.
Add a property to identify wich item should be colored differently:
for (int i = 0; i < this->actions().size(); i++){
actions().at(i)->setProperty("selection",false);
}
// Only the first item colored
actions().at(0)->setProperty("selection",true);
Reimplement void paintEvent(QPaintEvent *e) function of your widget:
void WidgetMenuBarMapEditor::paintEvent(QPaintEvent *e){
QPainter p(this);
QRegion emptyArea(rect());
// Draw the items
for (int i = 0; i < actions().size(); ++i) {
QAction *action = actions().at(i);
QRect adjustedActionRect = this->actionGeometry(action);
// Fill by the magic color the selected item
if (action->property("selection") == true)
p.fillRect(adjustedActionRect, QColor(255,0,0));
// Draw all the other stuff (text, special background..)
if (adjustedActionRect.isEmpty() || !action->isVisible())
continue;
if(!e->rect().intersects(adjustedActionRect))
continue;
emptyArea -= adjustedActionRect;
QStyleOptionMenuItem opt;
initStyleOption(&opt, action);
opt.rect = adjustedActionRect;
style()->drawControl(QStyle::CE_MenuBarItem, &opt, &p, this);
}
}
You can see here how to implement paintEvent function.
I have a QTableWidget that is displayed in the user interface that I can add and remove rows and columns using buttons. The problem is, when I add a row or column, I can change the data in the actual cells, but I cannot name the row or column. The name is simply a static number.
Is there a way to allow the user of my program to perhaps double-click on a row/column header and edit the name in-line or something similar?
Thanks.
As far as I know there is no built-in way to do this. However this can be implemented manually. The main idea of the following code is to detect double clicks on header items, place QLineEdit over them and save edited text once it loses focus. The example is based on Qt generated Designer Form Class with a table named ui->tableWidget which can be either QTableWidget or QTableView.
class MainWindow : public QMainWindow {
Q_OBJECT
public:
explicit MainWindow(QWidget *parent = 0);
~MainWindow();
private:
Ui::MainWindow *ui;
QLineEdit* header_editor;
int editor_index;
bool eventFilter(QObject*, QEvent*);
};
Source:
MainWindow::MainWindow(QWidget *parent) :
QMainWindow(parent),
ui(new Ui::MainWindow)
{
header_editor = 0;
ui->setupUi(this);
ui->tableWidget->horizontalHeader()->viewport()->installEventFilter(this);
ui->tableWidget->verticalHeader()->viewport()->installEventFilter(this);
}
MainWindow::~MainWindow() {
delete ui;
}
bool MainWindow::eventFilter(QObject* object, QEvent* event) {
if ((object == ui->tableWidget->horizontalHeader()->viewport() ||
object == ui->tableWidget->verticalHeader()->viewport()) &&
event->type() == QEvent::MouseButtonDblClick) {
if (header_editor) { //delete previous editor just in case
header_editor->deleteLater();
header_editor = 0;
}
QMouseEvent* e = static_cast<QMouseEvent*>(event);
QHeaderView* header = static_cast<QHeaderView*>(object->parent());
int mouse_pos = header->orientation() == Qt::Horizontal ? e->x() : e->y();
int logical_index = header->logicalIndex(header->visualIndexAt(mouse_pos));
if (logical_index >= 0) { // if mouse is over an item
QRect rect; // line edit rect in header's viewport's coordinates
if (header->orientation() == Qt::Horizontal) {
rect.setLeft(header->sectionPosition(logical_index));
rect.setWidth(header->sectionSize(logical_index));
rect.setTop(0);
rect.setHeight(header->height());
} else {
rect.setTop(header->sectionPosition(logical_index));
rect.setHeight(header->sectionSize(logical_index));
rect.setLeft(0);
rect.setWidth(header->width());
}
rect.adjust(1, 1, -1, -1);
header_editor = new QLineEdit(header->viewport());
header_editor->move(rect.topLeft());
header_editor->resize(rect.size());
header_editor->setFrame(false);
//get current item text
QString text = header->model()->
headerData(logical_index, header->orientation()).toString();
header_editor->setText(text);
header_editor->setFocus();
editor_index = logical_index; //save for future use
header_editor->installEventFilter(this); //catch focus out event
//if user presses Enter it should close editor
connect(header_editor, SIGNAL(returnPressed()),
ui->tableWidget, SLOT(setFocus()));
header_editor->show();
}
return true; // filter out event
} else if (object == header_editor && event->type() == QEvent::FocusOut) {
QHeaderView* header = static_cast<QHeaderView*>(
header_editor->parentWidget()->parentWidget());
//save item text
header->model()->setHeaderData(editor_index, header->orientation(),
header_editor->text());
header_editor->deleteLater(); //safely delete editor
header_editor = 0;
}
return false;
}
Downsides of this method are that it's hacky, things go bad when headers are resized or table is scrolled. It's just an example that can be improved.
I have a feeling there has to be a simpler way. But Qt headers ignore Qt::ItemIsEditable flag and can't use delegates.
I have a bunch of QTreeWidgetItems that have embedded widgets in them that i set using the setItemWidget() function in the QTreeWidgetItem class.
Problem is anytime I move QTreeWidgetItem using drag/drop or any other means the embedded widget I set before disappears. I've seen around various forums that others have had this same problem (see link below)
http://www.qtcentre.org/threads/40500-QTreeWidget-setItemWidget%28%29-item-disappears-after-moving-item
Any possible solutions?
The problem is caused by QTreeWidget's implementation. When items are moved within the model, it deletes items at old positions and recreates them at new positions. We need to ensure 3 thinngs:
Rescue embedded widget from being deleted when its item is deleted.
Attach some information to items so we can track them and choose which widget belongs to an item.
Re-insert widget after item is moved.
Here is proof-of-concept implementation. Tree_widget_keeper_wrapper ensures 1st objective, setItemWidget's reimplementation ensures the 2nd one, and rows_inserted slot ensures the 3rd one. I tested that it works but it should be improved before using in real projects. Qt::UserRole should be changed to a configurable role. We should use role that is not used by the model itself. I put all implementation to class declaration to make it more readable but you should separate them in real code.
class Tree_widget_keeper_wrapper : public QWidget {
Q_OBJECT
public:
Tree_widget_keeper_wrapper(QWidget* child) {
_child = child;
QVBoxLayout* layout1 = new QVBoxLayout(this);
layout1->setContentsMargins(0, 0, 0, 0);
layout1->addWidget(_child);
}
~Tree_widget_keeper_wrapper() {
if (_child->parent() == this) {
_child->hide();
_child->setParent(0);
}
}
private:
QWidget* _child;
};
class Fixed_tree_widget : public QTreeWidget {
Q_OBJECT
public:
Fixed_tree_widget(QWidget* parent) : QTreeWidget(parent) {
connect(model(), SIGNAL(rowsInserted(QModelIndex,int,int)),
this, SLOT(rows_inserted(QModelIndex,int,int)));
}
void setItemWidget(QTreeWidgetItem* item, int column, QWidget* widget) {
QTreeWidget::setItemWidget(item, column, new Tree_widget_keeper_wrapper(widget));
item->setData(column, Qt::UserRole, all_widgets.count());
all_widgets << widget;
}
private:
QWidgetList all_widgets;
private slots:
void rows_inserted(QModelIndex parent, int start, int end) {
for(int column = 0; column < model()->columnCount(parent); column++) {
for(int row = start; row <= end; row++) {
QModelIndex index = model()->index(row, column, parent);
QVariant data = model()->data(index, Qt::UserRole);
if (data.type() == QVariant::Int) {
int i = data.toInt();
QTreeWidgetItem* item = itemFromIndex(index);
if (item && i >= 0 && i < all_widgets.count()) {
setItemWidget(item, column, all_widgets[i]);
all_widgets[i]->show();
}
}
}
}
}
};
I tested it against InternalMove mode and dragging items with mouse. Maybe in some other cases you will need to listen to other model's signals.
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.
I have a list of objects that I use to add objects into a QGraphicsScene:
for(int i = 0; i < levelObjects.length(); i++)
{
QRect objRect;
objRect = spriteSheetLocations.value(levelObjects.at(i).value("frame_name"));
//Q_ASSERT_X(objRect != QRect(0,0,0,0), "MainWindow::loadFile()", "Could not find sprite location!");
QImage img = spriteSheet.copy(objRect);
int height = levelObjects.at(i).value("height").toInt();
int width = levelObjects.at(i).value("width").toInt();
int x = levelObjects.at(i).value("x").toInt();
int y = levelObjects.at(i).value("y").toInt();
img = img.scaled(QSize(width, height), Qt::IgnoreAspectRatio);
item = scene->addPixmap(QPixmap::fromImage(img));
int xPos = x - width/2;
int yPos = levelPlist.value("level_height").toInt() - (y + height/2);
item->setPos(xPos, yPos);
}
Later on, in the GraphicsScene class, I detect when the user clicks on an item and drags it to move it:
void LevelGraphicsView::mousePressEvent(QMouseEvent *event)
{
if (QGraphicsItem *item = itemAt(event->pos())) {
qDebug() << "You clicked on item" << item;
draggedItem = item;
int mouseX = draggedItem->pos().x() - mapToScene(event->pos()).x();
int mouseY = draggedItem->pos().y() - mapToScene(event->pos()).y();
mouseOffset = QPointF(mouseX, mouseY);
} else {
qDebug() << "You didn't click on an item.";
draggedItem = NULL;
mouseOffset = QPointF(0,0);
}
}
void LevelGraphicsView::mouseMoveEvent(QMouseEvent *event)
{
if(!draggedItem) // no item selected
return;
QPointF pos = mapToScene(event->pos()) + mouseOffset;
draggedItem->setPos(pos);
}
This works fine for moving the items in the graphics view, but I'm having trouble tracing the QGraphicsItem back to the list item that created it.
What's the best way to link the QGraphicsItem with the list item from which it was made so that the list item can be changed to reflect the change of position?
You could assign each item in your domain object a QUuid property and pass this along to a property in your QGraphicsItem. I have used this on a project and it works quite well. I added a QHash lookup table to my domain model to make it more efficient, but this would not be necessary for shorter lists.
The best way would be a way that does not require to manualy sync items from your list and items on the scene.
The best way to do that depends on your design - may be your items can become pointers to the items on the scene or they can hold ones.