Connecting multiple signals to a single slot in Qt - qt

I'm trying to keep track of the textChanged() signal on for handful of QTextEdits. I want to do the same thing regardless of the text edit emitting the signal: uncheck its associated checkbox in a QListWidget if it becomes empty and leave it checked otherwise. The function I have so for is as follows:
void MainWindow::changed()
{
QString tempStr = ui->hNMRedit->toPlainText();
if(tempStr != "")
{
ui->checkList->item(0)->setCheckState(Qt::Checked);
}
else
{
ui->checkList->item(0)->setCheckState(Qt::Unchecked);
}
}
With the current approach, I would have to make a function like this for every QTextEdit; each function containing virtually identical code. If I stored each of the text edits in an array (so I could find their associated index in the QListWidget), would it be possible for me to have a slot like this?
void MainWindow::changed(QWidget *sender) // for whichever text edit emits the
// textChanged() signal
{
QString tempStr = sender->toPlainText();
if(tempStr != "")
{
// I would potentially use some sort of indexOf(sender) function on the array I
// mentioned earlier here... a little new to Qt, sorry
ui->checkList->item(array.indexOf(sender))->setCheckState(Qt::Checked);
}
else
{
// same as above...
ui->checkList->item(array.indexOf(sender))->setCheckState(Qt::Unchecked);
}
}
Is this possible or should I just create a separate slot for every text edit?
Please let me know if any further clarification is needed!
Lastly, I feel like the only meaningful difference between QLineEdits and QTextEdits is the default size. In favor of keeping things consistent, should I just use one of these objects throughout my UI?
Thanks!!!

I think you are missing the point of slots and signals. How are you creating the connections?
Are you trying to check a box when any of the text boxes change? If so use a QSignalMapper to map the textChanged() signals to send a value of true and connect that to the QCheckBox setChecked(bool) slot.
If that is too complicated subclass QCheckBox and create a set of functions checkBox() uncheckBox() so you can toggle states without a variable. Then connect the QTextEdit textChanged() to your subclass checkBox()
If this is not what you are looking for, at least subclass QTextEditto take in a QCheckBox that it can change when the text changes instead of duplicating code for every QTextEdit

All you need is a hash of QAbstractButton*, keyed by QTextEdit*. In the slot, you look up the sender() in the hash, if found you've got the button you need. This is precisely what is done by the QSignalMapper: you can map from a sender QWidget* to your button QWidget*. Use qobject_cast to cast to QAbstractButton*.

Related

QListView with a QStandardItemModel and checkbox-enabled QStandardItems: How to capture *only* check-state changes?

I have the following (simplified) code to add QStandardItem's to a QStandardItemModel, attach the model to a QListView, and connect signals of my choice from the model to a function of my choice:
// MyUIContainer derives from QWidget
MyUIContainer::SetItems()
{
// Inside a member function where model/items are added to a QListView...
// This code does not show deletion of existing ItemSelectionModel
QStandardItemModel * model = new QStandardItemModel(ui->listView);
// In the real code, data is set in each QStandardItem
model->setItem( 0, new QStandardItem() );
model->setItem( 1, new QStandardItem() );
model->setItem( 2, new QStandardItem() );
connect(model,
SIGNAL(itemChanged(QStandardItem*)),
this,
SLOT(ReceiveChange(QStandardItem*)));
// I have also tried connecting to this signal - same problem described below
//connect(model, SIGNAL(dataChanged(const QModelIndex &, const QModelIndex &, const QVector<int>)), this, SLOT(ReceiveChange(const QModelIndex &, const QModelIndex &, const QVector<int>)));
ui->listView->setModel(model);
}
MyUIContainer::ReceiveChange(QStandardItem * item)
{
// Problem! This slot is called *three times*
// whenever the check state of an item changes
}
As the code comment indicates, there is a problem: The itemChanged() \ ReceiveChange() signal \ slot combination is called three times whenever the check state of a checkbox in the QListView changes once.
I understand that I can create my own Model class that is derived from QAbstractItemModel, and (if I understand correctly) handle mouse clicks in the view myself, emitting signals such as, for example, the itemChanged signal with a proper user-defined role so that a change in checkbox state can be handled only once.
However, the latter approach requires me to be careful to handle all possible scenarios of mouse clicks & keyboard events that could result in a changed checkbox state. I was hoping, and assuming, that this logic has been handled inside the Qt library itself, so that I don't have to - so that I can simply receive a signal (or some other message) when the check state of a checkbox changes.
Note that it seems that QListWidget (rather than QListView) also has exactly the same problem.
So, my question is: Without writing my own Model class derived from QAbstractItemModel - with its requirement that I myself write code to handle every possible way that a check state can change - how do I capture a changed check state (via a signal or otherwise) such that I can unambiguously capture every time a check state changes once and only once per check state change?
ADDENDUM
With apologies - as I took great care in ruling everything else out - Riateche's comment made it clear to me that the behavior is not supposed to work in the way this question highlighted. Finally, I have indeed tracked down the problem to the fact that I was calling connect() multiple times.

Qt: update pixmap grid layout with 2d array values

I am undertaking a game using a combination of c++ in visual studios 2010 and Qt 4.7 (both windows). The game is a clone of battleship and is console input based. I have created my gui how I want it to look, and on the Qt side in Qt designer, my gui consists of a grid layout 10x10, using labels to hold pixmaps of game cells:
I have painstakingly named each label to represent its position in the 2d array (ie. fleet map => F_00 => F[0,0] => F[i],[j]). I can manually choose what pixmap I would like to display using the properties editor, but I would like something dynamic.
I use an update mapboard class to redraw the game board after a player fires, which keeps storing over a char array. I would like to update my pixmaps for each, using a generic getupdatearray type function. As we traverse the array it will update the pixmap currently associated with individual labels to match their cousins from the array. (say F[5][6] = 'X' for hit, then when the loops got to that position in the array it would update the grid of pixmaps at F_56 to equal hit.png, replacing the empty.png.
I have an idea how to make the loop that would accomplish this, but unsure how i would go about getting the pixmap for each label to be more along the lines of a runtime feature versus the now compile time (static) feature. I have read about QPainter and another Qt class that deals with images, but still having a hard go at it.
Question to any of you, how do I update these pixmaps based on a 2d array?
loop structure - i can figure out
condition statements - i can figure out
qt specific syntax dealing with labels- newbie so no idea atm.
Here's some pseudocode of the kind of thing I am trying to do with map.h:
#include <QtCore>
#include <QtGui>
// WARNING: PSEUDOCODE, DOES NOT COMPILE
// AT A LOSS ON HOW TO SELECT THE CORRECT LABEL
// MAYBE A CHILD CLASS FOR THAT?
class map {
public:
char updateboard(char mapname, char b[][10]){
for(int i=0;i<10;i++){
for(int j=0;j<10;j++){
char C = b[i][j];
if (C == 'M'){//miss
Qlabel mapname_[i][j](<img src='images/missspace.png'/>);
mapname_[i][j].show();
}
else if(C == 'X'){//hit
Qlabel mapname_[i][j](<img src='images/hitspace.png'/>);
mapname_[i][j].show();
}
else if(C == ' '){//undiscovered space
Qlabel mapname_[i][j](<img src='image/emptyspace.png'/>);
mapname_[i][j].show();
}
}
}
}
};
Then in my mainwindow.cpp, I include map.h and say:
// calls class function update board
// takes updated array values and replaces old pixmap with new
map.updateboard(T,b[][10]); // target map update
map.updateboard(F,v[][10]); // fleet map update
Thanks in Advance
UPDATE:
I've gotten to the point where I can swap pixmaps with buttons presses, but I would like to create something more dynamic. I wanted to use a Qstring in which I place the name of the label I want to change using by appending x y values using:
TR_position.append(QString::number(xvalue));
When I try to call it using:
ui->TR_position->setPixmap(QPixmap(":/images/missspace.png"));
...it obviously doesnt work. Is there a way to type case it, or use the contents of the string as the Qlabel name?
You manually entered and named 200 label widgets? Let no one call you lazy. :)
Per your update, you now know how to use QLabel::setPixmap(). What you think you need is getting a QLabel pointer from a name, which would be a combination of two things:
QWidget::findChild to get a QWidget* from a QString
qobject_cast to get a QLabel* from a QWidget
If you go down this path, what you'd wind up with is something like:
QWidget* cellWidget = ui->findChild(TR_position);
QLabel* cellLabel = qobject_cast<QLabel*>(cellWidget);
cellLabel->setPixmap(QPixmap(":/images/missspace.png"));
But BEWARE! There are many things wrong with this approach.
It's brittle: What if there doesn't happen to be any widget with that name (mysterious crash)? Or even worse, what if there are multiple widgets with that name and this code marches along blissfully ignorant of that odd condition that is likely a bug?
It's poor OOP: While there are some decent cases to use dynamic casting (or "downcasting"), it usually indicates a flaw in a design. You know that all QLabels are QWidgets, but not all QWidgets are QLabels...so that qobject_cast call might return NULL. It's just one more point of failure. Sometimes you can't avoid this, but really there is no reason your program needs to be structured in such a way.
It's terribly slow: Searching for a widget by its name is essentially a naive recursive search. If you've set aside a separate widget frame for each grid and only search that, Qt will have to do 100 string compares to find the last element (so 50 in the average case). Imagine clearing the grid with a loop...now you're talking about 100*50 string compares!
All these things are avoidable. Just as it's possible to use loops to set the images on the controls by name, it's possible to use loops to create the widgets in the first place. You basically would leave the area for the game board blank in the design tool, and then dynamically create the controls with code...attach them to the layout with code...and save pointers to them in 2D array. (You wouldn't access them by label name at that point, you'd index them just as you are indexing your board.)
You could create your own class derived from QLabel (such as a GameCell class) which contained the information for your board cell and methods related to it. Then you wouldn't need an array of label widgets in parallel to an array representing your board. You'd simply have one array of objects that took care of both aspects of the implementation.
UPDATE: Since you asked in the comments for specifics, here's a GameCell class:
class GameCell : public QLabel
{
Q_OBJECT
public:
enum State { Undiscovered, Hit, Miss };
GameCell (QWidget *parent = 0) : QLabel (parent),
currentState (Undiscovered)
{
syncBitmap();
}
State getState() const { return currentState; }
void setState(State newState) {
if (currentState != newState) {
currentState = newState;
syncBitmap();
}
}
private:
void syncBitmap() { // you'd use setPixmap instead of setText
switch (currentState) {
case Undiscovered: setText("U"); break;
case Hit: setText("H"); break;
case Miss: setText("M"); break;
}
}
State currentState;
};
This does double duty by behaving like a QWidget as well as maintaining a piece of internal state. Then a GameMap widget can use a QGridLayout of these GameCells:
class GameMap : public QWidget {
Q_OBJECT
public:
static const int Rows = 10;
static const int Columns = 10;
GameMap (QWidget* parent = 0) :
QWidget (parent)
{
layout = new QGridLayout (this);
for (int column = 0; column < Columns; column++) {
for (int row = 0; row < Rows; row++) {
GameCell* cell = new GameCell (this);
cells[column][row] = cell;
layout->addWidget(cell, row, column);
}
}
}
private:
GameCell* cells[Columns][Rows];
QGridLayout* layout;
};
If you wanted to, you could just leave spaces in your layout in the designer you wanted to fill in with the GameMap widget. Or you can push on and do the whole thing programmatically. For the sake of simplicity I'll just put two boards next to each other with a vertical separator on the surface of a dialog:
class Game : public QDialog
{
Q_OBJECT
public:
Game (QWidget *parent = 0)
: QDialog(parent)
{
targetMap = new GameMap (this);
fleetMap = new GameMap (this);
verticalSeparator = new QFrame (this);
verticalSeparator->setFrameShape(QFrame::VLine);
verticalSeparator->setFrameShadow(QFrame::Sunken);
layout = new QHBoxLayout (this);
layout->addWidget(targetMap);
layout->addWidget(verticalSeparator);
layout->addWidget(fleetMap);
setLayout(layout);
setWindowTitle(tr("Battleship"));
}
private:
GameMap* targetMap;
QFrame* verticalSeparator;
GameMap* fleetMap;
QHBoxLayout* layout;
};
I'm not going to write a whole game here or make it look fancy. That's just the gist, showing how to get 200 labels up in a programmatic fashion:
With my code, getting a GameCell from an (x,y) coordinate doesn't require an average of 50 string compares. Due to the formalized and predictable nature of 2D arrays, indexing into cells[x][y] only requires a single multiply operation and a single addition operation. There's no downcasting, and you can simply write:
cells[x][y].setState(GameCell::Miss);
ADDENDUM: Creating a QWidget for each grid cell isn't necessarily the way to go in the first place. Some might consider that "heavyweight". If your game were being played out on a large virtual space of tiles then it could be much too slow. You might find it useful to look into QGraphicsGridLayout, which could be a more appropriate approach in the long run:
http://doc.qt.io/qt-5/qtwidgets-graphicsview-basicgraphicslayouts-example.html
Using QWidgets won't be much of an issue with a 10x10 grid, however, so if you want to just stick with that then you can. If you're going to do it that way, then at least you shouldn't be placing them all by hand!

QTableView, QStandardItemModel and Signals

I have a QTableView, populated with a QStandardItemModel.
I update the model frequently over network and the model is also updated by user directly via the QTableView.
Now I like to call a method when the user is changing some data, so i did:
connect(model, SIGNAL(itemChanged(QStandardItem*)), this, SLOT(dataChanged(QStandardItem*)));
The Problem now is, that my dataChanged method is called, also when the item is updated over the network.
model->setData(index, new_val);
Is there another signal which is only emitted if, the user is changing something inside the QTableview ???
No, AFAIK there is no such signal but you there is a way to hack it.
When editing an item from the QTableView the activated signal will have been emited. The idea is to catch this signal and connect it to a slot that will store the last manually changed item.
connect(view, SIGNAL(activated(QModelIndex), this, SLOT(manuallyActivated(QModelIndex)));
void manuallyActivated(QModelIndex index)
{
// This variable should be in your header file...
lastManuallyModifiedIndex = index;
}
Now simply modify your dataChanged slot to check if the item that changed corresponds to the last modified item.
void dataChanged(QStandardItem* item)
{
// If it is invalid simply ignore it...
if (lastManuallyModifiedIndex.isValid() == false)
return;
// only if it is modified manually we process it
if (item->index() == lastManuallyModifiedIndex)
{
// make last modified index invalid
lastManuallyModifiedIndex = QModelIndex();
doSomething();
}
}
You could block the table signals when an update comes in from your network.
QObject::blockSignals(bool block)
or you could listen for click and edit event in succession.

QListWidget Add Custom Items in Qt?

How to add 2 Images and Text in QListWidget at run time in Qt?
I want to place one image at the beginning of the list and one at the end and text should be soon after my first Image.
itemclicked event
connect(list, SIGNAL(itemClicked()), this, SLOT(clicked(QListWidgetItem *)));
void MyWidget::clicked(QListWidgetItem *item)
{
//code
}
Have a look at the setItemWidget function. You can design a widget (call it MyListItemWidget) that contains two icon labels and a text label, and in its constructor provide the two icons and the text. Then you could add it to your QListWidget. Sample code follows:
QIcon icon1, icon2; // Load them
MyListItemWidget *myListItem = new MyListItemWidget(icon1, icon2, "Text between icons");
QListWidgetItem *item = new QListWidgetItem();
ui->listWidget->addItem(item);
ui->listWidget->setItemWidget(item, myListItem );
You should also take a look at QListView and QItemDelegate which is the best option for designing and displaying custom list items.
EDIT CONCERNING YOUR CONNECTION
When connecting a signal to a slot their signature should match. This means that a slot cannot have more parameters than a signal. From the signals-slots documentation
The signals and slots mechanism is type safe: The signature of a
signal must match the signature of the receiving slot. (In fact a slot
may have a shorter signature than the signal it receives because it
can ignore extra arguments.)
This means that your signal must have the QListWidgetItem * argument in the connection.
connect(list, SIGNAL(itemClicked(QListWidgetItem *)), this, SLOT(clicked(QListWidgetItem *)))

Qt force QCheckBox to emit signal on setChecked

If I called QCheckBox::setChecked( x ) the toggled signal is only emitted if x is not the same as the current checkbox state. I understand the logic behind this, to avoid signaling if nothing has changed. However, in some situations where I have a more complicated widgets setup, I need the signal to always be emitted. This ensures anybody who has connected to the checkbox will receive the first state.
Is there a way to have QCheckBox::setChecked(bool) emit a signal regardless of whether the state has changed?
My simple workaround now is to just force the checkbox into multiple states by doing setChecked(!x) and setChecked(x). I was hoping for a more correct way of doing this.
Looking into the QAbstractButton implementation, I found the following lines of code:
if (!d->checkable || d->checked == checked) {
if (!d->blockRefresh)
checkStateSet();
return;
}
where checkStateSet is a virtual function. QCheckBox overrides this and emits a stateChanged() signal only if the state changed.
I haven't tested this, but I think d->blockRefresh is set to false if you call QCheckBox::setChecked( ... ) directly.
If this is the case, it means you could subclass QCheckBox and override the checkStateSet() method to something like this:
void MyCheckBox::checkStateSet()
{
QCheckBox::checkStateSet();
if( m_oldState == checkState() )
{
// emit the signal here, as QCheckBox::checkStateSet() didn't do it.
emit stateChanged( m_oldState );
}
else
{
// don't emit the signal as it has been emitted already,
// but save the old state
m_oldState = checkState();
}
}
where the header file contains
private:
Qt::CheckState m_oldState;
which must be initialised to Qt::Unchecked in the constructor.
Here is another solution which may or may not be possible for your case:
If you can be 100% sure that your signals and slots are connected before the checkbox has a chance to change its state, every connected class can initialize itself safely assuming the checkbox is not checked. This is because checkboxes are always unchecked upon construction.
This way you might not have to call setChecked() after connecting the signals.
However, This approach does not work if there is a chance a signal gets connected after the checkbox has already changed. I'm not 100% fond of this approach myself but it might be an option for you nevertheless.
One way would be to subclass QCheckBox and implement the emitting of signals in that where you need it, for example :
class MyCheckBox : public QCheckBox
{
Q_OBJECT
public:
MyCheckBox(QWidget *parent = 0) : QCheckBox(parent) {};
virtual void setChecked(bool checked) {
QCheckBox::setChecked(checked); emit checkWasSet(checked);
};
signals:
void checkWasSet(bool value);
};
Now use this class instead of the regular QCheckBox class, and you can connect to the checkWasSet() signal for whenever the check state is set.
You could emit the signal with the current state yourself:
checkbox.stateChanged.emit(checkbox.checkState())

Resources