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
In my QGraphicsView, I have many QGraphicsItem. I want to search specific QGraphicsItem out of all the items present in view and highlight the matched item. For highlighting item, I am trying to use paintEvent.
So I am calling paintEvent, but not understanding how to heighlight border of the matched object ?
Should I need co-ordinates of that matched object ?
I tried like this:
foreach(QGraphicsItem* currentItem, _scene->items())
{
pEvent = false;
QGraphicsRectItem* rItem = qgraphicsitem_cast<QGraphicsRectItem*>(currentItem);
if(rItem)
{
// some logic to get i->Name()
QString name1 = i->Name();
QString name2 = "reN"; // I want to find reN named item in view
if(name1 == name2)
{
pEvent = true;
qDebug()<<"Object Found ";
this->repaint();
break;
}
}
}
void myClass::paintEvent(QPaintEvent *event) {
Q_UNUSED(event);
qDebug()<<"In paint event ";
if(pEvent)
{
QPainter qp(this);
drawBody(&qp);
}
}
void myClass::drawBody(QPainter *qp) {
Q_UNUSED(qp);
// want logic for heighlighting border of the item
}
I understand from your problem that you are overridng QGraphicsView in MyClass since paintEnent is defined QWidgets classes.
To solve your problem, if I understand correctly. It's necessary to save the QGraphicsRectItem coordinates:
QRectF rectF = item.boundingRect();
then use the following in the function drawBody:
qp.save();
const float width = 5;
QPen pen;
pen.setWidth(width );
pen.setColor("green");
painter->drawRect(rectF.x(), rectF().y(),
rectF().width(), rectF().height());
qp.restore();
I would like to know how many pixels need this items in QComboBox:
red - checkbox
green - distance between end of checkbox and beggining of the text
blue - distance between end of border and beggining of the checkbox
In doc of QStyle I find two methods:
subElementRect()
pixelMetric()
I think I have to use them, but I don't know, which args I need to use.
It depends on the style.
QCommonStyle for example draws the ComboBox's label like this:
if (const QStyleOptionComboBox *cb = qstyleoption_cast<const QStyleOptionComboBox *>(opt)) {
QRect editRect = proxy()->subControlRect(CC_ComboBox, cb, SC_ComboBoxEditField, widget);
p->save();
p->setClipRect(editRect);
if (!cb->currentIcon.isNull()) {
QIcon::Mode mode = cb->state & State_Enabled ? QIcon::Normal
: QIcon::Disabled;
QPixmap pixmap = cb->currentIcon.pixmap(qt_getWindow(widget), cb->iconSize, mode);
QRect iconRect(editRect);
iconRect.setWidth(cb->iconSize.width() + 4);
iconRect = alignedRect(cb->direction,
Qt::AlignLeft | Qt::AlignVCenter,
iconRect.size(), editRect);
if (cb->editable)
p->fillRect(iconRect, opt->palette.brush(QPalette::Base));
proxy()->drawItemPixmap(p, iconRect, Qt::AlignCenter, pixmap);
if (cb->direction == Qt::RightToLeft)
editRect.translate(-4 - cb->iconSize.width(), 0);
else
editRect.translate(cb->iconSize.width() + 4, 0);
}
if (!cb->currentText.isEmpty() && !cb->editable) {
proxy()->drawItemText(p, editRect.adjusted(1, 0, -1, 0),
visualAlignment(cb->direction, Qt::AlignLeft | Qt::AlignVCenter),
cb->palette, cb->state & State_Enabled, cb->currentText);
}
p->restore();
}
This means, that the actual size of the icon's rectangle can be determined by subclassing QComboBox (to access its protected initStyleOption method) and creating a new public getIconRect method in the following way:
QRect ComboBox::getIconRect()
{
QStyleOptionComboBox opt;
initStyleOption(&opt);
QRect rect(style()->subControlRect(QStyle::CC_ComboBox, &opt, QStyle::SC_ComboBoxEditField));
rect.setWidth(opt.iconSize.width() + 4);
return rect;
}
Then in MainWindow for example create a ComboBox and call its getIconSize method like this:
auto *cmbBox = new ComboBox(this);
qDebug() << cmbBox->getIconRect();
For me, on Windows 10, this gives:
QRect(3,3 20x14)
There could be other ways, but if you insist on using a method similar to QStyle::subControlRect, those are the correct arguments;
I'm trying to get a custom scrolling widget in QT, and I'm getting redraw errors on scroll. Alt-tab or other redrawing events redraw correctly.
I'm basing it on the example at http://doc.qt.io/qt-5/qtwidgets-widgets-charactermap-example.html
repeatingwidget.cpp (excerpt):
QSize RepeatingWidget::sizeHint() const {
return QSize(500, itemHeight * displayItems.size() + 1);
}
void RepeatingWidget::paintEvent(QPaintEvent *event) {
QPainter painter(this);
painter.fillRect(event->rect(), QBrush(Qt::white));
painter.setFont(displayFont);
QRect itemRect = event->rect();
int top = itemRect.top();
QFontMetrics fontMetrics(*displayFont);
for (auto item : displayItems) {
painter.setPen(QPen(Qt::gray));
painter.drawRect(itemRect.left(), top, itemRect.right(), itemHeight);
painter.setPen(QPen(Qt::black));
painter.drawText(8, 4 + top + fontMetrics.ascent(), item.name);
top += itemHeight;
}
}
mainwindow.cpp (excerpt):
MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent)
{
QMenu *filemenu = menuBar()->addMenu(tr("File"));
filemenu->addAction(tr("Quit"), this, &QWidget::close);
auto *centralWidget = new QWidget;
scrollArea = new QScrollArea;
repeatingArea = new RepeatingWidget();
scrollArea->setWidget(repeatingArea);
auto *centralLayout = new QVBoxLayout;
centralLayout->addWidget(scrollArea, 1);
centralWidget->setLayout(centralLayout);
setCentralWidget(centralWidget);
setWindowTitle(tr("Widget Test"));
}
This seems to match the example, but I'm getting redraw errors that don't happen in charmap.
I've tried setGeometry, setWidgetResizable, and different size policies, but I'm still getting these redraw errors.
After scrolling:
I don't know what I'm doing wrong because it's largely identical in important ways to the example code from the charmap.
This is the full code: https://gist.github.com/jonasbuckner/2acc1a960e457946ce4756199de3fb57
QPaintEvent is a method that allows you to make an intelligent painting, that is, to paint where necessary, thus saving resources, for example it gives us the information of the rectangle that must be painted through event->rect(), with this we can calculate the items that have to be painted since others will be hidden and therefore it is not necessary to paint them:
void RepeatingWidget::paintEvent(QPaintEvent *event)
{
QPainter painter(this);
painter.fillRect(event->rect(), QBrush(Qt::white));
painter.setFont(displayFont);
QFontMetrics fontMetrics(displayFont);
int i = std::max(event->rect().top()/itemHeight, 0);
int j = std::min(event->rect().bottom()/itemHeight+1, displayItems.size());
QRect itemRect(0, i*itemHeight, width(), itemHeight);
for(; i < j; i++){
painter.setPen(QPen(Qt::gray));
painter.drawRect(itemRect);
painter.setPen(QPen(Qt::black));
painter.drawText(8, 4 + itemRect.top() + fontMetrics.ascent(), displayItems[i].name);
itemRect.translate(0, itemHeight);
}
}
Your original code didn't work because you were drawing all of the items, but using the event->rect, which may only be part of the RepeatingWidget.
Sometimes it is not easy to calculate which items are in the event->rect as #eyllanesc shows. In these cases, just use clientRect instead - Qt will clip the drawing for you.
I am trying to make a QTreeWidget such that each row contains a series of comboboxes. Depending on how the user interacts with the comboboxes I would like certain comboboxes to becomes line edits and some to become buttons.
It was suggested here that a QStackedWidget would serve my needs and its done a pretty good job except now I need a way to alter the QStackedWidget that is next to the one that contains the combobox sending me an indexChanged signal. (Basically I want to change the neighboring QStackWidgets index)
I thought that I would be able to simply store the QStackWidget in the childItem using setData and then retrieve it inside the indexChanged slot but for some reason it appears the QStackWidget is not set to the childItems data.
Any help is appreciated.
This is where I orginally setup my QTreeWidget and its Items
QTreeWidgetItem *childItem = new QTreeWidgetItem(itemParent);
QVariant itemParentVariant,widgetParentVarient;
widgetParentVarient.setValue(widgetParent);
itemParentVariant.setValue(itemParent);
QList<QVariant> stackWidgetList;
uint cycleSetup;
for(cycleSetup = 0;cycleSetup < methodBlocks.at(rowType).size()+2;cycleSetup++)
{
QComboBox *itemComboBox = new QComboBox;
itemComboBox->setProperty("rowType", rowType);
itemComboBox->setProperty("row", 0);
itemComboBox->setProperty("column",cycleSetup);
itemComboBox->setProperty("widgetParent",widgetParentVarient);
itemComboBox->setProperty("itemParent",itemParentVariant);
itemComboBox->addItems(methodBlocks.at(0).at(cycleSetup));
QObject::connect(itemComboBox, SIGNAL(currentIndexChanged(const QString&)), this, SLOT(OnComboIndexChanged(const QString&)));
QLineEdit *itemLineEdit = new QLineEdit;
QPushButton *itemButton = new QPushButton;
itemButton->setText("Reset");
QComboBox *timeComboBox = new QComboBox;
timeComboBox->setProperty("rowType", rowType);
timeComboBox->setProperty("row", 0);
timeComboBox->setProperty("column",cycleSetup);
timeComboBox->setProperty("widgetParent",widgetParentVarient);
timeComboBox->setProperty("itemParent",itemParentVariant);
timeComboBox->addItems(QString("Seconds;MilliSeconds;Reset").split(";"));
QStackedWidget *masterItemWidget = new QStackedWidget;
masterItemWidget->addWidget(itemLineEdit);
masterItemWidget->addWidget(itemComboBox);
masterItemWidget->addWidget(itemButton);
masterItemWidget->addWidget(timeComboBox);
masterItemWidget->setCurrentIndex(1);
QVariant stackParent;
stackParent.setValue(masterItemWidget);
itemComboBox->setProperty("stackParent",stackParent);
childItem->setData(0,Qt::UserRole,stackParent);
stackWidgetList.push_back(stackParent);
widgetParent->setItemWidget(childItem,cycleSetup,masterItemWidget);
itemParent->addChild(childItem);
}
And this is inside the slot where I am trying to retrieve the data (The QStackWidget)
QStackedWidget *itemMaster = combo->property("stackParent").value<QStackedWidget*>(); //this works
itemMaster->setCurrentIndex(0);
QTreeWidget *widgetParent = combo->property("widgetParent").value<QTreeWidget*>();
QTreeWidgetItem *parentItem = combo->property("itemParent").value<QTreeWidgetItem*>();
QTreeWidgetItem *childItem = new QTreeWidgetItem(parentItem);
QList<QVariant> stackList = childItem->data(0,Qt::UserRole).value<QList<QVariant>>(); //this doesnt
QStackedWidget *itemsibMaster = childItem->data(0,Qt::UserRole).value<QStackedWidget*>(); //neither does this
itemsibMaster->setCurrentIndex(2);
EDIT:
I've tried to set the data like this
QFrame *stackFrame = new QFrame;
QStackedWidget *masterItemWidget = new QStackedWidget(stackFrame);
masterItemWidget->addWidget(itemLineEdit);
masterItemWidget->addWidget(itemComboBox);
masterItemWidget->addWidget(itemButton);
masterItemWidget->addWidget(timeComboBox);
masterItemWidget->setCurrentIndex(1);
QVariant stackParent;
stackParent.setValue(masterItemWidget);
itemComboBox->setProperty("stackParent",stackParent);
QVariant frameVariant;
frameVariant.setValue(stackFrame);
childItem->setData(0,Qt::UserRole,frameVariant);
stackWidgetList.push_back(stackParent);
widgetParent->setItemWidget(childItem,cycleSetup,masterItemWidget);
itemParent->addChild(childItem);
And retrieve it like this
QStackedWidget *itemMaster = combo->property("stackParent").value<QStackedWidget*>();
itemMaster->setCurrentIndex(0);
QTreeWidget *widgetParent = combo->property("widgetParent").value<QTreeWidget*>();
QTreeWidgetItem *parentItem = combo->property("itemParent").value<QTreeWidgetItem*>();
QTreeWidgetItem *childItem = new QTreeWidgetItem(parentItem);
QFrame *frameObject = childItem->data(0,Qt::UserRole).value<QFrame*>();
//QList<QVariant> stackList = childItem->data(0,Qt::UserRole).value<QList<QVariant>>();
QStackedWidget *itemFrameMaster = frameObject->findChild<QStackedWidget*>();
if(itemFrameMaster)
{
qDebug() << "itemFrame Exists";
}
else
{
qDebug() << "itemFrame is NULL";//It goes to here
}
So I'm still unable to get the desired functionality.
Check if it works.
As stacked widget QStackedWidget derived from QFrame, Create a Frame object and add your stacked widget to it. Set the frame to your child item.
QFrame *stackFrame = new QFrame(Parent);
QStackedWidget *masterItemWidget = new QStackedWidget(stackFrame);
While querying first query for the frame and get the stacked widget child from it.
QStackedWidget *stackedWidget = FrameObject->findChild<QStackedWidget *>();
Alright so I managed to get the functionality that I was after, so in case anyone else is trying to do this here's the fix.
Essentially everything I had done before was correct except for one error.
When retrieving the data stored in the comboboxes child I had originally gotten that child here.
QTreeWidgetItem *parentItem = combo->property("itemParent").value<QTreeWidgetItem*>();
QTreeWidgetItem *childItem = new QTreeWidgetItem(parentItem);
The issue was that since the parentItem had more than one child (I suppose in this case the minimum is two children) I was not referencing the correct child. This issue was solved by creating a custom combobox property which held the child rather than the parent (parent can be derived from the parent more accurately than visa versa).
Now where I populate the QTreeWidget with combobox items looks like this.
QTreeWidgetItem *childItem = new QTreeWidgetItem(itemParent);
QVariant childItemVariant,widgetParentVarient;
widgetParentVarient.setValue(widgetParent);
childItemVariant.setValue(childItem);
uint cycleSetup;
for(cycleSetup = 0;cycleSetup < methodBlocks.at(rowType).size();cycleSetup++)
{
if(cycleSetup < methodBlocks.at(rowType).size())
{
QComboBox *itemComboBox = new QComboBox;
itemComboBox->setProperty("rowType", rowType);
itemComboBox->setProperty("row", 0);
itemComboBox->setProperty("column",cycleSetup);
itemComboBox->setProperty("widgetParent",widgetParentVarient);
itemComboBox->setProperty("childItem",childItemVariant);
itemComboBox->addItems(methodBlocks.at(0).at(cycleSetup));
QObject::connect(itemComboBox, SIGNAL(currentIndexChanged(const QString&)), this, SLOT(OnComboIndexChanged(const QString&)));
QLineEdit *itemLineEdit = new QLineEdit;
QFrame *stackFrame = new QFrame;
QStackedWidget *masterItemWidget = new QStackedWidget(stackFrame);
masterItemWidget->addWidget(itemLineEdit);
masterItemWidget->addWidget(itemComboBox);
//masterItemWidget->addWidget(timeComboBox);
masterItemWidget->setCurrentIndex(1);
QVariant stackParent;
stackParent.setValue(masterItemWidget);
itemComboBox->setProperty("stackParent",stackParent);
itemComboBox->setProperty("cycleSetupIT",cycleSetup);
QVariant frameVariant;
frameVariant.setValue(stackFrame);
childItem->setData(cycleSetup,Qt::UserRole,stackParent);
widgetParent->setItemWidget(childItem,cycleSetup,masterItemWidget);
itemParent->addChild(childItem);
}
}
}
And the code in the slot where I get the data from the childItem (which holds the combobox) looks like this.
QTreeWidget *widgetParent = combo->property("widgetParent").value<QTreeWidget*>();
widgetParent->setColumnCount(6);
QTreeWidgetItem *childItem = combo->property("childItem").value<QTreeWidgetItem*>();
QTreeWidgetItem *parentItem = childItem->parent();
int rowType = combo->property("rowType").toInt();
int cycleIT = combo->property("cycleSetupIT").toInt();
int offset = 0;
QStackedWidget *itemFrameMaster = childItem->data(cycleIT+1,Qt::UserRole).value<QStackedWidget*>();
itemFrameMaster->setCurrentIndex(0);
Hope this helps. Also if anyone knows how to get the number of data items in a QStandardItem::data() that'd be great albeit not super critical.
Cheers!
I'm currently trying to build a Qt table by subclassing QTableView, QAbstractTableModel and QStyledItemDelegate to maintian some semblance of MVC archiectecture.
I'm using this simple example as a base to build on as I haven't gone near Qt table classes before:
http://qt-project.org/doc/qt-4.8/modelview.html
Anyway the table is mostly text columns but it also needs a single toggle button column and a single checkbox column.
I've noticed that the data method of the model can be used to implement a checkbox but I'm going to need a custom delegate for the button so I was going to use it for the checkbox also.
Anyway I'm unable to find any decent examples on the internet that create tables by using the QTableView object with a mixture of text, checkboxes and buttons. Can any of you good sirs point me in the right direction?
You don't need a custom delegate for having checkbox and toggle button in your tableview. You can simply make your item checkable and set it to your model like:
QStandardItem *item = new QStandardItem( true );
item->setCheckable(true);
item->setCheckState(Qt::Unchecked);
QStandardItemModel * model = new QStandardItemModel( 0, 2 );
model->setRowCount(1);
model->setItem(0, 0, item);
For a toggle button you can do like:
QPushButton * but = new QPushButton(this);
but->setCheckable(true);
but->setText("Toggle");
ui->tableView->setIndexWidget(model->item(0,1)->index(),but);
Based on the information Dmitry provided above I've implemented the following paint method in my delegate for rendering my button and checkbox. Obviously this needs an editorEvent() to do anything I can add this also if it proves useful.
void DDUTableDelegate::paint(QPainter* painter, const QStyleOptionViewItem& option,
const QModelIndex& index) const
{
int col = index.column();
if (col == VIEW_COLUMN) {
// Draw our checkbox indicator
bool value = index.data(Qt::EditRole).toBool();
QStyleOptionButton checkbox_indicator;
// Set our button state to enabled
checkbox_indicator.state |= QStyle::State_Enabled;
checkbox_indicator.state |= (value) ? QStyle::State_On : QStyle::State_Off;
// Get our deimensions
checkbox_indicator.rect = QApplication::style()->subElementRect( QStyle::SE_CheckBoxIndicator, &checkbox_indicator, NULL );
// Position our indicator
const int x = option.rect.center().x() - checkbox_indicator.rect.width() / 2;
const int y = option.rect.center().y() - checkbox_indicator.rect.height() / 2;
checkbox_indicator.rect.moveTo( x, y );
if (option.state & QStyle::State_Selected) {
painter->fillRect(option.rect, option.palette.highlight());
}
QApplication::style()->drawControl( QStyle::CE_CheckBox, &checkbox_indicator, painter );
}
else if (col == TEST_COLUMN) {
bool value = index.data(Qt::EditRole).toBool();
QStyleOptionButton button;
// Set our button to fill the entire cell contents
button.rect = option.rect;
// Set our button state to enabled
button.state |= QStyle::State_Enabled;
if (value) {
button.state |= QStyle::State_Sunken;
button.text = STOP_TEST;
}
else {
button.text = START_TEST;
}
if (option.state & QStyle::State_Selected) {
painter->fillRect(option.rect, option.palette.highlight());
}
QApplication::style()->drawControl(QStyle::CE_PushButton, &button, painter);
}
}