PDA

View Full Version : QSignalMapper and argument problems



harmodrew
7th August 2010, 01:18
Hello!
I use a QSignalMapper to map the clicked() events of a QPushButton matrix:

buttonmatrix.h:


#ifndef BUTTONMATRIX_H
#define BUTTONMATRIX_H

#include <QPushButton>
#include <QObject>
#include <QSignalMapper>

class ButtonMatrix : QObject
{
Q_OBJECT
private:
QPushButton ** p;
int nrow;
int ncol;
QSignalMapper *signalMapper;
public:
ButtonMatrix(int nrow, int ncol);
~ButtonMatrix();
void Show(QWidget*);
public slots:
void SetItem(const QString &ID); // this slot is called by the signal clicked() of the button identified by ID
};

#endif // BUTTONMATRIX_H



buttonmatrix.cpp:


#include "buttonmatrix.h"
#include <QPushButton>
#include "mainwindow.h"
#include <QSignalMapper>
#include <QString>
#include <QChar>
#include "stdio.h"
#include "mymacros.h"
#include "string.h"

ButtonMatrix::ButtonMatrix(int nr, int nc)
{
ncol=nc;
nrow = nr;
p = new QPushButton*[nrow];
for (int i=0; i<nrow; i++)
p[i] = new QPushButton[ncol];
}

void ButtonMatrix::Show(QWidget *obj)
{
int alt = (obj->height()-100)/nrow;

signalMapper= new QSignalMapper(this);

QString ID; // the format of the ID string will be "iijj" where i = index of rows, j = index of columns
char aux1[3], aux2[3], aux3[5]; //used to convert into strings and then append the i&j indexes
for (int i=0; i<nrow; i++)
{
if (i>=10)
sprintf(aux1,"%d", i);
else
sprintf(aux1,"0%d", i); // this ensures that the format of the first part of ID will be "ii"

for (int j=0; j<ncol; j++)
{
if (j>=10)
sprintf(aux2,"%d", j);
else
sprintf(aux2,"0%d", j); // this ensures that the format of the second part of ID will be "jj"

p[i][j].setParent(obj);
p[i][j].setGeometry(50+(j*alt), 70 +(i*alt),alt, alt);
connect(&p[i][j], SIGNAL(clicked()), signalMapper, SLOT(map())); //connect the clicked() slot of the ij-th button with the map() slot of the signal mapper...
sprintf(aux3, "%s%s", aux1, aux2);
ID = tr(aux3); // (creating the ID string in "iijj" format)
signalMapper->setMapping(&p[i][j], ID); //...and then associate ID to the ij-th button of the matrix
p[i][j].show();
}
}

connect(signalMapper, SIGNAL(mapped(const QString &)),
this, SLOT(SetItem(const QString &))); // when a button of the matrix gets clicked the SetItem slot of the ButtonMatrix will be called
}

void ButtonMatrix::SetItem(const QString & ID) // HERE I'd like a few more arguments for SetItem to work properly.
{
int i, j;
const QChar *aux = ID.data();
i = aux[0].digitValue()*10 + aux[1].digitValue();
j = aux[2].digitValue()*10 + aux[3].digitValue();


// here I need an integer and a custom object coming from the main
//but the SLOT cannot gain more parameters than one
}

ButtonMatrix::~ButtonMatrix()
{
for (int i=0; i<nrow; i++)
delete [] p[i];
delete p;
}


in the code I highlited the problem. Simply I need at least two more parameters for the SLOT function that responses the clicked() SIGNALs of the button matrix, but the QSignalMapper doesn't allow me to gain more parameters than one...
is there another way to connect a button matrix without being obliged to use only one parameter in the SLOT function?

thanks

tbscope
7th August 2010, 09:34
I suggest creating custom signals and slots.

If you want to have a clicked signal with more parameters, then just create a subclassed widget and send a custom signal when clicked with the extra parameters.

harmodrew
7th August 2010, 10:48
I'm not sure how to get the things done.. this is the map of signal/slot that is implemented (every arrow is a connect() ):


(SIGNAL) p[i][j].clicked() -> (SLOT) ButtonMatrix.SetItem(const QString & ID) (this is set using signalMapper)

here is a plan of signal/slot I'd like to implement. p, one of the members of ButtonMatrix, is now a myButton pointer. myButton is a class derived from QPushButton:


(SIGNAL) p[i][j].clicked() -> (SLOT) p[i][j].CallNext1() ( this SLOT only emittes CallNext2(args) SIGNAL) -> p[i][j].CallNext2(QString &ID, int type, MyObj obj) -> (SLOT) p[i][j].doSomethingOnTheParameters(QString &ID, int type, MyObj obj)

I'm not convinced for three reasons:
1) it's too long and ugly, peraphs I can save one pass like CallNext1() SLOT but I should have access to clicked() SIGNAL to send the SIGNAL CallNext2( args );
2) I have no idea on how to implement the second "arrow". to send CallNext2 (arg) SIGNAL I need the args but I cannot gain them previously (it's a vicious circle)
3) peraphs I didn't catch all SIGNAL/SLOT functions?

(yes, I'm a newbie :) )

thanks a lot

Lykurg
7th August 2010, 10:56
Why do you need more arguments? Can't you get these information in the slot? With the mapper you can identify which button was clicked and you can get a pointer to that button. And all other information are - I guess so - in private variables. For them you can make getter function.

May be you say us which informations you need (as arguments) and where they are in your architecture.

harmodrew
7th August 2010, 11:16
I'm writing a program which allows to design the map of a labirith. this will be saved in a file and another program will allow to play this labirith.

I thought to divide the labirith into squares, each of them can assume 4 status: 0 = empty, 1 = entry, 2 = exit, 3 = wall. In this way I can gain all informations about it (where is the entry, the exit, where are the walls, etc...).

The user chooses the tool to use (set an entry, set an exit, set a wall, remove something) by using a toolbar with, of course, four buttons.

To gain the map of the labirith from the user I show a button matrix and every button is a square of the logical map, so every button can be "set" to a status. The status cannot be stored in the buttons (obvious).
So the problem is that on the click() event of the i-j button the program should:

1) change the background image of the button so that the user can see where (e.g.) the wall is; (I've done it because all I needed was ID string)
2) store the status of the matrix in a matrix of integers in which every item can assume 4 status depending on the tool choosen in the toolbar (THIS is the problem)

Now in che SLOT I need the ID string to identify the button on which I should operate, and at least the tool choosen by the user (an integer variable)! but the SLOT doesn't allow me to introduce the tool parameter...moreover it should be better to distinguish the button matrix from the status matrix (the integer matrix which stores the labirinth status), so I need another argument in the SLOT to set this matrix...

Lykurg
7th August 2010, 11:38
First you can store the information (wall/exit...) in the button if you subclass QPushButton and add that functionality. But beside that you can store in a local hash which id is belonging to x-y-index of your square. so you can query that information. And why are you not creating your id out of that two x y informations you need for storing the wall/exit-information?

harmodrew
7th August 2010, 11:51
And why are you not creating your id out of that two x y informations?

because the matrix of buttons is indexed with two integers...
even if I store the status of the buttons in a member of a QPushButton-derived object (and not in a dedicated integer matrix) , I have the problem of choosing the value that has to be assigned to a button depending on the selected tool...the "tool variable" (integer variable that stores the tool choosen by the user) cannot be included into the matrix of button. Infact it doesn't make sense to store the tool into the button matrix obect because they have no logical relationship between them...

Lykurg
7th August 2010, 12:21
because the matrix of buttons is indexed with two integers...

int x,y;
QString id = QString("%1-%2").arg(x).arg(y);
// in the slot:
int x, y;
QStringList tmp = id.split("-");
x = tmp.at(0).toInt();
y = tmp.at(1).toInt();

or you create a local
QHash< QString (id), QPair< int (x), int (y) > > mapping;


I have the problem of choosing the value that has to be assigned to a button depending on the selected tool... but you can surely access that information from your slot! there must be a pointer in your class, beside how would you do it when creating the connection, if you have there access you also have it in your slot.

harmodrew
7th August 2010, 12:41
No, I cannot access to the tool object:

mymacros.h:


#ifndef MYMACROS_H
#define MYMACROS_H

#define WALL 3
#define ENTRY 2
#define EXIT 1
#define REMOVE 0

#endif // MYMACROS_H


tool.h:


#ifndef TOOL_H
#define TOOL_H
#include <QObject>

class Tool : public QObject
{
Q_OBJECT
int Type;

public slots:
void SetWall();
void SetEntry();
void SetExit();
void SetRemove();
};
#endif // TOOL_H


tool.cpp:


#include "tool.h"
#include "mymacros.h"

void Tool:: SetWall()
{
Type = WALL;
}

void Tool :: SetEntry()
{
Type = ENTRY;
}

void Tool::SetExit()
{
Type = EXIT;
}

void Tool::SetRemove()
{
Type = REMOVE;
}


toolbar.h:


#ifndef TOOLBAR_H
#define TOOLBAR_H
#include <mainwindow.h>

void CreateToolBar(MainWindow* win, Tool *tool);

#endif // TOOLBAR_H


toolbar.cpp:


#include "toolbar.h"
#include "ui_mainwindow.h"
#include "tool.h"

void CreateToolBar(MainWindow *win, Tool *tool)
{
QToolBar *ToolBar = new QToolBar("ToolBar");

QAction *WallButton=new QAction(QIcon(QApplication::applicationDirPath().a ppend("/img/Wall.bmp")),"Wall",win);
QAction *EntryButton=new QAction(QIcon(QApplication::applicationDirPath().a ppend("/img/Entry.bmp")),"Entry",win);
QAction *ExitButton=new QAction(QIcon(QApplication::applicationDirPath().a ppend("/img/Exit.bmp")),"Exit",win);
QAction *RemoveButton=new QAction(QIcon(QApplication::applicationDirPath().a ppend("/img/Remove.bmp")),"Remove",win);

ToolBar->insertAction(EntryButton,WallButton);
ToolBar->insertAction(ExitButton,EntryButton);
ToolBar->insertAction(RemoveButton,ExitButton);
ToolBar->insertAction(0,RemoveButton);

win->addToolBar(ToolBar);

// here I connect the buttons of the toolbar with the tool SLOTs (which change its status)
win->connect(WallButton,SIGNAL(triggered()),tool, SLOT(SetWall()));
win->connect(RemoveButton,SIGNAL(triggered()),tool, SLOT(SetRemove()));
win->connect(ExitButton,SIGNAL(triggered()),tool, SLOT(SetExit()));
win->connect(EntryButton,SIGNAL(triggered()),tool, SLOT(SetEntry()));
}


so when the user chooses a button on the toolbar the status is stored in the integer member of Tool.
moreover in the mainwindow constructor I create a tool object which is passed to CreateToolbar to associate the toolbar buttons to the tool SLOTs:

part of the main.cpp:


//...
Tool *tool = new Tool;
MainWindow w(tool);
//...


mainwindowcpp:


#include "mainwindow.h"
#include "ui_mainwindow.h"
#include "toolbar.h"
#include "tool.h"

//MainWindow constructor
MainWindow::MainWindow(Tool *tool, QWidget *parent) :
QMainWindow(parent),
ui(new Ui::MainWindow)
{
ui->setupUi(this);
CreateToolBar(this, tool);
}

MainWindow::~MainWindow()
{
delete ui;
}



so the tool is declared in the main and is accessible only in the MainWindow constructor, not in the ButtonMatrix object (which is declared in the main)

Lykurg
7th August 2010, 12:49
Ehm, you really should reconsider your design...
A simple hack:

// in MainWindow add a private variable
Tool *m_tool;

//MainWindow constructor
MainWindow::MainWindow(Tool *tool, QWidget *parent) :
QMainWindow(parent),
ui(new Ui::MainWindow)
{
ui->setupUi(this);
CreateToolBar(this, tool);
m_tool = tool; // add this
}
now you can access tool via m_tool in your main window. (BUT that is really no good design!!!)

harmodrew
7th August 2010, 13:00
ok, I'll take a look to the design (I was doing it right now)
thanks :)

harmodrew
7th August 2010, 18:28
I've changed something in my code. Now the mainwindow class is:



mainwindow.h
#ifndef MAINWINDOW_H
#define MAINWINDOW_H

#include <QMainWindow>
#include <QPushButton>
#include "tool.h"
#include "buttonmatrix.h"
#include <QString>

namespace Ui {
class MainWindow;
}

class MainWindow : public QMainWindow
{
Q_OBJECT
private:
Ui::MainWindow *ui;
Tool *tool;
ButtonMatrix *buttonMatrix;
QToolBar *toolBar;
QString PathToIcons;

public:
explicit MainWindow(int inputnr, int inputnc, QString path_to_icons,QWidget *parent = 0);
~MainWindow();
void CreateToolBar();
void CreateButtonMatrix(int nrow, int ncol);
void InstanceTool();
public slots:
void SetMatrixItem(const QString & ID);
};



now I have to set properly the buttons parent which should be the window. I'd make it in che ButtonMatrix constructor (buttons is the pointer to the QPushButton matrix)


ButtonMatrix::ButtonMatrix(QWidget *win, int nr, int nc)
{
//...
for (int i=0; i<nrow; i++)
{
for (int j=0; j<ncol; j++)
{
//...
buttons[i][j].setParent(win); // <---
//...
}
}
//...
}


commenting this row the buttons are shown each in a single window, indipendent from the mainwindow. while, if I uncomment the row the matrix is shown but, when I close the program, I gain a program crash (tre program doesn't quit properly)...

squidge
7th August 2010, 18:38
How are you creating your matrix?

Actually, looking at your header file, why do you have a method called CreateButtonMatrix (with the name stating it creates a matrix) but takes a row and column parameter? From the parameter list it seems it (confusingly) creates a single button, not a matrix?

harmodrew
7th August 2010, 18:56
one of the members of MainWindow is, as you see, a ButtonMatrix pointer. the ButtonMatrix object is declared as follows:
buttonmatrix.h:


#ifndef BUTTONMATRIX_H
#define BUTTONMATRIX_H

#include <QPushButton>
#include <QSignalMapper>
#include <QString>
#include <QObject>

class ButtonMatrix : public QObject
{
Q_OBJECT
private:
QPushButton **buttons; // this is private so out of the ButtonMatrix object it is not accessible
int **status;
int nrow;
int ncol;
QSignalMapper *signalMapper;
public:
ButtonMatrix(){}
ButtonMatrix(QWidget* win, int nrow, int ncol);
~ButtonMatrix();
void Show();
void SetNRow(int nr);
void SetNCol(int nc);
void GenerateMatrix();
void SetBackground(int i, int j, QString aux);
};


#endif // BUTTONMATRIX_H


as commented in the code, the ButtonMatrix object has a member pointer which points the QPushButton matrix in the heap. of course all methods of the ButtonMatrix object can access the buttons pointer. From a MainWindow method I cannot access directly the buttons pointer but only the private ButtonMatrix object. So I need a function of ButtonMatrix which can be called from MainWindow to setup the buttons pointer:



void MainWindow::CreateButtonMatrix(int nrow, int ncol)
{
buttonMatrix->SetNRow(nrow);
buttonMatrix->SetNCol(ncol);
buttonMatrix->GenerateMatrix();
}


GenerateMatrix uses previously setted ncol and nrow to instance buttons and status pointers of the ButtonMatrix object:


void ButtonMatrix::GenerateMatrix()
{
buttons = new QPushButton*[nrow];
status = new int*[nrow];
for (int i=0; i<nrow; i++)
{
buttons[i] = new QPushButton[ncol];
status[i] = new int[ncol];
}

}

harmodrew
7th August 2010, 19:20
The error comes up even if i pass to the ButtonMatrix constructor a frame instead of the mainwindow...