pyQt sub-menu backtrack - qt

I have a menu that has a structure like this:
Elements
A
B
C\
1\
a
b
2\
a
b
D
where Elements is displayed on the menubar anything with a \ has a sub-menu.
In this example I have two a's. I want to be able to distinguish which a was clicked buy getting a list like this for example ['a', '1', 'C', 'Elements'].
Does Qt have a function where I can look up the top menus, or a way of backtracking?
I don't want to have to write a connection for each QAction in the menu because that would be a lot of extra code and rather redundant I think.

Make use of the QMenu.triggered, QMenuBar.triggered, and QToolBar.actionTriggered signals.
These signals all pass a reference to the action that was triggered, thus avoiding the need to connect each action to an individual slot.
An alternative approach would be to create a subclass of QAction that allows a handler to be passed as an argument to its constructor. All the boiler-plate connection code could then be factored out into the __init__ method. This approach can be more flexible if there are a lot of actions that are re-used in several different menus and toolbars.

QSignalMapper might be what you are looking for.

I found another way of solving the problem, below, but I'm going with ekhumoro's answer because his was is cleaner I think.
backtrack = [str(event.text())]
previousWidget = event.associatedWidgets()[0]
while previousWidget.__class__.__name__ != 'QMenuBar':
backtrack.append(str(previousWidget.title()))
previousWidget = previousWidget.parent()
print backtrack

Related

QFileDialog component signals

I am subclassing QFileDialog to try to get some custom behavior. I would like to connect to signals emitted by components of the dialog, e.g. the textEdited signal when the file name line edit is manually edited. I understand that QFileDialog emits some signals itself, but these do not cover the cases I would like to respond to.
I have two ways about this I can think of, but don't know how to implement. One is to somehow attain a reference to the component to connect to it's signal. The other would be something with event filters, but the event source is the dialog itself, so I don't know how to determine where mouse clicks or key presses occur.
Are either of these methods feasible? Or another way?
Here is one option (your first suggestion):
dialog = QFileDialog()
layout = dialog.layout()
# for i in range(layout.rowCount()):
# for j in range(layout.columnCount()):
# try:
# print i,j
# print layout.itemAtPosition(i,j).widget()
# except:
# pass
line_edit = layout.itemAtPosition(2,1).widget()
line_edit.setText('Hello Stack Overflow')
dialog.exec_()
This gives you access to the QLineEdit in the dialog, which has a bunch of signals you can connect to.
I've also included the code I used to find this widget. I just iterated over the widgets in the layout of the dialog and found the indices of the one I was after. So if you need access to anything else in the dialog, you should be able to find it pretty easily!
Downside to this method: If the layout changes in a future version of Qt, this will break. I suppose you could make the algorithm more robust by looking for widgets that are instances of QLineEdit, but there are always risks with hacky approaches like this!

Qt - signal for when QListWidget row is edited?

I am working in Qt4.7, and I have a QListWidget in my dialog. I have a QString that needs to match the current text in the row of this widget (the individual rows are editable). Looking at the signals associated with QListWidget, there seem to be signals for when a different index is selected but none for when the text of a the currently selected row changes. I thought currentTextChanged(QString) would do it, but it didn't. I also thought to try to connect each individual row to something, but QListWidgetItem doesn't have any built-in signals. Does anyone know of a way to do this? Thanks!
At first it seems like QListWidget::itemChanged is the way to go, but soon you run into a problem: the signal is sent for everything - inserts, changing colors, checking boxes, and anything else that "changes" the item! Predelnik pointed that out in his answer. Some people have tried to put in flags and filter everywhere by intercepting various signals to find out if editing was the actual event. It gets very messy.
There is also QAbstractItemModel::dataChanged , which would seem like a good solution. It even has a parameter "const QVector& lstRoles" so you could scan for Qt::EditRole and see if it was really edited. Alas, there's a catch - it gets called for everything just like QListWidget::itemChanged and unfortunately, for QListWidget anyway, the roles parameter is always empty when it's called (I tried it). So much for that idea...
Fortunately, there's still hope... This solution does the trick! :
http://falsinsoft.blogspot.com/2013/11/qlistwidget-and-item-edit-event.html
He uses QAbstractItemDelegate::closeEditor, but I prefer using QAbstractItemDelegate::commitData.
So make a connect like so...
connect(ui.pLstItems->itemDelegate(), &QAbstractItemDelegate::commitData, this, &MyWidget::OnLstItemsCommitData);
Then implement the slot like this...
void MyWidget::OnLstItemsCommitData(QWidget* pLineEdit)
{
QString strNewText = reinterpret_cast<QLineEdit*>(pLineEdit)->text();
int nRow = ui.pLstItems->currentRow();
// do whatever you need here....
}
Now you have a slot that gets called only when the list item's text has been edited!
I guess you need to look into the following signal:
void QListWidget::itemChanged(QListWidgetItem * item)
But be careful because it's being sent every time some property of item changed, not only text. I remember when we ran into the problem once when we changed item colors and got tons of false positive slots called because of that. If you need more fine tuning I guess it's better to write model/view classes yourself and not rely on QListWidget.

Qt QGraphicsScene::drawItems subsitute?

For QGraphicsScene::drawItems the reference says:
Reimplement this function to provide custom painting of all items for the scene; gaining complete control over how each item is drawn.
But this function is marked as obsolete.
Is there any new equivalent method?
QGraphicsView::paintEvent() now calls
d->scene->d_func()->drawItems()
which means the method is part of class QGraphicsScenePrivate which you cannot override afaik.
If you need to change the way your items are drawn, first try to think of another way (i.e. a solution which does not require stepping into the drawItems() method). If you can't find any solution like that, your only chance is reactivating the pre-4.6-behaviour by setting
QGraphicsView::setOptimizationFlag( QGraphicsView::IndirectPainting )

Flex: select tree node right after the dataProvider is been assigned / updated / replace

i have a Flex tree control and im trying to select a tree node 3 levels down right after the dataProvider is assigned with a collection object like the following.
basically treeItem1, treeItem2, treeItem3 are the nodes in the tree and treeitem3 is a child of treeItem2 which is a child of treeItem1. Assume these treeItem(1,2,3) are referenced correctly from the collection items.
my problem is that if i wait for the whole component to load completely then select the nodes, it open/select/scrolltoIndex correctly. However, if i were to select the node right after the dataProvider is assigned, then it doesn't even open or select (basically the this.treeService.selectedItem is always null).
can anyone point out what i did wrong? is there anything needs to happen after the dataProvider is assigned?
thanks
this.treeService.dataProvider = oPricingHelper.getCurrentPricingSercicesTreeSource();
this.treeService.expandItem(treeItem1, true);
this.treeService.expandItem(treeItem2, true);
this.treeService.selectedItem = treeItem3;
this.treeService.scrollToIndex(this.treeService.selectedIndex);
I have used the updateComplete event to know when a component (such as a DataGroup or List) has completed rendering after performing a simple task (such as updating the dataProvider reference). Of course, you have to be careful and remove listening to updateComplete because it can run a lot, unless you have a need for it to run.
Something like:
//...some function...
this.treeService.addEventListener(FlexEvent.UPDATE_COMPLETE, onTreeUpdateComplete);
this.treeService.dataProvider = oPricingHelper.getCurrentPricingSercicesTreeSource();
//...rest of some function...
private function onTreeUpdateComplete(event:FlexEvent):void {
this.treeService.removeEventListener(FlexEvent.UPDATE_COMPLETE, onTreeUpdateComplete);
this.treeService.expandItem(treeItem1, true);
this.treeService.expandItem(treeItem2, true);
this.treeService.selectedItem = treeItem3;
this.treeService.scrollToIndex(this.treeService.selectedIndex);
}
I'm not positive your experiencing the same issue but I seem to have the same type of problem with using the advanced data grid, it appears in these cases where the dataprovider is acceptable as multiple types, the components do some extra work in the background to wrap things up into something Hierarchical (HierarchicalData or HierarchicalCollectionView) and in doing so the dataprovider setter call is not synchronous (so it will return before actually having assigned the internal property storing the dataprovider). I've used callLater in this case with moderate success, callLater is generally a bad practice but basically adds a function to a list of functions to call once background processing is done, so this is assuming that something in the dataprovider setter called UIComponent.suspendBackgroundProcessing() and that it will subsequently call UIComponent.resumeBackgroundProcessing() and then it will execute the list of functions added by using callLater. Alternatively you could use setTimeout(someFunction,1000).
These are both "hacks" the real solution is to dig into the framework code and see what it's really doing when you tell it to set the dataprovider. Wherever you see that it actually has set the dataprovider you could extend that class and dispatch an event that you could listen for to run the function to do the selections after this point.
If anyone has a better solution please by all means correct me (I would love to have a better answer than this)

PyQt, Qt, one event handler working with many items

I have a couple of checkboxes on my form, and I don't want to write separate event handler for each, because they all will implement the same logic. Instead I want to write just one event handler that will know about what checkbox has been clicked.
E.g. in Delphi I can use it this way:
function click_handler(sender):
begin
checked_box := sender.tag;
end;
Here I remember current checked box number in some variable (tag property was manually set in Delphi IDE).
I searched and can't find how I can implement this using Qt, because event handlers don't have sender argument.
I implemented it like this, but it's not convenient:
# assign handlers (n assignments)
checkbox_1.clicked.connect(self.cb_1_click)
...
checkbox_<n>.clicked.connect(self.cb_<n>_click)
# separate handler for each check box (n functions, doing the same stupid work)
def cb_1_click:
self.cb_click(sender=1)
...
def cb_<n>_click:
self.cb_click(sender=n)
# main check box click logic (1 function)
def cb_click(sender):
# do something common for all checkboxes
Thank you.
You may use QObject::sender() in slots to find out who emitted the signal.
Also you might want to check out QSignalMapper which is intended exactly for solving these problems.

Resources