I have a QTreeWidget which displays a single root node and one level of child nodes only.
I need to permit the re-ordering of the child nodes.
They must never be re-parented.
This is how I enable the dragging of items in the QTreeWidget :
ui->levelElements->setSelectionMode(QAbstractItemView::SingleSelection);
ui->levelElements->setDragEnabled(true);
ui->levelElements->viewport()->setAcceptDrops(true);
ui->levelElements->setDropIndicatorShown(true);
ui->levelElements->setDragDropMode(QAbstractItemView::InternalMove);
The root item is inserted like this :
pItem = new QTreeWidgetItem(ui->levelElements);
pItem->setText(0, node.firstChild().nodeValue());
pItem->setFlags(Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsDropEnabled);
When I insert a child into the view, it is declared like this :
pItem = new QTreeWidgetItem();
pItem->setText(0, strFileName);
pItem->setFlags(Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsDragEnabled);
pTreeRoot->addChild(pItem);
At this point, I am able to drag a child item and "re-insert" it in the list of child items. I can also move it to the root level - which I do not want.
I am using QT Creator v3 and so the QTreeWidget is within my UI definition file.
Have I missed something here ?
For example you can override: virtual void dropEvent(QDropEvent * event)
#include <QApplication>
#include <QTreeWidget>
#include <QTreeWidgetItem>
#include <QDropEvent>
class TreeView: public QTreeWidget
{
public:
TreeView()
{
resize(200, 300);
setSelectionMode(QAbstractItemView::SingleSelection);
setDragEnabled(true);
viewport()->setAcceptDrops(true);
setDropIndicatorShown(true);
setDragDropMode(QAbstractItemView::InternalMove);
QTreeWidgetItem* parentItem = new QTreeWidgetItem(this);
parentItem->setText(0, "Test");
parentItem->setFlags(Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsDropEnabled);
for(int i = 0; i < 10; ++i)
{
QTreeWidgetItem* pItem = new QTreeWidgetItem(parentItem);
pItem->setText(0, QString("Number %1").arg(i) );
pItem->setFlags(Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsDragEnabled);
pItem->addChild(pItem);
}
}
private:
virtual void dropEvent(QDropEvent * event)
{
QModelIndex droppedIndex = indexAt( event->pos() );
if( !droppedIndex.isValid() )
return;
QTreeWidget::dropEvent(event);
}
};
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
TreeView widget;
widget.show();
return a.exec();
}
Related
The 'Country' QListwidget is going to be populated with country names items and if the user drags that an item and drops it in capital QListWidget, it should show me the name of its capital , i.e that item text should change in capital QListWidget.
for example - an item named "Russia" in country listwidget , after dragging and dropping , the item should be renamed to "Moscow".
so far to enable drag and drop, i have just written this code,
ui->country_listwidget->setDragEnabled(true);
ui->capital_listwidget->setAcceptDrops(true);
is this possible in QT ?
A simple solution is to create 2 roles that store each information, and then use a delegate to display the text depending on the view:
#include <QtWidgets>
enum CustomRoles {
CounrtyRole = Qt::UserRole,
CapitalRole
};
class DisplayDelegate: public QStyledItemDelegate{
public:
DisplayDelegate(int displayRole=Qt::DisplayRole, QObject *parent=nullptr)
:QStyledItemDelegate(parent), m_displayRole(displayRole){}
int getDisplayRole() const{
return m_displayRole;
}
void setDisplayRole(int value){
m_displayRole = value;
}
protected:
void initStyleOption(QStyleOptionViewItem *option, const QModelIndex &index) const{
QStyledItemDelegate::initStyleOption(option, index);
QVariant value = index.data(m_displayRole);
if (value.isValid() && !value.isNull()) {
option->features |= QStyleOptionViewItem::HasDisplay;
option->text = displayText(value, option->locale);
}
}
private:
int m_displayRole;
};
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
QListWidget *country_lw = new QListWidget;
country_lw->setItemDelegate(new DisplayDelegate(CounrtyRole));
QListWidget *capital_lw = new QListWidget;
capital_lw->setItemDelegate(new DisplayDelegate(CapitalRole));
country_lw->setDragEnabled(true);
capital_lw->setAcceptDrops(true);
QListWidgetItem *item1 = new QListWidgetItem;
item1->setData(CounrtyRole, "Country1");
item1->setData(CapitalRole, "Capital1");
country_lw->addItem(item1);
QListWidgetItem *item2 = new QListWidgetItem;
item2->setData(CounrtyRole, "Country2");
item2->setData(CapitalRole, "Capital2");
country_lw->addItem(item2);
QListWidgetItem *item3 = new QListWidgetItem;
item3->setData(CounrtyRole, "Country3");
item3->setData(CapitalRole, "Capital3");
country_lw->addItem(item3);
QWidget w;
QHBoxLayout *lay = new QHBoxLayout(&w);
lay->addWidget(country_lw);
lay->addWidget(capital_lw);
w.show();
return a.exec();
}
I am trying to design something like a timeline view for my video player. I decided to use QTableWidget as the timeline since it suits my purpose. My widget looks like this:
I want the green line to run through the widget when i click on play. Here is my MVCE example:
//View.cpp
View::View(QWidget* parent) : QGraphicsView(parent)
{
QGraphicsScene* scene = new QGraphicsScene(this);
TableWidget* wgt = new TableWidget;
scene->addWidget(wgt);
QGraphicsLineItem* item = new QGraphicsLineItem(30, 12, 30, wgt->height() - 9);
item->setPen(QPen(QBrush(Qt::green), 3));
item->setFlags(QGraphicsItem::ItemIsMovable);
scene->addItem(item);
setScene(scene);
}
Here is TableWidget
TableWidget::TableWidget(QWidget* parent) : QTableWidget(parent)
{
setColumnCount(10);
setRowCount(10);
//Hides the numbers on the left side of the table
verticalHeader()->hide();
//Prevents top header from highlighting on selection
horizontalHeader()->setHighlightSections(false);
//Makes the cells un-editable
setEditTriggers(QAbstractItemView::NoEditTriggers);
setSelectionMode(QAbstractItemView::MultiSelection);
}
Problem:
Moving the line item reflects changes to the scene it has been added to i.e. when i drag the line using mouse, the line moves in the scene but not inside the TableWidget.
What do i want
I want the green bar to act like a horizontal slider. It should go through the TableWidget horizontally making the widget scroll along with it showing the current position of the frame indicated by the numbers shown on the header.
Something like as shown below (notice the Red line):
I know this might not be the best way to implement a timeline but i would appreciate any other ideas to implement.
A possible solution is to overwrite the itemChange method to restrict movement as shown below:
#include <QApplication>
#include <QGraphicsRectItem>
#include <QGraphicsView>
#include <QTableWidget>
#include <QHeaderView>
#include <QGraphicsProxyWidget>
class SeekBarItem: public QGraphicsRectItem{
public:
SeekBarItem(QRectF rect, QGraphicsItem *parent=nullptr)
: QGraphicsRectItem(rect, parent)
{
setFlag(QGraphicsItem::ItemIsMovable, true);
setFlag(QGraphicsItem::ItemSendsGeometryChanges, true);
setBrush(Qt::red);
}
protected:
QVariant itemChange(GraphicsItemChange change, const QVariant &value){
if(change == QGraphicsItem::ItemPositionChange){
QPointF p = value.toPointF();
qreal max = parentItem()->boundingRect().bottom()- boundingRect().bottom();
qreal min = parentItem()->boundingRect().top()-boundingRect().top();
if(p.y() > max) p.setY(max);
else if (p.y() < min) p.setY(min);
p.setX(pos().x());
return p;
}
return QGraphicsRectItem::itemChange(change, value);
}
};
class TableWidget: public QTableWidget
{
public:
TableWidget(QWidget* parent=nullptr) : QTableWidget(10, 10, parent)
{
verticalHeader()->hide();
horizontalHeader()->setHighlightSections(false);
setEditTriggers(QAbstractItemView::NoEditTriggers);
setSelectionMode(QAbstractItemView::MultiSelection);
}
};
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
QGraphicsView view;
QGraphicsScene *scene = new QGraphicsScene;
view.setScene(scene);
QGraphicsProxyWidget *proxy = scene->addWidget(new TableWidget);
QGraphicsRectItem *it = new QGraphicsRectItem(QRectF(0, 0, 10, proxy->boundingRect().height()), proxy);
it->setBrush(Qt::green);
SeekBarItem *seekBarItem = new SeekBarItem(QRectF(-5, 0, 20, 50));
seekBarItem->setParentItem(it);
view.resize(640, 480);
view.show();
return a.exec();
}
Update:
#include <QApplication>
#include <QGraphicsRectItem>
#include <QGraphicsView>
#include <QTableWidget>
#include <QHeaderView>
#include <QGraphicsProxyWidget>
#include <QScrollBar>
class TableWidget: public QTableWidget
{
public:
TableWidget(QWidget* parent=nullptr) : QTableWidget(10, 10, parent)
{
verticalHeader()->hide();
horizontalHeader()->setHighlightSections(false);
setEditTriggers(QAbstractItemView::NoEditTriggers);
setSelectionMode(QAbstractItemView::MultiSelection);
setHorizontalScrollMode(QAbstractItemView::ScrollPerPixel);
}
};
class SeekBarItem: public QGraphicsRectItem{
public:
SeekBarItem(int width, QAbstractItemView *view, QGraphicsScene *scene)
: QGraphicsRectItem(nullptr),
proxy(new QGraphicsProxyWidget()),
m_view(view)
{
proxy->setWidget(m_view);
scene->addItem(proxy);
setParentItem(proxy);
setFlag(QGraphicsItem::ItemIsMovable, true);
setFlag(QGraphicsItem::ItemSendsGeometryChanges, true);
setBrush(Qt::red);
setRect(0, 0, width, m_view->height());
scrollbar = m_view->horizontalScrollBar();
}
protected:
QVariant itemChange(GraphicsItemChange change, const QVariant &value){
if(change == QGraphicsItem::ItemPositionChange){
QPointF p = value.toPointF();
qreal max = parentItem()->boundingRect().right()- boundingRect().right();
qreal min = parentItem()->boundingRect().left()-boundingRect().left();
if(p.x() > max) p.setX(max);
else if (p.x() < min) p.setX(min);
p.setY(pos().y());
float percentage = (p.x()-min)*1.0/(max-min);
int value = scrollbar->minimum() + percentage*(scrollbar->maximum() - scrollbar->minimum());
scrollbar->setValue(value);
return p;
}
return QGraphicsRectItem::itemChange(change, value);
}
private:
QGraphicsProxyWidget *proxy;
QAbstractItemView *m_view;
QScrollBar *scrollbar;
};
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
QGraphicsView view;
QGraphicsScene *scene = new QGraphicsScene;
view.setScene(scene);
TableWidget *table = new TableWidget;
SeekBarItem *seekBarItem = new SeekBarItem(15, table, scene);
view.resize(640, 480);
view.show();
return a.exec();
}
I am learning Qt for fun. And I got a question:
How could I drag and drop the QLabel in Qt among two different windows?
Here is what I have so far:
As you can tell from the .gif(which does not want to become downloaded and visible here for some reasons, but if you click on the link to it, you can clearly see it) provided above right now there are two main problems:
I can not move the QLabel outside of the window (and hence am not able to register the drag and drop event).
The label is flashing for some reasons when I am moving it.
Here is the relevant part of the implementation from the .gif:
#ifndef DRAGGERP_H
#define DRAGGERP_H
#include <QLabel>
#include <QApplication>
#include <QMouseEvent>
#include <QPoint>
class DraggerP : public QLabel
{
QPoint offset;
QPoint startingPosition;
public:
DraggerP(QWidget* parent = nullptr) : QLabel(parent){ }
protected:
void enterEvent(QEvent* event) override
{
QApplication::setOverrideCursor(Qt::PointingHandCursor);
}
void leaveEvent(QEvent* event) override
{
QApplication::restoreOverrideCursor();
}
void mousePressEvent(QMouseEvent* event) override
{
startingPosition = pos();
offset = QPoint(
event->pos().x() - pos().x() + 0.5*width(),
event->pos().y() - pos().y() + 0.5*height()
);
}
void mouseMoveEvent(QMouseEvent* event) override
{
move(event->pos() + offset);
}
void mouseReleaseEvent(QMouseEvent* event) override
{
move(startingPosition);
}
};
#endif // DRAGGERP_H
This is the extension of the QLabel I am using to create the drag and drop effect.
I do not need the whole solution, at least an idea of how to accomplish this and what am I doing wrong here.
Here is a pretty good example and I used it as a starting point.
That strange movement that the QLabel suffers is because the position of the QLabel now depends on the layout, the job of the layout is to establish the position of the widgets depending on the policies you establish.
The solution is not to implement those actions in the QLabel but in the MainWindow as I show below:
#include <QApplication>
#include <QLabel>
#include <QMainWindow>
#include <QScrollArea>
#include <QVBoxLayout>
#include <QTime>
#include <QDrag>
#include <QMimeData>
#include <QMouseEvent>
class MainWindow: public QMainWindow {
QScrollArea scrollArea;
QWidget contentWidget;
QVBoxLayout lay;
public:
MainWindow(QWidget* parent=nullptr): QMainWindow(parent){
qsrand((uint) QTime::currentTime().msec());
setCentralWidget(&scrollArea);
scrollArea.setWidget(&contentWidget);
contentWidget.setLayout(&lay);
scrollArea.setWidgetResizable(true);
for(int i=0; i< 20; i++){
QLabel *label = new QLabel(QString("label %1").arg(i));
QPalette pal = label->palette();
pal.setColor(QPalette::Background, QColor(10 +qrand() % 240, 10 +qrand() % 240, 10 +qrand() % 240));
label->setAutoFillBackground(true);
label->setPalette(pal);
lay.addWidget(label);
}
setAcceptDrops(true);
}
protected:
void mousePressEvent(QMouseEvent *event){
QMainWindow::mousePressEvent(event);
QWidget *child = childAt(event->pos());
if(qobject_cast<QLabel *>(child))
createDrag(event->pos(), child);
}
void dropEvent(QDropEvent *event){
QByteArray byteArray = event->mimeData()->data("Label");
QWidget * widget = *reinterpret_cast<QWidget**>(byteArray.data());
QLabel * new_label = qobject_cast<QLabel *>(widget);
QWidget *current_children = childAt(event->pos());
QLabel * current_label = qobject_cast<QLabel*>(current_children);
int index = 0;
if(new_label){
if(current_label)
index = lay.indexOf(current_label);
else{
index = 0;
QLayoutItem *item = lay.itemAt(index);
while(item->widget()->pos().y() < event->pos().y() && item)
item = lay.itemAt(index++);
}
lay.insertWidget(index, new_label);
}
}
private:
void createDrag(const QPoint &pos, QWidget *widget){
if(widget == Q_NULLPTR)
return;
QByteArray byteArray(reinterpret_cast<char*>(&widget),sizeof(QWidget*));
QDrag *drag = new QDrag(this);
QMimeData * mimeData = new QMimeData;
mimeData->setData("Label",byteArray);
drag->setMimeData(mimeData);
QPoint globalPos = mapToGlobal(pos);
QPoint p = widget->mapFromGlobal(globalPos);
drag->setHotSpot(p);
drag->setPixmap(widget->grab());
drag->exec(Qt::CopyAction | Qt::MoveAction);
}
protected:
void dragEnterEvent(QDragEnterEvent *event){
if(event->mimeData()->hasFormat("Label"))
event->acceptProposedAction();
}
};
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
MainWindow w1;
MainWindow w2;
w1.show();
w2.show();
return a.exec();
}
I have a tree like this:
|-Parent
| |-Child-Child
|-Parent
| |-Child-Child
...
Only the Parents are selectable. How can I get the data from the selected Parent?
I tried
ui->treeView->selectedIndexes()[0];
but it says that selectedIndexes() is protected.
You need to call QItemSelectionModel::selectedIndexes() instead, i.e.:
QModelIndexList indexes = ui->treeView->selectionModel()->selectedIndexes();
if (indexes.size() > 0) {
QModelIndex selectedIndex = indexes.at(0);
[..]
}
How to get the selected item in a QTreeView?
The question is simple and the answers are terrible, and the tutorial worse.
Below is a fully functional example which shows how to get the selected item. Specifically selected_item()
#include <QApplication>
#include <QTreeView>
#include <QStandardItemModel>
#include <QItemSelectionModel>
#include <QGridLayout>
#include <iostream>
struct Node:public QStandardItem {
Node(std::string name):QStandardItem(name.c_str()){}
virtual void operator()(){
std::cout<<"selected node named: "<<text().toStdString()<<std::endl;
}
};
class TreeView :public QWidget{
Q_OBJECT
public:
QTreeView tree;
using Model=QStandardItemModel;
Model* item_model(){ return (Model*)tree.model(); }
Node* selected_item() {
QModelIndex index = tree.currentIndex();
if(!index.isValid()) return nullptr; // if the user has selected nothing
return (Node*)(item_model()->itemFromIndex(index));
}
TreeView() {
// automatically sets to parent
auto layout=new QGridLayout(this);
layout->addWidget(&tree,0,0);
// set the item model, there is no sane choice but StandardItemModel
tree.setModel(new Model());
connect(tree.selectionModel(),
&QItemSelectionModel::selectionChanged,
this,
&TreeView::selected);
// create a small tree
auto top=new Node("top");
auto a=new Node("a");
a->appendRow(new Node("a0"));
a->appendRow(new Node("a1"));
auto b=new Node("b");
top->appendRow(a);
top->appendRow(b);
// add it to the treeview root
item_model()->invisibleRootItem()->appendRow(top);
}
private slots:
void selected(
const QItemSelection &news, // not used
const QItemSelection &olds)
{
auto* node=selected_item();
if(node) (*node)();
}
};
int main(int argc, char** argv){
QApplication a(argc, argv);
TreeView w;
w.show();
return a.exec();
}
I have created a table using qtablewidget. I would like to selectively merge cells of some rows. Is there any built-in function for that(sorry, I couldn't find one)? Otherwise, please specify how it can be done. Thanks
You could use the setSpan function of QTableView (which QTableWidget inherits) to achieve something like that for rows and columns.
That is, unless you're looking to really just merge individual cells. I'm not sure that can be achieved with setSpan, but have a look at it anyway.
Try QTableWidget::setSpan(int row, int column, int rowSpan, int columnSpan);
But in some versions it doesn't work.
Sample program
#include <QApplication>
#include <QVBoxLayout>
#include <QPushButton>
#include <QLabel>
#include <QTableWidget>
#include <QHeaderView>
class TestWidget: public QWidget
{
Q_OBJECT
private:
QTableWidget *testTable;
QVBoxLayout *mainLayout;
QPushButton *button;
public:
TestWidget(QWidget *parent=nullptr) : QWidget(parent)
{
mainLayout = new QVBoxLayout(this);
testTable = new QTableWidget;
testTable = new QTableWidget(this);
testTable->setObjectName("testTable");
testTable->setFixedWidth(400);
testTable->setSizePolicy(QSizePolicy::Fixed,QSizePolicy::Minimum);
testTable->setColumnCount(4);
testTable->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
testTable->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
QTableWidgetItem *itemC1 = new QTableWidgetItem("Column 1");
testTable->setHorizontalHeaderItem(0, itemC1);
QTableWidgetItem *itemC2 = new QTableWidgetItem("Column 2");
testTable->setHorizontalHeaderItem(1, itemC2);
QTableWidgetItem *itemC3 = new QTableWidgetItem("Column 3");
testTable->setHorizontalHeaderItem(2, itemC3);
QTableWidgetItem *itemC4 = new QTableWidgetItem("Column 4");
testTable->setHorizontalHeaderItem(3, itemC4);
testTable->setColumnWidth(0,100);
testTable->setColumnWidth(1,100);
testTable->setColumnWidth(2,100);
testTable->setColumnWidth(3,100);
testTable->horizontalHeader()->setSectionResizeMode(QHeaderView::Fixed);
testTable->setEditTriggers(QAbstractItemView::DoubleClicked | QAbstractItemView::AnyKeyPressed);
testTable->setProperty("showDropIndicator", QVariant(false));
testTable->setDragDropOverwriteMode(false);
testTable->setSelectionMode(QAbstractItemView::SingleSelection);
testTable->setSelectionBehavior(QAbstractItemView::SelectRows);
testTable->setShowGrid(true);
testTable->setGridStyle(Qt::SolidLine);
testTable->setSortingEnabled(false);
testTable->setCornerButtonEnabled(false);
testTable->horizontalHeader()->setHighlightSections(false);
testTable->horizontalHeader()->setProperty("showSortIndicator", QVariant(false));
testTable->verticalHeader()->setVisible(false);
testTable->setAlternatingRowColors(true);
button = new QPushButton("Group");
connect(button, SIGNAL(clicked(bool)), this, SLOT(buttonClicked(bool)));
mainLayout->addWidget(testTable);
mainLayout->addWidget(button);
this->setLayout(mainLayout);
}
void addRow(QString c1, QString c2, QString c3, QString c4)
{
int row = testTable->rowCount();
testTable->insertRow(row);
QTableWidgetItem *c1Item = new QTableWidgetItem(c1);
QTableWidgetItem *c2Item = new QTableWidgetItem(c2);
QTableWidgetItem *c3Item = new QTableWidgetItem(c3);
QTableWidgetItem *c4Item = new QTableWidgetItem(c4);
c1Item->setTextAlignment(Qt::AlignLeft | Qt::AlignVCenter);
c2Item->setTextAlignment(Qt::AlignLeft | Qt::AlignVCenter);
c3Item->setTextAlignment(Qt::AlignLeft | Qt::AlignVCenter);
c4Item->setTextAlignment(Qt::AlignLeft | Qt::AlignVCenter);
testTable->setItem(row, 0, c1Item);
testTable->setItem(row, 1, c2Item);
testTable->setItem(row, 2, c3Item);
testTable->setItem(row, 3, c4Item);
}
void updateCell(int rowNumber, int columnNumber, QString textString)
{
if(rowNumber>=testTable->rowCount() || columnNumber>=testTable->columnCount()) return;
testTable->item(rowNumber, columnNumber)->setText(textString);
}
void groupCells(int rowNumber, int columnNumber, int rowSpan, int columnSpan)
{
if(rowNumber>=testTable->rowCount() || columnNumber>=testTable->columnCount()) return;
testTable->setSpan(rowNumber,columnNumber,rowSpan,columnSpan);
}
void ungroupCells(int rowNumber, int columnNumber)
{
if(rowNumber>=testTable->rowCount() || columnNumber>=testTable->columnCount()) return;
testTable->setSpan(rowNumber,columnNumber,1,1);
}
public slots:
void buttonClicked(bool event)
{
Q_UNUSED(event)
if(button->text()=="Group")
{
groupCells(1,1,2,1);
button->setText("Ungroup");
}
else
{
ungroupCells(1,1);
button->setText("Group");
}
}
};
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
TestWidget wid;
wid.addRow("0,0", "0,1", "0,2", "0,3");
wid.addRow("1,0", "1,1", "1,2", "1,3");
wid.addRow("2,0", "2,1", "2,2", "2,3");
wid.addRow("3,0", "3,1", "3,2", "3,3");
wid.show();
return a.exec();
}
#include "main.moc"
Project file shall be downloaded from github