Python tkinter to modify only listbox with focus - python

Good day,
I have a python application that produces multiple listboxes each with it's own list of data. These listboxes are created dynamically according to the length of a user generated list.
I have a button that when clicked i want to trigger some code to effect the active listbox (removing the value from the list amongst other things).
So my plan is to iterate through all the listboxes and only delve deeper if the list box has focus. But alas, after 2-3 hours of peeling through questions and tkinter documentation I cannot find any way to determine if something has focus or not.
Thanks in advance!

Widgets are capable of emitting <FocusIn> and <FocusOut> events, so you can bind callbacks in order to manually keep track of which listbox has focus. Example:
from Tkinter import *
class App(Tk):
def __init__(self, *args, **kargs):
Tk.__init__(self, *args, **kargs)
self.focused_box = None
for i in range(4):
box = Listbox(self)
box.pack()
box.insert(END, "box item #1")
box.bind("<FocusIn>", self.box_focused)
box.bind("<FocusOut>", self.box_unfocused)
button = Button(text="add item to list", command=self.add_clicked)
button.pack()
#called when a listbox gains focus
def box_focused(self, event):
self.focused_box = event.widget
#called when a listbox loses focus
def box_unfocused(self, event):
self.focused_box = None
#called when the user clicks the "add item to list" button
def add_clicked(self):
if not self.focused_box: return
self.focused_box.insert(END, "another item")
App().mainloop()
Here, clicking the button will add "another item" to whichever listbox has focus.

Related

How do I bind callbacks to a listbox using tkinter?

I'm playing around with tkinter and am struggling somewhat with the use of listboxes. I want to bind an arbitrary function to a selection event. As I understand it - it should go something like this.
import Tkinter as tk
class SampleApp(tk.Tk):
def __init__(self, *args, **kwargs):
tk.Tk.__init__(self, *args, **kwargs)
lb = tk.Listbox(self)
lb.insert("end", "one")
lb.insert("end", "two")
lb.insert("end", "three")
lb.bind("<Double-Button-1>", self.OnDouble)
lb.pack(side="top", fill="both", expand=True)
def OnDouble(self, event):
widget = event.widget
selection=widget.curselection()
value = widget.get(selection[0])
print "selection:", selection, ": '%s'" % value
if __name__ == "__main__":
app = SampleApp()
app.mainloop()
My understanding is that when an element in the list box is double-clicked, a virtual event is created which can be bound to a callback function - in this case the OnDouble function defined.
I'm a little confused about the intricacies of this, would someone be able to explain exactly what is happening in this OnDouble function?
I'm trying to trigger an event when an element is selected from a listbox - i'm using pygubu to design the GUI but I understand that the syntax still follows tkinter conventions:
class TestApp:
def __init__(self):
self.builder = builder = pygubu.Builder()
builder.add_resource_path(PROJECT_PATH)
builder.add_from_file(PROJECT_UI)
self.mainwindow = builder.get_object('toplevel_1')
builder.connect_callbacks(self)
self.box = builder.get_object('listbox_1')
self.list = [1,2,3,4]
self.box.insert(tk.END, self.list[0])
self.box.insert(tk.END, self.list[1])
self.box.insert(tk.END, self.list[2])
self.box.insert(tk.END, self.list[3])
self.console = builder.get_object('text_1')
def print_console(self, msg):
self.console.configure(state="normal")
self.console.insert(tk.END, msg+"\n")
self.console.configure(state="disabled")
self.console.update_idletasks()
def run(self):
self.mainwindow.mainloop()
if __name__ == '__main__':
app = TestApp()
app.run()
Essentially I want the console to print (using the defined command) something like f'You have selected number {number}' when said number is double clicked on the list box. How can I do this? I would appreciate the workings behind it, so I can apply it to more complex exercises, and understand the solution rather than just copy/paste it.
Items in a listbox can be selected with a single click, but they can also be selected via the keyboard. For this reason, binding to a specific physical event such as <Double-1> is not the best way to be notified when the selection changes.
In the specific case of the listbox, binding to <<ListboxSelect>> is the best event to bind to, as that event will be generated after the selection has been set regardless of how it was set.
For example:
lb.bind("<<ListboxSelect>>", self.onSelect)
When a function is called via a binding it will automatically be passed an argument that contains details about the event that triggered the function call. One of the parameters is widget, which is a reference to the widget that received the call.
In your specific case you could ignore that and use self.listbox within the bound function, but using event.widget is a good habit to get into. That is why the code you copied starts with widget = event.widget
The next step is to get the selected item or items. In your case only a single selection is allowed at a time. However, the curselection method always returns a list. selection[0] will refer to the item selected, if there is a selection.
Note: the <<ListboxSelect>> event is triggered both when the selection is set or it is unset, so the code in your question could fail if the items are deselected by some means.
Finally, once you have the index of the selected item (eg: selection[0]), you can use that numerical index to get the text of the selected item. That is what the line value = widget.get(selection[0]) is doing.

How to sound a bell when the user clicks outside a modal window?

The situation is simple. I have a main window with a Help - About menu.
When this menu item is clicked, a modal window is opened (let's say it's an About-window).
With self.grab_set() I disabled the main-window (although the modal window does flicker when you click the main title bar).
So far, so good.
Here is the question: I really like to sound a bell when the user clicks outside the modal window on the main window.
This is what I could find about grab_set(), really not that much:
[effbot] ...a method called grab_set, which makes sure that no mouse or keyboard
events are sent to the wrong window.
[effbot] Routes all events for this application to this widget.
[kite.com] A grab directs all events to this and descendant widgets in the application.
[google books] grab_set() ensures that all of the application's events are sent to w until a corresponding call is made to grab_release ([Me:] or till the window is destroyed?)
I'm not quite sure how to understand this: does it mean you can handle an event on the main window within the modal window (like sounding my bell)?
So I tried things like:
self.bind('<Button-1>', self.bell) Exception in Tkinter callback: _tkinter.TclError: bad window path name
parent.bind('<Button-1>', self.bell) Nothing happens
So, how to sound a bell like when clicked outside the modal window on the main window, like in so many other applications?
Derived questions:
Is it still possible to cature events from the main window after using
grab_set for the modal window?
Is there a way to prevent the flickering?
I really like to understand this mysterious grab_set() method.
Stripped code:
import tkinter as tk
class About(tk.Toplevel):
def __init__(self, parent):
tk.Toplevel.__init__(self, parent)
self.geometry('200x150')
#--- OK button
btn_ok = tk.Button(self, text='OK', command=self.destroy) # destroy with OK
btn_ok.pack(side=tk.TOP)
btn_ok.focus() # destroy with spacebar
#--- Make window modal
self.grab_set()
# self.wait_window() # is this necessary in this case?
# self.bind('<Button-1>', self.bell) ??? The question
class MenuBar(tk.Menu):
def __init__(self, parent):
tk.Menu.__init__(self)
helpmenu = tk.Menu(self, tearoff=0)
helpmenu.add_command(label='About', command=lambda: About(parent))
self.add_cascade(label='Help', menu=helpmenu)
class MainApp():
def __init__(self, parent):
parent.configure(background='#000000')
parent.geometry('800x600')
menubar = MenuBar(parent)
parent.configure(menu=menubar)
if __name__ == '__main__':
root = tk.Tk()
MainApp(root)
root.mainloop()
When you set a grab, all button clicks will go to the window with the grab. You capture them the way you capture any other event. In the case of a button click you do that by binding a function to <1>.
It's important to know that a binding on a root window or a Toplevel window will apply to all widgets in that window. For example, binding to self in your code will fire even when you click on the "Ok" button. Therefore, the callback should probably only do work when the widget associated with the event is the same as the toplevel.
Example:
class About(tk.Toplevel):
def __init__(self, parent):
...
self.bind("<1>", self.capture_click)
...
def capture_click(self, event):
if event.widget == self:
<your logic here>
In the case of wanting to know if the user clicked outside the window, you can use the coordinates of the event object to compare against the window to see if the click is inside or outside.
def on_click(self, event):
if event.widget == self:
if (event.x < 0 or event.x > self.winfo_width() or
event.y < 0 or event.y > self.winfo_height()):
self.bell()
I found a second solution. Though my question was explicitly about using grab_set(), this method does the same for me: making the window as modal as possible and sound a bell.
Instead of using self.grab(), you can also disable the parent window:
parent.attributes('-disabled', True)
Of course it needs to be enabled again when the OK button is clicked (and when the windows is closed with the [X] close control. However, my original About-window has no window decoration). The command for the OK-button becomes:
btn_ok = tk.Button(self, text='OK', command=lambda: self.closeme(parent))
...which calls the closeme function:
def closeme(self, parent):
parent.attributes('-disabled', False)
self.destroy()
The bell sounds automatically when clicking a disabled window.
Method 1: Keeps you in full control of the main window but does not 'freeze' the main window: you can still move it around.
Method 2: Completely freezes the main window, but if it happens to be (partially) covered by another window (not of this application), then you can only bring back to top using Alt+Tab (windows).
I'm sure I will use both techniques in the future depending on my needs.

tkinter optionmenu close event

I am trying to display a tooltip for optionmenu items in tkinter. I have figured out how to do that. The problem I am left with is closing the tooltip (toplevel) window. I want to do so when the user selects an option or clicks outside the optionmenu (which closes the menu of the option menu). The enter and leave events don't seem to be doing the trick. Any help would be appreciated!
Edit: I have added some code on what I have tried. The "test" print occurs whenever I hover over menu item 1 (which is what I want) but I want to fire enter and leave events whenever the user opens or closes the menu as a whole. I tried binding them to the optionmenu but it is unreliable and sometimes triggers leave events when it shouldn't.
from tkinter import Tk, Frame, BOTH, Menu, Label, SUNKEN, X, BOTTOM
import tkinter as tk
class Application(Frame):
def __init__(self, parent):
Frame.__init__(self, parent, background = "white")
self.parent = parent
vals = ["1","2","3"]
var = tk.StringVar()
var.set("1")
option = tk.OptionMenu(root,var,*vals)
option.pack()
t = option.children["menu"]
tk.Menu
#Do I need to unbind
#t.bind("<<MenuSelect>>", self.test_func)
t.bind("<<MenuSelect>>", self.test_func)
t.bind("<Enter>",self.enter_func)
t.bind("<Leave>",self.leave_func)
def test_func(self,event = None):
if self.parent.call(event.widget,"index","active") == 0:
print("test")
def enter_func(self,event=None):
print("entered")
def leave_func(self,event=None):
print("left")
root = tk.Tk()
Application(root)
root.mainloop()
One way I have found to close the tooltip (not shown in example) is to trace the variable associated with the optionmenu and when it changes kill the topwindow. However, if the user opens the menu, hovers over options, then clicks outside the menu items, the topwindow tooltip stays because the menu item never changed.
The question is very similar to this one

Tkinter, show Calendar in Toplevel window on button click

I deleted my previous question about this in order to simplify my question and communicate the question clearer. I've got a project with multiple classes inside of it and I'd like to get a Calendar to display in a new window once I click a button. I am currently using this Calendar script with a minor change inside of my overall script. I changed Frame to Toplevel in the first part of the Calendar script like this:
class Calendar(tk.Toplevel):
def __init__(self, parent, **kw):
Toplevel.__init__(self, parent, **kw)
Now this does create the Calendar in a Toplevel window along with the rest of my script but it does it as soon as the program is started. I want to get it to show when it is called later on by the user.
example:
class Application(tk.Tk): # tk.Tk creates main window
def __init__(self):
tk.Tk.__init__(self)
self.title("T")
self.geometry('550x320')#x,y
self.create_options()
self.calendar = Calendar(self)
def create_options(self):
self.widgets = tk.Frame(self)
tk.Button(self,
text = "...", command=self.show_Calendar
).place(x=525, y=130)
which would call this:
def show_Calendar(self):
'''shows calendar'''
toplevel = Toplevel()
toplevel.Calendar.place(x=0, y=0)
The button does create a window but there is nothing in it. What would be the best way to get this Calendar to only show in the window that appears when the button is clicked?
self.calendar = Calendar(self)
Putting this line within your application init will create it at the same time that the application is created. You would want to move this into your show_Calendar method.
def show_Calendar(self):
'''shows calendar'''
toplevel = Toplevel()
toplevel.Calendar.place(x=0, y=0)
toplevel = Toplevel() does not make any sense here. You are creating a blank Toplevel and making it a local variable. This Toplevel is not related to your Calendar in any way.
Within the Calendar script, you made sure that the Calendar class inherits from Toplevel, so any time you create a Calendar, it will be attached to its own Toplevel.
def show_Calendar(self):
'''shows calendar'''
self.calendar = Calendar(self)
I was looking at your previous question before you deleted it, and if you would also like to remove the calendar when the user changes focus, you should look into Events and Bindings here, specifically <FocusOut>.

How to scroll an inactive Tkinter ListBox?

I'm writing a Tkinter GUI in Python. It has an Entry for searching with a results ListBox below it. The ListBox also has a Scrollbar. How can I get scrolling with the mouse and arrow keys to work in the ListBox without switching focus away from the search field? IE I want the user to be able to type a search, scroll around, and keep typing without having to tab back and forth between widgets. Thanks
Add bindings to the entry widget that call the listbox yview and/or see commands when the user presses up and down or uses the up/down scrollwheel.
For example, you can do something like this for the arrow keys:
class App(Tkinter.Tk):
def __init__(self):
Tkinter.Tk.__init__(self)
self.entry = Tkinter.Entry()
self.listbox = Tkinter.Listbox()
self.entry.pack(side="top", fill="x")
self.listbox.pack(side="top", fill="both", expand=True)
for i in range(100):
self.listbox.insert("end", "item %s" % i)
self.entry.bind("<Down>", self.OnEntryDown)
self.entry.bind("<Up>", self.OnEntryUp)
def OnEntryDown(self, event):
self.listbox.yview_scroll(1,"units")
def OnEntryUp(self, event):
self.listbox.yview_scroll(-1,"units")

Categories