Qt almost same main menu entries - qt

I'm using c++ Qt library and I want to do something which would do :
connect(actionB11, SIGNAL(triggered()), this, SLOT(SetSomething(1, 1)));
connect(actionB12, SIGNAL(triggered()), this, SLOT(SetSomething(1, 2)));
connect(actionB21, SIGNAL(triggered()), this, SLOT(SetSomething(2, 1)));
connect(actionB22, SIGNAL(triggered()), this, SLOT(SetSomething(2, 2)));
The code above doesnt work because SIGNAL function has to have same number and argument types as SLOT function.
Does exist a way how to do it? I dont want to have about 20 function as SetSomething11, SetSomething12 calling SetSomething(1, 1) etc.

In situations like this you have three simple options:
connect each QAction to its own slot (not good)
use a QSignalMapper
add each QAction to a QActionGroup and use the QActionGroup::triggered(QAction*) signal, coupled with setting each QAction's data (see QAction::setData() and QAction::data())
When you set the data for a QAction, you can only store one QVariant (i.e., one value). So if you want two values, I would recommend just creating a simple mapping, like this:
void Window::onActionGroupTriggered(QAction *action);
{
int i = action->data().toInt();
int a, b;
a = i / 10;
b = i - 10;
setSomething(a, b); // for example if i = 15, then a = 1 and b = 5
}

You may modify QAction class.
class QMyAction : public QAction
{
Q_OBJECT
QMyAction ( QObject * parent ) :
QAction(parent), _x(0), _y(0)
{
connect(this, SIGNAL(triggered(bool)), this, SLOT(re_trigger(bool)));
}
QMyAction ( const QString & text, QObject * parent ) :
QAction (text, parent), _x(0), _y(0)
{
connect(this, SIGNAL(triggered(bool)), this, SLOT(re_trigger(bool)));
}
QMyAction ( const QIcon & icon, const QString & text, QObject * parent ) :
QAction(icon, text, parent), _x(0), _y(0)
{
connect(this, SIGNAL(triggered(bool)), this, SLOT(re_trigger(bool)));
}
void setX(int x)
{
_x = x;
}
int getX()
{
return _x;
}
void setY(int y)
{
_y = y;
}
int getY()
{
return _y;
}
public slots:
void re_trigger(bool)
{
emit triggered(_x, _y);
}
signals:
void triggered(int,int);
private:
int _x;
int _y;
};
Now, you can connect triggered(int,int) to SetSomething(int,int). But, you have to set x and y. Unless, they will always be 0.

You cannot use constant in SLOT signature, you have to use types there. When connecting signal to slot the slot must have the same subset of parameters signal has, otherwise they cannot be connected and QObject::connect() will return false.
connect(actionB11, SIGNAL(triggered()),
this, SLOT(SetSomething()));
This slot takes no parameters, but you can use QObject::sender() to get pointer to the object, which emitted the signal. Then this pointer can be used to discriminate the source of the signal:
void SetSomething() {
switch(sender()) {
case actionB11;
// do something
break;
case actionB12;
// do something
break;
case actionB21;
// do something
break;
case actionB22;
// do something
break;
default:
// Exceptional situation
}
}
Alternatively you can use QSignalMapper to append additional discriminating parameters to slots.

Related

Connecting signals coming from widgets in a QVector

I'm working on the following Window with QT:
For my rows i have the following structure:
typedef struct
{
struct{
int x;
int y;
int width;
int height;
int layer;
int idx;
}outputSettings;
QDoubleSpinBox *xSpinBox;
QDoubleSpinBox *ySpinBox;
QDoubleSpinBox *heightSpinBox;
QDoubleSpinBox *widthSpinBox;
QDoubleSpinBox *layerSpinBox;
// Checkboxes
QCheckBox *channelCheckBox;
}myUI;
QVector<myUI> inputBoxes; // Create a row of input boxes per channel
I then create them in a for loop:
for(i = 0; i < inputChannels; ++i)
{
inputBoxes[i].channelCheckBox = new QCheckBox;
inputBoxes[i].channelCheckBox->setChecked(true);
inputBoxes[i].xSpinBox = new QDoubleSpinBox;
inputBoxes[i].xSpinBox->setRange(minXPos, maxXPos);
inputBoxes[i].xSpinBox->setSingleStep(1);
inputBoxes[i].xSpinBox->setValue(0);
inputBoxes[i].xSpinBox->setDecimals(0);
connect(inputBoxes[i].xSpinBox, SIGNAL(valueChanged(double)), this, SLOT(setXValue(double)));
inputBoxes[i].ySpinBox = new QDoubleSpinBox;
inputBoxes[i].ySpinBox->setRange(minYPos, maxYPos);
inputBoxes[i].ySpinBox->setSingleStep(1);
inputBoxes[i].ySpinBox->setValue(0);
inputBoxes[i].ySpinBox->setDecimals(0);
connect(inputBoxes[i].ySpinBox, SIGNAL(valueChanged(double)), this, SLOT(setYValue(double)));
...
Now i get stuck on the connect. I want to connect the valueChanged property of my spinboxes to my outputSettings struct. This struct will be my return type at the end.
I implemented the following slots:
public slots:
void setXValue(double x){inputBoxes[0].outputSettings.x = int(x);}
void setYValue(double y){inputBoxes[0].outputSettings.y = int(y);}
...
But here i don't know what vector item called the function. (currently i just entered inputBoxes[0] as a dummy)
My first idea was to add an extra parameter int channel. But then the connect doesn't work. So i tried to work around that with QMapper. But that doesn't seem to be a good option to me and i didn't really get it running.
I would largely appreciate if someone could help me out here or at least point me in the right direction.
Cheers.
Implement it by using a lambda function in your connect
connect(inputBoxes[i], static_cast<void (QDoubleSpinBox::*)(double)>(&QDoubleSpinBox::valueChanged),
[i](double value)
{
// use i as your vector index here
handleDoubleSpinBoxChanged(i, value);
});
Then you can change your slot function to
void handleDoubleSpinBoxChanged(int i, double value)
{
inputBoxes[i].outputSettings.x = int(x);
}
Second option is to get the spin box index from the sender object
you will have to store it first inside your loop
inputBoxes[i].xSpinBox->setProperty("index",i);
Then you can get it
void MainWindow::setXValue(double d)
{
QDoubleSpinBox * sb = qobject_cast<QDoubleSpinBox *>(QObject::sender());
int iCallerVectorIndex = -1;
if (sb != Q_NULLPTR)
{
iCallerVectorIndex = sb->property("index").toInt(); // to get the caller index.
}
}
If I got you right, in your SLOT method you can call sender() to retrieve the object who emitted the signal. You can compare in a loop the spinBoxes of each of your inputBoxes to find out what caused the SLOT to execute, something like:
// in your SLOT method:
for (int i = 0; i < inputChannels; i++){
if (inputBoxes[i].xSpinBox == (QDoubleSpinBox *)sender()){
// the xSpinBox of the i-th inputBox emitted the signal
break();
}
}
You can also just make myUI a QObject and add slot function there.
You wouldnt need any indexes then.
typedef struct
{
Q_OBJECT
struct{
int x;
int y;
int width;
int height;
int layer;
int idx;
}outputSettings;
QDoubleSpinBox *xSpinBox;
QDoubleSpinBox *ySpinBox;
QDoubleSpinBox *heightSpinBox;
QDoubleSpinBox *widthSpinBox;
QDoubleSpinBox *layerSpinBox;
// Checkboxes
QCheckBox *channelCheckBox;
public slots:
setXValue(double);
setYValue(double);
}myUI;
Example of connect call:
connect(inputBoxes[i].ySpinBox, SIGNAL(valueChanged(double)), inputBoxes[i], SLOT(setYValue(double))
or you can call connect in constructor of myUI:
myUI() {
connect(xSpinBox, SIGNAL(valueChanged(double)),
this, SLOT(setXValue(double))
connect(ySpinBox, SIGNAL(valueChanged(double)),
this, SLOT(setYValue(double))
}
I think that would be much simpler and intuitive because your object is responsible to setting his own members and you dont need to remember any indexes.

Qt: How to implement QDialogButtonBox with QSignalMapper for non-standard button ??

I have a QDialogButtonBox with All Standard Button and Non Standard Buttons (QPushbutton added to create Non-Standard Buttons).
I can implement SignalMapper saperately for Non-Standard Buttons.But in this case i have to add 2 slots (Slot for QDialogButtonBox Standard Buttons & Slot for Non-Standard Buttons).
I need to implement QSignalMapper with common slot for Standard Buttons as well as Non-Standard buttons.
Can anyone guide me for it ?
You shouldn't need to use QSignalMapper with QDialogButtonBox.
QSignalMapper is used to connect multiple signals to a single slot, and QDialogButtonBox already has a single signal that is emitted for all the buttons: clicked(QAbstractButton*).
You could simply assign a value to the button with a map (QMap, std::map) or through a dynamic property:
enum { MyRole1 = 1, MyRole2 }; // starting at 1 because an unset property would return 0
...
userButton−>setProperty("ActionRole", MyRole1); // a cast to int might be needed here
buttonBox->addButton(userButton, QDialogButtonBox::ActionRole);
connect(this, SIGNAL(clicked(QAbstractButton*)), SLOT(dialogButtonClicked(QAbstractButton *button)));
And in the slot, you would get the value back from the parameter passed by the signal:
void MyClass::dialogButtonClicked(QAbstractButton *button) {
StandardButton standardButton = buttonBox−>standardButton(button);
switch(standardButton) {
// Standard buttons:
case QDialogButtonBox::Ok:
...
break;
case QDialogButtonBox::Abort:
...
break;
// Non-standard buttons:
case QDialogButtonBox::NoButton:
int actionRole = button->property("ActionRole").toInt();
switch(actionRole) {
case MyRole1:
...
break;
case MyRole2:
...
break;
default:
// shouldn't happen
break;
}
}
}
QSignalMapper can map a QObject and a integer. So you can map your non-standardButton with its role and create a slot that takes in parameter a role:
For example:
void Widget::initMap() {
QPushButton* buttonReset = new QPushButton( "Reset" );
signalMapper = new QSignalMapper(this);
connect(buttonReset, SIGNAL(clicked()), signalMapper, SLOT(map()));
signalMapper->setMapping(button, QDialogButtonBox::ResetRole);
connect(signalMapper, SIGNAL(mapped(int)), this, SLOT(slot(int)));
}
void Widget::slot( int role) {
if ( role == QDialogButtonBox::ResetRole ) {
reset();
} else if ( QDialogButtonBox::Apply ) {
apply();
}
}

multiple signals for one slot

For my GUI i would like to have two pairs of buttons that scroll up and down a scrollarea. The first set of buttons should work on say scrollarea1 and the second set of buttons should work on a scrollarea2. The widgets that I put in the scrollarea are called viewport1 and viewport2.
Since both both set of buttons should do the same (scrolling up and down) I thought I would make two slots called scrollUp and scrollDown that would handle the scrolling for both sets of buttons. Unfortunately I cannot make this work and need some help. I have tried the following:
QPushButton up;
QPushButton down;
QPushButton up2;
QPushButton down2;
connect(&up,SIGNAL(clicked()),&up,SLOT(scrollUp()));
connect(&up2,SIGNAL(clicked()),&up,SLOT(scrollUp()));
connect(&down,SIGNAL(clicked()),&down,SLOT(scrollDown()));
connect(&down2,SIGNAL(clicked()),&down,SLOT(scrollDown()));
void MainWindow::scrollDown()
{
QScrollArea area;
QWidget view;
if((QPushButton) &sender = down)
{
area=scrollArea;
view=viewport;
}
if((QPushButton) &sender = down2)
{
area=scrollArea;
view=viewport;
}
int curpos = area.verticalScrollBar()->value();
area.verticalScrollBar()->setValue(curpos+15);
int newpos = area.verticalScrollBar()->value();
QPoint topLeft = area.viewport()->rect().topLeft();
view.move(topLeft.x(),topLeft.y()-(newpos));
}
void MainWindow::scrollUp()
{
QScrollArea area;
QWidget view;
if((QPushButton) &sender = up)
{
area=scrollArea;
view=viewport;
}
if((QPushButton) &sender = up2)
{
area=scrollArea2;
view=viewport2;
}
int curpos = area.verticalScrollBar()->value();
area.verticalScrollBar()->setValue(curpos-15);
int newpos = area.verticalScrollBar()->value();
QPoint topLeft = area.viewport()->rect().topLeft();
view.move(topLeft.x(),topLeft.y()-(newpos));
}
But this doesn´t work for several reasons. I also tried giving the slot some arguments, something like:
connect(&up,SIGNAL(clicked()),&up,SLOT(scrollUp(scrollarea1,viewport1)));
connect(&up2,SIGNAL(clicked()),&up,SLOT(scrollUp(scrollarea2,viewport2)));
But again, no succes. Can anybody help me?
First of all, "It doesn't work" does not mean anything, and it is hard to help you if you do not say what errors you get. Then, there are few problems.
All QObject's derived classes are not copiable, it means you can not do
QWidget a;
QWidget b;
b = a; // Wrong
You should use pointers (or perhaps references).
QWidget a;
QWidget * b = new QWidget(...);
QWidget * c;
c = & a; // Ok
c = b; // Ok
Then your connect calls are wrong:
connect(&up, SIGNAL(clicked()), &up, SLOT(scrollUp()));
The third argument is the object who has the slot. up is a QPushButton, it does not have a scrollUp() slot, it is your MainWindow who does:
connect(&up, SIGNAL(clicked()), this, SLOT(scrollUp()));
(since connect is called in MainWindow's constructor this points to the current MainWindow object).
Also in C++ the single = sign means assignment, for equality comparison use =='. Andsender` is a function.
Your approach should work if implemented in the right way:
class MainWindow: public QWidget
{
QScrollArea * scroll1;
QScrollArea * scroll2;
QWidget * view1;
QWidget * view2;
QPushButton * up1;
QPushButton * up2;
QPushButton * down1;
QPushButton * down2;
public:
MainWindow()
{
// Here initialize member variables.
...
connect(up1, SIGNAL(clicked()), this, SLOT(scrollUp()));
connect(up2, SIGNAL(clicked()), this, SLOT(scrollUp()));
connect(down1, SIGNAL(clicked()), this, SLOT(scrollDown()));
connect(down2, SIGNAL(clicked()), this, SLOT(scrollDown()));
}
public slots:
void scrollDown()
{
QScrollArea * area;
QWidget * view;
if(qobject_cast<QPushButton>(sender()) == down1) {
area = & scroll1;
view = & view1;
} else if(qobject_cast<QPushButton>(sender()) == down2) {
area = & scroll2;
view = & view2;
} else {
// Error.
}
// Now `area` and `view` point to the right widgets.
...
}
void scrollUp()
{
// The same as before.
}
};
Another approach would be to extract the actual scrolling instructions to a separate function:
class MainWindow: public QWidget
{
// Same variables as before
...
public:
MainWindow()
{
// Here initialize member variables.
...
connect(up1, SIGNAL(clicked()), this, SLOT(scrollUp1()));
connect(up2, SIGNAL(clicked()), this, SLOT(scrollUp2()));
connect(down1, SIGNAL(clicked()), this, SLOT(scrollDown1()));
connect(down2, SIGNAL(clicked()), this, SLOT(scrollDown2()));
}
public slots:
void scrollDown(QScrollArea * area, QWidget * view)
{
// Here you scroll over `area` and `view`.
}
void scrollDown1()
{
scrollDown(scroll1, area1);
}
void scrollDown2()
{
scrollDown(scroll2, area2);
}
// Again, the same for `scrollUp`.
};
There are several mistakes in your code :
About the sender of the signal : There is not a QObject called "sender" but a method QObject * QObject::sender() const; which returns a pointer on the sender of the signal.
In the if conditions : you are casting a QPushButton** into a QPushButton ((QPushButton) &sender) and you dont compare that thing with your buttons up(2) and down(2).
In your connections between slots and signals : the scrollUp and scrollDown slots do not belong to the QPushButton class but to your MainWindow class.
Finally, you should write something like this :
connect(&up, SIGNAL(clicked()), this, SLOT(scrollUp()));
connect(&up2, SIGNAL(clicked()), this, SLOT(scrollUp()));
connect(&down, SIGNAL(clicked()), this, SLOT(scrollDown()));
connect(&down2, SIGNAL(clicked()), this, SLOT(scrollDown()));
void MainVindow::scrollDown() {
// [...]
QPushButton * senderButton = qobject_cast<QPushButton *>(this->sender());
// QPushButton * senderButton = (QPushButton *) this->sender(); works too
if (senderButton == &down) {
// [...]
}
if (senderButton == &down2) {
// [...]
}
// [...]
}
void MainVindow::scrollUp() {
// [...]
QPushButton * senderButton = qobject_cast<QPushButton *>(this->sender());
// QPushButton * senderButton = (QPushButton *) this->sender(); works too
if (senderButton == &up) {
// [...]
}
if (senderButton == &up2) {
// [...]
}
// [...]
}
First of all the slot can have no other arguments than the signal hands to it. Clicked has no arguments and there fore the slot can have no arguments.
I would think that the easiest way to check whether scrollArea 1 or 2 has focus and decide from that which one should move.
I also think that there is an error in your code. Shouldn't this:
if((QPushButton) &sender = down2)
{
area=scrollArea;
view=viewport;
}
Be this:
if((QPushButton) &sender = down2)
{
area=scrollArea2;
view=viewport2;
}
First of all, this is pseudo code. It won't compile, but it should contain the necessary information.
I believe this problem can be most elegantly solved using the QSignalMapper class. It allows parameterless signals from multiple senders to connect to one slot.
In the header, write something like this:
class QSignalMapper;
class MainWindow : public QMainWindow
{
public:
void init();
public slots:
void handleScrollButtons(int id);
private:
enum { ScrollUp1, ScrollDown1, ScrollUp2, ScrollDown2 } // just makes it more convenient to use
QSignalMapper *m_scrollbuttonhandler;
}
In the source file, write something like this
#include <QSignalMapper>
void MainWindow::init()
{
m_scrollbuttonhandler = new QSignalMapper(this);
m_scrollbuttonhandler->setMapping(scrollup1button, ScrollUp1);
m_scrollbuttonhandler->setMapping(scrolldown1button, ScrollDown1);
m_scrollbuttonhandler->setMapping(scrollup2button, ScrollUp2);
m_scrollbuttonhandler->setMapping(scrolldown2button, ScrollDown2);
connect(scrollup1button, SIGNAL(clicked(bool)), m_scrollbuttonhandler, SLOT(map()));
connect(scrolldown1button, SIGNAL(clicked(bool)), m_scrollbuttonhandler, SLOT(map()));
connect(scrollup2button, SIGNAL(clicked(bool)), m_scrollbuttonhandler, SLOT(map()));
connect(scrolldown2button, SIGNAL(clicked(bool)), m_scrollbuttonhandler, SLOT(map()));
connect(m_scrollbuttonhandler, SIGNAL(mapped(int)), this, SLOT(handleScrollButtons(int)));
}
void MainWindow::handleScrollButtons(int id)
{
switch (id)
{
case ScrollUp1:
// stuff to do for scrollup1button
case ScrollDown1:
// stuff to do for scrolldown1button
case ScrollUp2:
// stuff to do for scrollup2button
case ScrollDown2:
// stuff to do for scrolldown2button
}
}

Need QGraphicsScene signal or event for _after_ change

I use QGraphicsScene of the Qt framework. Inside the scene I have some QGraphicsItems which the user can select and move.
I would like to have an info label where the current x and y coordinate of the currently moved selection (can consist of many items) is displayed.
I have tried with the signal changed of QGraphicsScene. But it is fired before the x() and y() property of the items is set to the new values. So the labels always show the second-to-last coordinates. If one moves the mouse slowly, the display is not very wrong. But with fast moves and sudden stops, the labels are wrong. I need a signal that is fired after the scene hast changed.
I have also tried to override the itemChange method of QGraphicsItem. But it is the same. It is fired before the change. (The new coordinates are inside the parameters of this method, but I need the new coordinates of all selected items at once)
I have also tried to override the mouseMove events of QGraphicsScene and of QGraphicsView but they, too, are before the new coordinates are set.
I did a test: I used a oneshot timer so that the labels are updated 100 ms after the signals. Then everything works fine. But a timer is no solution for me.
What can I do?
Make all items un-moveable and handle everything by my own?
QGraphicsItem::itemChange() is the correct approach, you were probably just checking the wrong flag. Something like this should work fine:
QVariant::myGraphicsItem( GraphicsItemChange change, const QVariant &value )
{
if( change == QGraphicsItem::ItemPositionHasChanged )
{
// ...
}
}
Note the use of QGraphicsItem::ItemPositionHasChanged rather than QGraphicsItem::ItemPositionChange, the former is called after the position changes rather than before.
The solution is to combine various things that you're already doing. Instrument itemChange, looking for and count the items with updated geometry. Once you've counted as many items as there are in the current selection, fire off a signal that will have everything ready for updating your status. Make sure you've set the QGraphicsItem::ItemSendsGeometryChanges flag on all your items!
This code was edited to remove the lag inherent in using a zero-timer approach. Below is a sscce that demonstrates it.
You create circles of random radius by clicking in the window. The selection is toggled with Ctrl-click or ⌘-click. When you move the items, a centroid diamond follows the centroid of the selected group. This gives a visual confirmation that the code does indeed work. When the selection is empty, the centroid is not displayed.
I've gratuitously added code to show how to leverage Qt's property system so that the items can be generic and leverage the notifier property of a scene if it has one. In its absence, the items simply don't notify, and that's it.
// https://github.com/KubaO/stackoverflown/tree/master/questions/scenemod-11232425
#include <QtWidgets>
const char kNotifier[] = "notifier";
class Notifier : public QObject
{
Q_OBJECT
int m_count = {};
public:
int count() const { return m_count; }
void inc() { m_count ++; }
void notify() { m_count = {}; emit notification(); }
Q_SIGNAL void notification();
};
typedef QPointer<Notifier> NotifierPointer;
Q_DECLARE_METATYPE(NotifierPointer)
template <typename T> class NotifyingItem : public T
{
protected:
QVariant itemChange(QGraphicsItem::GraphicsItemChange change, const QVariant &value) override {
QVariant v;
if (change == T::ItemPositionHasChanged &&
this->scene() &&
(v=this->scene()->property(kNotifier)).isValid())
{
auto notifier = v.value<NotifierPointer>();
notifier->inc();
if (notifier->count() >= this->scene()->selectedItems().count()) {
notifier->notify();
}
}
return T::itemChange(change, value);
}
};
// Note that all you need to make Circle a notifying item is to derive from
// NotifyingItem<basetype>.
class Circle : public NotifyingItem<QGraphicsEllipseItem>
{
QBrush m_brush;
public:
Circle(const QPointF & c) : m_brush(Qt::lightGray) {
const qreal r = 10.0 + (50.0*qrand())/RAND_MAX;
setRect({-r, -r, 2.0*r, 2.0*r});
setPos(c);
setFlags(QGraphicsItem::ItemIsMovable | QGraphicsItem::ItemIsSelectable |
QGraphicsItem::ItemSendsGeometryChanges);
setPen({Qt::red});
setBrush(m_brush);
}
};
class View : public QGraphicsView
{
Q_OBJECT
QGraphicsScene scene;
QGraphicsSimpleTextItem text;
QGraphicsRectItem centroid{-5, -5, 10, 10};
Notifier notifier;
int deltaCounter = {};
public:
explicit View(QWidget *parent = {});
protected:
Q_SLOT void gotUpdates();
void mousePressEvent(QMouseEvent *event) override;
};
View::View(QWidget *parent) : QGraphicsView(parent)
{
centroid.hide();
centroid.setRotation(45.0);
centroid.setPen({Qt::blue});
centroid.setZValue(2);
scene.addItem(&centroid);
text.setPos(5, 470);
text.setZValue(1);
scene.addItem(&text);
setRenderHint(QPainter::Antialiasing);
setScene(&scene);
setSceneRect(0,0,500,500);
scene.setProperty(kNotifier, QVariant::fromValue(NotifierPointer(&notifier)));
connect(&notifier, &Notifier::notification, this, &View::gotUpdates);
connect(&scene, &QGraphicsScene::selectionChanged, &notifier, &Notifier::notification);
}
void View::gotUpdates()
{
if (scene.selectedItems().isEmpty()) {
centroid.hide();
return;
}
centroid.show();
QPointF centroid;
qreal area = {};
for (auto item : scene.selectedItems()) {
const QRectF r = item->boundingRect();
const qreal a = r.width() * r.height();
centroid += item->pos() * a;
area += a;
}
if (area > 0) centroid /= area;
auto st = QStringLiteral("delta #%1 with %2 items, centroid at %3, %4")
.arg(deltaCounter++).arg(scene.selectedItems().count())
.arg(centroid.x(), 0, 'f', 1).arg(centroid.y(), 0, 'f', 1);
this->centroid.setPos(centroid);
text.setText(st);
}
void View::mousePressEvent(QMouseEvent *event)
{
const auto center = mapToScene(event->pos());
if (! scene.itemAt(center, {})) scene.addItem(new Circle{center});
QGraphicsView::mousePressEvent(event);
}
int main(int argc, char *argv[])
{
QApplication app{argc, argv};
View v;
v.show();
return app.exec();
}
#include "main.moc"

Qt signal to specific object's slot

I would like to know which of the following is the proper way of doing thing with signal/slot in Qt.
I need a way to have multiple instance of a Dialog, i.e: A and B. And I need to tell A to print "A" and B to print "B" from a different thread. So I believe I need something like either:
OPTION 1) A->print("A") and B->print("B")
or is it better to do:
OPTION 2) emit print("A") and emit print("B") and use a way that I don't know so only A catch the "A" and only B catch the "B".
I got the option 1 working like this:
class myClass : public QMainWindow
{
Q_OBJECT
public:
myClass (QWidget *parent = 0, Qt::WFlags flags = 0);
~myClass ();
void doPrint(char* text)
{
emit mySignal(text);
}
private:
Ui::myClass ui;
public slots:
void newLog(char* msg);
signals:
void mySignal(char* msg);
};
myClass::myClass(QWidget *parent, Qt::WFlags flags) : QMainWindow(parent, flags)
{
ui.setupUi(this);
connect(this, SIGNAL(mySignal(char*)), this, SLOT(newLog(char*)));
}
void myClass::newLog(char* msg)
{
ui.textEdit->append(msg);
}
and then all I have to do is:
myClass* instanceA = new myClass();
myClass* instanceB = new myClass();
instanceA->doPrint("A");
instanceB->doPrint("B");
is this right?
Thanks!
Since your slot is in another thread, you have to use the Meta-Object System to invoke the method asynchronously. The proper way to do this is to use QMetaObject::invokeMethod
DO NOT subclass QThread and override the run method. For details on this see: https://www.qt.io/blog/2010/06/17/youre-doing-it-wrong
void otherClass::printTo(myClass* instance, char* text)
{
QMetaObject::invokeMethod(instance, // pointer to a QObject
"doPrint", // member name (no parameters here)
Qt::QueuedConnection, // connection type
Q_ARG(char*, text)); // parameters
}
void myClass::doPrint(char* text)
{
ui.textEdit->append(text);
}
myClass* instanceA = new myClass();
myClass* instanceB = new myClass();
printTo(instanceA, "A");
printTo(instanceB, "B");
If the char* type hasn't been registered with the Meta-Object System yet, do so with
Q_DECLARE_METATYPE(char*);
then:
qRegisterMetaType<char*>("charPtr");
In this simplified example, I think you are on the correct path with option 1. However, it would be even better if you didn't need the doPrint() method, which would also eliminate the need for the mySignal signal (at least in myClass). Instead, I would suggest inheriting your threads from QThread if the aren't already, and doing something like this:
class myThread : public QThread
{
Q_OBJECT
public:
myThread (QWidget *parent = 0 ) : QThread(parent) {}
~myThread () {}
void run(char* text)
{
emit mySignal(text);
}
signals:
void mySignal(char* msg);
};
Then you need to do something like this:
myClass* instanceA = new myClass();
myThread* threadA = new myThread();
connect(threadA, SIGNAL(mySignal(char*)), instanceA, SLOT(newLog(char*)), Qt::QueuedConnection);
threadA->run( "A" );
Obviously, in most non-example code, you'd not pass the string into run, but rather generate strings to be run as threadA is running. The advantage is that this keeps the thread considerations out of myClass, and you only need to think about them where they are connected. On the flip side, you introduce fewer dependencies into the threads, since they don't need to know about myClass to be able to log.

Resources