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

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.

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)

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

Kivy RecycleView: Get indices of all data items that are currently in view

I need to get the indices of all data items that are currently in view in a Kivy RecycleView widget. I want to display many Image widgets for which I render a texture and apply it to them. To save memory I need to delete these textures if they are not in view any more. I tried using the Kivy RecycleView example and modify it for my needs:
class SelectableImage(RecycleDataViewBehavior, Image):
def refresh_view_attrs(self, rv, index, data):
#Catch and handle the view changes
#print in view
print(index)
#call initial function of image class
return super(SelectableImage, self).refresh_view_attrs(rv, index, data)
The problem here is that refresh_view_attrs() only fires when a new Image widget gets added to the view. Thus I can only know what that last Image is, but not if there are any other in view. For this I'd also need to know which widget disappeared from the view. Is there any function in the RecycleView widget that I can use to obtain such information? Maybe there is a function that gets called whenever the view changes?
Okay, I found three possible solutions for my issue:
1. Solution
Calculate the position of all the Image widgets yourself and compare it with the position of the scrollbar to get the widgets that are currently displayed.
This is quite hacky and the RecycleView already does this internally. Thus I'd save the unnecessary computing and avoid this approach.
2. Solution
Use the get_visible_view(index) function of the view_adapter property of a RecycleView. This returns the currently visible view associated with index. If it return None, the view associated with index is not visible right now. This is how it's called for example:
self.myRecycleView.view_adapter.get_visible_view(index)
You could loop through the entire length of your data list and check for each item (the index in the list) if it is currently displayed or not.
3. Solution
My favourite approach: Use get_view_index_at(pos) of the layout_manager property to check what view index is at the given coordinates. This way you could check which item is at the top of the RecycleLayout widget and which one is at the bottom. You need to use coordinate transformation though. Example:
#get position at top center of RecycleView (upper limit)
pos = self.myRecycleView.to_local(self.myRecycleView.center_x, self.myRecycleView.height)
#check which items collides with the given position
print(self.myRecycleView.layout_manager.get_view_index_at(pos))
I hope this clears some things up!

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

Resources