I'm messing around with QGraphicsView and QGraphicsScene to create a Tic Tac Toe clone. I add some QGraphicsLineItems to my scene and override the resizeEvent method of the Widget that contains the view, so that when the window is resized, the view and its contents are scaled appropriately. This works fine, except for the first time that I run the program:
Once I resize the window by any amount, the scene is scaled correctly:
Here's the code:
main.cpp:
#include <QtGui>
#include "TestApp.h"
int main(int argv, char **args)
{
QApplication app(argv, args);
TestApp window;
window.show();
return app.exec();
}
TestApp.h:
#ifndef TEST_APP_H
#define TEST_APP_H
#include <QtGui>
class TestApp : public QMainWindow
{
Q_OBJECT
public:
TestApp();
protected:
void resizeEvent(QResizeEvent* event);
QGraphicsView* view;
QGraphicsScene* scene;
};
#endif
TestApp.cpp:
#include "TestApp.h"
TestApp::TestApp()
: view(new QGraphicsView(this))
, scene(new QGraphicsScene(this))
{
resize(220, 220);
scene->setSceneRect(0, 0, 200, 200);
const int BOARD_WIDTH = 3;
const int BOARD_HEIGHT = 3;
const QPoint SQUARE_SIZE = QPoint(66, 66);
const int LINE_WIDTH = 10;
const int HALF_LINE_WIDTH = LINE_WIDTH / 2;
QBrush lineBrush = QBrush(Qt::black);
QPen linePen = QPen(lineBrush, LINE_WIDTH);
for(int x = 1; x < BOARD_WIDTH; ++x)
{
int x1 = x * SQUARE_SIZE.x();
scene->addLine(x1, HALF_LINE_WIDTH, x1, scene->height() - HALF_LINE_WIDTH, linePen);
}
for(int y = 1; y < BOARD_HEIGHT; ++y)
{
int y1 = y * SQUARE_SIZE.y();
scene->addLine(HALF_LINE_WIDTH, y1, scene->width() - HALF_LINE_WIDTH, y1, linePen);
}
view->setScene(scene);
view->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
view->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
view->show();
view->installEventFilter(this);
setCentralWidget(view);
}
void TestApp::resizeEvent(QResizeEvent* event)
{
view->fitInView(0, 0, scene->width(), scene->height());
QWidget::resizeEvent(event);
}
I've tried adding a call to fitInView at the end of TestApp's constructor, but it doesn't seem to do anything - resizeEvent seems to be called once at the start of the program's execution anyway.
Cheers.
Handle the view fit also inside the showEvent:
void TestApp::showEvent ( QShowEvent * event )
{
view->fitInView(0, 0, scene->width(), scene->height());
QWidget::showEvent(event);
}
Related
While using a QGraphicsScene and QGraphicsView I wanted to achieve that if the mouse is at one of the borders of the screen the view moves with it (like it is the case in most RTS games). However when dealing with the mouseMoveEvent I only get a stack overflow, most likely because the event is called infinitely many times once the mouse is at a certain location.
My camera class has a pointer to the view and inherits QGraphicsRectItem and is added to the scene in the main class.
Is there a way to prevent this event from happening at a certain point? Or is there even an elegant solution to this? One additional problem with my attempt is that the camera class has to grab the mouse when i want the mouseMoveEvent to work.
void Camera::mouseMoveEvent(QGraphicsSceneMouseEvent* e)
{
int view_x = view->mapFromScene(e->pos()).x();
int view_y = view->mapFromScene(e->pos()).y();
int horizontalSliderPos = view->horizontalScrollBar()->sliderPosition();
int verticalSliderPos = view->verticalScrollBar()->sliderPosition();
if (view_x < 100) {
view->horizontalScrollBar()->setSliderPosition(horizontalSliderPos - 5);
}
if (view_x > Constants::VIEWWIDTH - 100) {
view->horizontalScrollBar()->setSliderPosition(view->horizontalScrollBar()->sliderPosition() + 5);
}
if (view_y < 100) {
view->verticalScrollBar()->setSliderPosition(view->verticalScrollBar()->sliderPosition() - 5);
}
if (view_y > Constants::VIEWHEIGHT - 100) {
view->verticalScrollBar()->setSliderPosition(view->verticalScrollBar()->sliderPosition() + 5);
}
}
After some trying and experiments I obtained a solution, that might be a good starting point. As you already mentioned you have to break the recursive calls of mouseMoveEvent. I broke the recursive call with a simple boolean variable, but maybe also QSignalBlocker can be helpful here, even though it blocks all signals.
Another feature I tried to implement is that there is still a moving window in case of a non-moving mouse at the margin. I did this by using a QTimer, that fires every 25ms.
main.cpp
#include <QApplication>
#include <QGraphicsView>
#include <QGraphicsScene>
#include <QMainWindow>
#include "MyScene.h"
int main(int argc, char* argv[]) {
QApplication app(argc, argv);
auto view = new QGraphicsView;
auto model = new MyScene;
view->setMouseTracking(true);
view->setScene(model);
model->setView(view);
model->addRect(QRectF(20, 20, 20, 20));
model->addRect(QRectF(300, 20, 20, 20));
model->addRect(QRectF(0, 0, 500, 500), QPen(Qt::blue)); // Complete Scene
view->show();
view->setSceneRect(QRectF(120, 20, 20, 20));
return app.exec();
}
MyScene.h
#pragma once
#include <QDebug>
#include <QGraphicsSceneEvent>
#include <QGraphicsView>
#include <QTimer>
class MyScene : public QGraphicsScene {
Q_OBJECT
public:
MyScene(QWidget* parent=nullptr) : QGraphicsScene(parent) {
}
void setView(QGraphicsView* view) {
mView = view;
}
protected:
void mouseMoveEvent(QGraphicsSceneMouseEvent* event) override {
if (mTranslating) return;
delete mRepeater; // Destroys connect
mRepeater = new QTimer;
if (!mView) return;
mTranslating = true;
int tx{ 0 };
int ty{ 0 };
const int margin = 20;
int sx = mView->mapFromGlobal(event->screenPos()).x();
int sy = mView->mapFromGlobal(event->screenPos()).y();
if (sx < margin) {
tx = -1;
}
else if (sx > mView->width() - margin) {
tx = 1;
}
if (sy < margin) {
ty = -1;
}
else if (sy > mView->height() - margin) {
ty = 1;
}
if (tx != 0 || ty != 0) {
auto rect = mView->sceneRect();
rect.translate(QPointF{ (qreal)tx,(qreal)ty });
mView->setSceneRect(rect);
connect(mRepeater, &QTimer::timeout, [=]() { // Moves even if mouse is not moved
auto rect = mView->sceneRect();
rect.translate(QPointF{ (qreal)tx,(qreal)ty });
mView->setSceneRect(rect);
});
mRepeater->start(25);
}
mTranslating = false;
}
QGraphicsView* mView{ nullptr };
bool mTranslating{ false };
QTimer* mRepeater{ nullptr };
};
I think this problem could be avoided if you would handle the mouse move event in your QGraphicsView class instead of doing it in the QGraphicsScene class.
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 have an application where fixed-size child widgets need to be added programatically to a dock widget at run time based on user input. I want to add these widgets to a dock on the Qt::RightDockArea, from top to bottom until it runs out of space, then create a new column and repeat (essentially just the reverse of the flow layout example here, which I call a fluidGridLayout)
I can get the dock widget to resize itself properly using an event filter, but the resized dock's geometry doesn't change, and some of the widgets are drawn outside of the main window. Interestingly, resizing the main window, or floating and unfloating the dock cause it to 'pop' back into the right place (I haven't been able to find a way to replicate this programatically however)
I can't use any of the built-in QT layouts because with the widgets in my real program, they end up also getting drawn off screen.
Is there some way that I can get the dock to update it's top left coordinate to the proper position once it has been resized?
I think this may be of general interest as getting intuitive layout management behavior for dock widgets in QT is possibly the hardest thing known to man.
VISUAL EXMAPLE:
The code to replicate this is example given below.
Add 4 widgets to the program using the button
Resize the green bottom dock until only two widgets are shown. Notice that the 3 remaining widgets are getting painted outside the main window, however the dock is the right size, as evidenced by the fact that you can't see the close button anymore
Undock the blue dock widget. Notice it snaps to it's proper size.
Re-dock the blue dock to the right dock area. Notice it appears to be behaving properly now.
Now resize the green dock to it's minimum size. Notice the dock is now IN THE MIDDLE OF THE GUI. WTf, how is this possible??
THE CODE
Below I give the code to replicate the GUI from the screenshots.
main.cpp:
#include "mainwindow.h"
#include <QApplication>
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
MainWindow w;
w.show();
return a.exec();
}
mainwindow.h
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include <QMainWindow>
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
MainWindow(QWidget *parent = 0);
~MainWindow();
};
#endif // MAINWINDOW_H
mainwindow.cpp
#include "mainwindow.h"
#include "QFluidGridLayout.h"
#include "QDockResizeEventFilter.h"
#include <QDockWidget>
#include <QGroupBox>
#include <QPushButton>
#include <QWidget>
#include <QDial>
class QTestWidget : public QGroupBox
{
public:
QTestWidget() : QGroupBox()
{
setFixedSize(50,50);
setStyleSheet("background-color: red;");
QDial* dial = new QDial;
dial->setFixedSize(40,40);
QLayout* testLayout = new QVBoxLayout;
testLayout->addWidget(dial);
//testLayout->setSizeConstraint(QLayout::SetMaximumSize);
setSizePolicy(QSizePolicy::Maximum, QSizePolicy::Maximum);
setLayout(testLayout);
}
QSize sizeHint()
{
return minimumSize();
}
QDial* dial;
};
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
{
QDockWidget* rightDock = new QDockWidget();
QDockWidget* bottomDock = new QDockWidget();
QGroupBox* central = new QGroupBox();
QGroupBox* widgetHolder = new QGroupBox();
QGroupBox* placeHolder = new QGroupBox();
placeHolder->setStyleSheet("background-color: green;");
placeHolder->setMinimumHeight(50);
widgetHolder->setStyleSheet("background-color: blue;");
widgetHolder->setMinimumWidth(50);
widgetHolder->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
widgetHolder->setLayout(new QFluidGridLayout);
widgetHolder->layout()->addWidget(new QTestWidget);
QPushButton* addWidgetButton = new QPushButton("Add another widget");
connect(addWidgetButton, &QPushButton::pressed, [=]()
{
widgetHolder->layout()->addWidget(new QTestWidget);
});
central->setLayout(new QVBoxLayout());
central->layout()->addWidget(addWidgetButton);
rightDock->setWidget(widgetHolder);
rightDock->installEventFilter(new QDockResizeEventFilter(widgetHolder,dynamic_cast<QFluidGridLayout*>(widgetHolder->layout())));
bottomDock->setWidget(placeHolder);
this->addDockWidget(Qt::RightDockWidgetArea, rightDock);
this->addDockWidget(Qt::BottomDockWidgetArea, bottomDock);
this->setCentralWidget(central);
central->setSizePolicy(QSizePolicy::Expanding,QSizePolicy::Expanding);
this->setMinimumSize(500,500);
}
};
QFluidGirdLayout.h
#ifndef QFluidGridLayout_h__
#define QFluidGridLayout_h__
#include <QLayout>
#include <QGridLayout>
#include <QRect>
#include <QStyle>
#include <QWidgetItem>
class QFluidGridLayout : public QLayout
{
public:
enum Direction { LeftToRight, TopToBottom};
QFluidGridLayout(QWidget *parent = 0)
: QLayout(parent)
{
setContentsMargins(8,8,8,8);
setSizeConstraint(QLayout::SetMinAndMaxSize);
}
~QFluidGridLayout()
{
QLayoutItem *item;
while ((item = takeAt(0)))
delete item;
}
void addItem(QLayoutItem *item)
{
itemList.append(item);
}
Qt::Orientations expandingDirections() const
{
return 0;
}
bool hasHeightForWidth() const
{
return false;
}
int heightForWidth(int width) const
{
int height = doLayout(QRect(0, 0, width, 0), true, true);
return height;
}
bool hasWidthForHeight() const
{
return true;
}
int widthForHeight(int height) const
{
int width = doLayout(QRect(0, 0, 0, height), true, false);
return width;
}
int count() const
{
return itemList.size();
}
QLayoutItem *itemAt(int index) const
{
return itemList.value(index);
}
QSize minimumSize() const
{
QSize size;
QLayoutItem *item;
foreach (item, itemList)
size = size.expandedTo(item->minimumSize());
size += QSize(2*margin(), 2*margin());
return size;
}
void setGeometry(const QRect &rect)
{
QLayout::setGeometry(rect);
doLayout(rect);
}
QSize sizeHint() const
{
return minimumSize();
}
QLayoutItem *takeAt(int index)
{
if (index >= 0 && index < itemList.size())
return itemList.takeAt(index);
else
return 0;
}
private:
int doLayout(const QRect &rect, bool testOnly = false, bool width = false) const
{
int left, top, right, bottom;
getContentsMargins(&left, &top, &right, &bottom);
QRect effectiveRect = rect.adjusted(+left, +top, -right, -bottom);
int x = effectiveRect.x();
int y = effectiveRect.y();
int lineHeight = 0;
int lineWidth = 0;
QLayoutItem* item;
foreach(item,itemList)
{
QWidget* widget = item->widget();
if (y + item->sizeHint().height() > effectiveRect.bottom() && lineWidth > 0)
{
y = effectiveRect.y();
x += lineWidth + right;
lineWidth = 0;
}
if (!testOnly)
{
item->setGeometry(QRect(QPoint(x, y), item->sizeHint()));
}
y += item->sizeHint().height() + top;
lineHeight = qMax(lineHeight, item->sizeHint().height());
lineWidth = qMax(lineWidth, item->sizeHint().width());
}
if (width)
{
return y + lineHeight - rect.y() + bottom;
}
else
{
return x + lineWidth - rect.x() + right;
}
}
QList<QLayoutItem *> itemList;
Direction dir;
};
#endif // QFluidGridLayout_h__
QDockResizeEventFilter.h
#ifndef QDockResizeEventFilter_h__
#define QDockResizeEventFilter_h__
#include <QObject>
#include <QLayout>
#include <QEvent>
#include <QDockWidget>
#include <QResizeEvent>
#include "QFluidGridLayout.h"
class QDockResizeEventFilter : public QObject
{
public:
QDockResizeEventFilter(QWidget* dockChild, QFluidGridLayout* layout, QObject* parent = 0)
: QObject(parent), m_dockChild(dockChild), m_layout(layout)
{
}
protected:
bool eventFilter(QObject *p_obj, QEvent *p_event)
{
if (p_event->type() == QEvent::Resize)
{
QResizeEvent* resizeEvent = static_cast<QResizeEvent*>(p_event);
QMainWindow* mainWindow = static_cast<QMainWindow*>(p_obj->parent());
QDockWidget* dock = static_cast<QDockWidget*>(p_obj);
// determine resize direction
if (resizeEvent->oldSize().height() != resizeEvent->size().height())
{
// vertical expansion
QSize fixedSize(m_layout->widthForHeight(m_dockChild->size().height()), m_dockChild->size().height());
if (dock->size().width() != fixedSize.width())
{
m_dockChild->resize(fixedSize);
m_dockChild->setFixedWidth(fixedSize.width());
dock->setFixedWidth(fixedSize.width());
mainWindow->repaint();
//dock->setGeometry(mainWindow->rect().right()-fixedSize.width(),dock->geometry().y(),fixedSize.width(), fixedSize.height());
}
}
if (resizeEvent->oldSize().width() != resizeEvent->size().width())
{
// horizontal expansion
m_dockChild->resize(m_layout->sizeHint().width(), m_dockChild->height());
}
}
return false;
}
private:
QWidget* m_dockChild;
QFluidGridLayout* m_layout;
};
#endif // QDockResizeEventFilter_h__
The problem is, nothing in the code above actually causes the QMainWindowLayout to recalculate itself. That function is buried within the QMainWindowLayout private class, but can be stimulated by adding and removing a dummy QDockWidget, which causes the layout to invalidate and recalcualte the dock widget positions
QDockWidget* dummy = new QDockWidget;
mainWindow->addDockWidget(Qt::TopDockWidgetArea, dummy);
mainWindow->removeDockWidget(dummy);
The only problem with this is that if you dig into the QT source code, you'll see that adding a dock widget causes the dock separator to be released, which causes unintuitive and choppy behavior as the user tries to resize the dock, and the mouse unexpectedly 'lets go'.
void QMainWindowLayout::addDockWidget(Qt::DockWidgetArea area,
QDockWidget *dockwidget,
Qt::Orientation orientation)
{
addChildWidget(dockwidget);
// If we are currently moving a separator, then we need to abort the move, since each
// time we move the mouse layoutState is replaced by savedState modified by the move.
if (!movingSeparator.isEmpty())
endSeparatorMove(movingSeparatorPos);
layoutState.dockAreaLayout.addDockWidget(toDockPos(area), dockwidget, orientation);
emit dockwidget->dockLocationChanged(area);
invalidate();
}
That can be corrected by moving the cursor back onto the separator and simulating a mouse press, basically undoing the endSeparatorMove callafter the docks have been repositioned. It's important to post the event, rather than send it, so thatit occurs after the resize event. The code for doing so looks like:
QPoint mousePos = mainWindow->mapFromGlobal(QCursor::pos());
mousePos.setY(dock->rect().bottom()+2);
QCursor::setPos(mainWindow->mapToGlobal(mousePos));
QMouseEvent* grabSeparatorEvent =
new QMouseEvent(QMouseEvent::MouseButtonPress,mousePos,Qt::LeftButton,Qt::LeftButton,Qt::NoModifier);
qApp->postEvent(mainWindow, grabSeparatorEvent);
Where 2 is a magic number that accounts for the group box border.
Put that all together, and here is the event filter than gives the desired behavior:
Corrected Event Filter
#ifndef QDockResizeEventFilter_h__
#define QDockResizeEventFilter_h__
#include <QObject>
#include <QLayout>
#include <QEvent>
#include <QDockWidget>
#include <QResizeEvent>
#include <QCoreApplication>
#include <QMouseEvent>
#include "QFluidGridLayout.h"
class QDockResizeEventFilter : public QObject
{
public:
friend QMainWindow;
friend QLayoutPrivate;
QDockResizeEventFilter(QWidget* dockChild, QFluidGridLayout* layout, QObject* parent = 0)
: QObject(parent), m_dockChild(dockChild), m_layout(layout)
{
}
protected:
bool eventFilter(QObject *p_obj, QEvent *p_event)
{
if (p_event->type() == QEvent::Resize)
{
QResizeEvent* resizeEvent = static_cast<QResizeEvent*>(p_event);
QMainWindow* mainWindow = dynamic_cast<QMainWindow*>(p_obj->parent());
QDockWidget* dock = static_cast<QDockWidget*>(p_obj);
// determine resize direction
if (resizeEvent->oldSize().height() != resizeEvent->size().height())
{
// vertical expansion
QSize fixedSize(m_layout->widthForHeight(m_dockChild->size().height()), m_dockChild->size().height());
if (dock->size().width() != fixedSize.width())
{
m_dockChild->setFixedWidth(fixedSize.width());
dock->setFixedWidth(fixedSize.width());
// cause mainWindow dock layout recalculation
QDockWidget* dummy = new QDockWidget;
mainWindow->addDockWidget(Qt::TopDockWidgetArea, dummy);
mainWindow->removeDockWidget(dummy);
// adding dock widgets causes the separator move event to end
// restart it by synthesizing a mouse press event
QPoint mousePos = mainWindow->mapFromGlobal(QCursor::pos());
mousePos.setY(dock->rect().bottom()+2);
QCursor::setPos(mainWindow->mapToGlobal(mousePos));
QMouseEvent* grabSeparatorEvent = new QMouseEvent(QMouseEvent::MouseButtonPress,mousePos,Qt::LeftButton,Qt::LeftButton,Qt::NoModifier);
qApp->postEvent(mainWindow, grabSeparatorEvent);
}
}
if (resizeEvent->oldSize().width() != resizeEvent->size().width())
{
// horizontal expansion
// ...
}
}
return false;
}
private:
QWidget* m_dockChild;
QFluidGridLayout* m_layout;
};
#endif // QDockResizeEventFilter_h__
i want to draw some rhombuses with random colors in a Qwidget. The widget should be repainted only when the window is resized .The problem is that when the widget was obscured and has now been uncovered , it is repainted. How can i avoid calling paintEvent() in this case? Thanks in advance.
void Dialog::paintEvent(QPaintEvent *e)
{
QPainter painter(this);
QRect background(0,0,this->geometry().width(),this->geometry().height());
painter.setBrush( QBrush( Qt::white ) );
painter.setPen( Qt::NoPen );
// QBrush bbrush(Qt::black,Qt::SolidPattern);
painter.drawRect(background);
int width = this->geometry().width();
int height = this->geometry().height();
int rec_size=64;
int rows=floor((double)height/(double)rec_size);
int cols=floor((double)width/(double)rec_size);
QPointF points[4];
for (int i=0;i<floor(rows);i++)
{
for (int j=0;j<floor(cols);j++)
{
painter.setBrush( QBrush( colors[rand() % color_size] ) );
points[0] = QPointF(rec_size*(j),rec_size*(i+0.5));
points[1] = QPointF(rec_size*(j+0.5),rec_size*(i));
points[2] = QPointF(rec_size*(j+1),rec_size*(i+0.5));
points[3] = QPointF(rec_size*(j+0.5),rec_size*(i+1));
painter.drawPolygon(points, 4);
}
}
}
You are presupposing a solution, where in fact you should be focusing on the problem instead. Your problem isn't about when paintEvent is called. Qt's semantics are such that the paintEvent can be called at any time in the main thread. You must cope with it.
What you need to do, instead, is store the randomized colors in a container, to re-use them when painting. In the example below, the colors are generated on demand, and are stored in a dynamically growing list indexed by row and column. This way when you resize the widget, the colors of the existing items don't have to change. You can then regenerate the colors at any time by simply clearing the container and forcing an update.
The example below allows you to select whether to preserve the colors during resizing.
The code uses Qt 5 and C++11. Note that the use of rand() % range is discouraged in modern code - it doesn't preserve the uniformity of the distribution.
#include <QApplication>
#include <QWidget>
#include <QPushButton>
#include <QCheckBox>
#include <QGridLayout>
#include <QPainter>
#include <random>
std::default_random_engine rng;
class Dialog : public QWidget {
Q_OBJECT
Q_PROPERTY(bool recolorOnResize READ recolorOnResize WRITE setRecolorOnResize)
QList<QColor> m_palette;
QList<QList<QColor>> m_chosenColors;
bool m_recolorOnResize;
void paintEvent(QPaintEvent *) {
QPainter p(this);
p.fillRect(rect(), Qt::white);
p.setRenderHint(QPainter::Antialiasing);
int rec_size=64;
int rows=height()/rec_size;
int cols=width()/rec_size;
std::uniform_int_distribution<int> dist(0, m_palette.size()-1);
while (m_chosenColors.size() < rows) m_chosenColors << QList<QColor>();
for (QList<QColor> & colors : m_chosenColors)
while (colors.size() < cols)
colors << m_palette.at(dist(rng));
QPointF points[4];
for (int i=0; i<rows; i++) {
for (int j=0; j<cols; j++) {
points[0] = QPointF(rec_size*(j),rec_size*(i+0.5));
points[1] = QPointF(rec_size*(j+0.5),rec_size*(i));
points[2] = QPointF(rec_size*(j+1),rec_size*(i+0.5));
points[3] = QPointF(rec_size*(j+0.5),rec_size*(i+1));
p.setBrush(m_chosenColors[i][j]);
p.drawPolygon(points, 4);
}
}
}
void resizeEvent(QResizeEvent *) {
if (m_recolorOnResize) m_chosenColors.clear();
}
public:
Dialog(QWidget * parent = 0) : QWidget(parent), m_recolorOnResize(false) {
m_palette << "#E2C42D" << "#E5D796" << "#BEDA2C" << "#D1DD91" << "#E2992D" << "#E5C596";
setAttribute(Qt::WA_OpaquePaintEvent);
}
Q_SLOT void randomize() {
m_chosenColors.clear();
update();
}
bool recolorOnResize() const { return m_recolorOnResize; }
void setRecolorOnResize(bool recolor) {
m_recolorOnResize = recolor;
setAttribute(Qt::WA_StaticContents, !m_recolorOnResize);
}
};
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
QWidget w;
QGridLayout l(&w);
Dialog d;
QCheckBox recolor("Recolor on Resize");
QPushButton update("Repaint"), randomize("Randomize");
d.setMinimumSize(256, 128);
l.addWidget(&d, 0, 0, 1, 2);
l.addWidget(&recolor, 1, 0, 1, 2);
l.addWidget(&update, 2, 0);
l.addWidget(&randomize, 2, 1);
recolor.setChecked(d.recolorOnResize());
QObject::connect(&recolor, &QAbstractButton::toggled, [&d](bool checked){
d.setRecolorOnResize(checked);}
);
QObject::connect(&update, &QAbstractButton::clicked, &d, static_cast<void(QWidget::*)()>(&QWidget::update));
QObject::connect(&randomize, &QAbstractButton::clicked, &d, &Dialog::randomize);
w.show();
return a.exec();
}
#include "main.moc"
I have an image stored in a Qlabel in the scene. I want to get the Qlabel image to follow where ever the cursor moves when inside the scene. I've tried QGraphicsSceneMouseMove and haven't come close yet.
void scene::mouseMoveEvent(QGraphicsSceneMouseEvent /*mouseEvent*/)
{
QPointF P1 = ui->tankarmplay1->mapFromParent(QCursor.pos());
int x = P1.x();
int y = P1.y();
ui->tankarmplay1->setGeometry(x,y, 50, 50);
}
UPDATE: Added a QGraphicsLineItem that points at the mouse. This could be replaced by a full drawing of a turret of some sort using a QGraphicsItemGroup, and using the same rotation calculation.
The following links talks about a lot of the things you should be familiar with:
http://qt-project.org/doc/qt-5/graphicsview.html
http://qt-project.org/doc/qt-5/application-windows.html
void scene::mouseMoveEvent(QGraphicsSceneMouseEvent * e /*mouseEvent*/)
{
// QPointF P1 = (e->pos());
// int x = P1.x();
// int y = P1.y();
// ui->tankarmplay1->setGeometry(x, y, 50, 50);
ui->tankarmplay1->move((int) e->pos().x(), (int) e->pos().y());
}
http://qt-project.org/doc/qt-5/qgraphicsscenemouseevent.html#pos
I haven't personally used QCursor. I think it is a very round-about way of finding about the mouse when you have the mouse event's pos information handy. If you did you QCursor, you would probably need to use mapFromGlobal not mapFromParent.
http://qt-project.org/doc/qt-5/qcursor.html#details
http://qt-project.org/doc/qt-5/qcursor.html#pos
Here is something I wrote up before using the specific QGraphicsSceneMouseEvent methods.
To get those to work I had to use mapToScene() to get the coordinates to probably match.
How to draw a point (on mouseclick) on a QGraphicsScene?
In the pos property of QWidget, you modify it with move() usually. setGeometry also works, but you also end up referencing the width and height a lot.
http://qt-project.org/doc/qt-4.8/qwidget.html#pos-prop
http://qt-project.org/doc/qt-4.8/qwidget.html#mouseTracking-prop
UPDATE: Awesome example that shows mouse tracking used in the scene and outside the scene
Note, using a QGraphicsTextItem would probably be cleaner than using a QLabel + QGraphicsProxyWidget to move some text around in the scene.
widget.h
#ifndef WIDGET_H
#define WIDGET_H
#include <QWidget>
#include <QFrame>
#include <QLabel>
#include <QPointF>
#include "mygraphicsscene.h"
#include <QGraphicsView>
class Widget : public QWidget
{
Q_OBJECT
public:
Widget(QWidget *parent = 0);
~Widget();
qreal map(const qreal & x1, qreal x, const qreal & x2, const qreal & y1, const qreal & y2);
public slots:
void on_sceneMouseMove(QPointF);
private:
QLabel * m_label;
MyGraphicsScene * m_scene;
QGraphicsView * m_view;
QFrame * m_labelContainer;
};
#endif // WIDGET_H
widget.cpp
#include "widget.h"
#include <QGraphicsView>
#include "mygraphicsscene.h"
#include <QVBoxLayout>
#include <QLabel>
#include <QDebug>
Widget::Widget(QWidget *parent)
: QWidget(parent)
{
QVBoxLayout * vbox = new QVBoxLayout;
m_view = new QGraphicsView;
m_scene = new MyGraphicsScene;
m_view->setScene(m_scene);
m_view->setMouseTracking(true);
m_scene->setSceneRect(-300,-300, 600, 600);
m_view->fitInView(m_scene->sceneRect());
vbox->addWidget(m_view, 1);
m_labelContainer = new QFrame;
m_labelContainer->setFrameShape(QFrame::Box);
m_label = new QLabel("Tracking Label");
m_labelContainer->setFixedSize(300, 300);
m_label->setParent(m_labelContainer);
vbox->addWidget(m_labelContainer, 1);
QObject::connect(m_scene, SIGNAL(mouseMoved(QPointF)),
this, SLOT(on_sceneMouseMove(QPointF)));
this->setLayout(vbox);
}
void Widget::on_sceneMouseMove(QPointF pt)
{
QPointF pt2;
pt2.setX(map(m_scene->sceneRect().left(), pt.x(), m_scene->sceneRect().right(),
m_labelContainer->rect().left(), m_labelContainer->rect().right()));
pt2.setY(map(m_scene->sceneRect().top(), pt.y(), m_scene->sceneRect().bottom(),
m_labelContainer->rect().top(), m_labelContainer->rect().bottom()));
// qDebug() << pt << pt2 << m_scene->sceneRect() << m_labelContainer->rect();
m_label->move(pt2.x(), pt2.y());
// m_label->setGeometry(pt.x(), pt.y(),
// m_label->width(), m_label->height());
}
qreal Widget::map(const qreal & x1, qreal x, const qreal & x2, const qreal & y1, const qreal & y2)
{
if(x < x1)
x = x1;
if(x > x2)
x = x2;
return (x - x1) * (y2 - y1) / (x2 - x1) + y1;
}
Widget::~Widget()
{
}
main.cpp
#include "widget.h"
#include <QApplication>
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
Widget w;
w.show();
return a.exec();
}
mygraphicsview.h
#ifndef MYGRAPHICSSCENE_H
#define MYGRAPHICSSCENE_H
#include <QGraphicsScene>
#include <QGraphicsSceneMouseEvent>
#include <QGraphicsProxyWidget>
#include <QGraphicsLineItem> // Added this
class MyGraphicsScene : public QGraphicsScene
{
Q_OBJECT
public:
explicit MyGraphicsScene(QObject *parent = 0);
signals:
void mouseMoved(QPointF);
public slots:
void mouseMoveEvent(QGraphicsSceneMouseEvent * );
private:
QGraphicsProxyWidget * m_labelProxy;
QGraphicsLineItem * m_lineItem; // Added this
};
#endif // MYGRAPHICSSCENE_H
mygraphicsview.cpp
#include "mygraphicsscene.h"
#include <QDebug>
#include <QLabel>
#include <QVector2D>
#include <qmath.h>
#include <QLineF>
MyGraphicsScene::MyGraphicsScene(QObject *parent) :
QGraphicsScene(parent)
{
QLabel * label = new QLabel("Tracking Widget\n in Scene");
m_labelProxy = this->addWidget(label);
// added the lines below to setup an item, pointing in the positive x direction
int x1 = 0;
int y1 = 0;
m_lineItem = new QGraphicsLineItem(x1, y1, x1 + 20, y1);
// m_lineItem->setTransformOriginPoint(x1, y1);
this->addItem(m_lineItem);
m_lineItem->setPos(-100, -100);
}
void MyGraphicsScene::mouseMoveEvent(QGraphicsSceneMouseEvent * e)
{
// qDebug() << e->pos() << e->screenPos() << e->scenePos();
emit mouseMoved(e->scenePos());
m_labelProxy->setPos(e->scenePos());
// Added these lines below to calculate and set the rotation.
// QVector2D v;
// v.setX(e->scenePos().x() - m_lineItem->pos().x());
// v.setY(e->scenePos().y() - m_lineItem->pos().y());
// m_lineItem->setRotation(qAtan2(v.y(), v.x())*180./(3.1459));
QLineF line(m_lineItem->pos(), e->scenePos());
m_lineItem->setRotation(360 - line.angle());
}
Hope that helps.