Qt push button is calling 2 slots and I only need one - c++

I need to call a 2 functions with different buttons
I have this code:
signalMapperSelections = new QSignalMapper();
QPushButton *selected_type_button = new QPushButton();
selected_type_button->setObjectName("selected_type_button");
selected_type_button->setText(get_selected_type().replace(" ", "\n"));
selected_type_button->setMinimumHeight(80);
selected_type_button->setMinimumWidth(80);
selected_type_button->setMaximumHeight(80);
selected_type_button->setMaximumWidth(80);
selected_type_button->setStyleSheet(style_toolbutton);
ui->verticalLayout_selections->addWidget(selected_type_button);
connect(selected_type_button, SIGNAL(clicked()), signalMapperSelections, SLOT(map()));
signalMapperSelections->setMapping(selected_type_button, get_selected_type());
connect(signalMapperSelections, SIGNAL(mapped(QString)), this, SLOT(show_brands(QString)));
QPushButton *selected_brand_button = new QPushButton();
selected_brand_button->setObjectName("selected_brand_button");
selected_brand_button->setText(get_selected_brand().replace(" ", "\n"));
selected_brand_button->setMinimumHeight(80);
selected_brand_button->setMinimumWidth(80);
selected_brand_button->setMaximumHeight(80);
selected_brand_button->setMaximumWidth(80);
selected_brand_button->setStyleSheet(style_toolbutton);
ui->verticalLayout_selections->addWidget(selected_brand_button);
connect(selected_brand_button, SIGNAL(clicked()), signalMapperSelections, SLOT(map()));
signalMapperSelections->setMapping(selected_brand_button, get_selected_brand());
connect(signalMapperSelections, SIGNAL(mapped(QString)), this, SLOT(show_models(QString)));
When I click "selected_type_button" I only want to run "show_brands". But it's running both functions, "show_brands" and "show_models"...
I tried Qt::UniqueConnection, but it doesn't fix this problem.
I think this is happening because both buttons are using the same signal... But I don't know how to fix it.
How can I fix this?

When I click "selected_type_button" I only want to run "show_brands".
There is no reason to use QSignalMapper in your situation at all. You just have to connect clicked signal from selected_type_button to the show_brands slot, and clicked signal from selected_brand_button to the show_models slot.
QString is the selected type or selected brand....
This QString argument has nothing to do with the clicked signal's source (So, it does not need any mapping using QSignalMapper, read about QSignalMapper in the docs here). The mapping you are currently using is set up at the connection time (not at emit time) , this means that get_selected_type()/get_selected_brand() will return the selected items at the time of calling setMapping (this is obviously not what you meant).
To get the item at the time of clicking the button, you can call your get_selected_type()/get_selected_brand() functions in your slots directly, your code will be something like this:
QPushButton *selected_type_button = new QPushButton();
selected_type_button->setObjectName("selected_type_button");
selected_type_button->setText(get_selected_type().replace(" ", "\n"));
selected_type_button->setMinimumHeight(80);
selected_type_button->setMinimumWidth(80);
selected_type_button->setMaximumHeight(80);
selected_type_button->setMaximumWidth(80);
selected_type_button->setStyleSheet(style_toolbutton);
ui->verticalLayout_selections->addWidget(selected_type_button);
//Qt 5 new connect syntax (replace ClassName with the current class's name)
connect(selected_type_button, &QPushButton::clicked, this, &ClassName::show_brands);
QPushButton *selected_brand_button = new QPushButton();
selected_brand_button->setObjectName("selected_brand_button");
selected_brand_button->setText(get_selected_brand().replace(" ", "\n"));
selected_brand_button->setMinimumHeight(80);
selected_brand_button->setMinimumWidth(80);
selected_brand_button->setMaximumHeight(80);
selected_brand_button->setMaximumWidth(80);
selected_brand_button->setStyleSheet(style_toolbutton);
ui->verticalLayout_selections->addWidget(selected_brand_button);
//replace ClassName with the current class's name)
connect(selected_brand_button, &QPushButton::clicked, this, &ClassName::show_models);
and your show_brands slot should look something like:
//no need for the QString argument
void ClassName::show_brands(){
QString selectedType= get_selected_type();
//show_brands here
}
the same thing for show_models slot:
void ClassName::show_models(){
QString selectedBrand= get_selected_brand();
//show_models here
}

Related

QSlider reporting incorrect value for valueChanged()

I'm trying to connect a QSlider object to a QLineEdit such to enable a user to either specify a value using the slider or direct input into a form. The goal here is when the slider position changes, we update the text in the QLineEdit box and vice versa. However, when I try to report out the value of QSlider->valueChanged(), I'm just getting back a value of 1, regardless of where the slider position is set to. What am I doing incorrectly?
Here is my MWE:
QSlider* decay_slider = new QSlider(Qt::Horizontal, this);
decay_slider->setMinimum(1);
decay_slider->setMaximum(11000); // increments of 0.1 years
decay_slider->setTickPosition(QSlider::TicksBothSides);
QLineEdit* le_decay_time = new QLineEdit(this);
// Map slider position signal to LineEdit text update
QSignalMapper* mapper = new QSignalMapper(this);
QObject::connect(mapper,
SIGNAL(mapped(const QString&)),
le_decay_time,
SLOT(setText(const QString&)));
QObject::connect(decay_slider, SIGNAL(valueChanged(int)), mapper, SLOT(map()));
mapper->setMapping(decay_slider,
QString::number(decay_slider->sliderPosition()));
(This is Qt-5.15, if it matters.)
Your call to setMapping hard-codes the value of sliderPosition to the initial version.
Good thing we have lambdas now, so you can replace the entire second paragraph with:
QObject::connect(decay_slider, &QSlider::valueChanged, le_decay_time,
[=](int value) { le_decay_time->setText(QString::number(value)); });

Qt RightClick on QListWidget Opens Contextmenu and Delete Item

I want to know how can I open a popup menu when I right click on the table items. In the popup menu some actions like add and delete should be given, which will create a new row or delete the selected row.
I am a new in the Qt world, so if anybody can give me the full details (with code if possible) then I will be really grateful towards him/her.
Thank you.
My goal: Only in the area of QListWidget and only if you click on an item, the menu with Delete will be opened.
Edit : Ok I solved the problem with the QListWidget and the menu. Now the following must be accomplished:
If you click on an item with the right mouse button, and then click Delete, then the item will be deleted.
My code:
void ProvideContextMenu(const QPoint &); // MainWindow.h
// In MainWindow.cpp
ui->listFiles->setContextMenuPolicy(Qt::CustomContextMenu);
connect(ui->listFiles, SIGNAL(customContextMenuRequested(const QPoint &)),
this, SLOT(ProvideContextMenu(const QPoint &)));
void MainWindow::ProvideContextMenu(const QPoint &pos)
{
QPoint item = ui->listFiles->mapToGlobal(pos);
QMenu submenu;
submenu.addAction("ADD");
submenu.addAction("Delete");
QAction* rightClickItem = submenu.exec(item);
if (rightClickItem && rightClickItem->text().contains("Delete") )
{
ui->listFiles->takeItem(ui->listFiles->indexAt(pos).row());
}
}
Edit2 : Ok I solved the whole problem :D. I uploaded my code, if somebody needs something like that it can help him/her.
Firstly you need to create slot for opening context menu:
void showContextMenu(const QPoint&);
At constructor of your class, which used QListWidget, set context menu policy to custom and connect QListWidget::customContextMenuRequested(QPoint) signal and showContextMenu() slot like this:
MainWindow::MainWindow(QWidget *parent) :
QMainWindow(parent)
{
setupUi(this);
listWidget->setContextMenuPolicy(Qt::CustomContextMenu);
connect(listWidget, SIGNAL(customContextMenuRequested(QPoint)), this, SLOT(showContextMenu(QPoint)));
}
Then need to realize context menu opening:
void MainWindow::showContextMenu(const QPoint &pos)
{
// Handle global position
QPoint globalPos = listWidget->mapToGlobal(pos);
// Create menu and insert some actions
QMenu myMenu;
myMenu.addAction("Insert", this, SLOT(addItem()));
myMenu.addAction("Erase", this, SLOT(eraseItem()));
// Show context menu at handling position
myMenu.exec(globalPos);
}
After this we need to realize slots for adding and removing QListWidget elements:
void MainWindow::eraseItem()
{
// If multiple selection is on, we need to erase all selected items
for (int i = 0; i < listWidget->selectedItems().size(); ++i) {
// Get curent item on selected row
QListWidgetItem *item = listWidget->takeItem(listWidget->currentRow());
// And remove it
delete item;
}
}
As you can see we iterate all selected items (for set multiple selection mode use setSelectionMode() method) and delete it by ourself, because docs says that
Items removed from a list widget will not be managed by Qt, and will
need to be deleted manually.
Adding some items is easier, my solution with static variable for different item caption looks like:
void MainWindow::addItem()
{
static int i = 0;
listWidget->addItem(QString::number(++i));
}
To simplify your code use Qt5 sytax for signals and slots. It eliminates the need to create intermediate slots.
I hope it helps to you.
It's much simpler than the accepted answer. You don't need to deal with creating a context menu or cursor positions or any of that. Instead of Qt::CustomContextMenu, use Qt::ActionsContextMenu and just add your actions directly to the widget:
MainWindow::MainWindow(QWidget *parent) :
QMainWindow(parent)
{
ui->setupUi(this);
// you can create the actions here, or in designer
auto actInsert = new QAction("Insert", this);
auto actDelete = new QAction("Delete", this);
// you can set up slot connections here or in designer
connect(actInsert, SIGNAL(triggered()), this, SLOT(addItem()));
connect(actDelete, SIGNAL(triggered()), this, SLOT(eraseItem()));
// and this will take care of everything else:
listWidget->setContextMenuPolicy(Qt::ActionsContextMenu);
listWidget->addActions({ actInsert, actDelete });
}
void MainWindow::addItem () {
...; // add an item
}
void MainWindow::eraseItem () {
...; // erase an item
}
Everything above except addActions (I think) can also be done in Designer.
Alternatively, if you don't want to add actual slot functions for whatever reason, you can do everything in a lambda on connect, too:
MainWindow::MainWindow(QWidget *parent) :
QMainWindow(parent)
{
ui->setupUi(this);
// you can create the actions here, or in designer
auto actInsert = new QAction("Insert", this);
auto actDelete = new QAction("Delete", this);
connect(actInsert, &QAction::triggered, [=]() {
...; // add an item
});
connect(actDelete, &QAction::triggered, [=]() {
...; // erase an item
});
// and this will take care of everything else:
listWidget->setContextMenuPolicy(Qt::ActionsContextMenu);
listWidget->addActions({ actInsert, actDelete });
}
The signal/slot option is more organized and flexible, but the lambda option is good for short highly specialized bits of code (or binding to functions that aren't slots).
This works for context menus on any widget. Also, the same QAction can be used on multiple widgets.

How to pass a QString to a Qt slot from a QMenu via QSignalMapper or otherwise

I have a QMenu with many submenus. These are dynamically created i.e. the names menus come from a db and created in a loop. Now i wanted to fire the same slot triggered() or similar when a menu is clicked, but i needed the QString menu name to be passed to slot so i could perform menu specific actions. I have tried this i.e. passing a QAction * to the triggered event and used setData but i am getting the run time error.
object::connect: No such signal QAction::triggered(QAction *)
for(int j=0; j<channelTypes[i].getNumChannels() ; j++){
QAction *subMenuAct = subMenu->addAction(tr(c_name)); // c_name the menu name
subMenuAct->setData(ch_name);
connect(subMenuAct, SIGNAL(triggered(QAction *)), this, SLOT(playChannel(QAction *))); // playChannel is the slot
}
void <ClassName>::playChannel(QAction *channelAction)
{
QString str = channelAction->data().toString();
qDebug() << "Selected - " << str;
}
Alternately, i have also tried QSignalMapper where signalMapper is a data member initialized in the constructor
signalMapper = new QSignalMapper(this);
and
for(int j=0; j<channelTypes[i].getNumChannels() ; j++){
QAction *subMenuAct = subMenu->addAction(tr(c_name));
connect(subMenuAct, SIGNAL(triggered()), signalMapper, SLOT(map()));
signalMapper->setMapping(subMenu, ch_name);
connect(signalMapper, SIGNAL(mapped(QString)), this, SLOT(playChannel(QString)));
}
In the second case, i don't get any error, however the slot function playChannel is not being called. Would really appreciate if some one could help resolving it.
Update 1: The only difference that i see from other examples i have seen is that usually people are connecting signals from several widgets to a single slot (say different buttons). In my case i am connecting several sub menus (with different names) to a single slot. Should this make any difference?
Update 2: It worked after the correction suggested in the solution below for QSignalMapper. Also the fact that i was using SubMenu as argument to setMapping , where as MenuAction item should have been used instead. But now i am getting event fired multiple times i.e. as many times as there are entries in the main menu for the selected sub menu category. If channel type is English (main menu) with four entries), HBO, star movies etc. (sub menu), and i choose HBO, then event is fired four times with string HBO. It works fine if i create a separate signal mapper for each submenu. But i was hoping a single mapper should be used and i am doing something incorrectly here. Some more details in the comments to the answer.
After adding the QAction to the menu, you only have to connect QMenu to the slot. You don't connect each action individually to the slot:
for(int j=0; j<channelTypes[i].getNumChannels() ; j++){
ch_name = <name from the database for the channel j>;
QAction *subMenuAct = subMenu->addAction(tr(ch_name));
subMenuAct->setData(ch_name);
}
connect(subMenu, SIGNAL(triggered(QAction *)),
this, SLOT(playChannel(QAction *)), Qt::UniqueConnection);
As I don't know how you if you delete subMenu each time the dynamic menu is filled, the Qt::UniqueConnection ensure that the slot won't be reconnected multiple times.
For the signal mapper version, you should only connect the actions to the mapper in the loop. The connection from the mapper to the slot should only be done once.
for(int j=0; j<channelTypes[i].getNumChannels() ; j++){
ch_name = <name from the database for the channel j>;
QAction *subMenuAct = subMenu->addAction(tr(ch_name));
connect(subMenuAct, SIGNAL(triggered()), signalMapper, SLOT(map()));
signalMapper->setMapping(subMenuAct, ch_name);
}
connect(signalMapper, SIGNAL(mapped(QString)), this, SLOT(playChannel(QString)));
And for that case, the slot playChannel should accept a QString instead of a QAction*.

Qt, PushButton, id attribute? Any way to know which button was clicked

void MainWindow::addRadioToUI()
{ int button_cunter=4;
while(!database.isEmpty())
{ button_cunter++;
QPushButton *one = new QPushButton("Play: "+name(get_r.getTrackId()));
one->setIcon(QIcon(":/images/play_button.png"));
one->setMaximumWidth(140);
one->setFlat(true);
QGroupBox* get_rGB = new QGroupBox("somethink");
QFormLayout* layout = new QFormLayout;
if(button_cunter%5 == 0){
layout->addWidget(one);
}
get_rGB->setLayout(layout);
scrollAreaWidgetContents->layout()->addWidget(get_rGB);
}
}
I have a few QPushButtons which are added automaticlly.
Is there a way to add "id attribute or sth else" to button and next know which button was clicked? I have different action for each button.
QApplication offers sender() which contains which object sent the signal. So you can do:
//slot, this could also be done in a switch
if(button[X] == QApplication::sender()){
doX();
}else if(button[Y] == QApplication::sender()){
doY();
}
http://doc.qt.io/qt-4.8/qobject.html#sender
QSignalMapper is pretty good for this type of thing.
You would define your slot like this for instance:
public slots:
void clicked(int buttonId); // or maybe trackId
Then add a QSignalMapper* member to your class and connect it to that slot:
signalMapper = new QSignalMapper(this);
connect(signalMapper, SIGNAL(mapped(int)),
this, SLOT(clicked(int)));
In the addRadioToUI, after creating your push button, do:
signalMapper.setMapping(one, button_cunter);
// or trackId if that's more practical
If all you need is a pointer to the object that triggered the signal though, you can use the static QOjbect::sender function in your slot to get a handle to that.
Use QButtonGroup. It takes id as a parameter when a button is added and provides the id to a slot when a button in the group is pressed.

Qt4 existing slots are not recognized

I am currently trying to complete a project using Qt4 and C++. I am using buttons to switch between states. While trying to connect the buttons' clicked() signals to the textEdit to display the relevant state, I got stuck on an error:
Object::connect No such slot
QTextEdit::append("move state")
Object::connect No such slot
QTextEdit::append("link state")
Only, QTextEdit definitely has an append(QString) slot.
Any ideas?
Some code samples:
QPushButton *move = new QPushButton("Move");
connect(move, SIGNAL(clicked()), textEdit, SLOT(append("move state")));
You can't pass in an argument (literally) to the append() slot when making a signal to slot connection.
You refer to it like a method signature:
SLOT(append(QString)) //or const QString, I'm not sure
If you need the textbox to append the words "move state" every time that button is clicked, then you should define your own slot that will do the append.
Chris has it in a nutshell.
That is one of the many reasons I like boost::signals a lot more (you are allowed to use boost::bind). You are basically going to need to create another function that captures the signal and then performs the append.
...
QPushButton *move = new QPushButton("Move");
connect(move, SIGNAL(clicked()), textEdit, SLOT(MoveState()));
}
...
void MyTextEdit::MoveState()
{
append("move state");
}
Use a QSignalMapper to pass a hard-coded argument to the text edit's slot.
Example:
QSignalMapper* signalMapper = new QSignalMapper(this);
QPushButton* move = new QPushButton("Move");
signalMapper->setMapping(move, QString("move state"));
connect(move, SIGNAL(clicked()), signalMapper, SLOT(map()));
connect(signalMapper, SIGNAL(mapped(QString)), textEdit, SLOT(append(QString)));
Beware of the bugs in the above code.
Assuming you will have other QPushButtons that will cause other states to occur, you could put them inside a QButtonGroup. Then, you can use an enumeration, such as { MOVE_ID, STOP_ID, ... } to refer to the possible states.
QPushButton* move = new QPushButton( "Move" ) ;
QPushButton* stop = new QPushButton( "Stop" ) ;
QButtonGroup* buttonGroup = new QButtonGroup() ;
buttonGroup->addButton( move, MOVE_ID ) ;
buttonGroup->addButton( stop, STOP_ID ) ;
// Connecting QButtonGroup to writing function
connect( buttonGroup, SIGNAL( buttonClicked( int ) ),
textEdit, SLOT( append( int ) ) ) ;
In textEdit, you'll define a function that appends the appropriate text depending on the state in which you get.
void append( int i )
{
switch ( i )
{
case MOVE_ID:
textEdit->append( "move state" ) ;
break ;
case STOP_ID:
textEdit->append( "stop state" ) ;
break ;
}
}