How to notify a widget about changes in another widget in Qt? - c++

I am trying to implement a widget in Qt that has 2 child widgets of its own: one is a render area where I draw some points and connect lines between them and the other one is a ListBox where I would like to insert the list of all the points I drew with their coordinates from the render area. The 2 widgets where added with Qt Designer. Here is my code until now:
renderarea.h:
class RenderArea : public QWidget
{
Q_OBJECT
public:
RenderArea(QWidget *parent = 0);
QPoint point;
QList&ltQPoint> list;
protected:
void mousePressEvent(QMouseEvent *);
void paintEvent(QPaintEvent *event);
void updateList(QPoint p);
};
renderarea.cpp:
RenderArea::RenderArea(QWidget *parent)
: QWidget(parent)
{
setBackgroundRole(QPalette::Base);
setAutoFillBackground(true);
}
void RenderArea::mousePressEvent(QMouseEvent *e)
{
point = e->pos();
updateList(point);
this->update();
}
void RenderArea::updateList(QPoint p)
{
list.append(p);
}
void RenderArea::paintEvent(QPaintEvent * /* event */)
{
QPainter painter(this);
painter.setPen(QPen(Qt::black,2));
for (int i = 0; i &lt list.size(); ++i)
painter.drawPoint(list[i]);
if (list.size()>1)
for(int j = 0; j &lt list.size()-1; ++j)
painter.drawLine(list[j], list[j+1]);
}
paintwidget.h:
class PaintWidget : public QWidget
{
Q_OBJECT
public:
explicit PaintWidget(QWidget *parent = 0);
~PaintWidget();
private:
Ui::PaintWidget *ui;
};
paintwidget.cpp:
PaintWidget::PaintWidget(QWidget *parent) :
QWidget(parent),
ui(new Ui::PaintWidget)
{
ui->setupUi(this);
}
PaintWidget::~PaintWidget()
{
delete ui;
}
My question is how to transmit from the render area widget to my ListBox that I drew another point and it should be displayed along with its coordinates in the list?

The general approach used in QT development is using signal/slots for communication between components of software. So basically you need to define a signal in your source component (for instance RenderArea or whereever your like) and connect your slot defined in another component somewhere (i.e your Form Window) and fire a signal upon an action.
There are examples in the referenced link too.

OrcunC gave you a good advice.
If your are new to signal/slots implementation here some hints you can start from.
renderarea.h
signal:
void pointAdded(QPoint*);
renderarea.cpp
void RenderArea::updateList(QPoint p)
{
list.append(p);
emit pointAdded(&list.back());
}
listbox.h
public slots:
void onPointAdded(QPoint*);
listbox.cpp
void ListBox::onPointAdded(QPoint* point)
{
//lets assume your ListBox is a QListWidget
addItem( QString::number(point->x()) + "," + QString::number(point->y()))
}
somewhere instance of ListBox and RenderArea are accessible
QObject::connect( renderArea, SIGNAL(pointAdded(QPoint*),
listBox, SLOT(onPointAdded(QPoint*)));
NOTE: nameing is very important for readability and maintenance the void RenderArea::updateList(QPoint p) in this case it's more void RenderArea::addPoint( const QPoint& p) (also notice the const reference telling the compiler that we are not changing p event if we have it's reference)

Related

QTreeWidget snaps back when moving items

When I'm using a QTreeWidget (via qdesigner) with the dragDropMode set to InternalMove and then attempt to move one of the items, the scrollbar snaps back to the original position of the item I moved. I would like the scrollbar to stay at the position the item was dropped. Is there an easy way to do that? I adjusted the defaultDropAction, but to no avail.
Figured it out...
class MyTree : public QTreeWidget
{
Q_OBJECT
public:
MyTree(QWidget *parent = 0);
signals:
void verticalScrollBarValue(int val);
protected slots:
void setTreeVerticalScroll(int val);
protected:
void dropEvent(QDropEvent *event);
};
MyTree::MyTree(QWidget *parent) : QTreeWidget(parent)
{
connect(this, &MyTree::verticalScrollBarValue,
this, &MyTree::setTreeVerticalScroll);
}
void MyTree::setTreeVerticalScroll(int val)
{
verticalScrollbar()->setValue(val);
}
void MyTree::dropEvent(QDropEvent *event)
{
int v = verticalScrollbar()->value();
QTreeWidget::dropEvent(event);
emit verticalScrollBarValue(v);
}
Disclaimer: There may be a typo in there somewhere, I can't copy directly from my work environment.

Qt : Having a single instance of QPainter object in a class

I am doing a project in Qt and there is an object QPainter which is declared as :
QPainter painter(this);
Where this points to the present class. My problem is that I need to declare this object such that it is accessible to the entire class functions.
If I declare it inside the constructor then its scope is not valid for other functions, and I cannot declare outside all function in my .cpp file as this variable doesn't make any sense.
So how can I declare my object such that it is accessible to all the functions?
Edit : Painter Code :
void MainWindow :: paintEvent(QPaintEvent * e)
{
QMainWindow::paintEvent(e);
if(1)
{
QPainter painter(this);
QPen paintpen(Qt::red);
paintpen.setWidth(5);
QPoint p1;
p1.setX(mFirstX);
p1.setY(mFirstY);
painter.setPen(paintpen);
painter.drawPoint(p1);
}
}
Mouse Event Code :
void MainWindow :: mousePressEvent(QMouseEvent *e)
{
mFirstX=0;
mFirstY=0;
mFirstClick=true;
mpaintflag=false;
if(e->button() == Qt::LeftButton)
{
//store 1st point
if(1)
{
mFirstX = e->x();
mFirstY = e->y();
mFirstClick = false;
mpaintflag = true;
qDebug() << "First image's coordinates" << mFirstX << "," << mFirstY ;
update();
}
}
}
PROBLEM : I want to create the point but I don't want it to disappear from the widget(mainWindow). Here since the object is created during the paintEvent method each time the point that I am drawing is disappearing when the next point is drawn.
And if I declare it outside the paintEvent then I get the following error:
QWidget::paintEngine: Should no longer be called
QPainter::begin: Paint device returned engine == 0, type: 1
QPainter::setPen: Painter not active
In general, declaring the QPainter object globally is not a good idea. It is better to construct it in the QWidget::paintEvent (where it will be active) and, as #Mat said, pass it (by reference) to the functions that need it.
In your particular case the point disappears because the widget is redrawn, not because the QPainter is destroyed. So the strategy should be to create and manage a buffer of points. Add a point to the buffer in a mouse event, e.g. QWidget::mousePressEvent (there do some management too, e.g. limiting the number of points) and in the paint event - paint all the points from the buffer.
Here is an oversimplified example which could easily be adapted for your specific purpose:
MainWindow.h
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include <QMainWindow>
#include <QMouseEvent>
#include <QPaintEvent>
#include <QPainter>
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
explicit MainWindow(QWidget *parent = nullptr);
protected:
void mousePressEvent(QMouseEvent *event);
void paintEvent(QPaintEvent *event);
private:
QList<QPoint> m_points;
};
#endif // MAINWINDOW_H
MainWindow.cpp
#include "MainWindow.h"
MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent)
{
QPalette p = QPalette();
p.setColor(QPalette::Window, Qt::white);
setPalette(p);
setAutoFillBackground(true);
resize(400, 400);
}
void MainWindow::mousePressEvent(QMouseEvent *event)
{
m_points.append(event->pos());
update();
}
void MainWindow::paintEvent(QPaintEvent *event)
{
QPainter painter(this);
painter.setClipping(true);
painter.setClipRect(event->rect());
painter.setPen(QPen(Qt::red, 5));
foreach (QPoint point, m_points) { painter.drawPoint(point); }
}
That's what class attributes are for :
class MyObject : public QObject {
Q_OBJECT
public:
MyObject(QObject *parent = 0) : QObject(parent), painter_(this) {
// Other things if you need them
}
void aFunction() {
// You can use painter_ here !!
}
private :
QPainter painter_;
}
In the private access specifier of your main window class in mainWindow.h, you could write this line
QPainter *painterPointer
creating a pointer accessible in all MainWindow slots. You could use that painterPointer as though it were any painter pointer initialised in any slot. However if you wanted to use this pointer in a developer defined function or method outside of the MainWindow class, you would have to pass the pointer as a reference (or pointer to the pointer).
might have to include a line similar to this in the MainWindow Constructor
painterPointer = new QPainter(this);

signal a slot in another class in Qt

Situation:
I have a Dialog class in Qt on which I draw a raster of squares. The squares are implemented in the MySquare class (MySquare: QGraphicsItem).
Question:
I want to signal the Dialog slot setTarget() that a square was clicked (and obviously I want to give it some information about that square like for example it's x and y coordinates with it later). However I get an 'undefined reference to 'MySquare::targetChanged()' error. I have looked for a solution but have not found any; anybody an idea?
edit: I have added a Q_OBJECT macro inside the MySquare however the error does not dissapear and I get an additional "'undefined reference to 'vtable for MySquare()' error
dialog.h
class Dialog : public QDialog
{
Q_OBJECT
public:
explicit Dialog(QWidget *parent = 0);
~Dialog();
public slots:
void setTarget();
private:
Ui::Dialog *ui;
QGraphicsScene *scene;
};
dialog.cpp
Dialog::Dialog(QWidget *parent) : QDialog(parent), ui(new Ui::Dialog)
{
ui->setupUi(this);
scene = new QGraphicsScene(this);
ui->graphicsView->setScene(scene);
MySquare *item;
item = new MySquare(30,30,30,30);
QObject::connect(item,SIGNAL(targetChanged()),this,SLOT(setTarget()));
}
void Dialog::setTarget(){
qDebug() << "signal recieved" << endl;
}
mysquare.h
#include <QGraphicsItem>
#include <QPainter>
#include <QDebug>
#include <QKeyEvent>
#include <QObject>
class MySquare : public QGraphicsItem, public QObject
{
Q_OBJECT
public:
MySquare(int x,int y,int h, int w);
QRectF boundingRect() const;
void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget);
int x,y,h,w;
signals:
void targetChanged();
protected:
void mousePressEvent(QGraphicsSceneMouseEvent *event);
void mouseReleaseEvent(QGraphicsSceneMouseEvent *event);
void mouseMoveEvent(QGraphicsSceneMouseEvent *event);
};
mysquare.cpp
#include "mysquare.h"
#include <QGraphicsSceneDragDropEvent>
#include <QWidget>
MySquare::MySquare(int _x,int _y, int _w, int _h)
{
setAcceptDrops(true);
color=Qt::red;
color_pressed = Qt::green;
x = _x;
y = _y;
w = _w;
h = _h;
}
QRectF MySquare::boundingRect() const
{
return QRectF(x,y,w,h);
}
void MySquare::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget)
{
QRectF rec = boundingRect();
QBrush brush(color);
if (Pressed){
brush.setColor(color);
} else {
brush.setColor(color_pressed);
}
painter->fillRect(rec,brush);
painter->drawRect(rec);
}
void MySquare::mousePressEvent(QGraphicsSceneMouseEvent *event)
{
Pressed=true;
update();
QGraphicsItem::mousePressEvent(event);
emit targetChanged();
}
void MySquare::mouseReleaseEvent(QGraphicsSceneMouseEvent *event)
{
Pressed=false;
update();
QGraphicsItem::mouseReleaseEvent(event);
qDebug() << "mouse Released";
}
void MySquare::mouseMoveEvent(QGraphicsSceneMouseEvent *event){
qDebug() << "mouse Moved";
QDrag *drag = new QDrag(event->widget());
QMimeData *mime = new QMimeData;
drag->setMimeData(mime);
drag->exec();
}
void MySquare::keyPressEvent(QKeyEvent *event){
//out of bounds check?
int x = pos().x();
int y = pos().y();
QGraphicsItem::keyPressEvent(event);
}
Edit: If you have an undefined reference to a vtable, then it's probably because you did not implement certain virtual functions. Have you implemented the mouse event handlers of the MySquare class somewhere? Have you also implemented the boundingRect() and paint() functions of the MySquare class?
Old answer:
You need to write the Q_OBJECT macro after the opening '{' in the MySquare class. Also Qt will complain about multiple inheritance at some point. Therefore, instead of inheriting from QGraphicsItem and QObject, inherit from QGraphicsObject instead.
The actual reason, the linker complains about a missing function definition is the following: When you put a Q_OBJECT macro into the right place, then the Qt Meta Object Compiler (MOC) will generate a new cpp file for the project which contains the definition of the signal functions of the respective class. So Qt implements a function for every signal for you. If you don't insert the Q_OBJECT macro, then MOC won't do anything for you and the function definition for the signal will be missing to the linker.
Try to do the following steps:
Build-clean
Build-run qmake
Build-build.
Maybe the moc Option is not Set in the Makefile after you added the Q_Object Makro. Therefore you have to refresh your Makefile.
Hope this helps.

Qt - drag and drop with graphics view framework

I'm trying to make a simple draggable item using the graphics framework. Here's the code for what I did so far:
Widget class:
class Widget : public QWidget
{
Q_OBJECT
public:
Widget(QWidget *parent = 0);
~Widget();
};
Widget::Widget(QWidget *parent)
: QWidget(parent)
{
DragScene *scene = new DragScene();
DragView *view = new DragView();
QHBoxLayout *layout = new QHBoxLayout();
DragItem *item = new DragItem();
view->setAcceptDrops(true);
scene->addItem(item);
view->setScene(scene);
layout->addWidget(view);
this->setLayout(layout);
}
Widget::~Widget()
{
}
DragView class:
class DragView : public QGraphicsView
{
public:
DragView(QWidget *parent = 0);
};
DragView::DragView(QWidget *parent) : QGraphicsView(parent)
{
setRenderHints(QPainter::Antialiasing);
}
DragScene class:
class DragScene : public QGraphicsScene
{
public:
DragScene(QObject* parent = 0);
protected:
void dragEnterEvent(QGraphicsSceneDragDropEvent *event);
void dragMoveEvent(QGraphicsSceneDragDropEvent *event);
void dragLeaveEvent(QGraphicsSceneDragDropEvent *event);
void dropEvent(QGraphicsSceneDragDropEvent *event);
};
DragScene::DragScene(QObject* parent) : QGraphicsScene(parent)
{
}
void DragScene::dragEnterEvent(QGraphicsSceneDragDropEvent *event){
}
void DragScene::dragMoveEvent(QGraphicsSceneDragDropEvent *event){
}
void DragScene::dragLeaveEvent(QGraphicsSceneDragDropEvent *event){
}
void DragScene::dropEvent(QGraphicsSceneDragDropEvent *event){
qDebug() << event->pos();
event->acceptProposedAction();
DragItem *item = new DragItem();
this->addItem(item);
// item->setPos(event->pos()); before badgerr's tip
item->setPos(event->scenePos());
}
DragItem class:
class DragItem : public QGraphicsItem
{
public:
DragItem(QGraphicsItem *parent = 0);
QRectF boundingRect() const;
void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget = 0);
protected:
void mouseDoubleClickEvent(QGraphicsSceneMouseEvent *event);
void mouseMoveEvent(QGraphicsSceneMouseEvent *event);
void mousePressEvent(QGraphicsSceneMouseEvent *event);
void mouseReleaseEvent(QGraphicsSceneMouseEvent *event);
};
DragItem::DragItem(QGraphicsItem *parent) : QGraphicsItem(parent)
{
setFlag(QGraphicsItem::ItemIsMovable);
}
QRectF DragItem::boundingRect() const{
const QPointF *p0 = new QPointF(-10,-10);
const QPointF *p1 = new QPointF(10,10);
return QRectF(*p0,*p1);
}
void DragItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget){
if(painter == 0)
painter = new QPainter();
painter->drawEllipse(QPoint(0,0),10,10);
}
void DragItem::mouseDoubleClickEvent(QGraphicsSceneMouseEvent *event){
}
void DragItem::mouseMoveEvent(QGraphicsSceneMouseEvent *event){
}
void DragItem::mousePressEvent(QGraphicsSceneMouseEvent *event){
QMimeData* mime = new QMimeData();
QDrag* drag = new QDrag(event->widget());
drag->setMimeData(mime);
drag->exec();
}
void DragItem::mouseReleaseEvent(QGraphicsSceneMouseEvent *event){
}
main.cpp instantiates a Widget and shows it. When I try to drag the circle, the app just creates another circle over the original one, regardless of where I release the drag. qDebug() in DragScene's dropEvent() shows QPointF(0,0) everytime the drag ends. I'm having a hard time trying to understand exactly what I have to do, which classes I should subclass, which methods needs to be overriden, to make this work. The documentation on this isn't very detailed. I'd like to know how to make this work, and if there's some other, more comprehensive resource to learn about the graphics view framework, besides the official documentation (which is excellent btw, but it would be great if there was a more detailed treatise on the subject).
EDIT:
Following badgerr's advice, I replaced item->pos() in DragScene::dropEvent() with item->scenePos(), now the drop event creates a new circle in the drop site, which is more or less what I wanted. But the original circle is still in place, and while the drag is in progress, the item doesn't follow the mouse cursor.
The QGraphicsSceneDragDropEvent documentation says that pos() should return the cursor position in relation to the view that sent the event, which, unless I got it wrong, shouldn't be (0,0) all the time. Weird.
I've read in a forum post that you can use QDrag::setPixMap() to show something during the drag, and in examples I've seen pictures being set as pixmaps, but how do I make the pixmap just like the graphics item I'm supposed to be dragging?
There is an example with Qt Assistant called the "Drag and Drop Robot Example" which appears to use the QDrag method, I don't know if you've had a look at that already.
edit: Just a quick observation, you appear to be creating a new DragItem in your DropEvent, instead of using the mimeData() of the event itself, and since your item draws itself at 0,0, that might explain why you have a new one appearing at that position regardless of where you drop your DragItem.
When I wrote a graphics dragging thing similar to this, I went about it a slightly different way. Maybe it will help you:
Instead of using the QDrag stuff, I used only the mousePressEvent, mouseReleaseEvent, and mouseMoveEvent functions of QGraphicsItem. The mouse press/release set a flag indicating the drag state, and move followed a process of
call prepareGeometryChange();
update the bounds of the QGraphicsItem (my boundingRect() function returns these bounds. All my graphics item bounds are in QGraphicsScene space)
call update();
Then in my paint() function I draw a shape using the boundingRect().
Sorry I don't have a code sample to share, I'll knock one up if I get time.

Qt - reloading widget contents

I'm trying to modify the fridge magnets example by adding a button that will reload the widget where the draggable labels are drawn, reflecting any changes made to the text file it reads. I defined another class that would contain the button and the DragWidget object, so there would be an instance of this class instead of DragWidget in main():
class wrapWidget: public QWidget
{
Q_OBJECT
public:
wrapWidget();
};
wrapWidget::wrapWidget()
{
QGridLayout *gridlayout = new QGridLayout();
DragWidget *w = new DragWidget();
QPushButton *b = new QPushButton("refresh");
gridlayout ->addWidget(w,0,0);
gridlayout ->addWidget(b,1,0);
setLayout(gridlayout );
connect(b,SIGNAL(clicked()),w,SLOT(draw()));
}
The call to connect is where I'm trying to do the refresh thing. In the original fridge magnets example, all the label drawing code was inside the constructor of the DragWidget class. I moved that code to a public method that I named 'draw()', and called this method from the constructor instead. Here's DragWidget definition and implementation:
#include <QWidget>
QT_BEGIN_NAMESPACE
class QDragEnterEvent;
class QDropEvent;
QT_END_NAMESPACE
class DragWidget : public QWidget
{
public:
DragWidget(QWidget *parent = 0);
public slots:
void draw();
protected:
void dragEnterEvent(QDragEnterEvent *event);
void dragMoveEvent(QDragMoveEvent *event);
void dropEvent(QDropEvent *event);
void mousePressEvent(QMouseEvent *event);
void paintEvent(QPaintEvent *event);
};
DragWidget::DragWidget(QWidget *parent)
: QWidget(parent)
{
draw();
QPalette newPalette = palette();
newPalette.setColor(QPalette::Window, Qt::white);
setPalette(newPalette);
setMinimumSize(400, 100);//qMax(200, y));
setWindowTitle(tr("Fridge Magnets"));
setAcceptDrops(true);
}
void DragWidget::draw(){
QFile dictionaryFile(":/dictionary/words.txt");
dictionaryFile.open(QFile::ReadOnly);
QTextStream inputStream(&dictionaryFile);
int x = 5;
int y = 5;
while (!inputStream.atEnd()) {
QString word;
inputStream >> word;
if (!word.isEmpty()) {
DragLabel *wordLabel = new DragLabel(word, this);
wordLabel->move(x, y);
wordLabel->show();
wordLabel->setAttribute(Qt::WA_DeleteOnClose);
x += wordLabel->width() + 2;
if (x >= 245) {
x = 5;
y += wordLabel->height() + 2;
}
}
}
}
I thought that maybe calling draw() as a slot would be enough to reload the labels, but it didn't work. Putting the draw() call inside the widget's overriden paintEvent() instead of the constructor didn't work out as well, the program would end up in an infinite loop.
What I did was obviously not the right way of doing it, so what should I be doing instead?
My quick guess is, you haven't added Q_OBJECT macro to dragwidget.h header, the moc file for DragWidget class wasn't generated and the connect failed with "no such slot as draw()" error.
It might be also a good idea to add "CONFIG += console" to .pro file - you'll see all warning messages (like the one about connect error), so tracking such mistakes would be easier. You might also check return value of connect.
I noticed that you opened file this way:
QFile dictionaryFile(":/dictionary/words.txt");
Note that the file name starts with ":", and it means that the file will be read from your qrc resource package instead of your local disk. So if you made the change on words.txt, it will be read by code only when you compiled qrc file next time. So you must have understood how to fix it, right? Good Luck:)