Qt: Check mark next to a menu item - qt

In my application a user is able to select a comport from a list of all available comports. This is to be done in a menu that is populated with QAction widgets. However, the current code does not show which port has been selected, and in order to make the application more intuitive, I would like to indicate which port has been selected, thus showing the user what state the program is in. By including some sort of icon (like a check mark, for example) in the menu right next to the selected port, it would be obvious which port has been selected. What is the correct way to do this?
My code:
# Populate the serial port menu with all the available ports.
for port in comports():
_port = QtWidgets.QAction(port[0], mainWindow)
_port.setCheckable(True) # WRONG!
self.menuChoose_port.addAction(_port)
_port.triggered.connect(self.comportSelect)
This code obviously doesn't do what I want, because it puts checkboxes next to each menu item. Additionally, it allows the user to check more than one comport at time, which is not at all desirable.

Coming to this years late, and in C++, but in the event someone down the line gets here, this is what I did (disclaimer: this is a bit of a hack):
Keep a reference to the action that's currently selected
When selected, set the checked and checkable flags to true for the selected item.
Once the selection changes, remove the check from the selected action, and update the reference
This seems to allow for items to be checked, but only one item at a time. This solution really only works for trying to maintain a single selection.
// in the header:
QAction *selectedAction = nullptr;
// in the cpp file
QAction *act = new QAction("exampleItem");
connect(act, &QAction::triggered, [this, act] {
if (selectedAction != nullptr) {
selectedAction->setChecked(false);
selectedAction->setCheckable(false);
}
act->setCheckable(true);
act->setChecked(true);
selectedAction = newAction;
})
menu->addAction(act);

Related

Qt's QTest doesn't select an item in a drop-down list with a click

There are two drop-downs. I'm trying to click on an item in a drop-down from a QComboBox.
For this purpose I created a function, launched for both drop-downs, here's an excerpt:
constexpr int DELAY{ 1000 };
void clickDropDown(int row, QComboBox *comboBox)
{
QListView *dropDownList = comboBox->findChild<QListView *>();
QModelIndex foundIndex{ dropDownList->model()->index(row, 0) };
QRect foundDropDownItem = dropDownList->visualRect(foundIndex);
QPoint foundDropDownItemPosition = foundDropDownItem.center();
QWidget *activeWidget = dropDownList->viewport();
QTest::mouseClick(activeWidget, Qt::LeftButton, Qt::NoModifier, foundDropDownItemPosition);
QTest::qWait(DELAY); // waits 1 second
}
Now the flow looks like this:
First drop-down list is shown,
The item is highlighted (mouseClick) as chosen, but not really selected, not chosen
UI test goes to the second column, the first column shows the first element in the list (i.e., the upper-most), despite what was highlighted;
An item in the second drop-down is selected/chosen
So, the second list seems to be working with this function, the first one doesn't. I need these items to be selected, not just highlighted.
I run on Ubuntu 21.04. Seems to work well on Windows 10. Seems to fail on Mac. Any suggestions how to make it work?
What works:
two clicks + Enter, but this will crash on MacOs. So that's not really a solution.
Also, it destroys the dropDownList object, and if make with an interval, will lead to a crash.
What else doesn't work: key down N times.
Any suggestions?
I found the answer by myself.
That's Qt's bug (as of Fall 2021), described here:
https://bugreports.qt.io/browse/QTBUG-77772. The mouse clicked not the button, but some point nearby. Why it was selected and why it failed to react otherwise is still a mystery.
Decision: I left this test to run on Windows and Linux only, closing it for Mac.

QT Creator: Trigger a Slot with Code?

I may have worked myself into a corner but this sounded to me like a good idea at the time.
I have been developing an interface that permits a user to modify settings of a robotic device, i.e. speed, directions, force, etc. with a very large series of options in the form of ComboBoxes. The problem is that there are about a thousand of these things, in sub categories. e.g. Speed category x1, x2, x3, Y1, y2, etc. So rather than create a thousand comboboxes in QT, I thought the good idea was to create one set of 50 (ish) and then provide a few button to switch between categories. So when the user selects speed QT, populates the comboboxes with the appropriate options, sets the style sheets and text for the labels etc. So it appears as though a dedicated page exists. Then if the user selects Direction, QT Writes the current index of each box to a dedicated array and then repopulates the boxes, labels etc with the appropriate content. I then do this over and over for the various needs of the system.
All of that seems to work fine. However I am now in a bind where the options provided to navigate to each page have grown. For instance I have forward / backward buttons (like you woudl expect in a set-up wizard), as well as action menus at the top to jump to a page. So now the code is becoming very repetitious. If you select the next button, I write the current values to array, then repopulate. If you jump to the page from anywhere, I look to see where I am, write it to array, and populate the boxes. Thus if I need to change anything I have to make the change in numerous places in the code.
I know that this is not optimal. What I woudl like to do is run a continuous loop as I woudl normally do with Micros in C. So the program can look at a variable in each pass and if it is then it does. I am not however skilled enough to figure this loop out in QT. So my new thought was...
Is it possible to trigger an action or slot with a variable. For example, if the user presses the Next button it triggers a slot for a button that does not exist, so that QT will execute a particular line of Code? Then I can have 1 dedicated section focused on reading and writing boxes, with a bunch of actions that will take me there.
You can make a signal that is triggered with an emit call in your code, so you'd hook up the next button signal of clicked to a slot that does some work and moves on, or directly calls another signal that you've created that triggers a slot elsewhere, or do some work in a lambda triggered by the button press.
I would first load all the ComboBoxes options in a QStringList array (or maybe an array of QList<QLatin1String> lists - for memory saving and code efficiency).
Then I would keep an array of a 1000 integers for current ComboBox indexes.
When the user changes a value in some ComboBox, the currentIndexChanged signal will trigger the corresponding slot (a single slot for all the ComboBoxes would be enough - sender()->objectName() to get the name of the ComboBox which had sent the signal):
void WindowWidget::on_ComboBox_currentIndexChanged(int index)
{
name = sender()->objectName();
/* here change the corresponding integer in the current
indexes array */
}
On Next/Back button push repopulate the ComboBoxes. Also, provide some 'Save' button for saving the ComboBoxes indexes (or trigger the Save slot on some action, i.e. on window close either even on a timer signal).

Qtreewidget -- undo the selection change

In wx-widget we can undo any event by calling VETO().
Here i am doing my first GUI in QT. I have created a new node test case 3.
Now i want that selection on tree should be only allowed to changed till i have not saved this new node. If i have not saved this node at least once, selection change should revert back to this unsaved node test case 3. To indicate that i have saved the node i am using a global variable signal.
How can i achive it?
I tried something like this but no result.
Inside selection changed event handler when new node created signal is set, then if i change the treewidget selection i am trying to set the selection of the treewidget to the last item of the treewidget:
QPoint prevPoint;
QModelIndex index;
int count = ui->treeWidgetLeft->topLevelItemCount();
//prevPoint.setX(currentXmlRootNodeNumber +1 );
prevPoint.setX(count);
prevPoint.setY(0);
index = ui->treeWidgetLeft->indexAt(prevPoint);
abortEvent = TRUE;
ui->treeWidgetLeft->selectionModel()->select(index ,QItemSelectionModel::Select);
And check at begning of selection changed event handler:
if (abortEvent) {
abortEvent = false;
return;
}
That's not how the users expect treewidget and selections to work. I suggest that you reconsider the workflow of your program.
What if you let the user change the test case without saving when the user selects another test case from the treewidget? Show a messagebox and ask if the user wants to save. Then save/discard and change the test case to the one the user clicked.
Or remove the "Save" button completely. Always save the data the user enters to the test case. If you like, you could add an undo button instead. It would restore the test case to the state it was when the user selected it. That is quite easy to implement, just create a copy of the test case when editing starts and revert to that if the user selects the undo operation.
And I really recommend that you change the "Perv" button to "Prev".

Is there way to access QAction::data() without using an intermediate QAction group?

When creating dynamic menus, I often need the associated signal to respond to a particular index value (e.g., a database row identifier associated with that menu item). To do this I seem to need to use the code below, which feels like a hack.
QActionGroup *oneOffgroup = new QActionGroup(menu);
QAction *action = new QAction(tr("New text form..."),menu);
action->setData( writingSystem.id() );
oneOffgroup->addAction(action);
connect( oneOffgroup, SIGNAL(triggered(QAction*)), this, SLOT(newTextForm(QAction*)) );
submenu->addAction(action);
This answer suggests using the QMenu::triggered(QAction*) signal. That would just mean that I would have to sort out every signal that was sent to that menu, though. (That would be okay in some contexts, not in the one I am currently working on, though.)

PyQt (or just QT). How to get QComboBox to fire a signal whenever it is set to a value (even if unchanged)

I am using PyQt4, but this is general enough that it could just apply to QT.
I have a series of QComboBoxes that I fill from left to right (i.e. selecting an item in the leftmost will populate the next one. Selecting an item in that one will populate the next, and so on)
I am having difficulty getting my signals to fire under all situations (i.e. regardless of whether the current index changes or not AND regardless of whether the item is set by the user or set programatically).
More detail:
I rely on the signals of the first QCombox to fire whenever an item is selected so that I can populate the next QCombobox in the gui. I then rely on THAT QCombobox to emit a signal so that I can populate the next one. And so on.
I want to pre-select an item in each QCombobox based on the user's last interaction with the gui.
I have a unique function per QCombobox that is responsible for populating and pre-selecting just that QCombobox. The code looks something like this:
comboBox1.blockSignals(True)
comboBox1.clear()
comboBox1.addItems(sorted(itemList))
comboBox1.blockSignals(False)
comboBox1.setCurrentIndex(intLastSavedState1)
where intLastSavedState1 is an integer that is derived from the text that was last selected by the user the last time they had used the app. I had hoped that the last line of this function would fire a signal that would cause the next combo box's function to load and pre-select an item (comboBox2). And that action would then cause the next comboBox's function to activate and it would cascade to the next and the next. But it is not working across all cases.
I have tried two versions of the signals:
self.connect(comboBox1, QtCore.SIGNAL("currentIndexChanged(const QString&)"), self.load_comboBox2)
and
self.connect(comboBox1, QtCore.SIGNAL("activated(const QString&)"), self.load_comboBox2)
In the first case, the signal will fire only if the intLastSavedState1 is different than whatever is currently selected in the combo box. This causes an issue if the user had last selected item 0 from that list. In this case QT does not recognize my script setting the the current index to 0 as being a change (since after loading the box it appears to think it is already on index 0), and so the signal does not fire.
In the second case, the signal will fire regardless of what is currently selected in the combo box... but only if activated by the user. It will not fire when my script tries to set the current index programatically.
These appear to be my only two options regarding the signals. So... is there another way of pre-selecting items in a QCombobox that will trigger a signal each and every time?
Well... sometimes just the act of asking a question can lead you to a (partial) answer.
I have a work-around but I am still interested in hearing if someone has a better idea.
I am now programatically setting the index of the QCombobox to -1 immediately after loading it up. Then, when I programatically set the actual index based on the user's history, it will always be considered a change (i.e. it will never be -1) and the signal will fire
using: currentIndexChanged(const QString&)
So my code looks like this now:
comboBox1.blockSignals(True)
comboBox1.clear()
comboBox1.addItems(sorted(itemList))
comboBox1.setCurrentIndex(-1)
comboBox1.blockSignals(False)
comboBox1.setCurrentIndex(intLastSavedState1)
and my signal looks like this:
self.connect(comboBox1, QtCore.SIGNAL("currentIndexChanged(const QString&)"), self.load_comboBox2)
This functions... does anyone have a better idea?
Thanks agian.
You can check current Index of your ComboBox and then either call your slot or do call setCurrentIndex().
Example:
if comboBox1.currentIndex() == index:
self.load_comboBox2(index)
else
comboBox1.setCurrentIndex(index)
This way you will not end up calling slot twice.

Resources