Qt - reloading widget contents - c++

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:)

Related

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);

Adding multiple text field in a Qt pushButton element

I am trying to implement a Qt push button and some of its properties are going to change under certain circumstances, such as the size, the color and the text field. While I have implemented most of the functionality, I'm looking to add (probably) another text field at a custom place over the button which is going also to change according to different input. For example:
Desired button layout
In this example, I want to be able to change dynamically both the numeric value and the currency. Is there a way to do it, or something similar?
EDIT: I tried to derive my button class from QPushButton and add some extra text in it, but with no luck. Below you'll find the relevant code:
.h file:
namespace Ui {
class myButton;
}
class myButton : public QPushButton
{
Q_OBJECT
public:
explicit myButton(QWidget *parent = 0);
~myButton();
void setSize(int _xs, int _ys);
void setPosition(int _xp, int _yp);
private:
Ui::Tile *ui;
int xSize = 95;
int ySize = 95;
protected:
void paintEvent(QPaintEvent *);
};
.cpp file
myButton::myButton(QWidget *parent) :
QPushButton(parent),
ui(new Ui::myButton)
{
ui->setupUi(this);
}
myButton::~myButton()
{
}
//Paint event of button
void myButton::paintEvent(QPaintEvent *paint){
QPushButton::paintEvent(paint);
QPainter p(this);
p.save();
p.drawText(QPoint(80,10),"FirstName"); // Simple Text.
p.setPen(Qt::blue); // Changing the color of pen.
p.setFont(QFont("Arial", 50)); // Changing the font.
p.drawText(QPoint(80,20),"MiddleName");
p.drawText(QPoint(80,30),"Lastname");
p.restore();
}
Then I'm calling my new button with something like:
myButton *newBtn2 = new myButton(this);
newBtn2->show();

Drawing lines programmatically with Qt

I want to add lines programmatically in a QLabel between two points. From what I found, it seems that the only way to do it is to subclass a QWidget to change the PaintEvent() protected method.
So, I create a new class 'QLineObject' from QWidget. This is my header file :
class QLineObject : public QWidget
{
Q_OBJECT
public:
QLineObject();
QLineObject(Point from, Point to);
protected:
void paintEvent(QPaintEvent *event);
private:
Point fromPoint;
Point toPoint;
};
And the implementation file :
QLineObject::QLineObject()
{
Point point;
point.x = 0.0;
point.y = 0.0;
fromPoint = point;
toPoint = point;
}
QLineObject::QLineObject(Point from, Point to)
{
fromPoint = from;
toPoint = to;
}
void QLineObject::paintEvent(QPaintEvent *event)
{
QPainter painter(this);
painter.drawLine(fromPoint.x, fromPoint.y, toPoint.x, toPoint.y);
}
Here's come the problem. I can't find how to add this widget in my main window. If I create a new instance of QLineObject and call show(), it popup a new window. I'm sure I'm just missing something. Is there someone who want to help me? I would like to know how to create and add it from somewhere else that my main window constructor.
Thank you!
You shouldn't be calling show on the QLineObject. Instead, pass the main window as the parent to your constructor and pass that to the inherited QWidget. Then call show on the main widget, which in this case is the main window...
class QLineObject : public QWidget
{
public:
QLineObject(QWidget* parent);
};
QLineObject::QLineObject(QWidget* parent)
: QWidget(parent)
{
}
QWidget* pWidget = new QWidget;
QLineObject* pLineObject = new QLineObject(pWidget);
pWidget->show();
Alternatively, you can use the QLabel as the parent.
QLabel* pLabel = new QLabel(pWidget);
QLineObject* pLineObject = new QLineObject(pLabel);
pWidget->show();
Also, you probably want to be calling QWidget::paintEvent in your overridden paintEvent.
I would do the following:
QMainWindow mw;
QLineObject lo;
mw.setCentralWidget(&lo);
mw.show();

How to notify a widget about changes in another widget in Qt?

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)

Qt, can't display child widget

I have two widgets defined as follows
class mainWindow : public QWidget
{
Q_OBJECT
public:
mainWindow();
void readConfig();
private:
SWindow *config;
QVector <QString> filePath;
QVector <QLabel*> alias,procStatus;
QVector <int> delay;
QGridLayout *mainLayout;
QVector<QPushButton*> stopButton,restartButton;
QVector<QProcess*> proc;
QSignalMapper *stateSignalMapper, *stopSignalMapper, *restartSignalMapper;
public slots:
void openSettings();
void startRunning();
void statusChange(int);
void stopProc(int);
void restartProc(int);
void renew();
};
class SWindow : public QWidget
{
Q_OBJECT
public:
SWindow(QWidget *parent=0);
void readConfig();
void addLine(int);
private:
QVector<QPushButton*> selectButton;
QVector<QLabel*> filePath;
QVector<QLineEdit*> alias;
QSignalMapper *selectSignalMapper;
QVector<QSpinBox*> delay;
QGridLayout *mainLayout;
public slots:
void selectFile(int);
void saveFile();
void addLineSlot();
};
when i create and display SWindow object from mainWindow like this
void mainWindow::openSettings()
{
config = new SWindow();
config->show();
}
everything is ok, but now i need to access the mainWindow from SWindow, and
void mainWindow::openSettings()
{
config = new SWindow(this);
config->show();
}
doesn't display SWindow. How can i display SWindow?
How do i call a function on widget close?
By default a QWidget isn't a window. If it is not a window and you specify a parent, it will be displayed inside the parent (so in your case it is probably hidden by other widgets inside your mainWindow).
Look at windowFlags() too. Or you could make your SWindow inherit from QDialog, depending on what you use it for.
As for calling a function on widget close : you could reimplement closeEvent().
When you do config = new SWindow(this); you're setting the parent of config to be the instance of mainWindow.
This means config is no longer a top-level widget, therefore it won't display outside the mainWindow instance (specifically, it would need to be the central widget or inside the mainWindow instance's layout to be displayed).
EDIT: Sorry - I missed your last question; How do i call a function on widget close
You will want to override the QWidget::closeEvent(QCloseEvent *event) method. This gets called when you close a top-level widget. The most practical thing to do is emit() a signal so that another class can handle it having been closed.
As noted by Leiaz, you can use the windowsFlags flag when you create the widget. It would look like this:
void mainWindow::openSettings()
{
config = new SWindow(this, Qt::window);
config->show();
}
To reimplement the closeEvent:
header:
protected:
virtual void closeEvent ( QCloseEvent * event )
cpp:
void sWindow::closeEvent(QCloseEvent *event)
{
this->parentWidget()->SomeFunction();
qWidget::closeEvent(event);
}
However, its probably better to use signal/slots for your case here. Since you said you want to call the parent's renew method on some button click in sWindow, what you want is to EMIT a signal everytime the button is clicked, and connect this signal in the parent with the parent's refresh slot.
void sWindow::sWindow()
{
...
connect(ui.button, SIGNAL(clicked()), this, SLOT(btnClicked()));
}
void sWindow::btnClicked()
{
// whatever else the button is supposed to do
emit buttonClicked();
}
and in your parent class
void mainWindow::openSettings()
{
config = new SWindow(this, Qt::window);
connect(config, SIGNAL(buttonClicked()), this, SLOT(refresh()));
config->show();
}