In Python3/tkinter is there a way to temporarily stop accepting clicks in a Treeview widget? - python-3.x

I have a GUI based in Python 3 and tkinter that has a big ttk.Treeview. I have defined methods for row selection (one click) and opening an advanced info panel (double-click). I need to ensure that, after being double-clicked, for the next one or two seconds, the Treeview state won't be changed by another click. Is it possible to deactivate Treeview mouse bindings, like what we do with buttons?

Doing a little more research, I was able to come up with a solution for this. I just created an empty method that is called when the tree widget is supposed to be inactive. So, we can use something like this to "unbind" all the mouse events and re-bind them a few seconds later, as needed:
def nothing(self, *event):
""" # Hacking moment: A function that does nothing, for those times you need it...
"""
pass
def bind_tree(self):
""" # Bind mouse and keyboard events to their respective functions or methods...
"""
self.tree.bind('<<TreeviewSelect>>', self.selectItem_popup)
self.tree.bind('<Double-1>', self.show_details)
self.tree.bind("<Button-2>", self.popupMenu)
self.tree.bind("<Button-3>", self.popupMenu)
def unbind_tree(self):
""" # Unbind all mouse and keyboard events, by binding them to an empty method...
"""
self.tree.bind('<<TreeviewSelect>>', self.nothing)
self.tree.bind('<Double-1>', self.nothing)
self.tree.bind("<Button-2>", self.nothing)
self.tree.bind("<Button-3>", self.nothing)
Then, in the rest of the code, We only need to call bind_tree() and unbind_tree() as needed.

This worked for me:
tree.bind("<ButtonRelease-1>", my_select_function)
# Do some stuff
tree.unbind("<ButtonRelease-1>")

Related

How to get list of widgets in pyqt?

I am designing a interface with QtDesigner and editing its functionalities with PyQt. One of the widgets that i created has several pushButtons and i want them all to have the property Checkable = True.
So currently what i am doing is:
class MyWidget(QWidget):
def __init__(self):
super(MyWidget, self).__init__()
uic.loadUi('./my_widget.ui', self)
self.pushButton_X.setCheckable(True)
self.pushButton_Y.setCheckable(True)
self.pushButton_Z.setCheckable(True)
self.pushButton_ETC.setCheckable(True)
self.show()
Is there any way i can do something like:
pushbuttons_list = self.get_all_pushbuttons()
for i in pushbuttons_list:
i.setCheckable(True)
?
Im trying the answers to this question but i keep getting
> File "./testing_class.py", line 12, in __init__
items = (self.layout.itemAt(i) for i in range(self.layout.count()))
AttributeError: 'builtin_function_or_method' object has no attribute 'count'
Your example failed because all Qt properties can be accessed by calling the attribute (see the parentheses used for self.layout()):
items = (self.layout().itemAt(i) for i in range(self.layout().count()))
Note that this will only get access to the layout items, not the widgets they might (or might not) contain. For this, using comprehensions will only complicate things unnecessarily, as one-liners should be used only as long as they keep the code readable; the comprehension above is already complex enough, and since you will need to cycle through all items anyway, there's little use in it. Just use a for loop, which is far more readable:
for i in range(self.layout().count()):
widget = self.layout().itemAt(i).widget()
if isinstance(widget, QPushButton):
widget.setCheckable(True)
Note that if you have several buttons that you want checkable and you are doing this only to avoid manually setting the checkable property for all of them, you can just use the extended selection mode that is common for all UI elements that allow selection by keeping pressed Ctrl when clicking multiple objects.
The property editor will automatically show all common properties for the selected widgets, and apply the changed property to all of them.
Another option is to use findChildren():
for button in self.findChildren(QPushButton, Qt.FindDirectChildrenOnly):
button.setCheckable(True)
The Qt.FindDirectChildrenOnly flag is required whenever you have complex layouts that have nested widgets containing other buttons, if you remove that you will find any push button that exists inside the window (or the widget referenced as self).
Finally, buttons can be part of a QButtonGroup, which can also be created in Designer. Just select all buttons (as explained above), right click on one of them and select "Assign to button group", and then:
for button in self.buttonGroup.buttons():
button.setCheckable(True)
Note that the self.buttonGroup above is the object name assigned by Designer, if you change it or you create more than one, ensure that the reference matches it.

wxPython - Tooltips for notebook tabs

I'm creating a GUI using wxPython that uses the wx.Notebook widget.
Since I removed the labels on the Notebook tabs for design purposes,
I would like to add tooltips for each tab individually that show
the tab's purpose.
I thought I could use the AddPage() method of the Notebook class. But it only returns bool values, so that I cannot use the SetToolTip() method for either tab.
How would I go about doing that?
This is not something that is built-in to the wx.Notebook widget. You could probably do it yourself by binding to wx.EVT_MOTION and checking you mouse's position to determine when to set a tooltip.
The AUINotebook might be a better choice since it has a TabHitTest method. Someone on the wxPython group mentioned a way to add tooltips to it here: http://wxpython-users.1045709.n5.nabble.com/setting-a-tooltip-on-auinotebook-tab-td5548750.html
Here's the code they ended up using in the mouse over event handler:
def OnMouseOver(self, evt):
""" mouse over on a tab """
desc = None
n = self.GetSelection()
if n != -1:
page_info = self._tabs.GetPage(n)
ctrl, ctrl_idx = self.FindTab(page_info.window)
if ctrl:
ctrl.SetToolTipString(desc)

PyQt: how to create a dialog as a child of another dialog

In PyQt, I have a dialog that spawns another dialog (when you click a button to do so in the first dialog). I want to maintain a strong parent-child relationship, for garbage collection purposes, and to make the .findChild and .findChildren functions usable.
The root of the question may be: how do you use .setParent() but still have the object in question be shown as a separate window, rather than shown within the parent widget?
The 'parent' dialog (actually a container widget within a tab within a dialog) is 'newEntryWidget'. It spawns 'clueDialog' when a signal (not shown here) calls newEntryWidget.quickTextClueAction as a slot. Visually, clueDialog should be a "top level window" with its own banner, its own window attributes (I want to keep it on top of everything else), etc.
class newEntryWidget(QWidget,Ui_newEntryWidget):
def __init__(self,parent,sec=0,formattedLocString='',fleet='',dev='',origLocString='',amendFlag=False,amendRow=None):
QDialog.__init__(self)
self.parent=parent # just an attribute; not the same as setParent
...
...
def quickTextClueAction(self):
self.newClueDialog=clueDialog(self,self.ui.timeField.text(),self.ui.teamField.text(),self.ui.radioLocField.text(),lastClueNumber+1)
self.newClueDialog.show()
class clueDialog(QDialog,Ui_clueDialog):
def __init__(self,parent,t,callsign,radioLoc,newClueNumber):
QDialog.__init__(self)
self.parent=parent # just an attribute; not the same as setParent
...
...
Right now, since I am using self.parent=parent, which is just an attribute and not true "parent/child relationship" in Qt terms, clueDialog does display as a top level window, which is what I want:
But, if I add 'self.setParent(parent)' in the clueDialog init function, I get this:
How can I preserve the top-level-window behavior, and have the real-honest-parent-child-relationship so that .findChild(clueDialog) will work from within the newEntryWidget object?
Ultimately, I want to enforce that the newEntryWidget object should not be closed if it still has and 'child' clueDialogs open.
Instead of calling .setParent, call QDialog.__init__(self, parent) which constructs the clue dialog with a parent from the beginning. Setting it this way allows Qt establishes the parent-child relationship at the beginning of clueDialog's lifetime.
I believe this will fix both your issues: 1) the clue window frame, caption, etc. will be painted, 2) you will be able to iterate for proper children of newEntry.

Getting pyqt application focus for popup menu to disappear when clicking away from it

Let me quickly explain the background to this. I'm developing a custom menu system inside a 3D application called Softimage XSI. It has a PyQt application object created already and ProcessEvents is being called a certain number of times every second so that PyQt applications can exist in a non-modal state within XSI.
To implement the menu, I've got a webpage embedded in a toolbar which is calling a plugin for XSI that I've written to show a PyQt menu. This all works fine (albeit, slightly contrived!).
The issue is that when I show the menu, it won't disappear when I click away from it. If I move the mouse over the menu, and then click away from it, it will disappear. It's only when it first pops up.
I've tried everything I can think of. Here's a list:
Using QtGui.qApp.installEventFilter(menu) to try and catch the mousepressed signal. It never gets triggered. I suspect the application itself isn't receiving the click.
Using menu.raise_() makes no difference
Neither does QtGui.qApp.setActiveWindow(menu)
Or menu.setFocus()
I've also tried:
event = QtGui.QMouseEvent(QtCore.QEvent.MouseMove, pos, QtCore.Qt.NoButton, QtCore.Qt.NoButton, QtCore.Qt.NoModifier)
QtGui.qApp.sendEvent(menu, event)
I had a go writing my own QEventLoop, but it just crashed XSI. I suspect trying to run a modal loop inside the other one probably isn't a legal thing to do. Either that, or I really don't know what I'm doing (equally probable)
The only thing I have partial success with is using grabMouse(). This is what makes the menu close if I click away from the menu (only after the mouse has passed over the menu once), but I have to call it a couple of times for it to "stick".
So this is my code at the moment:
class MyMenu (QtGui.QMenu):
def __init__(self, parent = None):
QtGui.QMenu.__init__(self, parent)
self.grabbed=2
def getMouse(self):
if self.grabbed>0:
self.grabMouse()
self.grabbed-=1
def paintEvent(self, event):
QtGui.QMenu.paintEvent(self, event)
self.getMouse()
def hideEvent(self, event):
self.releaseMouse()
def ShowMenu():
menu = MyMenu()
menu.addAction("A")
menu.addAction("B")
menu.addAction("C")
submenu = MyMenu()
submenu.addAction("D")
submenu.addAction("E")
submenu.addAction("F")
menu.addMenu(submenu)
menu.setTearOffEnabled(True)
menu.setStyleSheet("font: 8pt \"Sans Serif\";")
submenu.setStyleSheet("font: 8pt \"Sans Serif\";")
submenu.setTitle("Window")
submenu.setTearOffEnabled(True)
pos = QtGui.QCursor.pos()
pos.setX(105)
menu.popup(pos)
#Prevent garbage collection
QtGui.XSIMenu=menu
QtGui.XSISubMenu=submenu
#Desperate acts!
menu.raise_()
QtGui.qApp.setActiveWindow(menu)
menu.setFocus()
Any thoughts or random suggestions would be very gratefully received as this is driving me nuts! Don't be afraid to suggest modifications to stuff I've already tried, as I'm relatively new to PyQt and I may well have missed something.
Many thanks,
Andy
Just before calling popup with self.trayMenu.popup(QtGui.QCursor.pos()), call self.trayMenu.activateWindow(). Putting activateWindow before popup makes the left-click menu work the same as the right-click menu and it goes away when you click elsewhere. :)

PyQt4: disable deleting widget after setCentralWidget

I have two custom widgets (two classes based on QtGui.QWidget). In __init__ of QtGui.QMainWindow I create their instances:
self.MyWidget1 = MyWidget1()
self.MyWidget2 = MyWidget2()
There are also two buttons (QtGui.QPushButton) in __init__ part, and there are two slots when user clicks each of them:
def clickButton1(self):
self.setCentralWidget(self.MyWidget1)
def clickButton2(self):
self.setCentralWidget(self.MyWidget2)
But it works only on first click and then PyQt says that underlying widget (MyWidget1 or MyWidget2) was deleted. I think it was done by sip module. Is there a way to prevent deleting widgets after reseting of central widget? Thanks!
I think it's almost impossible.
From setCentralWidget docs:
Note: QMainWindow takes ownership of the widget pointer and deletes it at the appropriate time.
So, you should create new MyWidget instance.
def clickButton2(self):
self.setCentralWidget(MyWidget2())
But the right way of doing such things is to use QStackedWidget

Resources