QListWidget::itemChanged signal triggered twice in qt - c++

I have a QListWidget to store usernames, and I use this signal to detect if username is being changed:
connect(listWidget, &QListWidget::itemChanged, this, &MainWindow::changeUserName);
void MainWindow::changeUserName(QListWidgetItem *editItem)
{
qDebug() << "Name:" << editItem->text();
}
And this is how I make QListWidget editable in another function:
connect(listWidget, &QListWidget::itemDoubleClicked, this, &MainWindow::makeListEditable);
void MainWindow::makeListEditable()
{
QListWidgetItem *editItem = listWidget->currentItem();
editItem->setFlags(editItem->flags() | Qt::ItemIsEditable);
qDebug() << "Name edit";
}
But which confused me is, whenever I double clicked the list widget, the itemChanged signal will be triggered once, and when I input a new username, the signal will be triggered again. Why would this happen?
This is the debug output, when I double clicked the list, it says:
Name: "Testing name_1"
Name edit
after I entered a new name and hit the enter, it says:
Name: "Testing name_2" //a new name I changed to
And what if I only want a signal be triggered once whenever I input a new name and hit the enter, what should I do to achieve this?
Thanks

You can use the item delegate commitData signal, this way:
QObject::connect(listWidget->itemDelegate(), SIGNAL(commitData(QWidget*)), this, SLOT(dataCommited(QWidget*)));
the slot is like this:
void dataCommited(QWidget * w)
{
QString data = (static_cast<QLineEdit *>(w))->text();
//...
}
The signal will be emitted at the end of the editing (i.e. enter key pressed or focus lost etc.)

As #Rafalon said, calling setFlags calls your slot changeUserName, and the signal currentTextChanged is emitted when the current item changed, it is the same as currentItemChanged except it gives you the text instead of the item.
What you can do is, make your item editable as you instantiate it:
QListWidgetItem* pItem = new QListWidgetItem();
pItem->setText("Testing name_1");
pItem->setFlags(pItem->flags() | Qt::ItemIsEditable);
listWidget->addItem(pItem);
or you can activate/deactivate the connection when you need it:
void MainWindow::makeListEditable(QListWidgetItem *editItem)
{
editItem->setFlags(editItem->flags() | Qt::ItemIsEditable);
connect(ui->listWidget, SIGNAL(itemChanged(QListWidgetItem *)), this, SLOT(changeUserName(QListWidgetItem *)));
qDebug() << "Name edit";
}
void MainWindow::changeUserName(QListWidgetItem *editItem)
{
qDebug() << "Name:" << editItem->text();
disconnect(ui->listWidget, SIGNAL(itemChanged(QListWidgetItem *)), this, SLOT(changeUserName(QListWidgetItem *)));
}
but you will have to make another connection when the current element is not modified after a double click, maybe with the signal currentItemChanged.

Related

Passing parameters from QtMessageBox signals to buttonClick signal Handler slot

I am trying to create a popup that will (1) be non-modal, (2) carry context data that will be handled later when the user clicks the ok event. So far I have the code below which does pop up as a non-modal. I know that msgBox->open(this, SLOT(msgBoxClosed(QAbstractButton *)) and msgBoxClosed(QAbstractButton *button) work but when I added the QStringList collisionSections to the SLOT parameter. I get this error:
QObject::connect: No such slot MainWindow::msgBoxClosed(QAbstractButton *, collisionSections) in src\mainwindow.cpp:272
QObject::connect: (receiver name: 'MainWindow')
which I understand because it is declaring the SLOT there, but I don't know how to go about doing what I want which is passing in the QString as contents to my signal and have it play well with the buttonClicked() event that qmessagebox throws on the OK click. I could also be approaching this the wrong way, please let me know if so. Any help is much appreciated!
void MainWindow::do_showCollisionEvt(QStringList collisionSections)
{
QString info = "Resolve sections";
for (QString section : collisionSections)
{
if (!section.isEmpty())
{
info.append(" [" + section + "] ");
qDebug() << "Emitting node off for:" << section;
emit nodeOff(section);
}
}
QMessageBox *msgBox = new QMessageBox;
msgBox->setAttribute(Qt::WA_DeleteOnClose);
msgBox->setText("Collision event detected!");
msgBox->setInformativeText(info);
msgBox->setStandardButtons(QMessageBox::Ok);
msgBox->setDefaultButton(QMessageBox::Ok);
msgBox->setModal(false);
msgBox->open(this, SLOT(msgBoxClosed(QAbstractButton *, collisionSections)));
}
void MainWindow::msgBoxClosed(QAbstractButton *button, QStringList collisionSections) {
QMessageBox *msgBox = (QMessageBox *)sender();
QMessageBox::StandardButton btn = msgBox->standardButton(button);
if (btn == QMessageBox::Ok)
{
for (QString section : collisionSections)
{
if (!section.isEmpty())
{
qDebug() << "Emitting nodeON for:" << section;
emit nodeOn(section);
}
}
}
else
{
throw "unknown button";
}
}
First of all, the open() connects your slot to the finished() singnal with no arguments or to buttonClicked() signal if first slot argument is pointer (your case).
Second. you are not correctly passing param. You can't do it during declaration, the parameters that slot recieves are set in signal emission, which in your case you can't control. In SLOT you could only declare the arguments TYPES not their values (note the SLOT is just a macro, in reality result of SLOT(...) is just a string (char*)).
I suggest you to use the setProperty() method on message box, e.g.:
msgBox->setModal(false);
msgBox->setProperty("collisionSections", collisionSections);
msgBox->open(this, SLOT(msgBoxClosed(QAbstractButton *)));
And your slot could look like then:
void MainWindow::msgBoxClosed(QAbstractButton *button) {
QMessageBox *msgBox = (QMessageBox *)sender();
QStringList collisionSections = msgBox->property("collisionSections").toStringList();

I want to get the names of my spinboxes in Qt

How can I pull the names of my spinBoxes? I tried looking at a lot of the documentation, however, I couldn't find anything that would show the names of each of the child spinBoxes. I've tried changing the result to a string. However, I just get a Hex or Long Int, of the address I’d imagine, returned instead.
QList<QSpinBox*> spinBoxes= findChildren<QSpinBox*>();
//create the QSignalMapper object
QSignalMapper* signalMapper= new QSignalMapper(this);
//loop through your spinboxes list
QSpinBox* spinBox;
foreach(spinBox, spinBoxes){
//setup mapping for each spin box
connect(spinBox, SIGNAL(valueChanged(int)), signalMapper, SLOT(map()));
signalMapper->setMapping(spinBox, spinBox);
}
//connect the unified mapped(QWidget*) signal to your spinboxWrite slot
connect(signalMapper, SIGNAL(mapped(QWidget*)), this, SLOT(spinboxWrite(QWidget*)));
.
.
.
void GuiTest::SpinBoxChanged(QWidget* wSp){
QSpinBox* sp= (QSpinBox*)wSp; //now sp is a pointer to the QSpinBox that emitted the valueChanged signal
int value = sp->value(); //and value is its value after the change
//do whatever you want to do with them here. . .
qDebug() << value << "SpinBoxChanged";
}
void GuiTest::spinboxWrite(QWidget* e){
SpinBoxChanged(e);
QString* value = (QString*)e;
qDebug() << e << value << " SpinBoxWrite";
}
Please note qDebug() << e as this is where I'm having trouble getting some information about the spinboxes
The name you are trying to retrieve is the objectName property, which every QObject and QObject-derived class has. Call objectName() to retrieve this value.
You can also use this with the QObject::findChild() function.
This should get what you want:
void GuiTest::spinboxWrite(QWidget* e){
SpinBoxChanged(e);
qDebug() << e->objectName() << " SpinBoxWrite";
And will output:
"norm_spinBox_10" SpinBoxWrite
Note
This line is dangerous:
QSpinBox* sp= (QSpinBox*)wSp;
Use qobject_cast instead of C-style casts.
There is no direct way to get the name of a variable as a string.
However, you can use a QMap<QSpinBox*, QString> to map each spin-box to its name.
In the constructor you have to assign these manually:
map[ui->spinBox] = "spinBox";
map[ui->spinBoxWithStrangeName] = "spinBoxWithStrangeName";
Then you can simply get the strings using:
QString name = map[ui->spinBox];
Just give them names in the designer file and then use that name to retrieve them in the C++ code.
QSpinBox* mySpinner = findChild<QSpinBox*>("myGivenName");

Removing item from QListWidget from inside a Widget

I have a QListWidget in my MainWindow that displays a list of VideoWidgets (a custom QWidget).
VideoWidget has a clickable label where on clicking the label it should delete a file and then remove the QListItem which holds the VideoWidget from the QListWidget. Here is my VideoWidget class:
VideoWidget::VideoWidget(QWidget *parent) : QWidget(parent)
{
ClickableLabel *smallRed = new ClickableLabel(this)
//...
QObject::connect(smallRed,SIGNAL(clicked()),this,SLOT(removeVideo()));
}
void VideoWidget::removeVideo(){
//...code to remove a file
QListWidget* list = myParent->getList();
QListWidgetItem* item = list->takeItem(list->currentIndex().row());
myList->removeItemWidget(item);
}
The problem is that clicking the smallRed label will not select its item in the QListWidget which means that list->currentIndex().row() will return -1. Clicking anywhere else in the Widget does select the current item. For the code to work I currently have to first click anywhere in the VideoWidget and then click its ClickableLabel. Is there any way I can achieve the same effect with one single click on my ClickableLabel?
From your previous qestion, we suggested use signal and slots. For example:
for(int r=0;r<3;r++)
{
QListWidgetItem* lwi = new QListWidgetItem;
ui->listWidget->addItem(lwi);
QCheckBox *check = new QCheckBox(QString("checkBox%1").arg(r));
check->setObjectName("filepath");
connect(check,SIGNAL(clicked()),this,SLOT(echo()));
ui->listWidget->setItemWidget(lwi,check);
}
Slot:
void MainWindow::echo()
{
qDebug() << sender()->objectName() << "should be remmoved";
}
It is not unique way to solve this problem, but it shows all main things, with signals and slots mechanism, objectName and sender() you can achieve all what you need.
sender() return object which send signal, you can cast it, but if you need only objectName you should not cast.

Forward Key Press from QGraphicsView

I'm attempting to forward all key press events from my QGraphicsView to a widget that is currently on the scene.
My QGraphicsView looks like this:
Character_controller::Character_controller(Game_state * game_state) : Game_base_controller(game_state) {
this->character = new Character(this->game_state);
this->scene->addWidget(this->character);
connect(this, SIGNAL(keyPress(QKeyEvent *)), this->character, SLOT(update()));
}
And then, my character which subclasses QWidget, which should recieve all keypress events
Character::Character(Game_state * game_state) : Base_object(game_state) {
}
Character::~Character() {
}
void Character::update() {
cout << "HELLO FROM TIMER CONNECTED ITEM" << endl;
}
For some reason, this isn't working. How can I forward all keypress events from the view to my character?
The error I get is this:
Object::connect: No such signal game::Character_controller::keyPress(QKeyEvent *) in implementation/game_controllers/character_controller.cpp:21
keyPress(QKeyEvent*) doesn't exist as a signal, hence the error message that you're getting. As such, you can't do this:
connect(this, SIGNAL(keyPress(QKeyEvent *)), this->character, SLOT(update()));
In order to capture key press events in your graphics view, you will need to override the keyPressEvent function:
void Character_controller::keyPressEvent(QKeyEvent* event)
{
// Call functions on your character here.
switch (event->key())
{
case Qt::Key_A:
character->moveLeft(); // For example
break;
case Qt::Key_D:
character->moveRight(); // For example
break;
...
}
// Otherwise pass to QGraphicsView.
QGraphicsView::keyPressEvent(event);
}
You could just pass the QKeyEvent to the character to manage its own key presses, but you might find it difficult to ensure that different items in your scene don't rely on the same key(s) if you don't keep all your key press handling code in one place.
You have to override the keyPressEvent event to capture key press events

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.