I have added a widget to a graphic scene QGraphicScene through a QGraphicsProxyWidget. To move it I have set QGraphicsRectitem as its parent. The widget is resized with the use of a sizegrip.
The first time I create an object I can enlarge it upto some dimension. The second time I can enlarge it less than the first one. The third time less than the second one and so on.
It seems to me that it behaves randomly. Why is this happening?
Here is the code:
void GraphicsView::dropEvent(QDropEvent *event)// subclass of QGraphicsView
{
if(event->mimeData()->text() == "Dial")
{
auto *dial= new Dial; // The widget
auto *handle = new QGraphicsRectItem(QRect(event->pos().x(),event->pos().y(), 120, 120)); // Created to move and select on scene
auto *proxy = new QGraphicsProxyWidget(handle); // Adding the widget through the proxy
dial->setGeometry(event->pos().x()+10,event->pos().y()+10, 100, 100);
proxy->setWidget(dial);
QSizeGrip * sizeGrip = new QSizeGrip(dial);
QHBoxLayout *layout = new QHBoxLayout(dial);
layout->setContentsMargins(0, 0, 0, 0);
layout->addWidget(sizeGrip, 0, Qt::AlignRight | Qt::AlignBottom);
handle->setPen(QPen(Qt::transparent));
handle->setBrush(Qt::gray);
handle->setFlags(QGraphicsItem::ItemIsMovable |
QGraphicsItem::ItemIsSelectable);
scene->addItem(handle); // adding to scene
connect(dial, &Dial::sizeChanged, [dial, handle](){ handle->setRect(dial->geometry().adjusted(-10, -10, 10, 10));});
} }
I cannot enlarge the widget more than that, what is shown in the image.
Your Dial can't be resized past the GraphicView's right (horizonally) and bottom (vertically) edges. If you make the scene big enough, say 2000x2000 (setSceneRect(2000, 2000);), scrollbars will appear. If you move the scrollbars manually, you will be able to enlarge your widgets more.
You could also experiment with automatic scrollbar movement by changing the lambda function like this:
connect(dial, &Dial::sizeChanged, [this, dial, handle](){
handle->setRect(dial->geometry().adjusted(-10, -10, 10, 10));
int dx = handle->rect().bottomRight().x() > viewport()->rect().bottomRight().x();
int dy = handle->rect().bottomRight().y() > viewport()->rect().bottomRight().y();
if (dx > 0) {
horizontalScrollBar()->setValue(horizontalScrollBar()->value() + dx);
}
if (dy > 0) {
verticalScrollBar()->setValue(verticalScrollBar()->value() + dy);
}
});
Please note, that although this code works, is is very cumbersome. However, it could give you an idea how to start.
Related
Given this example:
Suppose A and B are QWidgets
Is it possible to keep everything starting from B static when the QScrollArea widget is scrolled?
The QScrollArea occupies the entire GUI, B is inside it to let the vertical ScrollBar closer to the right border of the GUI, or it would look like this:
What I can do in this case?
It is possible. You have to make use of the scrollbar being a widget of its own.
Here is the method:
Change your window in Qt Designer to be: spacer, QScrollArea (that will contain widget A), spacer, widget B, spacer; everything is in a QGridLayout (or QHBoxLayout but please read until the end).It is because widget B is outside the scroll area that it will not move during scrolling.
In your widget constructor, after ui->setupUi(); reparent and move the vertical scrollbar of your QScrollArea with these lines of code:
scrollArea->verticalScrollBar()->setParent(w);
layout->addWidget(sa->verticalScrollBar()); //Adds the scrollbar to the right of the layout.
Note about the margins:Obviously, you can easily push the scrollbar to the very right of your window by setting the layout right margin to 0.
If you also want it to cover the entire height of your window, while keeping some space between the other widgets and the window's border, that is where a QHBoxLayout will not suffice and you need a QGridLayout instead, set its top and bottom margin to 0 and add spacers (fixed size) to obtain the same visual result.
The C++ code for such a window would be:
QWidget* widget = new QWidget();
QGridLayout* layout = new QGridLayout(widget);
layout->setSpacing(0);
layout->setContentsMargins(9, 0, 0, 0);
widget->setLayout(layout);
QScrollArea* scrollArea = new QScrollArea(widget);
scrollArea->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOn);
QWidget* widgetB = new QLabel("Widget B", widget);
//Creates the spacer that will "simulate" the top and bottom margins
QSpacerItem* topSpacer = new QSpacerItem(0, 9, QSizePolicy::Minimum, QSizePolicy::Fixed),
* bottomSpacer = new QSpacerItem(0, 9, QSizePolicy::Minimum, QSizePolicy::Fixed);
layout->addItem(topSpacer, 0, 0);
layout->addWidget(scrollArea, 1, 0);
layout->addItem(bottomSpacer, 2, 0);
layout->addWidget(widgetB, 1, 1);
//Moves the scrollbar outside the scroll area
scrollArea->verticalScrollBar()->setParent(widget);
layout->addWidget(scrollArea->verticalScrollBar(), 0, 2, 3, 1);
QLabel* innerLabel = new QLabel("Some big label to force the scrolling");
scrollArea->setWidget(innerLabel);
innerLabel->setMinimumHeight(500);
widget->show();
I have a QGraphicsView widget which displays a pixmap item. The user can move the item. When the item reachs the right (or the left) end of the graphicView, I would like the item enters from the other side (with the same X and Y coordination (like what happens in the snake game)
Solution
When setting the position of the pixmap, check if it is less than the width of the scene rectangle - the width of the pixmap and if not, set the position to zero.
Note: Do a similar check if you move in the oposite direction, i.e. set the position of the pixmap to the width of the scene rectangle - the width of the pixmap, if it is not greater than the width of the pixmap.
Example
Here is a minimal example I have prepared for you in order to demonstrate how the proposed solution could be implemented:
MainWindow::MainWindow(QWidget *parent) :
QMainWindow(parent)
{
QPixmap pixmap(64, 64);
auto *view = new QGraphicsView(this);
auto *item = new QGraphicsPixmapItem(pixmap);
auto *animation = new QVariantAnimation(this);
view->setScene(new QGraphicsScene(this));
view->setAlignment(Qt::AlignLeft | Qt::AlignTop);
view->setSceneRect(0, 0, 300, 200);
view->scene()->addItem(item);
setCentralWidget(view);
resize(302, 202);
connect(animation, &QVariantAnimation::valueChanged, [view, item](){
int x = item->x();
if (x < (view->sceneRect().width() - item->pixmap().width()))
item->setX(x + 1);
else
item->setX(0);
});
animation->setStartValue(0);
animation->setEndValue(1000);
animation->setDuration(10000);
animation->start();
}
The code of a more comprehensive example of a Snake Game I have prepared is available on GitHub.
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 have added a widget to a graphic scene (QGraphicScene) through a QGraphicsProxyWidget. To move and select the widget added QGraphicsRectItem handle.
To resize widget added QSizegrip to widget. But when i resize widget more than the QGraphicsRect item rect right and bottom edges goes behind .How to overcome this problem?
When i resize widget graphics rect item should resize or vice-versa should happen. how to do this? Any other ideas are welcome.
Here is the code
auto *dial= new QDial(); // The widget
auto *handle = new QGraphicsRectItem(QRect(0, 0, 120, 120)); // Created to move and select on scene
auto *proxy = new QGraphicsProxyWidget(handle); // Adding the widget through the proxy
dial->setGeometry(0, 0, 100, 100);
dial->move(10, 10);
proxy->setWidget(dial);
QSizeGrip * sizeGrip = new QSizeGrip(dial);
QHBoxLayout *layout = new QHBoxLayout(dial);
layout->setContentsMargins(0, 0, 0, 0);
layout->addWidget(sizeGrip, 0, Qt::AlignRight | Qt::AlignBottom);
handle->setPen(QPen(Qt::transparent));
handle->setBrush(Qt::gray);
handle->setFlags(QGraphicsItem::ItemIsMovable |
QGraphicsItem::ItemIsSelectable);
Scene->addItem(handle); // adding to scene
Here is the Output::
Before Resize
After Resize
Cause
The QGraphicsRectItem, which you use as a handle, is not aware of the size changes of QDial, so it does not respond by resizing itself.
Limitation
QWidget and its subclases do not provide something like a sizeChanged signal out of the box.
Solution
Considering the cause and the given limitation, my solution would be the following:
In a subcalss of QDial, say Dial, add a new signal void sizeChanged();
Reimplement the resizeEvent of Dial like this:
in dial.cpp
void Dial::resizeEvent(QResizeEvent *event)
{
QDial::resizeEvent(event);
sizeChanged();
}
Change auto *dial= new QDial(); to auto *dial= new Dial();
Add the following code after Scene->addItem(handle); // adding to scene:
in the place, where your example code is
connect(dial, &Dial::sizeChanged, [dial, handle](){
handle->setRect(dial->geometry().adjusted(-10, -10, 10, 10));
});
Note: This could be also solved using eventFilter instead of subclassing QDial. However, from your other question I know that you already subclass QDial, that is why I find the proposed solution more suitable for you.
Result
This is the result of the proposed solution:
I have less than 1 day of experience in QT (that's why I do not know much of it) I have a window full of information (labels, text, buttons, etc) organized by layouts.
I need that after I press one button, all of the components in a window be hidden (which I already did) except for one label which should increase to barely the size of the whole window
Despite I tried modifying the "geometry" attribute (with code) the hidden layouts do not let the label to be increased. I thought also of using the option of layout breaking, but the label losses its dynamism. Could anyone please recommend me anything to do? Thanks.
Has anyone done something like this before. Thanks.
I once provided an answer to SO: Qt - How to create Image that scale with window, and keeps aspect ratio?. The actual intention was to scale an image in a QLabel with original aspect ratio to consume maximum available size.
However, I got the feedback that the suggested solution would not work properly when my Label would be used in a QGridLayout. (This sounds very similar to the issue of the OP.) Hence, I modified the sample to reproduce the issue and fiddled a little bit around with. For me, it seems that resize events of the main window are processed in the QGridLayout but affect layouted image label only partially. (Shrinking is applied but growing not.) Fortunately, I found a very simple work-around: Setting a non-empty frame to the QLabel solved the problem. I had a look into the source code on woboq.org. I hoped to get a hint what the changed frame style would activate (to apply this as fix for my resize issue). Finally, I was not patient enough and put it aside.
Beside of this QLabel in a QGridLayout resize issue, changing the visibility of widgets should cause a proper re-layout. I would prefer show/hide (instead of delete and re-new) as this is surely easier to implement, more efficient, and less error-prone.
I took the old sample code and added a tool button which can be used to toggle the visibilty of some of the layouted widgets:
// Qt header:
#include <QtWidgets>
class LabelImage: public QLabel {
private:
QPixmap _qPixmap, _qPixmapScaled;
public:
void setPixmap(const QPixmap &qPixmap) { setPixmap(qPixmap, size()); }
protected:
virtual void resizeEvent(QResizeEvent *pQEvent);
private:
void setPixmap(const QPixmap &qPixmap, const QSize &size);
};
void LabelImage::resizeEvent(QResizeEvent *pQEvent)
{
QLabel::resizeEvent(pQEvent);
setPixmap(_qPixmap, pQEvent->size());
}
void LabelImage::setPixmap(const QPixmap &qPixmap, const QSize &size)
{
_qPixmap = qPixmap;
_qPixmapScaled = _qPixmap.scaled(size, Qt::KeepAspectRatio);
QLabel::setPixmap(_qPixmapScaled);
}
int main(int argc, char **argv)
{
qDebug() << "Qt Version:" << QT_VERSION_STR;
// main application
QApplication app(argc, argv);
// setup GUI
QMainWindow qWin;
QToolBar qToolbar;
QAction qCmdTgl(QString::fromUtf8("Decoration"));
qCmdTgl.setCheckable(true);
qCmdTgl.setChecked(true);
qToolbar.addAction(&qCmdTgl);
qWin.addToolBar(&qToolbar);
QGroupBox qBox;
QGridLayout qGrid;
// a macro for the keyboard lazy:
#define Q_LBL_WITH_POS(ROW, COL) \
QLabel qLbl##ROW##COL(QString::fromLatin1(#ROW", "#COL)); \
/*qLbl##ROW##COL.setFrameStyle(QLabel::Raised | QLabel::Box);*/ \
qGrid.addWidget(&qLbl##ROW##COL, ROW, COL, Qt::AlignCenter)
Q_LBL_WITH_POS(0, 0);
Q_LBL_WITH_POS(0, 1);
Q_LBL_WITH_POS(0, 2);
Q_LBL_WITH_POS(1, 0);
LabelImage qLblImg;
qLblImg.setFrameStyle(QLabel::Raised | QLabel::Box);
qLblImg.setAlignment(Qt::AlignCenter);
//qLblImg.setMinimumSize(QSize(1, 1)); // seems to be not necessary
qLblImg.setSizePolicy(
QSizePolicy(QSizePolicy::Ignored, QSizePolicy::Ignored));
QPixmap qPM;
if (qPM.load("cats.jpg")) qLblImg.setPixmap(qPM);
else {
qLblImg.setText(
QString::fromLatin1("Sorry. Cannot find file 'cats.jpg'."));
}
qGrid.addWidget(&qLblImg, 1, 1, Qt::AlignCenter);
qGrid.setRowStretch(1, 1); // tell QGridLayout to stretch this cell...
qGrid.setColumnStretch(1, 1); // ...prior to other cells (w/ stretch 0)
Q_LBL_WITH_POS(1, 2);
Q_LBL_WITH_POS(2, 0);
Q_LBL_WITH_POS(2, 1);
Q_LBL_WITH_POS(2, 2);
qBox.setLayout(&qGrid);
qWin.setCentralWidget(&qBox);
qWin.show();
// install signal handlers
QObject::connect(&qCmdTgl, &QAction::triggered,
[&](bool on) {
qLbl00.setVisible(on); qLbl01.setVisible(on); qLbl02.setVisible(on);
qLbl10.setVisible(on); qLbl12.setVisible(on);
qLbl20.setVisible(on); qLbl21.setVisible(on); qLbl22.setVisible(on);
});
// run application
return app.exec();
}
I compiled and tested in VS2013 on Windows 10:
After toggling the Decoration tool button:
Note:
Out of curiosity, I commented the line which changes the frame style
qLblImg.setFrameStyle(QLabel::Raised | QLabel::Box);
and again, resizing of image didn't work properly anymore.
You can remove and hide widgets inside a layout using QLayout::removeWidget(*widget); but you do not need to actually remove it. You should use QWidget::hide() for the content to disappear and for the video label's cell to be able to take that space. I think you need to pay attention to the video label's size policy if it does not increase in size. Assuming you have a QGridLayout like so:
label1 label2 label3
label4 videoLabel label5
button1 button2 button3
And let's say, when you click button3, label1, label2 and label4 should all disappear and videoLabel takes the newly created space. I would group the widgets label1, label2, label4 and videoLabel into a single widget having its own sub-layout. I use QSizePolicy::Expaning to make sure my videoLabel takes the maximum space possible. Here is the implementation:
Widget::Widget(QWidget *parent) :
QWidget(parent)
{
setStyleSheet("QLabel{font-size:20px;}");
fullScreen = false; //current fullscreen state
//main grid layout
baseLayout = new QGridLayout(this);
baseLayout->setMargin(0);
baseLayout->setAlignment(Qt::AlignCenter);
setLayout(baseLayout);
//widget container for label1, label2, label4, videolabel
groupWidget = new QWidget();
//sub-layout inside the group layout
subLayout = new QGridLayout();
subLayout->setAlignment(Qt::AlignCenter);
subLayout->setMargin(0);
groupWidget->setLayout(subLayout);
//label and button instantializing. I set background colors to show their sizes
label1 = new QLabel("Label1");
label1->setStyleSheet("background-color:white;");
label2 = new QLabel("Label2");
label2->setStyleSheet("background-color:orange;");
label3 = new QLabel("Label3");
label4 = new QLabel("Label4");
label4->setStyleSheet("background-color:blue;color:white;");
label5 = new QLabel("Label5");
videoLabel = new QLabel("videoLabel");
videoLabel->setStyleSheet("background-color:red;");
videoLabel->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
button1 = new QPushButton("button1");
button2 = new QPushButton("button2");
button3 = new QPushButton("button3");
//the grouped widget spans for 2 rows and columns, hence "2,2"
baseLayout->addWidget(groupWidget, 0,0,2,2);
subLayout->addWidget(label1, 0, 0);
subLayout->addWidget(label2, 0, 1);
subLayout->addWidget(label4, 1, 0);
subLayout->addWidget(videoLabel, 1, 1);
//adding rest of the labels and buttons to the base grid
baseLayout->addWidget(label3, 0, 2);
baseLayout->addWidget(label5, 1, 2);
baseLayout->addWidget(button1, 2, 0);
baseLayout->addWidget(button2, 2, 1);
baseLayout->addWidget(button3, 2, 2);
//button3 toggles fullscreen
connect(button3, SIGNAL(clicked(bool)), this, SLOT(onButton3Clicked(bool)));
}
//slot for button3 click
void Widget::onButton3Clicked(bool)
{
if (!fullScreen){
//removing widget from layouts is not really necessary. Make sure to hide
/*subLayout->removeWidget(label1);
subLayout->removeWidget(label2);
subLayout->removeWidget(label4);*/
label1->hide();
label2->hide();
label4->hide();
fullScreen = true;
}
else{
label1->show();
label2->show();
label4->show();
/*subLayout->addWidget(label1, 0, 0);
subLayout->addWidget(label2, 0, 1);
subLayout->addWidget(label4, 1, 0);*/
fullScreen = false;
}
}
I got the following results for this:
Keep in mind there are other approaches to this question. This one need not be necessarily the best when it comes to memory, but it is quite easy to follow.