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.
Related
So I have a situation where I can't get a couple slots to fire in the order that I would like them to.
The basic set-up is that I have a Mainwindow with a statusbar that needs to get updated based on a signal from a child widget (SearchWidget). When the "Go" button is clicked on the child widget, I would like it to update the status bar to say "Searching..." and then perform the actual database search. However, I can only get the updateStatusBar slot to trigger AFTER the search is complete and displayed in a tablewidget. I have tried re-arranging the connections to the appropriate order, I have tried a separate function that emits the signal for the statusbar and then the signal for the search, but nothing seems to work. The search always executes first and the statusbar doesn't change until after that is complete.
I'm a newbie a this, but I'm guessing maybe the issue has something to do with the parent-child relationship between the mainwindow and the widget? Perhaps slots within the same widget are prioritized in some way? See basic code below.
Mainwindow class:
MainWindow::MainWindow(QWidget *parent) :
QMainWindow(parent)
{
initMembers();
initUI();
connect(searchWidget, SIGNAL(searchingStatus(QString)), this, SLOT(updateStatusBar(QString)));
}
void MainWindow::initMembers()
{
tabWidget = new QTabWidget(this);
searchWidget = new SearchWidget(this);
saleWidget = new SaleWidget(this);
statusBar = new QStatusBar();
setCentralWidget(tabWidget);
setStatusBar(statusBar);
}
void MainWindow::initUI()
{
tabWidget->addTab(searchWidget, "Search");
tabWidget->addTab(saleWidget, "Sale Data");
}
void MainWindow::updateStatusBar(QString status)
{
statusBar->showMessage(status);
}
SearchWidget class:
SearchWidget::SearchWidget(QWidget *parent) : QWidget(parent)
{
connect(goButton, SIGNAL(clicked), this, SLOT(buildQuery()));
}
void SearchWidget::buildQuery()
{
emit searchingStatus("Searching...");
//builds sql query
}
Any enlightening info would be much appreciated!
I suspect that it's not an ordering issue, but the fact that the SQL query is blocking the main GUI event loop: try inserting a call to QApplication::processEvents() after your emit searchStatus(...) call. If I'm correct, you should see the status bar update before the database search completes.
However, because you're still blocking the event loop, your GUI will still freeze while the DB call executes, which isn't great. You can eliminate this by running the query on a different thread (one of the simplest ways is via http://doc.qt.io/qt-5/qtconcurrent.html#run), but beware you then have to worry about concurrency issues (e.g., now you can click the go button lots of times in a row...).
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.
I use checkboxes in my GUI to toggle bits in a class controlling a maschine connected by signals/slots.
I also need the opposite direction, because if some commands to the maschine toggle the same bits, my GUI should reflect the changes.
My problem is:
When I click the checkbox, its state is not yet updated when the signal is sent.
So the first signal arrives at the maschine, the bit gets toggled, the maschine responds with the second signal and arrives at the GUI handler method 'updateCheckBoxXYZ'.
Now I want to figure out if I need to update the checkbox.
If the whole chain is started by user, the checkbox will be updated automatically at last.
If an internal command directly toggled the bit, the checkbox will need to be checked to reflect the internal change.
Because the checkbox has not been checked yet, I can not ask for 'isChecked()'.
What should I do to not getting trapped in an endless loop?
Update:
I just tried something like the following code:
// slot called by toggled(bool)
void DialogXY::checkBoxXYChanged(bool bState)
{
if (bState != m_bGuiStateXY)
{
m_bGuiStateXY = bState;
emit GuiXYChanged(bState);
// optional: .. do some GUI related things ..
}
}
// slot called on signal 'GuiXYChanged(bState)'
void Machine::changeXY(int iModul, bool bState)
{
if (bState != m_bMachineStateXY)
{
emit MachineXYChanged(bState);
}
// .. change machine configuration ..
}
// slot called on signal 'MachineXYChanged(bState)'
void DialogXY::updateCheckBoxXY(bool bState)
{
if (bState != m_bStateXY)
{
ui.checkBoxXY->setChecked(bState);
// will signal toggled()
}
}
But I need extra variables for each GUI item,
I have to initialize then correctly, etc.
I would prefer something more elegant.
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())
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*.