Catch QTableWidgetItem check state change - c++

I have one QTableWidget with some QTableWidgetsItems on it. Some items use checkboxes. I've added the checkboxes using the follow code:
QTableWidgetsItem->setCheckState(Qt::Checked);
I would like now to call some function when this checkbox state change. Using a signal for example.
What may be the easiest way to accomplish this?

The easiest way to do this is to capture signal(s) of QTableWidget with slot(s) in the class that contains QTableWidget. While it would seem that QTableWidget::itemActivated might be our best bet, it is uncertain whether or not this is emitted when the Qt::CheckState is equal to Qt::Checked. In addition, even if this was true, the signal would not provide you the capabilities of handling item unchecking which your application may need to do.
So, here is my proposed solution. Capture the QTableWidget::itemPressed and QTableWidget::itemClicked signals with slots defined in the class that contains the QTableWidget. As itemPressed should be called BEFORE the mouse button is released, and itemClicked should be called AFTER the mouse button is released, the Qt::CheckState for that QTableWidgetItem should only be set in between these two signal emissions. Thus, you can determine exactly when a QTableWidgetItem's checkState has changed with low memory overhead.
Here is an example of what these slots could look like:
void tableItemPressed(QTableWidgetItem * item)
{
// member variable used to keep track of the check state for a
// table widget item currently being pressed
m_pressedItemState = item->checkState();
}
void tableItemClicked(QTableWidgetItem * item)
{
// if check box has been clicked
if (m_pressedItemState != item->checkState())
{
// perform check logic here
}
}
And the signals/ slots would be connected as follows:
connect(m_tableWidget,SIGNAL(itemPressed(QTableWidgetItem *)),this,SLOT(tableItemPressed(QTableWidgetItem *)));
connect(m_tableWidget,SIGNAL(itemClicked(QTableWidgetItem *)),this,SLOT(tableItemClicked(QTableWidgetItem *)));
Where m_tableWidget is the QTableWidget * you associate with your table widget.

Related

C++ QT Creator create slot function for QTextEdit::verticalSlideBar?

I'm using QT Creator to create a simple Application that has two textEdit fields next to each oteher. I want both fields to be linked when it comes to scrolling so that when one field scrolls up or down, the other one will as well automatically and vice versa. For this, I need a callback function that is triggered whenever the user moves the slideBar of one of the fields. Unfortunately, when I right click the textEdit fields and press "Go to slots" I can not find an event for the movement of the slideBar.
How can I achieve this?
QTextEdit does not have a signal for when the sliderbar in it changes, since it is not a scrollbar. However QScrollBar has the sliderMoved(int value) signal which is emitted when the slider moves. QScrollBar also has a way to set its scroll value via slots (with setValue(int value))
We can therefore tie two scrollbars together using signals and slots very easily.
For example:
...
// Get easy pointers to the scrollbars
QScrollBar* textbx_slider_1 = ui->textbx1->verticalScrollBar();
QScrollBar* textbx_slider_2 = ui->textbx2->verticalScrollBar();
// Connect them too each other
connect(textbx_slider_1, &QScrollBar::sliderMoved, textbx_slider_2, &QScrollBar::setValue); // Connect the first scrollbar to the second
connect(textbx_slider_2, &QScrollBar::sliderMoved, textbx_slider_1, &QScrollBar::setValue); // Connect the second scrollbar to the first
...
(This assumes that your QTextEdit widgets have ids' of textbx1 and textbx2)
Edit:
It is worth mentioning that sliderMoved will not be emitted when using the scroll wheel on the text box. To detect those inputs you must use something like QScrollBar::valueChanged. You have to be careful with this however since setValue emits valueChanged, meaning you will get an infinite feedback loop if you simply modify the above code.
To prevent this you could use a lambda, something like this:
...
int old_pos = textbx_slider_1->value()
std::function<void(int, QScrollBar*)> f = [old_pos](int new_pos, QScrollBar* slider){
if (new_pos != old_pos) {
// Only trigger if the value has changed
slider->setValue(new_pos);
old_pos = new_pos;
};
connect(textbx_slider_1, &QScrollBar::sliderMoved, std::bind(f, std::placeholders::_1, textbx_slider_2)); // Connect the first scrollbar to the second
connect(textbx_slider_2, &QScrollBar::sliderMoved, std::bind(f, std::placeholders::_1, textbx_slider_1)); // Connect the second scrollbar to the first
...
(The weirdness with std::bind() is simply so we don't repeat virtually the same lambda twice)

QTreeWidgetItem setting not selectable clears the selection

I have a QTreeWidget and I want certain rows to be non select-able, which can be achieved by QTreeWidgetItem::setFlags(treeWidgetItem->flags() & ~Qt::ItemIsSelectable).
The problem is that I have an existing row that is already selected and later I click on the non select-able row, selectedItems() returns an empty list. I want the selected row to keep its selection if the user tries to select a non select-able row.
Should I keep track of the selection and handle this scenario in the code, or this can be achieved somehow else. I'd rather not reinvent the wheel.
Thank you.
Cause
Calling QTreeView::mousePressEvent(event) clears the selection when clicked on a non-selectable item if the selection mode is set to QAbstractItemView::SingleSelection.
Solution
My solution would be to either:
Set the selection mode to QAbstractItemView::MultiSelection,
or (in case this is not desired):
Reimplement the mouse events in a subclass of QTreeWidget in order to bypass the default behavior.
Note: In either case, use the QItemSelectionModel::selectionChanged signal to get the list of the selected items.
Example
Here is an example re-implementation of the mouse events in MyTreeWidget preventing the selection of being cleared by clicking a non-selectable item. The top item is expanded/collapsed on a double click:
void MyTreeWidget::mousePressEvent(QMouseEvent *event)
{
if (indexAt(event->pos())->flags() & Qt::ItemIsSelectable)
QTreeWidget::mousePressEvent(event);
}
void MyTreeWidget::mouseDoubleClickEvent(QMouseEvent *event)
{
QTreeWidget::mouseDoubleClickEvent(event);
QTreeWidgetItem *item = itemAt(event->pos());
if (item && item->childCount())
item->setExpanded(!item->isExpanded());
}
The modified in the described manner version of the provided example is available on GitHub.
Improvements
Special thanks to #eyllanesc for making this example more waterproof by:
adding a check if item is not NULL
replacing itemAt with indexAt

Qt C++ : How to get event target object?

There is a container Widget instance A and there are contained Widget instances C1, C2, C3, ... etc within A.
There is a slot that handles Widget A's action_triggreed() signal.
Is there a way to determine which of the target Widgets C1, C2, C3, ... was clicked?
The reason for this necessity is that there are numerous contained widgets, and it doesn't make sense to use connect() method on each, which would require 50+ lines of extra connect statements, one for each!
For example: Consider a QToolBox with lots of Buttons. How would you determine which Button is pressed by using a QToolBox action_triggered or similar signals, without using signals and slots for individual Buttons separately?
You can use the QObject::sender() function.
void A::triggeredSlot() {
QObject* obj = sender();
QButton* but = qobject_cast<QButton*>(obj);
}
Just use the correct types etc.
There are a few general approaches.
The first one is straightforward - you should connect each widget's signal to the slot and check what sender() is. Yes, lines of code for connecting.
The second one is to use a kind of mapper. Every widget is connected to the mapper and only mapper is connected to the target slot. Lines of code again, at least in the part where you connect widgets to the mapper. There is a generic QSignalMapper or you can use more specific one suited for your widgets. For example, if they are buttons then you can use QButtonGroup class. Every button is registered in the group and only one signal/slot connection is required.
QButtonGroup group;
group->addButton(buttonC1,C1_ID);
...
group->addButton(buttonC1,Cn_ID);
connect(&group,SIGNAL(buttonClicked(QAbstractButton*),this,SLOT(buttonClicked(QAbstractButton*));
The third approach is to detect mouse event only on the mother's widget A and then iterate over all its children and find which one is under mouse. Less code, you can easily add new widgets, but the cost is iterating over all widgets in runtime. Below is an example. Note, that you can add specific QObject names or properties to the widgets C1... so that you could filter them if you are interested only in a part of children widgets of the given type.
void mousePressEvent(QMouseEvent* event)
{
if (event->button()==Qt::LeftButton)
{
QList<QToolButton*> buttons=findChildren<QToolButton*>(); // you can also use specific object names on the widgets under your interest
foreach (QToolButton* button, buttons)
{
if (button->underMouse()) // you could try isDown() for button, but I'm not sure if that will work here
{
emit buttonClicked(button);
break;
}
}
}
}
Well, you could try another approach also. Detect the mouse event, get cursor position and find the child widget on that position. Not costly in runtime.
void mousePressEvent(QMouseEvent* event)
{
if (event->button()==Qt::LeftButton)
{
QPoint pt=mapFromGlobal(QCursor::pos());
QWidget* child=childAt(pt);
if (child)
{
emit childClicked(child);
}
}
}

Deleting a QGraphicsItem/QGraphicsObject from QGraphicsScene?

I have created Qt GUI application. It consists of QGraphicsScene, and items (QGraphicsItems) are added to them by pressing or triggering pushbuttons. Each item added to the scene are members of different classes derived from QGraphicsItem. Now, my challenge is to delete an added item off the scene through one of the following mechanisms:
1) Right click an added item, create a context menu, and then use
scene->removeItem(addedItem);
2) Double click the item which deletes the item
3) Select an item using the flag ItemIsSelectable, and then delete the item by pressing the delete key on the keyboard
But having said that, as a newbie to Qt, I'm unable to do number 1 since the context menu doesn't show up when right clicked. In the case of number 2, I used signals and slots, a single emitted whenever an item is double clicked, and a slot in the mainWindow absorbs the signal and removes the item. But this way, the programs fails to compile because of the error "duplicate symbol found" when I add a Q_OBJECT macro to the header file of the item's class.
So my final option is to select an item on the screen and propane the keyboard signal to delete the item by pressing delete. How can be this done? Please give me advice if any of the above methods can be easily done in case I might be doing it completely wrong.
P.S. : I know there a lot of queries regarding deleting QGraphicsItem off QGraphicsScene, but none of them document a solid answer.
... I'm unable to do number 1 since the context menu doesn't show up when right clicked.
There are two possible methods to accomplish this:
Create a QWidget based menu, attached to the QGraphicsView.
Create your own menu item, derived from a QGraphicsItem.
Whilst the 2nd method will take more time, it's probably a better system in my opinion, as it will feel more integrated with the item you're deleting in the scene. The first method is also possible and if it's not working, then you could post an example question on SO.
2, I used signals and slots, ... because of the error "duplicate symbol found" when I add a Q_OBJECT macro to the header file
It sounds like you're trying to add the signal / slot functionality to a class derived from QGraphicsItem. You don't need to do this. Qt provides the QGraphicsObject class, which you can derive from, instead of QGraphicsItem, if you want signals and slots on items in a QGraphicsScene.
propane the keyboard signal to delete the item by pressing delete.
I assume you mean to 'propagate' keyboard signals. By overriding the QGraphicsScene and its keyPressEvent or keyReleaseEvent, you can get a list of selected items and delete them from the scene. Here's a skeleton example: -
class MyScene : public QGraphicsScene
{
protected:
void keyReleaseEvent(QKeyEvent * keyEvent);
};
void MyScene::keyReleaseEvent(QKeyEvent * keyEvent)
{
if(keyEvent->key() == Qt::Key_Backspace)
{
QList<QGraphicsItem*> selectedItems = selectedItems(); // get list of selected items
foreach(QGraphicsItem* item, selectedItems)
{
removeItem(item);
delete item;
}
}
}
You're seeking a lot of answers, Not so much how to handle QGraphicsItem or QGraphicsScene.
1) Right click an added item, create a context menu, and then use scene->removeItem(addedItem); here.
2) Double click the item, which deletes the item - you'll need to handle double clicks, and hit-testing the QGraphicsItems, you'll have to implement mouseDoubleClickEvent(QMouseEvent *e) and pass e's pos() to this to determine if a QGraphicsItem was clicked or not.
3) Select an item using the flag ItemIsSelectable and then delete the item by pressing the delete key on the keyboard - I'm not sure about the ItemIsSelectable flag. However, you'll need #2. And to learn how to handle keyboard input, by overriding this:
void QWidget::keyPressEvent( QKeyEvent *k ){
switch ( tolower(k->ascii()) ) {
case '\x08': \\backspace
break;
case '\x7F': \\delete
break;
}
}
There's also the Qt::key enumeration, which has Key_Backspace, and Key_Delete. It can be tested against the QKeyEvent::Key()'s return if you don't like dealing with ASCII character codes.

How do I properly set up generic QT actions for a menu constructed at run time?

I am populating a sytem tray icon menu (QMenu) from entries in an xml file which is read when my application starts up.
I am unsure of how to properly set up the SLOT end of the action:
QList<CMenuItem> menuItems = m_layout->getMenuItems();
QListIterator<CMenuItem> iter(menuItems);
while (iter.hasNext())
{
CMenuItem menuItem = iter.next();
QAction *action = new QAction(menuItem.qsTitle, this);
connect(action, SIGNAL(triggered()), this, SLOT(launchMenuItem()));
trayIconMenu->addAction(action);
}
How does my "launchMenuItem()" SLOT know which menu item was triggered? I can't make a SLOT for each menu item as I don't know how many items will exist until run time.
I can think of some ugly ways to do this, but I am looking for the RIGHT way.
What I usually do is to use QAction::setData(const QVariant&) to store whatever action ID I need. Then on slot side I retrieve ID with QAction::data() and behave accordingly.
Note that QVariant obviously accepts much more than basic int (which is what I use to identify actions), you can pass any QVariant-compatible info.
edit : oh! btw, this is somehow ugly because I make use of QObject::sender() to cast triggered action back. Sorry for that, but it works anyway.