wxPython - Tooltips for notebook tabs - python-3.x

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)

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.

How to have a screen that lists multiple results, each of which is a clickable link (on Tkinter)

I'm new to GUI's,
I made a basic search engine that finds documents that contain certain words and puts them in a list.
Now, I want this list of paths to be displayed on my GUI, one under the other (using \n I guess), all of them clickable and automatically opening the right document for you with something like:
os.startfile(path, 'open')
In the current version I am only displaying one result (the first in the list) and I'm doing it with a label as so:
my_label.config(text=path)
my_label.bind("<Button-1>", lambda e: os.startfile(path, 'open'))
I could just make more labels but then it's inefficient and also not dynamic (the one I envision would list all results and be scrollable for example).
Appreciate any help in this.
You can use a set of Label and bind right click to it, or a single Listbox to show your paths and bind on it.
from tkinter import *
import os
root = Tk()
lst = [f'path {i}' for i in range(1,6)]
def select(e):
path = e.widget.get(*e.widget.curselection())
os.startfile(path,'open')
lstbox = Listbox(root)
lstbox.pack(padx=10,pady=10)
for i in lst:
lstbox.insert('end',i)
lstbox.bind('<<ListboxSelect>>',select) # Or lstbox.bind('<Double-1>',select) for double click
root.mainloop()
lst is supposed to be replaced with your required list. Another viable approach is set of Buttons with command that opens the required path.

How to I fix my code to only access one item in the list on each iteration?

I have been working with pygame for Python these past few days but I have encountered an issue that I can not seem to overcome. First let me talk about the issue and provide the code. The code with the issue is in the WIP branch of my repo. That is were new features are tested before been deployed.
https://github.com/Aeryes/Demented/blob/WIP/menu_testing.py
I have been trying to place my game menus in a class like structure to make it easier to create menus in the future by having a universal class for the menu creation. This has worked except for accessing individual members of my button list located in the menu class. When I check to see if a button is been hovered over to change color the check works but the new color is applied to all buttons in the list not just the one that is been hovered over.
I have tried many things thus far including the following:
I have tried to reference the individual item like this:
--> main_menu.buttons[0].hovered = True
When I do this the code does not work nor does it break the program. It just makes no color change at all.
I have tried to remove the buttons = [] list from the Menu() class and create each button as an individual instance by doing the following:
play_button = main_menu.add_button(button info here)
This just give me a NullType error when I call hovered = True.
I have been using this as a reference for help with this issue:
https://python-forum.io/Thread-PyGame-Creating-a-state-machine
Here is my direct question: How do I get hovering to work for an individual button and not all of them at the same time?
Thank you for your help.
Your problem is here:
if main_menu.buttons[0].rect.collidepoint(pygame.mouse.get_pos()):
Button.hovered = True
else:
Button.hovered = False
You're not setting the hovered flag to true on the actual button, but rather the class (which, as far as I know, doesn't generally serve a function)
To correct it, you should be using
if main_menu.buttons[0].rect.collidepoint(pygame.mouse.get_pos()):
main_menu.buttons[0].hover = True
else:
main_menu.buttons[0].hover = False
The bigger problem is that you never redraw your buttons after the update. The way I solved this (using your preexisting code) was to update your conditional to this:
if main_menu.buttons[0].rect.collidepoint(pygame.mouse.get_pos()):
main_menu.buttons[0].hovered = True
main_menu.buttons[0].draw()
else:
main_menu.buttons[0].hovered = False
main_menu.buttons[0].draw()
There are a couple things to think about.
A for loop iterating over indices would probably be much nicer than three individual conditionals.
You have a bug in your menu class, where you write "for button in buttons:" instead of "for button in self.buttons:"
Finally, you might want to look into your else condition. Given that you're currently recreating your menu continuously, it might not be necessary to un-hover the button, but I'll leave that to you

In Python3/tkinter is there a way to temporarily stop accepting clicks in a Treeview widget?

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>")

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. :)

Resources