Drawing lines programmatically with Qt - c++

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

Related

How to set visibility on widgets within a subclass of QWidgetAction

Qt 5.6.3, eglfs Linux platform.
I have a selection of classes derived from QWidgetAction. The QWidgetActions are all parented from a menu, and the widgets they contain are parented from the same menu. The contained widgets are all set as the default widget for the QWidgetAction. Nothing has been reimplemented from QWidgetAction.
I thought that setting the visibility of the QWidgetAction would automatically set the visibility of the custom widget set contained within? Is this not true, as doing so is certainly not showing and hiding the widgets as required!? Must I do something else to pass the visibility change to the contained widgets? Must I directly request the widget from the QWidgetAction and then apply visibility to it directly (which seems like a hack)?
I'm interested in how the QWidgetActions are supposed to be implemented. The documentation is almost non-existent, so I'm after peoples experience with them as much as anything. I have intermittent issues with what looks like a double delete of a custom widget and visibility not behaving as it should.
class Base : public QWidgetAction
{
Q_OBJECT
public:
explicit Base(QWidget* parent, QString labelText = "", QString iconPath = "", Qt::AlignmentFlag alignment = Qt::AlignHCenter) :
QWidgetAction(parent),
mCustomWidget(nullptr),
mParentWidget(nullptr),
mTextLabel(nullptr),
mAlignment(alignment),
mLabelText(labelText),
mIconPath(iconPath) {}
virtual ~Base() {}
protected:
QWidget *mCustomWidget;
QWidget *createTheWidgetSet(QWidget *parent)
{
if (mParentWidget == nullptr) {
mParentWidget = new QWidget(parent);
mCustomWidget = createCustomWidget(mParentWidget);
if (mCustomWidget != nullptr) {
if (!mLabelText.isEmpty()) {
mCustomWidget->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Ignored);
}
}
int rightMargin = QApplication::style()->pixelMetric(QStyle::PM_SmallIconSize);
QBoxLayout* layout = new QBoxLayout(QBoxLayout::LeftToRight, mParentWidget);
layout->setContentsMargins(1, 2, rightMargin, 2);
if (!mLabelText.isEmpty()) {
QString some_calced_text{};
mTextLabel = new QLabel(some_calced_text, mParentWidget);
layout->addWidget(mTextLabel);
} else {
if(mAlignment == Qt::AlignLeft){
int some_calced_val{20};
layout->addSpacing(some_calced_val);
}
}
if(mAlignment == Qt::AlignRight){
layout->addStretch();
}
layout->addWidget(mCustomWidget);
if(mAlignment == Qt::AlignLeft){
layout->addStretch();
}
}
setDefaultWidget(mParentWidget);
return mCustomWidget;
}
virtual QWidget *createCustomWidget(QWidget *parent) = 0;
private:
Q_DISABLE_COPY(Base)
QWidget *mParentWidget;
QLabel *mTextLabel;
Qt::AlignmentFlag mAlignment;
QString mLabelText;
QString mIconPath;
};
class SpinBoxActionWidget : public Base
{
Q_OBJECT
public:
explicit SpinBoxActionWidget(QWidget* parent, QString labelText = "", QString iconPath = "") :
Base(parent, labelText, iconPath),
mSpinBox(nullptr)
{
createTheWidgetSet(parent);
}
virtual ~SpinBoxActionWidget() {}
QSpinBox* getSpinBox() const
{
return mSpinBox;
}
protected:
QWidget *createCustomWidget(QWidget *parent) override
{
if (mSpinBox == nullptr) {
mSpinBox = new QSpinBox(parent);
mSpinBox->setFixedHeight(22);
}
return mSpinBox;
}
private:
Q_DISABLE_COPY(SpinBoxActionWidget)
QSpinBox *mSpinBox;
};
/* Elsewhere in code.... */
{
QMenu theMenu = new QMenu(parentWindow);
SpinBoxActionWidget theAct = new SpinBoxActionWidget(theMenu);
SpinBoxActionWidget theSecondAct = new SpinBoxActionWidget(theMenu);
theMenu->addAction(theAct);
theMenu->addAction(theSecondAct);
/* I now assume that I can do this, and the entire entry in the menu
* represented by "theAct" can be made visible and invisible.
* This doesn't work however, either the widget remains visible,
* or is partially hidden.
theAct->setVisible(true);
theAct->setVisible(false);
*/
}
You are not reimplementing the interface, that's why it doesn't work.
First, note that QWidgetAction derives from QAction which is not a QWidget; however, it does have a setVisible() function, which will actually just forward the call to all widgets created by the action.
You have to reimplement QWidgetAction::createWidget(parent) to add a new widget; your createCustomWidget was doing nothing useful. Here is a very simple example:
class SpinAction : public QWidgetAction
{
Q_OBJECT
public:
SpinAction(QObject* parent) : QWidgetAction(parent) {}
virtual ~SpinAction() {}
QWidget* createWidget(QWidget* parent) { return new QSpinBox(parent); }
// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
// reimplement this function
};
You can add your action to whatever container you want, menus, toolbars, etc... This example will create a new widget for each container, and these created widgets won't be synchronized (for example on the spinbox value).
I just tested it in a main window, with a widget action added to a menu and a toolbar, and calling setVisible() works flawlessly.

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)

Events in QGraphicsView

I'm having trouble getting events in QGraphicsView working. I've subclassed QGraphicsView and tried to overload the mousePressEvent and wheelEvent. But neither mousePressEvent nor wheelEvent get called.
Here's my code (Edited a few things now):
Declaration:
#include <QGraphicsView>
#include <QGraphicsScene>
class rasImg: public QGraphicsView
{
public:
rasImg(QString file);
~rasImg(void);
initialize();
QGraphicsView *view;
QGraphicsScene *scene;
private:
virtual void mousePressEvent (QGraphicsSceneMouseEvent *event);
virtual void wheelEvent ( QGraphicsSceneMouseEvent * event );
}
Definition:
#include "rasImg.h"
void rasImg::initialize()
{
view = new QGraphicsView();
scene = new QGraphicsScene(QRect(0, 0, MaxRow, MaxCol));
scene->addText("HELLO");
scene->setBackgroundBrush(QColor(100,100,100));
view->setDragMode(QGraphicsView::ScrollHandDrag);
view->setScene(scene);
}
void rasImg::mousePressEvent (QGraphicsSceneMouseEvent *event)
{
qDebug()<<"Mouse released";
scene->setBackgroundBrush(QColor(100,0,0));
}
void rasImg::wheelEvent ( QGraphicsSceneMouseEvent * event )
{
qDebug()<<"Mouse released";
scene->setBackgroundBrush(QColor(100,0,0));
}
So, what am I doing wrong?.Why don't I see a message or background color change when I click the view or use the mouse wheel?
You're not getting the events because they're being handled by the scene object you're creating, not your class.
Remove the QGraphicsScene *scene; from your rasImg and try something like this for the constructor:
rasImg::rasImg(QString file)
: QGraphicsScene(QRect(0, 0, MaxRow, MaxCol))
{
addText("HELLO");
setBackgroundBrush(QColor(100,100,100));
setDragMode(QGraphicsView::ScrollHandDrag);
view = new QGraphicsView();
view->setScene(this);
}
If you want that in two steps, you could do:
rasImg::rasImg(QString file)
: QGraphicsScene()
{
// any constructor work you need to do
}
rasImg::initialize()
{
addText("HELLO");
setSceneRect(QRect(0, 0, MaxRow, MaxCol));
setBackgroundBrush(QColor(100,100,100));
setDragMode(QGraphicsView::ScrollHandDrag);
view = new QGraphicsView();
view->setScene(this);
}
The point is that the scene that is displayed must be an actual instance of your rasImg, not an instance of QGraphicsScene.
If it's the view you're subclassing, do the same thing. The view you're displaying must be an instance of your class, not a plain QGraphicsView.
rasImg::rasImg(QString file)
: QGraphicsView()
{
// constructor work
}
void rasImg::initialize()
{
scene = new QGraphicsScene(QRect(0, 0, MaxRow, MaxCol));
scene->addText("HELLO");
scene->setBackgroundBrush(QColor(100,100,100));
setDragMode(QGraphicsView::ScrollHandDrag);
setScene(scene);
}
QGraphicsView is derived from QWidget. Therefore it receives mouse events like regular widgets. If your code really is
void rasImg::mousePressEvent (QGraphicsSceneMouseEvent *event)
It can not receive events since it should be
void rasImg::mousePressEvent ( QMouseEvent *event )
QGraphicsSceneMouseEvent is for items in QGraphicsScene that receive mouse input.
If you would like to handle clicks on a specific GUI element rather than handle click on the whole scene, you should derive your own class either from QGraphicsItem (see example of SimpleClass here) or derive from one of existing elements, e.g. QGraphicsPixmapItem.
In both cases in your derived class you can override void mousePressEvent(QGraphicsSceneMouseEvent *event);

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

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