QGraphicsItem itemChange function doesn't match with the item's state? - c++

I used the following itemChange function from my own subclass to trigger a signal as:
QVariant WayPointItem::itemChange(QGraphicsItem::GraphicsItemChange change, const QVariant &value)
{
if (change == QGraphicsItem::ItemSelectedChange){
emit selcState(value.toBool());
}
return QGraphicsItem::itemChange(change, value);
}
The selcState signal is connected to a slot as:
void WaypointLineItem::toggleText(bool value)
{
if (wp->isSelected()) //wp is the waypointitem which emit the signal selcState()
text.show();
else
text.hide();
}
I found that when I select a waypointitem, it did trigger the itemChange function, and the QGraphicsItem::ItemSelectedChange's bool value is also correct.
However, when it comes to the slot, the wp->isSelected() return's a value that is different.
How could this happen, is there any solution to this?

The QGraphicsItem::ItemSelectedChange is called, when the selected state is about to change. This means the new selected state is in the value parameter.
You have to react on the QGraphicsItem::ItemSelectedHasChanged, where the selected state has already changed.

Related

Call QAbstractTableModel setData method from QML

I am trying to make a fully generic connection between the QML TableView and my C++ class that subclasses the QAbstractTableModel. So far I am able to read the data through data method, as this is done internally by the TableView module. From what I have read over SO however, I need to call setData myself on the QML side. The problem is, the function header looks as follows:
bool setData(const QModelIndex &index,
const QVariant &value,
int role = Qt::EditRole) override;
In order to call it, I need the QModelIndex, which I dont know how to obtain on the QML side. I would appreciate a QML example.
Edit: I have worked around this issue by wrapping the setData as follows:
Q_INVOKABLE bool setData(const int row,
const int column,
const QVariant& value);
bool CVarTableModel::setData(const int row,
const int column,
const QVariant& value)
{
return setData(index(row, column), value);
}
I can now call it directly on the QML side. The problem is, even though the actual setData is called now, the dataChanged signal doesnt make the QML TableView to update the cell... Is there anything else I am missing?
I can probably answer your edit. It seems like you might not be emitting dataChanged() signal in your setData function. This would explain why the view is not updated.
From QAbstractTableModel::setData() documentation:
The dataChanged() signal should be emitted if the data was successfully set.
Also, about your original question. You could use index method from the qml: model.setData(model.index(row,column), data) to avoid overriding setData.

Value of spin box, just before it changes

I'm writig a code using qt libraries, in which I need to get the value of a spin box (by a signal) just before it changes.
I've got:
QSpinBox spinBoxWidth:
QSpinBox spinBoxScale;
I want to connect a signal from spinBoxWidth to spinBoxScale, so that the value of SpinBoxScale is always "the Value of SpinBoxWidth after changing" to "its value before changing".
(Scale = width_new/width_old)
I didn't find any slot in Qt which returns the old value of a spin box while changing the value. Can I somehow write a slot for that?
Best Regards
There are two ways of doing this:
Catch the change before it happens and store the old value using the event system (QKeyEvent, QMouseEvent). This is error-prone, as the value of spinBoxWidth can be set manually.
Connect spinBoxWidth's valueChanged(int) signal to a slot and reference the last value it was called with. I recommend this method.
Try something like this:
class MonitoringObject : public QObject
{
Q_OBJECT
int lastValue;
int currentValue;
...
public Q_SLOTS:
void onValueChanged(int newVal)
{
lastValue = currentValue;
currentValue = newVal;
if (lastValue == 0) //catch divide-by-zero
emit ratioChanged(0);
else
emit ratioChanged(currentValue/lastValue);
}
Q_SIGNALS:
void ratioChanged(int);
After your signals are connected, the flow should look like this:
spinBoxWidth emits valueChanged(int)
MonitoringObject::onValueChanged(int) is invoked, does its work and emits ratioChanged(int)
spinBoxScale receives the signal in its setValue(int) slot and sets the appropriate value.
The easiest way is probably a lambda that caches the value of valueChanged for the next call:
auto const width = new QSpinBox();
width->setValue(200);
connect(width, &QSpinBox::valueChanged,
[prev_value = width->value()](int const value) mutable {
auto const scale = double(value) / double(prev_value);
// do stuff
prev_value = value;
});
I believe there is no specific signal to the "value before change" because you can always store it from the previous signal "onValueChanged()" you received.
So the basic idea would be:
First time, receive signal onValueChanged(value) and store the value value_old;
Next time you receive the signal, you can compute you scale!value/value_old;
Then you can send a new signal, or directly modify the object with the new value.
You can derived your own version of QSpinBox including this code or implemented in the class it has to receive the signal. It depends on your architecture.

QComboBox Get The Varient When "currentIndexChanged(int)" Emitted

I am having difficulty finding documentation on this or an example.
Could someone concretely show me how to access the QVariant of the currently selected index in a QComboBox
QComboBox * combo = new QComboBox();
combo->addItem("Bla1", QVariant(1));
combo->addItem("Bla2", QVariant(2));
combo->addItem("Bla3", QVariant(3));
combo->addItem("Bla4", QVariant(4));
connect(combo, SIGNAL(currentIndexChanged(int)), this, slot(HANDLEITMAN(int))
And of course else where in the source
void TheCooler::HANDLEITMAN(int index)
{
//What do I do with index?
//sender()?
}
First, make combo a member of TheCooler, or otherwise put HANDLEITMAN in a class which has combo as a member. Unless it's available to TheCooler::HANDLEITMAN somehow you can't get the data, and this is the logical way to do it. Then it's just
void TheCooler::HANDLEITMAN(int index)
{
QVariant data = combo->itemData(index);
}
If you don't want to make combo a member of the class TheCooler, you can use the sender() function that returns a pointer to the QObject that sent the triggering signal (in this case, currentIndexChanged(int)).
void TheCooler::HANDLEITMAN(int index)
{
QComboBox * combo = qobject_cast< QComboBox * >(sender());
if (combo == 0)
return; // something wrong happened
QVariant data = combo->itemData(index);
}
If combo is null, then you probably tried to call the slot by yourself, or you have connected it with a signal emitted by a class that is not a QComboBox.

Automatically refreshing a QTableView when data changed

I have written a custom data model to be displayed with several QTableViews.
Technically, everything works fine: my views show the changes made from my model. My data model is editable, and the setData() method does emit the dataChanged() signal and returns true on successful edition.
However, my problem is that I have to move my mouse over a QTableView for it to show the actual change, while I would like all views to show the changes when they were made, without needing to interact with the views in order for them to be updated.
Any idea? Thanks,
It may be relevant to mention that I do not use the default Qt::EditRole role to edit data, but rather a custom enum value (named ActiveRole).
Here is what I am seeking: my data model contains properties on how to display data, used to generate style sheets that are fed to the viewS.
Thus, when altering the model, for each view, all its items are impacted, which is why the dataChanged() signal is sent with indices covering all cells.
I also tried to emit layoutChanged(), but it does not seem to change the behavior in my case.
Here is an excerpt of the setData() method:
bool DataModel::setData(QModelIndex const& idx, QVariant const& value, int role)
{
if (ActiveRole == role)
{
// Update data...
QModelIndex topLeft = index(0, 0);
QModelIndex bottomRight = index(rowCount() - 1, columnCount() - 1);
emit dataChanged(topLeft, bottomRight);
emit layoutChanged();
return true;
}
return false;
}
Here is a sample of the data() method:
QVariant DataModel::data(QModelIndex const& idx, int role) const
{
if (ActiveRole == role)
{
boost::uuids::uuid id;
return qVariantFromValue(id);
}
return QVariant();
}
And flags() does indicate an editable model:
Qt::ItemFlags DataModel::flags(QModelIndex const& idx) const
{
if (false == idx.isValid())
{
return Qt::ItemIsEditable;
}
return QAbstractTableModel::flags(idx) | Qt::ItemIsEditable;
}
I have a custom delegate, which relies heavily on this SO thread for overriding the paint and the sizeHint methods in order to draw a QTextDocument. Also, it provides the content of the ActiveRole to the editor in setEditorData, and calls DataMode::setData in setModelData:
void DataModelDelegate::setEditorData(QWidget* editor, QModelIndex const& idx) const
{
auto active = qVariantValue<boost::uuids::uuid>(idx.data(ActiveRole));
static_cast<DataModelEditor*>(editor)->setActive(active);
}
void DataModelDelegate::setModelData(QWidget* editor, QAbstractItemModel* model, QModelIndex const& idx) const
{
auto active = static_cast<DataModelEditor*>(editor)->getActive();
model->setData(idx, qVariantFromValue(active), ActiveRole);
}
In the createEditor(), I plug a signal from the editor to a slot of my delegate for committing data:
QWidget* DataModelDelegate::createEditor(QWidget* parent, QStyleOptionViewItem const& option, QModelIndex const& idx) const
{
auto editor = new DataModelEditor(parent);
connect(editor, SIGNAL(activeItem()), this, SLOT(commitEditorData()));
return editor;
}
When clicking on an item, the editor triggers the activeItem signal; the connected slot commitEditorData in turn raises the commitData signal with the editor in argument.
So all my views use these custom delegate, editor, and data model. The view that I am interacting with does show the change immediately, but the other views need to have the mouse hovering over them to show changes as well.
I actually found the problem, which was that my other view was not properly notified of the data changes: my views each showed different portions of my data, so the other views needed to be notified of the dataChanged(), but for their own, proper, indices.
On a side note, I also had the problem of updating my views while my Qt application was not the active window in my window manager. The solution was to call repaint() on the main window.
I have met the same problem, and let me add a detailed explanation to the piwi's answers. If you change the data,and what to update the single or several columns(or rows,depending on your requirement), you should emit a set of index for topleft to bottomright.For example,if you have a table like below:
and, now you have changed some data, and want to update the cell row 1, column 1-2, then you should emit signal dataChange
emit datachange(index(1,1),index(1,2));
Are you calling the setData()? Is the dataChanged() signal really emitted? Connect some debug logging slot to it. I dare to speculate that this is very similar problem to yours:
http://www.qtcentre.org/threads/18388-Refreshing-a-QTableView-when-QAbstractTableModel-changes?s=fd88b7c4e59f4487a5457db551f3df2c

Qt attaching itemChanged signal to QStandardItem doesn't work

I'm using qtreeview trying to find out when ever the check box state changes,
but the SLOT method never fires.
Here is my code:
// in the init
connect(ui.treeView_mainwindow, SIGNAL(itemChanged( const QModelIndex &)), this,
SLOT(tree_itemChanged( const QModelIndex &)));
// this method never trigered
void GroupMainWindowContainer::tree_itemChanged(const QModelIndex & index)
{
QStandardItem* standardItem = m_model->itemFromIndex(index);
Qt::CheckState checkState = standardItem->checkState();
if(checkState == Qt::Checked)
{
WRITELOG("Qt::Checked")
}
else if(checkState == Qt::Unchecked)
{
WRITELOG("Qt::Unchecked")
}
}
// this is how i build the items :
QList<QStandardItem *> items;
items.insert(0,new QStandardItem());
items.at(0)->setCheckable(true);
items.at(0)->setCheckState(Qt::Unchecked);
m_model->insertRow(0,items);
QTreeView doesn't have an itemChanged signal, so your QObject::connect call will fail.
This is a good example of why you should always check the return value from QObject::connect. Also, the failed connection would have appeared in your debug output, which you should also be monitoring.
Possibly you're looking for QTreeWidget, which inherits from QTreeView and does have an itemChanged signal, albeit one that has an QTreeWidgetItem* as a parameter, not a const QModelIndex&.