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();
Related
I connect two signals to same slot. like this:
check = new QCheckBox();
connect(check, SIGNAL(clicked()), this, SLOT(MySlot()));
connect(check, SIGNAL(toggled(bool)),this,SLOT(MySlot()));
I don't want to define an other slot. In MySlot is it possible to recognize which signal callbacks the slot?
How can I do this?
You might be able to use the QMetaObject/QMetaMethod data associated with the sender to get what you want (untested)...
void MyClass::MySlot ()
{
auto index = senderSignalIndex();
if (index == sender()->indexOfSignal("clicked()")) {
/*
* Got here as the result of a clicked() signal.
*/
} else if (index == sender()->indexOfSignal("toggled(bool)")) {
/*
* Got here as the result of a toggled(bool) signal.
*/
}
}
Rather than that, however, if you're using Qt5 I would suggest making use of the new signal/slot syntax along with lambdas...
check = new QCheckBox();
connect(check, &QCheckBox::clicked,
[this]()
{
MySlot(false);
});
connect(check, &QCheckBox::toggled,
[this](bool toggled)
{
MySlot(true, toggled);
});
Along with a change to the signature of MySlot...
/**
* #param from_toggled_signal If true this call was triggered by a
* QCheckBox::toggled signal, otherwise it's
* the result of a QCheckBox::clicked signal.
*
* #param toggle_value If from_toggled_signal is true then this was the
* value passed to QCheckBox::toggled, otherwise unused.
*/
void MyClass::MySlot (bool from_toggled_signal, bool toggle_value = false)
{
.
.
.
}
New slots can be defined on the fly using lambdas :)
class MyClass : public QWidget {
QSomeLayout m_layout{this};
QCheckBox m_check;
enum Signal { Clicked, Toggled };
Q_SLOT void mySlot(Signal);
public:
MyClass( ... ) : ... {
m_layout.addWidget(&m_check);
connect(&m_check, &QCheckBox::clicked, this, [this]{ mySlot(Clicked); });
connect(&m_check, &QCheckBox::toggled, this, [this]{ mySlot(Toggled); });
}
};
You can also add your own context to the signal if that helps. For instance I had a service that downloaded user avatars for multiple windows. I needed the window to only load the user it was interested in something so I would pass in the user's id as the context. Something like:
void UserService::downloadAvatar(const QString& url, const int context = 0) {
...// Make the http request, on finished:
emit onAvatarDownloaded(context, responseBody);
}
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.
Implementation:
void Test::addProcessToList(const QString &command, const QString &id, const BasicInfo &basicInfo) {
QProcess *console = new QProcess();
QSignalMapper* signalMapper = new QSignalMapper (this) ;
connect (console, SIGNAL(readyRead()), signalMapper, SLOT(map())) ;
connect (console, SIGNAL(finished(int)), signalMapper, SLOT(processFinished(int))) ;
signalMapper->setMapping (console, id) ;
connect (signalMapper, SIGNAL(mapped(int)), this, SLOT(pidOut(QString))) ;
console->start(command);
}
void Test::registerProcess(QString id) {
QProcess *console = qobject_cast<QProcess*>(QObject::sender());
QByteArray processOutput = console->readAll();
int mainPID = parsePID(processOutput);
BasicInfo basicInfo;
qDebug() << "Registering id: " + id + " mainPID: " + mainPID;
if(mainPID != 0) {
Main::getInstance()->addProcessToList(mainPID, packageId, basicInfo);
} else {
qWarning() << "pidOut Error fetching mainPID";
}
}
void Test::processFinished(int exitCode) {
QProcess *console = qobject_cast<QProcess*>(QObject::sender());
QByteArray processOutput = console->readAll() + QString("Finished with code %1").arg(exitCode).toLatin1();
qDebug() << " processFinished: " + processOutput;
}
prototypes:
private
void addProcessToList(const QString &command, const QString &id, const BasicInfo &basicInfo);
private slots:
void registerProcess(QString);
void processFinished(int);
I get this errors when I call connect, which tells me I'm doing it wrong:
"QObject::connect: Incompatible sender/receiver arguments
QSignalMapper::mapped(int) --> Test::registerProcess(QString)"
I'm not understanding where I'm suppose to specify my parameter (QString id) so that registerProcess will receive it when it's called? I'm assuming I'm doing this part wrong, cut from above:
signalMapper->setMapping (console, id) ;
connect (signalMapper, SIGNAL(mapped(int)), this, SLOT(pidOut(QString))) ;
QSignalMapper can emit either mapped(const QString & text) or mapped(int i) signals. The type is defined by setMapping(QObject * sender, int id) or setMapping(QObject * sender, const QString & text).
That led to confusion probably by autocompletion in
connect (signalMapper, SIGNAL(mapped(int)), this, SLOT(pidOut(QString)));
The types of signal and slot must be the same for connection.
You set string mapping (QString &id), so the signal in the connection should be QString:
connect (signalMapper, SIGNAL(mapped(QString)), this, SLOT(pidOut(QString)));
Update
After deeper review of the code flow I suspect that you wanted to connect mapper to registerProcess() slot instead of pidOut(). In that slot you can have as an argument QString id that was passed to signalMapper in setMapping() call. That is the purpose of using QSignalMapper.
However, beside that id it is not possible to extract console pointer, since in that case sender() is signalMapper object. If it is the case, QSignalMapper cannot help you here. You should use direct connection of console and this on readReady (of course with slot of this with void argument as readReady()). To get the string id in that slot it is possible to use simple QMap<QProces*, QString> map stored as a Test class member.
// addProcessToList(...):
map[console] = id;
//registerProcess():
QString id = map[console];
//processFinished(...):
map.remove(console);
By the way, it is not needed to created a new instance of QSignalMapper for each map item.
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
Hi i try to bind slot with argument to QAction triggered SIGNAL
i have this code ,the context menu working great . BUT the OpenPublishWin never triggered .
void MyApp::ShowContextMenu(const QPoint& pos) // this is a slot
{
QString groupID;
QPoint globalPos = ui.treeView_mainwindow->mapToGlobal(pos);
QModelIndex modelIndx = ui.treeView_mainwindow->indexAt(pos);
groupID = modelIndx.model()->index(modelIndx.row(),0,modelIndx.parent()).data(Qt::UserRole).toString();
QMenu myMenu;
OpenPublishAction = new QAction(tr("Send"), this);
myMenu.addAction(OpenPublishAction);
connect(OpenPublishAction, SIGNAL(triggered()),m_SignalMapper, SLOT(map()) );
m_SignalMapper->setMapping(OpenPublishAction,groupID);
connect(m_SignalMapper, SIGNAL(mapped(QString)), this, SLOT(OpenPublishWin(QString)));
QAction* selectedItem = myMenu.exec(globalPos);
}
void MyApp::OpenPublishWin(QString gid)
{
WRITELOG(gid)
}
A quick look at the Qt docs for QSignalMapper (assuming that is what you're using based on the question title) states that the parameter for the mapped signal is const QString&. I can't recall if the parameter needs to be exact in this case for the connection but it may be a factor.
Additionally, double check that your connects are being made by wrapping them in an assert or some form of verify. Qt will also print out to the console if a connection cannot be made.