Fewer connections in a Qt calculator - c++

I'm writing a simplified calculator using Qt with C++, for learning purposes. Each number is a QPushButton that uses the same slot to modify the text in a lineEdit widget being used as a display.
The slot uses the sender() method to figure out which button was pressed, so the correct number would be written on the display widget.
In order to have all the buttons working, I'd have to write a connection to each one of them, kinda like this:
connect(ui->button1, SIGNAL(clicked()), this, SLOT(writeNum()));
Since they all use the same slot, the only thing that changes is the button being used, so the next sender would be ui->button2, ui->button3, and so on. My question is, is there a way to reduce the number of defined connections?
Edit: Here's a useful link discussing precisely about this problem, in detail.

If you use QtDesigner or the form editor of QtCreator you can just drag lines between the 2 and it will fill in the code for you.
You could also keep all the buttons in a list structure, but I would use a QVector not a standard array.
You might also want to reconsider using the sender() method, it violates OOP design. Instead connect all the buttons to a QSignalMapper and then connect mapped() to your text box.

You should use an int in this case to identify the button which sent the signal to your slot. Essentially you use QSignalMapper for that task:
QSignalMapper sm;
QPushButton* one = new QPushButton(this);
QPushButton* two = new QPushButton(this);
QPushButton* three = new QPushButton(this);
//and so on...
sm.setMapping(one, 1);
sm.setMapping(two, 2);
sm.setMapping(three, 3);
//and so on...
connect(one, SIGNAL(clicked()), &sm, SLOT(map()));
connect(two, SIGNAL(clicked()), &sm, SLOT(map()));
connect(three, SIGNAL(clicked()), &sm, SLOT(map()));
//and so on...
connect(&sm, SIGNAL(mapped(int)), this, SLOT(yourslothere(int)));
Note: QSignalMapper is VERY useful, keep that in mind ;)

I think you can try allocating the QPushButton in a array, something like this
QPushButton* numbers = new QPushButton[10];
And then, perform the connections using a for loop
for(size_t i = 0; i < 9; ++i)
{
connect(numbers[i],SIGNAL(clicked()),this,SLOT(writeNum()));
}
But I don't think it's worth. Explicit connection, while making the code more verbose, make the connections more clear to the reader.

Related

How to have QLabel update as various numbered pushbuttons are clicked

I have a dialpad with numbers 1-9 and 0, and a QLabel above it to show the numbers when clicked(same as a keypad on any phone). All are push buttons. What is the easiest way to get the QLabel to show the numbers as the push buttons are clicked?
For example, if 2 then 0 then 7 is clicked, the label would update in real time with 207. The format of the Qlabel should follow standard phone numbers, 000-000-0000. I understand how to setText for one number at a time, but they keep overriding each other.
Any help is appreciated.
Thank you in advance
What you are looking for is a QSignalMapper. It maps multiple inputs through a single interface and does the sender dispatching for you.
QSignalMapper *mapper(new QSignalMapper(parent));
for (int i=0; i<10; ++i){
QPushButton *button = some_new_button_function();
connect(button, &QPushButton::clicked, mapper, &QSignalMapper::map);
mapper->setMapping(button, i);
}
connect(mapper, QOverload<int>::of(&QSignalMapper::mapped),
[this](int i){/*here your append code*/});
The easiest is to connect the clicked signal of the buttons to a slot (possibly a lambda) that changes the text of the QLabel (using setText()). If you want to append to the current text, then just do setText(label.text() + "new text");.
You have to connect the signals clicked() emitted by each QPushButton to a slot that update the QLabel text.
A brief example
In the parent constructor:
connect(qpb1, &QPushButton::clicked, this, &MyClass::handleQLabel);
And the possible slot implementation:
void MyClass::handleQLabel()
{
QPushButton * qpb = qobject_cast<QPushButton*>(sender()); // Find the sender of the signal
if(qpb != nullptr)
this->myLabel->setText(qpb->text()); // Write anything you want in the QLabel
else
{
// Do what you want.
}
}
This will do the job.
Of course if you don't want use sender() (for multi-threading concerns for example) you can either create one slot by QPushButton and do the same number of connect (heavy and quite a dirty workaround), or create a subclass of QPushButton to add a custom signal to emit with an identifier of the QPushButton and get it with a slot for example.
I hope it can help :)
QLineEdit might better suit your needs in this case if you also want your data representation to follow phone number standard such as "000-000-0000". You can make it read-only, disable interaction flags if you like (but from UI/UX perspective it is better not to, since mostly there is no reason to disallow copying), and also you can set input mask you like. Given your current situation, you can base your needs on the following example:
// Set your format.
ui->lineEdit->setInputMask("000-000-0000");
// Make sure that your text would be in the format you like initially.
ui->lineEdit->setText("999-999-9999");
// Text will be not editable.
ui->lineEdit->setReadOnly(true);
// And here, you can use QSignalMapper as other members have suggested. Or you can just connect multiple buttons somehow. The choice is yours to make.
connect(ui->pushButton, &QPushButton::clicked, ui->lineEdit, [this]
{
// Just keep in mind taht when returning text, some of the mask elements might be here, too.
ui->lineEdit->setText(ui->lineEdit->text().replace("-", "") + "1");
});

Adding QListWidgetItem To QListWidget

So I have a class SnapshotPanel : public QListWidget that I am trying to add a QListWidgetItem to dynamically, however it when ever I try I get a segfault. I have verified that my code to add the item is correct as I can add to the list when I construct my SnapshotPanel. However I cannot add to the panel when the code is called via signals and slot, insight into what I am missing would be apprecited.
Here is the constructor:
SnapshotPanel::SnapshotPanel(QWidget *parent):QListWidget(parent)
{
this->setViewMode(QListWidget::IconMode);
this->setIconSize(QSize(256,256));
this->setResizeMode(QListWidget::Adjust);
QIcon icon("icon.jpeg");
QListWidgetItem *widget = new QListWidgetItem(icon,"Earth");
this->addItem(widget);
}
So is there any reason I wouldn't be able to use the following code when called via signals and slots:
{
QIcon icon("icon.jpeg");
QListWidgetItem *widget = new QListWidgetItem(icon,"Earth");
this->addItem(widget);
}
I think it should just work. "Slots are normal C++ functions" according the documentation.
If you are using multiple threads you need to look into the connection mechanism. Perhaps you need to use queued connections. You would change your connect statements from:
connect(button, &QPushButton::clicked, this, &MainWidget::on_button_clicked);
to
connect(button, &QPushButton::clicked, this, &MainWidget::on_button_clicked, Qt::QueuedConnection);
But read the official documentation here. A SO question (basically pointing you back to the documentation) is here.

Passing QLabel as parameter Qt C++

I have a little GIF which is animated on a QLabel with a QMovie, and I want that when the animation of the GIF is complete, to remove the Qlabel. I tried this, but it doesn't work :
QMovie *movie = new QMovie("countdown.gif");
QLabel *processLabel = new QLabel(this);
processLabel->setMovie(movie);
movie->start();
QTimer::singleShot(1000, this, SLOT(movie_finished(backgroundLabel)));
Here is my function :
void movie_finished(QLabel *processLabel){
processLabel->deleteLater();
}
Basic misunderstanding, this is illegal:
QTimer::singleShot(1000, this, SLOT(movie_finished(backgroundLabel)));
You can't give parameters like that to connections. Just types in SLOT, like this:
QTimer::singleShot(1000, this, SLOT(movie_finished(QLabel*)));
There are (at least) three ways to solve this. First remove the QLabel* parameter from slot. Then:
Use QSignalMapper, which basically encapsulates the two alternatives below.
Create an intermediate slot in some class, which has QLabel* member variable, which it then uses in the slot without parameter, and connect the timer signal to this slot.
Use sender() method in your slot (but this is generally considered ugly, breaking encapsulation, and QSignalMapper is preferred).
Using a QTimer to synchronize the end of your movie is not really needed here.
The really simply way to accomplish this is to just have the movie delete the label when it is finished:
connect(movie, SIGNAL(finished()), processLabel, SLOT(deleteLater()));
The QMovie will emit finished() when it is done. So just wire it to the deleteLater() slot of your QLabel.
Because this might make you leak the QMovie when the QLabel is deleted, you may want to parent it to the QLabel, as setting it as the movie does not mean the QLabel actually cleans it up.
QLabel *processLabel = new QLabel(this);
QMovie *movie = new QMovie("countdown.gif");
movie->setParent(processLabel);

How to connect the same Widget to multiple QSignalMapper mappings

Following on from this question, I have a menu system as follows:
MainMenu > (Load Game button clicked) > LoadGameMenu
LoadGameMenu > (Back To Main Menu button clicked) > MainMenu
LoadGameMenu > (Load button clicked) > GameScreen
However, when I click on the Load button, the MainMenu is shown instead. Here's my code:
ApplicationWindow::ApplicationWindow()
{
resize(800, 600);
stack = new QStackedWidget(this);
signalMapper = new QSignalMapper(this);
mainMenu = new MainMenu(this);
loadGameMenu = new LoadGameMenu(this);
gameScreen = new GameScreen(this);
stack->addWidget(mainMenu);
stack->addWidget(loadGameMenu);
stack->addWidget(gameScreen);
connect(mainMenu, SIGNAL(loadGameClicked()), signalMapper, SLOT(map()));
connect(loadGameMenu, SIGNAL(backToMainMenuClicked()), signalMapper, SLOT(map()));
connect(loadGameMenu, SIGNAL(loadClicked()), signalMapper, SLOT(map()));
signalMapper->setMapping(loadGameMenu, 0);
signalMapper->setMapping(mainMenu, 1);
signalMapper->setMapping(gameScreen, 2);
connect(signalMapper, SIGNAL(mapped(int)), stack, SLOT(setCurrentIndex(int)));
setCentralWidget(stack);
}
I know I'm doing something wrong with the signal mapping, but I don't know what it is.
Cheers.
1) QSignalMapper maps a QObject* sender to a (int, string, QWidget* or QObject*). Since your "load" and "return to main menu" signals are being sent from the same sender object they will be mapped the same way. The solution is to connect the clicked signal of the different buttons to the mapper directly (rather than through your LoadGameMenu object), so the sender is different.
2) Since you're using a QStackedWidget, I would strongly consider using the QWidget* mapping of QSignalMapper, and connect to QStackedWidget::setCurrentWidget. This will be easier to maintain if/when you add more menu screens, rather than trying to keep up with which int index is which widget. It also makes things more readable in your code, in my opinion.
Edit: Looks like you're not understanding the setMapping function. I'd take a look at the docs for better a better idea. What you're doing there is mapping the first argument (the sender) to a signal to be sent (the second argument). In your original code, you're mapping LoadGameMenu to 0. Any time a signal is sent to the mapper from LoadGameMenu, 0 is sent to stackedWidget::setCurrentIndex` - which is why both buttons are returning you to the same place.
QSignalMapper relies on the sender of the signal to do the mapping, and you are connecting loadGameMenu twice.
If the sender is loadGameMenu, it will always show index 0.
It'd be probably best to connect directly to the buttons inside your loadGameMenu widget.

why append Slot doesn't work?

I have got a problem when I try to make following simple connections
QSpinBox *spinBox = new QSpinBox;
QSlider *slider = new QSlider(Qt::Horizontal);
QTextEdit *text = new QTextEdit("Hello QT!");
QObject::connect(spinBox, SIGNAL(valueChanged(int)),slider, SLOT(setValue(int)));
QObject::connect(slider, SIGNAL(valueChanged(int)),spinBox, SLOT(setValue(int)));
QObject::connect(slider,SIGNAL(valueChanged(int)),text, SLOT(append("slider changed!")));
QObject::connect(spinBox,SIGNAL(valueChanged(int)),text, SLOT(append("spinbox changed!")));
QObject::connect(text,SIGNAL(textChanged()),spinBox,SLOT(clear()));
It can be successfully compiled and excuted.But the two append slots seem not work.I've checked the help manual about QTextEdit and there's a public slot append there.Have I missed something?Help would be appreciated!
Unfortunately, you cannot pass custom values to your slots via QObject::connect (only type information for the arguments is allowed/interpreted correctly). Instead, create your own slot, something like
void MyWidget::mySliderChangedSlot(int newValue)
{
text->append("slider changed!");
}
and use
QObject::connect(slider, SIGNAL(valueChanged(int)), pMyWidget, SLOT(mySliderChangedSlot(int)));
to achieve your desired behaviour.
I hope that helps.
What exactly are you trying to do? That has now way of working because you connect a signal which has an int param to a slot with a string parameter for one, the other thing is that the signal slots where not meant for this kind of usage you just say wich function are conected and they pass parameters betwen them you dont pass the values yourself, you are not using them correctly read the documentation at http://doc.trolltech.com/4.6/signalsandslots.html for correct usage examples.