I currently have this functionality:
void MainWindow::onJeopardySquareClicked(){
QPushButton *temp;
for(int i =0; i < 6;i++){
for (int j = 0; j < 6; ++j) {
if(button[i][j] == sender()){
temp = button[i][j];
break;
}
}
}
temp->setStyleSheet("background-color: gray;");
}
Which is connected to a grid of QPushButtons:
QObject::connect(button[arrI][j],SIGNAL(pressed()),this, SLOT(onJeopardySquareClicked()));
Is there any ways to send the actual button so I don't need to loop through each button in the array? I don't want to subclass QPushButton if I don't have to, it seems overkill. I also want to avoid using static variables.
This is kind of what I want:
void MainWindow::onJeopardySquareClicked(QPushButton clickedButton){
clickedButton.setStyleSheet("background-color: gray;");
}
In onJeopardySquareClicked you can use QObject::sender() to find out from which QObject the signal originated. Otherwise you can also use a QSignalMapper to map the object/widget.
sender() is ugly and non-OOP. Use QSignalMapper.
Related
I am creating a checkers board as part of a university assignment. There has been very little teaching around the GUI side of things. So I may have overcomplicated this project a bit and become stuck while trying to access the clicks from the board GUI.
I have read about lambda's in the documentation https://doc.qt.io/qt-5/qsignalmapper.html for signal mappers, but, I am not sure if it is right for the application I am coding. I have tried to implement the signal mapping so that when a particular button is pushed on the checker's board, the slot can identify it with its unique signal.
If there is a better way, or if lambdas are the best way, could you please explain how to implement this?
At the moment, my current code can't access the clicks made. I also get an error for the connect() function:
no known conversion from 'void QAbstractButton::* (bool)' to 'const char*' for the second argument.
connect(&checkers[i], &QPushButton::clicked, signalMapper, &QSignalMapper::map);
However, this compiles but does not work:
connect(&checkers[i], SIGNAL(clicked), signalMapper, SLOT(map));
Below is more complete code for greater context.
Constructor and board set function:
// Constructor
GameWindow::GameWindow(QWidget *parent):
QDialog(parent), ui(new Ui::GameWindow)
{
ui->setupUi(this);
BoardSetup(4);
}
/* Sets up the initial gameboard display with the other game information displays also being
* initialised. The game board is an array of QpushButtons that have there Icon
* changed according to where the pieces are on the board.
* Called by the GameWindow Constructor*/
void GameWindow::BoardSetup(int size)
{
{
QSignalMapper *signalMapper = new QSignalMapper(this->checkers);
connect(signalMapper, &QSignalMapper::mappedInt, this, &GameWindow::SquareClicked );
bool col=1;
for (int j=0;j<size;j++)
{
col=!col;
for(int i=0; i<size; i++)
{
if(col == 1) // logically check to see what Color the square needs to be.
{
checkers[i+j*size].setGeometry(QRect(width/size*j,width/size*i,width/size,width/size));
checkers[i+j*size].setFixedSize(QSize(width/size,width/size));
checkers[i+j*size].setStyleSheet("background-color: black");
}
else if(col==0)
{
checkers[i+j*size].setGeometry(QRect(width/size*j,width/size*i,width/size,width/size));
checkers[i+j*size].setFixedSize(QSize(width/size,width/size));
checkers[i+j*size].setStyleSheet("background-color: white");
}
col=!col;
signalMapper->setMapping(&checkers[i], i);
connect(&checkers[i], &QPushButton::clicked, signalMapper, &QSignalMapper::map);
}
}
Grid = new QGridLayout;
Grid->setGeometry(QRect(0,0,width,width));
for(int j=0; j<size; j++)
{
for(int i=0; i<size; i++)
{
Grid->addWidget(&checkers[i+j*size], j, i, Qt::AlignmentFlag::AlignCenter);
}
}
GridGroup = new QGroupBox();
GridGroup->setLayout(Grid);
ui->BoardGrid->addWidget(GridGroup);
ui->BoardGrid->setGeometry(QRect(0,0,width,width));
ui->BoardGrid->addWidget(GridGroup);
setWindowTitle("Checkers Game");
ui->P1PieceCount->setFontPointSize(40);
ui->P1PieceCount->setAlignment(Qt::AlignCenter);
ui->P2PieceCount->setFontPointSize(40);
ui->P2PieceCount->setAlignment(Qt::AlignCenter);
ui->TurnCount->setFontPointSize(40);
ui->TurnCount->setAlignment(Qt::AlignCenter);
ui->Player2Name->setAlignment(Qt::AlignCenter);
CheckersBoard(size);
SetInitialDisplay(size);
}
return;
}
How I am trying to access the clicks.
GetClicks() and WaitForClick() functions:
/* This function will get the clicks that are required to make the human move.
* Called by GameWindow::HumanMove */
void GameWindow::GetClicks()
{
connect(this, &GameWindow::SquareClicked, this, &GameWindow::WaitForClick);
}
/* This function will get the clicks that are required to make the human move.
* Called by GameWindow::GetClicks via signal SquareClicked */
void GameWindow::WaitForClick(int square)
{
firstclick = !firstclick;
unsigned int test=square;
if (firstclick ==0 && test != StartPos)
{
StartPos = square;
checkers[square].setChecked(1);
}
else if (firstclick == 1 && test == StartPos)
{
checkers[square].setChecked(0);
}
else if (firstclick==1 && test!= StartPos)
{
EndPos = square;
}
}
The StartPos and EndPos clicked are used in a possible move function to validate the move. It is then updated to the GUI.
Any help on how best to implement this signal and slot mechanism would be greatly appreciated.
Have a look at Qt calculator example?
https://doc.qt.io/qt-5/qtwidgets-widgets-calculator-example.html
you can connect multiple buttons to the same slot.
in slot, find out which button sent the signal using QObject::sender().
Once have the button, extract the position using MyButton::Pos().
(MyButton is the widget used for each of the board button)
i create multiple QPushButtons in the following way:
QList<QByteArray> pBList;
pBList= rec_data.split(',');
for (int i = 1; i < pBList.size() -1; i++){
QPushButton *newpB = new QPushButton(ui->verticalLayoutWidget);
newpB->setText(pBList[i]);
ui->verticalLayoutWidget->layout()->addWidget(newpB);
}
This works fine and the QPushButtons are shown on the GUI.
But how do i connect them to a clicked()-Signal and to a Slot?
I tried it this way, but this dosen't work...
QObject::connect(ui->verticalLayoutWidget->layout()->itemAt(1)->widget(), SIGNAL(clicked()),this, SLOT(_on_send_name()));
Thanks for the help
QList<QByteArray> pBList;
pBList= rec_data.split(',');
for (int i = 1; i < pBList.size() -1; i++){
QPushButton *newpB = new QPushButton(ui->verticalLayoutWidget);
newpB->setText(pBList[i]);
ui->verticalLayoutWidget->layout()->addWidget(newpB);
//This will CONNECT all buttons to a single slot
connect (newpB,&QPushButton::clicked,this,&YOUR_CLASS_NAME::_on_send_name);
}
You can use sender() inside _on_send_name to get a pointer to the clicked button. But sender() is not recommended. https://doc.qt.io/qt-5/qobject.html#sender
I would go with the QSignalMapper for your scenario.
I have created a QTableWidget in which I've used setCellWidget(QWidget*). I've set QLineEdit in the cell widget. I've also created a delete button and clicking that button sends a signal to the function deleteRow. I've also used a function currentRow() to get the current row, but it returns -1 because of the QLineEdit. The code snippet is below.
void createTable() {
m_table = new QTableWidget(QDialog); //member variable
for (int i = 0; i < 3; i++)
{
QLineEdit *lineEdit = new QLineEdit(m_table);
m_table->setCellWidget(i, 0, lineEdit);
}
QPushButton *deleteBut = new QPushButton(QDiaolg);
connect(deleteBut, SIGNAL(clicked()), QDialog, SLOT(editRow()));
}
editRow() {
int row = m_table->currentRow(); // This gives -1
m_table->remove(row);
}
In above scenario I click in the QLineEdit and then click on the button delete. Please help me out with a solution.
Just tried it here, it seems that currentRow of the table returns -1 when clicking the button right after program start, and when first selecting a cell, then selecting the QLineEdit and then clicking the button, the correct row is returned.
I would do the following as a workaround: Save the row number in the QLineEdit, e.g. by using QObject::setProperty:
QLineEdit *lineEdit = new QLineEdit(m_table);
lineEdit->setProperty("row", i);
m_table->setCellWidget(i, 0, lineEdit);
Then, in the editRow handler, retrieve the property by asking the QTableWidget for its focused child:
int row = m_table->currentRow();
if (row == -1) {
if (QWidget* focused = m_table->focusWidget()) {
row = focused->property("row").toInt();
}
}
The accepted solution, as is, would not work if rows might get deleted while the program runs. Thus the approach would require to update all the properties. Can be done, if this is a rare operation.
I got away with an iteration approach:
for(unsigned int i = 0; i < table->rowCount(); ++i)
{
if(table->cellWidget(i, relevantColumn) == QObject::sender())
{
return i;
}
}
return -1;
Quick, dirty, but worked, and in my case more suitable, as rows got deleted often or changed their positions, only buttons in the widget were connected to the slot and the slot was never called directly. If these conditions are not met, further checks might get necessary (if(QObject::sender()) { /* */ }, ...).
Karsten's answer will work correctly only if QLineEdit's property is recalculated each time a row is deleted, which might be a lot of work. And Aconcagua's answer works only if the method is invoked via signal/slot mechanism. In my solution, I just calculate the position of the QlineEdit which has focus (assuming all table items were set with setCellWidget):
int getCurrentRow() {
for (int i=0; i<myTable->rowCount(); i++)
for (int j=0; j<myTable->columnCount(); j++) {
if (myTable->cellWidget(i,j) == myTable->focusWidget()) {
return i;
}
}
return -1;
}
In my menu, I am setting data to the menu actions. How can I extract that data in my slot? Or even better, instead of connecting a slot, can I also connect a member function that is able to extract the action data (like in the 1st connect)? The action data is meant to identify each action. As a sidenode, I am not sure if I can use several menu action entries on only one openNote-action.
void Traymenu::createMainContextMenu() {
QAction *actionNewNote = m_mainContextMenu.addAction("Neue Notiz");
actionNewNote->setIcon(QIcon("C:\\new.ico"));
actionNewNote->setIconVisibleInMenu(true);
QObject::connect(actionNewNote,&QAction::triggered,this,&Traymenu::newNote);
QString menuEntryName;
QAction *openNote;
QVariant noteID;
for (int i = 0; i<m_noteList.count(); i++) {
std::string noteTitle = m_noteList[i].data()->getTitle();
menuEntryName = QString::fromStdString(noteTitle);
openNote = m_mainContextMenu.addAction(menuEntryName);
connect(openNote,SIGNAL(triggered()),this,SLOT(s_showNote()));
noteID.setValue(m_noteList[i].data()->getID());
openNote->setData(noteID);
}
m_mainIcon.setContextMenu(&m_mainContextMenu);
}
And the slot:
void Traymenu::s_showNote() {
QObject* obj = sender();
//int noteID = data.toInt();
//Search all notes in noteList for that ID and show it
}
Using QObject::sender()
You can use QObject::sender() to get the signal's sender, followed by qobject_cast to cast the sender pointer to the right type.
void Traymenu::s_showNote()
{
QAction* act = qobject_cast<QAction *>(sender());
if (act != 0)
{
QVariant data = act->data();
int noteID = data.toInt();
showNote(noteID); // isolate showNote logic from "get my ID" stuff
}
}
void Traymenu::showNote(int noteID)
{
// Do the real work here, now that you have the ID ...
}
As the Qt documentation warns, "This function violates the object-oriented principle of modularity." It's still a fairly safe and standard practice, though — just one with some shortcomings. In particular, note that you're committing to having a s_showNote method that only works when it's accessed as a slot (otherwise sender is 0).
Using QSignalMapper
Alternatively, you can use the QSignalMapper class to return a pointer to teh item or to associate a unique identifier (int or QString) with each item.
Something like this:
void Traymenu::createMainContextMenu()
{
signalMapper = new QSignalMapper(this); // (or initialize elsewhere)
// ... (create your newNote here same as before) ...
QString menuEntryName;
QAction *openNote;
int noteID;
for (int i = 0; i<m_noteList.count(); i++) {
std::string noteTitle = m_noteList[i].data()->getTitle();
menuEntryName = QString::fromStdString(noteTitle);
openNote = m_mainContextMenu.addAction(menuEntryName);
noteID = m_noteList[i].data()->getID();
openNote->setData(QVariant(noteID)); // (if you still need data in the QActions)
signalMapper->setMapping(openNote, noteID);
}
connect(signalMapper, SIGNAL(mapped(int)),
this, SLOT(showNote(int)));
m_mainIcon.setContextMenu(&m_mainContextMenu);
}
void Traymenu::showNote(int noteID) {
// Now you have the ID ...
}
This pattern has the benefit of isolating all the ugly "Wait, how do I get my identifier?" stuff in one spot, instead of having both the initialization code and the slot function having code for associating actions and IDs.
I would write it like:
void Traymenu::s_showNote() {
QObject* obj = sender();
QAction *action = qobject_cast<QAction *>(obj);
int id = action->data().toInt();
for (int i = 0; i < m_noteList.count(); i++) {
if (m_noteList[i].data()->getID() == id) {
[..]
}
}
}
for(i=0; i<height; i++)
{
for(j=0; j<width; j++)
{
button[i][j] = new QPushButton("Empty", this);
button[i][j]->resize(40, 40);
button[i][j]->move(40*j, 40*i);
connect(button[i][j], SIGNAL(clicked()), this, SLOT(changeText(button[i][j])));
}
}
If i changed function changeText with function (fullScreen for example) it works
but when i use a slot defined by me (changeText) this Error Appears and i don't know how to solve it
QObject::connect: No such slot buttons::changeText(&button[i][j])
and this is the function changeText:
void buttons::changeText(QPushButton* button)
{
button->setText("Fish");
}
NOTE: in the header file i defined the slot like this :
class buttons : public QWidget
Q_OBJECT
public slots:
void changeText(QPushButton* button);
slot can have less arguments then signal but type of arguments it has must match exactly with types of arguments in connected signal.
you can't have dynamic slot like that.
probably what you need is a QSignalMapper.
here is sample:
QSignalMapper *map = new QSignalMapper(this);
connect (map, SIGNAL(mapped(QString)), this, SLOT(changeText(QString)));
for(i=0; i<height; i++)
{
for(j=0; j<width; j++)
{
button[i][j] = new QPushButton("Empty", this);
button[i][j]->resize(40, 40);
button[i][j]->move(40*j, 40*i);
connect(button[i][j], SIGNAL(clicked()), map, SLOT(map()));
map->setMapping(button[i][j], QString("Something%1%2").arg(i).arg(j));
}
}
Probably you can remove a table.
If the SIGNAL doesn't provide certain parameter, the SLOT can't recieve it.
The signal clicked() doesn't provide any parameter. SLOTs receiving it shouldn't have any, either. In any case, you can have a SLOT receiving less parameters than the SIGNAL provides (ignoring some others), but not otherwise. You can, however, get to know the sender of the signal, cast it to QPushButton* and work on it:
void buttons::changeText()
{
QPushButton *pb = qobject_cast<QPushButton *>(sender());
if (pb){
pb->setText("fish");
} else {
qDebug() << "Couldn't make the conversion properly";
}
}
QButtonGroup is a class that has been designed as a handy collection for buttons. It give you direct access to the button which triggered the slot. It also provide you the possibility to register button with a given id. This can be useful if you want to retrieve easily some meta information from the button id.
QButtonGroup* buttongrp = new QButtonGroup();
for(i=0; i<height; i++)
{
for(j=0; j<width; j++)
{
button[i][j] = new QPushButton("Empty", this);
button[i][j]->resize(40, 40);
button[i][j]->move(40*j, 40*i);
buttongrp->addButton(button[i][j], i << 16 + j);
}
}
QObject::connect(buttongrp, SIGNAL(buttonClicked(int)),
this, SLOT(getCoordinates(int)));
QObject::connect(buttongrp, SIGNAL(buttonClicked(QAbstractButton *)),
this, SLOT(changeText(QAbstractButton * button)));
...
void MyObject::changeText(QAbstractButton * button)
{
button->setText("Fish");
}
void MyObject::getCoordinates(int id){
int i = id >> 16;
int j = ~(i << 16) & id;
//use i and j. really handy if your buttons are inside a table widget
}
Usually you don't need to connect to both slots. For the id I assumed that height and width are less that 2^16.
Retrospectively, It seems to me you are reimplementing some of the functions of the button group.