QSlider reporting incorrect value for valueChanged() - c++

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

Related

How to get the row number of widget placed in a cell of Qtablewidget when it get clicked?

What i'm trying is to get the row number of QcomboBox when user selects items. Although its easy to to get the cell column and row using
cellClicked(int,int)
signal, but it only works when there is no widget on the cell.
so how to get the row number in case if there is a widget placed in a cell.
Note: All the combobox are added dynamically
At-last i found 2 ways of doing it.
By setting the property of QComboBox
Using the QSignalMapper
First Method
QComboBox* mCombo = new QCombobox();
mComboBox->setProperty("row",(int) i); // i represents the row number in qtablewidget
In handler function where you are handling the clicked QComboBox
int row = sender()->property("row").toInt();
Second Method
QSignalMapper *signalMapper= new QSignalMapper(this); //Create a signal mapper instance
for (each row in table) {
QComboBox* mCombo = new QComboBox();
table->setCellWidget(row,col,combo);
connect(mCombo, SIGNAL(currentIndexChanged(int)), signalMapper, SLOT(map()));
/*connect each signal of QComboBox to signal Mapper slot (i.e map()) which in turns connected to the signal of signalMapper calling the SLOT associated with it (i.e rowFinder) */
signalMapper->setMapping(combo, (int)row); //assign mapping to each widgetusing set mapping
}
connect(signalMapper, SIGNAL(mapped(int)),
this, SLOT(rowFinder(int)));
function : rowFinder(int rowIndex)
int row = rowIndex; //here is the row indexof selected QComboBox

QT how to emit a signal in this case

I need to develop a Sudoku game. After reading a text file containing the number values, I create 9*9 widgets.
If (the value is already set then I instantiate to a qlabel containing the number,
else I instantiate a combobox containing the possible values of each case).
Until here everything is OK.
The problem is that when a value is chosen from the combobox, I need to draw it in a square in my view (MVC). The problem is how can I know which one was chosen?
The only signal can I use from combobox signals is currenttextchanged(QString), but I'll not know which combo made that signal.
The ideal for me would be something like this SIGNAL(curretextchanged(QString, int, int)), but I don't know if I can define a new signal?
Here is some code:
QWidget *tab[9][9];
SudModel *modele = ???;
QComboBox *combobox = new QComboBox();
combobox->setStyleSheet("border: 1px solid red");
int tmp = modele->valuesof(i, j).size();
for (int s = 0; s < tmp; s++) {
combobox->addItem(QString::number(modele->valuesof(i, j)[s]));
}
connect(combobox, SIGNAL(currentTextChanged(QString)), this, SLOT(update()));
tab[i][j] = combobox;
One solution would be to attach that extra information when you connect to the signal for a specific combobox.
For example, say you have your function update(QString text, int x, int y) then you could attach the signal to a lambda that calls the function with the extra arguments, captured at connect time. Something like this:
connect(combobox, &QComboBox::currentTextChanged, [x, y, this](const QString& text){ this->update(text, x, y); });
That would then call the update functions with the x and y values captured when the connection was made along with the text argument that originated from the signal.
You can ask the slot for a sender()
QObject *QObject::sender() const
Returns a pointer to the object that sent the signal, if called in a
slot activated by a signal; otherwise it returns 0. The pointer is
valid only during the execution of the slot that calls this
function from this object's thread context.
You can use QSignalMapper.
In this you map every item with mapper with any specific string. Now when any mapped item emits signal you can know which item emitted it by the string.
You can use the same with minor changes like using Signal 'currentTextChanged' as mentioned. But the same result can be obtained by using 'currentIndexChanged' signal with some modifications.
signalMapper = new QSignalMapper(this);
for (int i = 0; i < 2; ++i) {
QComboBox *combo = new QComboBox();
connect(combo, SIGNAL(currentIndexChanged()), signalMapper, SLOT(map()));
signalMapper->setMapping(combo, "combo" + i);
}
connect(signalMapper, SIGNAL(mapped(QString)),
this, SIGNAL(indexChanged(QString)));
NOTE : (source Qt Assistant)
QSignalMapper class collects a set of parameterless signals, and
re-emits them with integer, string or widget parameters corresponding
to the object that sent the signal

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

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
}

QTableView row remove

I created a table view like this:
I have a create button to create new rows and as you can see I defined a button for each row to delete that row by this code:
int i = 0;
QPushButton *viewButton;
QStandardItemModel *model;
void MainWindow::on_pushButton_clicked()
{
model->appendRow(new QStandardItem(QString("")));
viewButton = new QPushButton();
viewButton->setText("Delete " + QString::number(i));
ui->tableView->setIndexWidget(model->index(i , 7), viewButton);
connect(viewButton , SIGNAL(clicked()) , this , SLOT(button_clicked()));
i++;
}
and I created a slot for each button clicked for remove a row:
void MainWindow::button_clicked()
{
// by this line I can get the sender of signal
QPushButton *pb = qobject_cast<QPushButton *>(QObject::sender());
}
as you can see I know witch button sends signal and now I need to delete that row.
here is my question:
how can I get the row of sender button in table view to remove that row?
I searched everywhere and I didn’t realise how to get the row and column of an item.
On workaround is to use QObject::setObjectName and set some names to the buttons you add :
viewButton.setObjectName(QString("%1").arg(i));
And in button_clicked slot you can retrieve the row number using the object name :
void MainWindow::button_clicked()
{
// by this line I can get the sender of signal
QPushButton *pb = qobject_cast<QPushButton *>(QObject::sender());
int row = pb->objectName().toInt();
}
Note that you should update object names after a row is removed.
Another way is to use the QSignalMapper class which collects a set of parameterless signals, and re-emits them with integer, string or widget parameters corresponding to the object that sent the signal. So you can have one like:
QSignalMapper * mapper = new QSignalMapper(this);
QObject::connect(mapper,SIGNAL(mapped(int)),this,SLOT(button_clicked(int)));
When adding your buttons in each row of table view, you can connect the clicked() signal of the button to the map() slot of QSignalMapper and add a mapping using setMapping so that when clicked() is signaled from a button, the signal mapped(int) is emitted:
viewButton = new QPushButton();
viewButton->setText("Delete " + QString::number(i));
ui->tableView->setIndexWidget(model->index(i , 7), viewButton);
QObject::connect(viewButton, SIGNAL(clicked()),mapper,SLOT(map()));
mapper->setMapping(but, i);
This way whenever you click a button in a row, the mapped(int) signal of the mapper is emitted containing the row number and consequently button_clicked is called with a parameter containing the row number.
Also here you should update the mappings in button_clicked slot since the row is removed when you click an item.

QSignalMapper and original Sender()

I have a bunch of QComboBoxes in a table. So that I know which one was triggered I remap the signal to encode the table cell location (as described in Selecting QComboBox in QTableWidget)
(Why Qt doesn't just send the cell activated signal first so you can use the same current row/column mechanism as any other cell edit I don't know.)
But this removes all knowledge of the original sender widget. Calling QComboBox* combo = (QComboBox* )sender() in the slot fails, presumably because sender() is now the QSignalMapper.
I can use the encoded row/column to lookup the QComboBox in the table widget but that seems wrong. Is there a more correct way to do it?
e.g.:
// in table creator
_signalMapper = new QSignalMapper(this);
// for each cell
QComboBox* combo = new QComboBox();
connect(combo, SIGNAL(currentIndexChanged(int)), _signalMapper, SLOT(map()));
_signalMapper->setMapping(combo, row);
// and finally
connect(_signalMapper, SIGNAL(mapped(int)),this, SLOT(changedType(int)));
// slot
void myDlg::changedType(int row)
{
QComboBox* combo = (QComboBox* )sender(); // this doesn't work !!
}
EDIT: Added for future search: there is a new book "Advanced Qt Programming" by Mark Summerfield that explains how to do this sort of thing.
Why not connect the QComboBox's signal straight to your slot?
QComboBox *combo = ...
connect(combo, SIGNAL(currentIndexChanged(int)), this, SLOT(changedType(int)));
And then in your slot you can use the sender() method to retrieve the QComboBox that was changed.
void myDlg::changedType(int row)
{
QComboBox *combo = qobject_cast<QComboBox *> sender();
if(combo != 0){
// rest of code
}
}
Alternatively, to use the QSignalMapper method you would just need to change your slot to use the mapping you set up:
void myDlg::changedType(int row)
{
QComboBox *combo = qobject_cast<QComboBox *>(_signalMapper->mapping(row));
if(combo != 0){
// rest of code
}
}
I don't know exact answer, but maybe you should use: QComboBox* combo = qobject_cast(sender()) instead of QComboBox* combo = (QComboBox* )sender(). Someting like this:
QObject* obj = sender();
QComboBox* combo = qobject_cast<QComboBox*>(obj);
if(combo)
{
doSomethingWithCombo(combo);
}
else
{
// obj is not QComboBox instance
}
But maybe QSignalMapper really substitutes itself instead of real sender...