I want to create grid layout with text items and I want this layout to be updated after added text to text item, but this not work.
Here is fragment of my source code:
MyItem::MyItem(const QString &text, QGraphicsLayout* layout, QGraphicsItem *parent):
QGraphicsLayoutItem(),
QGraphicsTextItem(parent),
mLayout(layout)
{
setGraphicsItem(this);
setHtml(text);
setFlag(QGraphicsItem::ItemIsSelectable, true);
}
void MyItem::setGeometry(const QRectF &geom)
{
prepareGeometryChange();
QGraphicsLayoutItem::setGeometry(geom);
setPos(geom.topLeft());
}
QSizeF MyItem::sizeHint(Qt::SizeHint which, const QSizeF &constraint) const
{
return boundingRect().size();
}
void MyItem::mousePressEvent(QGraphicsSceneMouseEvent *event)
{
if (event->button() == Qt::LeftButton && textInteractionFlags() == Qt::NoTextInteraction) {
setTextInteractionFlags(Qt::TextEditorInteraction);
}
QGraphicsTextItem::mousePressEvent(event);
}
void MyItem::focusOutEvent(QFocusEvent *event)
{
setTextInteractionFlags(Qt::NoTextInteraction);
auto cursor = textCursor();
cursor.clearSelection();
setTextCursor(cursor);
QGraphicsTextItem::focusOutEvent(event);
}
void MyItem::keyPressEvent(QKeyEvent *event)
{
QGraphicsTextItem::keyPressEvent(event);
qDebug() << boundingRect();
updateGeometry();
mLayout->activate();
}
Result is that when i add text to text item and his width is growing the next cell is not moving to make a place for first cell:
MainWindow::MainWindow(QWidget *parent) :
QMainWindow(parent),
ui(new Ui::MainWindow)
{
ui->setupUi(this);
auto scene = new QGraphicsScene;
auto layout = new QGraphicsGridLayout;
auto t1 = new MyItem("cell1", layout);
layout->addItem(t1, 0, 0);
auto t2 = new MyItem("cell2", layout);
layout->addItem(t2, 0, 1);
auto container = new QGraphicsWidget;
container->setLayout(layout);
scene->addItem(container);
auto view = new QGraphicsView(scene);
setCentralWidget(view);
resize(800, 600);
}
Adding mLayout->updateGeometry(); in void MyItem::keyPressEvent(QKeyEvent *event) method solved my problem.
Related
I read a lot of posts/threads but I can't get it to work.
I'd like to fit every Image to a GraphicsView regardless if it is smaller or bigger then the view.
What's wrong?
void frmMain::on_btLoadImage_clicked()
{
QGraphicsScene *scene;
QPixmap image;
QString imgPath = "O:/IMG_0001.JPG";
QRectF sceneRect = ui->imgMain->sceneRect();
image.load(imgPath);
image.scaled (sceneRect.width (),sceneRect.height (), Qt::KeepAspectRatio, Qt::SmoothTransformation);
scene = new QGraphicsScene(this);
scene->addPixmap(image);
scene->setSceneRect(sceneRect); //image.rect());
//ui->imgMain->fitInView (scene->itemsBoundingRect(), Qt::KeepAspectRatio); //ui->imgMain->width (), ui->imgMain->height ());
ui->imgMain->setScene(scene);
}
Here is a basic custom QGraphicsView implementation which displays one image and keeps it sized/scaled to fit the available viewport space. Note that the image needs to be rescaled every time the viewport size changes, which is why it is simplest to reimplement the QGraphicsView itself and change the scaling in resizeEvent(). Although it could be done inside a custom QGraphicsScene instead. (Or, really, a number of other ways depending on the exact needs.)
The same technique could be used to keep a QGraphicsWidget as the root item in the scene to always take up the full space. Then a layout could be used in the widget to keep children aligned/resized/positioned/etc.
#include <QGraphicsView>
#include <QGraphicsScene>
#include <QGraphicsPixmapItem>
class GrpahicsImageView : public QGraphicsView
{
Q_OBJECT
public:
using QGraphicsView::QGraphicsView;
public slots:
void setImage(const QString &imageFile)
{
if (m_imageFile != imageFile) {
m_imageFile = imageFile;
loadImage(viewport()->contentsRect().size());
}
}
void setImageScaleMode(int mode)
{
if (m_scaleMode != Qt::AspectRatioMode(mode)) {
m_scaleMode = Qt::AspectRatioMode(mode);
if (m_item)
loadImage(viewport()->contentsRect().size());
}
}
void loadImage(const QSize &size)
{
if (!scene())
return;
if (m_imageFile.isEmpty()) {
// remove existing image, if any
removeItem();
return;
}
// Load image at original size
QPixmap pm(m_imageFile);
if (pm.isNull()) {
// file not found/other error
removeItem();
return;
}
// Resize the image here.
pm = pm.scaled(size, m_scaleMode, Qt::SmoothTransformation);
if (createItem())
m_item->setPixmap(pm);
}
protected:
void resizeEvent(QResizeEvent *e) override
{
QGraphicsView::resizeEvent(e);
if (!scene())
return;
// Set scene size to fill the available viewport size;
const QRect sceneRect(viewport()->contentsRect());
scene()->setSceneRect(sceneRect);
// Keep the root item sized to fill the viewport and scene;
if (m_item)
loadImage(sceneRect.size());
}
private:
bool createItem() {
if (m_item)
return true;
if (!m_item && scene()) {
m_item = new QGraphicsPixmapItem();
scene()->addItem(m_item);
return true;
}
return false;
}
void removeItem()
{
if (m_item) {
if (scene())
scene()->removeItem(m_item);
delete m_item;
m_item = nullptr;
}
}
Qt::AspectRatioMode m_scaleMode = Qt::KeepAspectRatio;
QString m_imageFile;
QGraphicsPixmapItem *m_item = nullptr;
};
Usage example:
#include <QApplication>
#include <QtWidgets>
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
QDialog d;
d.setLayout(new QVBoxLayout);
d.resize(350, 350);
GrpahicsImageView *view = new GrpahicsImageView(new QGraphicsScene, &d);
QComboBox *imgCb = new QComboBox(&d);
imgCb->addItems({
"./so-logo.png",
"./se-logo.png",
"./su-logo.png"
});
QComboBox *scaleCb = new QComboBox(&d);
scaleCb->addItems({
"IgnoreAspectRatio",
"KeepAspectRatio",
"KeepAspectRatioByExpanding"
});
QHBoxLayout *cbLayout = new QHBoxLayout;
cbLayout->setSpacing(9);
cbLayout->addWidget(imgCb);
cbLayout->addWidget(scaleCb);
d.layout()->addItem(cbLayout);
d.layout()->addWidget(view);
QObject::connect(imgCb, QOverload<const QString &>::of(&QComboBox::currentIndexChanged), view, &GrpahicsImageView::setImage);
QObject::connect(scaleCb, QOverload<int>::of(&QComboBox::currentIndexChanged), view, &GrpahicsImageView::setImageScaleMode);
view->setImageScaleMode(scaleCb->currentIndex());
view->setImage(imgCb->currentText());
return d.exec();
}
https://cdn.sstatic.net/Sites/stackoverflow/company/img/logos/so/so-logo.png
https://cdn.sstatic.net/Sites/stackoverflow/company/img/logos/so/se-logo.png
https://cdn.sstatic.net/Sites/stackoverflow/company/img/logos/so/su-logo.png
The QGraphicsObject in green rectangle, it is the parent of the QGraphicsObject in red rectangle.
childInRed->setParentItem(this);
When I drag the parent object in green rect and move it fast, the background of the child object in red rect is not repainted correctly.
I know I can use update in the parent's mouseMoveEvent force the child to repaint. But this is not good, because I don't need to repaint the parent at all.
#include "asdf.h"
#include <QtWidgets/QGraphicsScene>
#include <QtWidgets/QGraphicsView>
#include <QtWidgets>
class CTestGraphicsObject : public QGraphicsObject
{
public:
QColor m_c;
CTestGraphicsObject(QColor c)
: QGraphicsObject(NULL)
, m_c(c)
{
setFlag(QGraphicsItem::ItemIsMovable, true);
setFlag(QGraphicsItem::ItemIsFocusable, true);
setFlag(QGraphicsItem::ItemIsSelectable, true);
auto effect = new QGraphicsDropShadowEffect;
effect->setOffset(4, 4);
effect->setBlurRadius(20);
setGraphicsEffect(effect);
}
virtual QRectF boundingRect() const override
{
auto rc = QRectF(0, 0, 100, 100);
return rc;
}
virtual void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) override
{
painter->setPen(QPen(m_c));
painter->drawRect(this->boundingRect());
}
};
asdf::asdf(QWidget *parent)
: QMainWindow(parent)
{
ui.setupUi(this);
auto s = new QGraphicsScene(this);
auto v = new QGraphicsView;
v->setScene(s);
CTestGraphicsObject* pParent = new CTestGraphicsObject(Qt::green);
CTestGraphicsObject* pChild = new CTestGraphicsObject(Qt::red);
pChild->setParentItem(pParent);
pChild->setPos(0, 100);
s->addItem(pParent);
s->addItem(pChild);
QVBoxLayout* l = new QVBoxLayout(this->centralWidget());
l->addWidget(v);
}
asdf::~asdf()
{
}
The QGraphicsDropShadowEffect causes this problem, It seems I'm not using it in right way.
According to the Qt documentation, the scene uses the bounding rect and region to define the area to repainted when an item is updated (moved in your case).
If you child is outside its parent, the scene will miss some part when repainting...
Extend the bouding rect/region to cover its children.
If you do something like that, it will work:
virtual QRectF boundingRect() const override
{
if (this->childrenBoundingRect().isEmpty()) // No children
return QRectF(0, 0, 100, 100);
return QRectF(0, 0, 100, 100).united(this->childrenBoundingRect());
}
virtual void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) override
{
painter->setPen(QPen(m_c));
painter->drawRect(QRectF(0, 0, 100, 100));
}
I have to store many items in a QMenu. If there is too many items QMenu wraps them ans begins a new column, but it happens only if these items can not fit into screen height.
I'd like to have QMenu which wraps items when the menu height reaches, for example, parent widget's height or any other custom value.
I wasn't able to find any properties in QMenu for achieving this. Setting maximumHeight gave no result. After digging into QMenu sources I found that the "wrapping logic" works based on popupGeometry method result. But popupGeometry uses screen size, and it is private so I don't know a way to change it.
As I didn't find the answer, I had to implement this control by myself.
It is a popup widget, using QToolButton as an owner. It can arrange items in a grid depending on item's height and required menu height.
class myLabel:public QLabel
{
Q_OBJECT
// QObject interface
public:
myLabel(QWidget* parent=0):QLabel(parent){}
bool event(QEvent *e)
{
if(e->type()==QEvent::MouseButtonPress)
emit clicked();
return QLabel::event(e);
}
void setAction(QAction *a)
{
setText(a->text());
_action=a;
}
QAction* action()
{
return _action;
}
signals:
void clicked();
private:
QAction* _action;
};
class myMenu: public QWidget
{
Q_OBJECT
public:
myMenu(QWidget* owner,QWidget* parent=0):QWidget(parent){
this->setWindowFlags(Qt::Popup);
l = new QGridLayout(this);
l->setContentsMargins(QMargins(3,3,3,3));
_owner=owner;
QString style="QLabel:hover{background-color: white;} ";
setStyleSheet(style);
}
void addAction(QAction*a){_actions.append(a);}
QVector<QAction*> actions(){return _actions;}
void setItemHeight(int val){_itemHeight=val;}
void setHeight(int val){_height=val;}
private:
QVector<QAction*> _actions;
QGridLayout *l ;
QWidget*_owner;
int _itemHeight=30;
int _height=200;
private slots:
void popup()
{
clear();
//move popup under toolbutton
QPoint p = _owner->geometry().bottomLeft();
p.setY(p.y()+1);
this->move(_owner->parentWidget()->mapToGlobal(p));
//calculate rows count
int rows = _height/_itemHeight;
//calculate cols count
int cols = _actions.size()/rows;
int d = _actions.size()%rows;
if(d>0)
cols++;
for(int i=0;i<rows;i++)
for(int j=0;j<cols;j++)
{
int index = i+j*rows;
if(index<_actions.size())
{
myLabel *lb = new myLabel(this);
connect(lb,SIGNAL(clicked()),this,SLOT(onClick()));
lb->setFixedHeight(_itemHeight);
lb->setAction(_actions[index]);
l->addWidget(lb,i,j);
}
}
this->repaint();
this->show();
}
void clear()
{
while(l->itemAt(0)!=NULL)
{
QLayoutItem* i = l->takeAt(0);
if(i->widget())
delete i->widget();
if(i->layout())
delete i->layout();
delete i;
}
}
void onClick()
{
myLabel *g = qobject_cast<myLabel*>(sender());
g->action()->trigger();
close();
}
// QWidget interface
protected:
void closeEvent(QCloseEvent *)
{
qobject_cast<QToolButton*>(_owner)->setDown(false);
}
signals:
void closed();
};
There's also an example showing how to create and fill myMenu and how to receive a selected action.
MainWindow::MainWindow(QWidget *parent) :
QMainWindow(parent),
ui(new Ui::MainWindow)
{
ui->setupUi(this);
//set-up myMenu, btMyMenu is a QToolButton
myMenu *mMenu = new myMenu(ui->btMyMenu,this);
connect(ui->btMyMenu,SIGNAL(pressed()),mMenu,SLOT(popup()));
for(int i=0;i<20;i++)
{
QAction *a = new QAction("Action "+QString::number(i),this);
connect(a,SIGNAL(triggered(bool)),this,SLOT(onActSelected()));
mMenu->addAction(a);
}
//mMenu can be customized
mMenu->setHeight(100);
mMenu->setItemHeight(50);
}
void MainWindow::onActSelected()
{
QAction *a = qobject_cast<QAction*>(sender());
ui->btMyMenu->setText(a->text());
}
Any comments about how to improve this solution are appreciated!
There are N columns with manual resizing width from left. Other columns widths are resizing only when columns with manual resizing are resizing. I need to prevent cursor icon changing when cursor is under borders of sections without manual resizing.
What did I try to do. But this is work not very good.
table_header_border.zip
#include "mainwindow.h"
#include "ui_mainwindow.h"
const int N = 2;
//==============================================================================
int nWidth(const QTableWidget *table)
{
int ret = 0;
if (table->verticalHeader()->isVisible())
{
ret += table->verticalHeader()->width();
}
for (int i = 0; i < N; i++)
{
ret += table->columnWidth(i);
}
return ret;
}
bool isInNColumns(const QTableWidget *table)
{
QPoint cursorPos = table->mapFromGlobal(QCursor::pos());
return cursorPos.x() < nWidth(table) + 5;
}
//==============================================================================
class MyHorizontalHeader : public QHeaderView
{
public:
MyHorizontalHeader(QWidget *parent=0) : QHeaderView(Qt::Horizontal, parent)
{
setMouseTracking(true);
}
private slots:
void mouseMoveEvent(QMouseEvent *event)
{
QHeaderView::mouseMoveEvent(event);
if (cursor().shape() == Qt::SplitHCursor)
{
QTableWidget *table = dynamic_cast<QTableWidget *>(parent());
if (table != NULL && !isInNColumns(table))
{
qApp->setOverrideCursor(QCursor(Qt::ArrowCursor));
return;
}
qApp->setOverrideCursor(QCursor(Qt::SplitHCursor));
}
}
};
//==============================================================================
MainWindow::MainWindow(QWidget *parent) :
QMainWindow(parent),
ui(new Ui::MainWindow)
{
ui->setupUi(this);
ui->tableWidget->setHorizontalHeader(new MyHorizontalHeader(this));
}
MainWindow::~MainWindow()
{
delete ui;
}
You should use QEvent::Enter and QEvent::Leave for better result.
Use next event filter:
In header:
protected:
bool eventFilter(QObject *obj, QEvent *event);
In constructor:
qApp->installEventFilter(this);
EventFilter:
bool MainWindow::eventFilter(QObject *obj, QEvent *event)
{
if (obj == ui->tableWidget && event->type() == QEvent::Enter)
{
qApp->setOverrideCursor(QCursor(Qt::ArrowCursor));
//or
qApp->setOverrideCursor(ui->tableWidget->cursor());
qDebug() << "added";
}
if (obj == ui->tableWidget && event->type() == QEvent::Leave)
{
qApp->restoreOverrideCursor();
}
return QObject::eventFilter(obj, event);
}
horizontalHeader()->setSectionResizeMode(i, QHeaderView::Fixed);
i need to use checkbox with text, like this "Check all":
in header of QStanndardItemModel. I tried like this
QStandardItem* item0 = new QStandardItem("some text");
item0->setCheckable(true);
item0->setCheckState(Qt::Checked);
item0->setText("some text");
_model->setHorizontalHeaderItem(1, item0);
This way only works for items not for header, i mean for items if i use
_model->setItem(new QStandardItem(some_item);
I heard about writing my own class which inherit QHeaderView but i dont know if this can help in my problem. I would ask if there is a easy way to achieve this?
Regards
i did something like this:
QStandardItemModel *model = new QStandardItemModel(this);
ui->tableView->setModel(model);
QtCheckHeaderView *header = new QtCheckHeaderView(Qt::Horizontal, ui->tableView);
QtCheckHeaderView *vheader = new QtCheckHeaderView(Qt::Vertical, ui->tableView);
ui->tableView->setHorizontalHeader(header);
ui->tableView->setVerticalHeader(vheader);
QStandardItem *root = model->invisibleRootItem();
QList<QList<QStandardItem *> > items;
for (int i = 0; i < 10; ++i)
{
QList<QStandardItem *> res;
for (int j = 0; j < 1000; ++j)
{
res.append(new QStandardItem(QString("%1;%2").arg(j).arg(i)));
vheader->addCheckable(j);
}
items.append(res);
root->appendColumn(res);
header->addCheckable(i);
}
this works great. Draws checkbox with text in vertical and horizontal header.
But this works great only if i put this code to MainWindow constructor. If i put this code to for example pushbutton function this wont work. Datas are fine but headers are invisible.
ui->tableView->repaint();
wont work. Someone maybe knows the answer why this is happening and/or how to solve this?
thanks for answer
Qt models don't support item flags for headers. Setting item flags on items that are used as headers has no effect. QHeaderView doesn't support drawing checkboxes. I'm afraid subclassing QHeaderView is the simpliest way.
I wrote an example of how it can be implemented based on this FAQ page. This class assumes that you use QStandardItemModel and uses flags of its header items. If someone would want to use other model class, subclassing QAbstractItemModel and implementing missing interface and changing headerview implementation would be needed.
class MyHeader : public QHeaderView
{
public:
MyHeader(Qt::Orientation orientation, QWidget * parent = 0);
protected:
QStandardItemModel* standard_model;
virtual void paintSection(QPainter *painter, const QRect &rect, int logicalIndex) const;
virtual void mousePressEvent(QMouseEvent *event);
virtual void setModel(QAbstractItemModel* model);
};
MyHeader::MyHeader(Qt::Orientation orientation, QWidget *parent) : QHeaderView(orientation, parent)
{
standard_model = 0;
}
void MyHeader::paintSection(QPainter *painter, const QRect &rect, int logical_index) const {
painter->save();
QHeaderView::paintSection(painter, rect, logical_index);
painter->restore();
if (standard_model && orientation() == Qt::Horizontal) {
QStandardItem* item = standard_model->horizontalHeaderItem(logical_index);
if (item && item->isCheckable()) {
int offset = (height() - style()->pixelMetric(QStyle::PM_IndicatorHeight))/2;
int pos = sectionViewportPosition(logical_index);
QStyleOptionButton option;
option.rect = QRect(offset + pos, offset,
style()->pixelMetric(QStyle::PM_IndicatorWidth),
style()->pixelMetric(QStyle::PM_IndicatorHeight));
if (item->checkState() == Qt::Checked) {
option.state = QStyle::State_On;
} else {
option.state = QStyle::State_Off;
}
option.state |= QStyle::State_Enabled;
style()->drawPrimitive(QStyle::PE_IndicatorCheckBox, &option, painter);
}
}
}
void MyHeader::mousePressEvent(QMouseEvent *event) {
int offset = (height() - style()->pixelMetric(QStyle::PM_IndicatorHeight))/2;
if (standard_model && orientation() == Qt::Horizontal) {
for(int logical_index = 0; logical_index < count(); logical_index++) {
int pos = sectionViewportPosition(logical_index);
QRect rect(offset + pos, offset,
style()->pixelMetric(QStyle::PM_IndicatorWidth),
style()->pixelMetric(QStyle::PM_IndicatorHeight));
if (rect.contains(event->pos())) {
QStandardItem* item = standard_model->horizontalHeaderItem(logical_index);
if (item && item->isCheckable()) {
item->setCheckState(item->checkState() == Qt::Checked ? Qt::Unchecked : Qt::Checked);
return;
}
}
}
}
QHeaderView::mousePressEvent(event);
}
void MyHeader::setModel(QAbstractItemModel *model) {
QHeaderView::setModel(model);
standard_model = qobject_cast<QStandardItemModel*>(model);
}
//usage
QTableView view;
QStandardItemModel model;
model.setColumnCount(5);
QStandardItem* item0 = new QStandardItem("some text");
item0->setCheckable(true);
item0->setCheckState(Qt::Checked);
item0->setText("some text");
model.setHorizontalHeaderItem(0, item0);
view.setModel(&model);
MyHeader *myHeader = new MyHeader(Qt::Horizontal, &view);
view.setHorizontalHeader(myHeader);
view.show();