How to scroll an inactive Tkinter ListBox? - python

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

Related

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

No touch scrolling when using `ttk.scrollbar` over `tk.scrollbar`

I have discovered that using tk.Scrollbar allows use of touch the scroll on a text widget. When I was overhauling the graphics of my application I could no longer use touch to scroll the text widget that used ttk.Scrollbar instead.
I am positive it is a difference between the widgets because I have toggled between the two when testing the problem.
That being said it is not a huge problem, since only a handful of devices this program will be installed on will have touch capabilities.
Is there any way to get touch on a ttk.scrollbar?
EDIT: Relevant Section of Code:
EDIT #2: Added the two functions at the bottom
self.text=tk.Text(self,font=("Consolas","11"),wrap="none",undo=True,bg="white",relief="flat")
self.numb=tk.Text(self,font=("Consolas","11"),wrap="none",width=4,relief="flat")
self.vsb=ttk.Scrollbar(self,command=self.scroller)
self.hsb=ttk.Scrollbar(self,command=self.text.xview,orient="horizontal")
self.text.configure(yscrollcommand=self.on_textscroll,xscrollcommand=self.hsb.set)
self.numb.config(bg="grey94",yscrollcommand=self.on_textscroll)
def scroller(self,*args):#Move me
self.text.yview(*args)
self.numb.yview(*args)
def on_textscroll(self, *args):#Move me
self.vsb.set(*args)
self.scroller('moveto', args[0])
(Not necessarily an answer, but at least code to experiment with and maybe build upon to make an answer.)
The following scrolls in all directions with middle mouse button held down with either tk (line 2 commented out) or ttk Scrollbar (line 2 uncommented).
from tkinter import Tk, Text, Scrollbar
from tkinter.ttk import Scrollbar
class Test(Tk):
def __init__(self):
super().__init__()
self.text = Text(self, wrap="none",undo=True,bg="white",relief="flat")
self.numb = Text(self, wrap="none",width=4,relief="flat")
self.vsb = Scrollbar(self,command=self.scroller)
self.hsb = Scrollbar(self,command=self.text.xview,orient="horizontal")
self.text.configure(yscrollcommand=self.on_textscroll,xscrollcommand=self.hsb.set)
self.numb.config(bg="grey94",yscrollcommand=self.on_textscroll)
self.numb.grid(row=0, column=0)
self.text.grid(row=0, column=1)
self.vsb.grid(row=0, column=2, sticky='ns')
self.hsb.grid(row=1, column=1, sticky='ew')
for i in range(100):
self.text.insert('insert',
('abcdefg%2d '%i)*10 + '\n')
def scroller(self,*args):#Move me
self.text.yview(*args)
self.numb.yview(*args)
def on_textscroll(self, *args):#Move me
self.vsb.set(*args)
self.scroller('moveto', args[0])
root=Test()
Adding the following at the end of init exposes middle mouse button clicks (it is botton 2, not 3), without disabling the scroll effect. Do fingers touches trigger this, with either scrollbar? If not, try to find out what event touches do generate.
def pr(event):
print(event)
self.text.bind('<Button-2>', pr)
self.text.bind('<ButtonRelease-2>', pr)

selection moves from ttk.Entry to tkinter.Text

Here's a python/tkinter program that puzzles me. The window displays a ttk.Entry that is readonly and a tkinter.Text that is disabled. It programmatically selects one character in the Entry box and never changes this selection. However the selection will change if I try to select text in the other box (the disabledText). This doesn't seem right.
Python 3.5.0 and tcl/tk 8.5.18 on OS X
When you run the program, you can see the "A" highlighted in the Entry (upper) box.
Push the "Write Data" button a few times; the print statement will display the "A" that's selected in the Entry box.
Sweep the mouse over some text in the Text (lower) box; it won't be highlighted, but the highlighting in the Entry will disappear.
Push the "Write Data" button; the print statement will display the characters you swept with the mouse.
Those characters came from selection_get() on the Entry! You can tell that it got them from the Text because the two boxes have no characters in common.
If somebody can explain this, I'd be most grateful.
import tkinter
from tkinter import ttk
class ButtonPanel(ttk.Frame):
def __init__(self, parent):
super().__init__(parent)
self.data = ttk.Entry(self, width=27, takefocus=False)
self.data.insert(0, "ABCDEFG")
self.data.select_range(0, 1) # select the "A"
self.data.state(["readonly"])
self.data.bind('<ButtonPress>', lambda e: 'break') # ignore mouse clicks
button = ttk.Button(self, text="Write Data", command=self.master.write)
self.data.grid(column=0, row=0, padx=10)
button.grid(column=1, row=0, padx=10)
def get(self):
return self.data.selection_get() # should always be the "A"
class App(ttk.Frame):
def __init__(self, parent):
super().__init__(parent)
self.bp = ButtonPanel(self)
self.display = tkinter.Text(self, width=50, height=10, wrap="char", takefocus="False")
self.display.insert('end', "HIJKLMNOPQRSTUV")
self.display.config(state="disabled")
self.bp.pack()
self.display.pack()
def write(self):
char = self.bp.get() # should always be the "A"
print("this should be just one character: ->{}<-".format(char))
if __name__ == "__main__":
root = tkinter.Tk()
root.title("What's up here?")
App(root).pack()
root.mainloop()
What you are observing is the default behavior. Both of those widgets (as well as the listbox) have an attribute named exportselection, with a default value of True. When True, the widget will export the selection to be the primary selection. On old unix systems (where tcl/tk and tkinter got its start), you could only have one "primary" selection at a time.
The simple solution is to set this option to False for the text widget. This will allow your application to have multiple items selected at once, but only the entry widget exports the selection to the clipboard (which is required for selection_get to work.
...
self.display = tkinter.Text(self, ..., exportselection=False)
...
The other issue is that on OSX the selection won't show for a disabled text widget. The text is being selected, you just can't see it. More accurately, the selection won't show except when the widget has focus, and by default, it is not given focus when you click on it.

How to pass an event to parent tkinter widget?

Is it possible in Tkinter to pass an event directly to the parent widget?
I have a canvas wich is covered by a grid of other canvases (is that the plural?), which I added using the parent_canvas.create_window() method. I want some of the events, e.g mouse release events, to be handled by the parent canvas.
If I only bind the event a parent method, the event.x and event.y coordinates come out relative to the child canvas which catches the event.
Tkinter does not pass events to parent widgets. However, you can simulate the effect through the use of bind tags (or "bindtags").
The shortest explanation I can give is this: when you add bindings to a widget, you aren't adding a binding to a widget, you are binding to a "bind tag". This tag has the same name as the widget, but it's not actually the widget.
Widgets have a list of bind tags, so when an event happens on a widget, the bindings for each tag are processed in order. Normally the order is:
bindings on the actual widget
bindings on the widget class
bindings on the toplevel widget that contains the widget
bindings on "all"
Notice that nowhere in that list is "bindings on the parent widget".
You can insert your own bindtags into that order. So, for example, you can add the main canvas to the bind tags of each sub-canvas. When you bind to either, the function will get called. Thus, it will appear that the event is passed to the parent.
Here's some example code written in python 2.7. If you click on a gray square you'll see two things printed out, showing that both the binding on the sub-canvas and the binding on the main canvas fire. If you click on a pink square you'll see that the sub-canvas binding fires, but it prevents the parent binding from firing.
With that, all button clicks are in effect "passed" to the parent. The sub-canvas can control whether the parent should handle the event or not, by returning "break" if it wants to "break the chain" of event processing.
import Tkinter as tk
class Example(tk.Frame):
def __init__(self, parent):
tk.Frame.__init__(self, parent)
self.main = tk.Canvas(self, width=400, height=400,
borderwidth=0, highlightthickness=0,
background="bisque")
self.main.pack(side="top", fill="both", expand=True)
# add a callback for button events on the main canvas
self.main.bind("<1>", self.on_main_click)
for x in range(10):
for y in range(10):
canvas = tk.Canvas(self.main, width=48, height=48,
borderwidth=1, highlightthickness=0,
relief="raised")
if ((x+y)%2 == 0):
canvas.configure(bg="pink")
self.main.create_window(x*50, y*50, anchor="nw", window=canvas)
# adjust the bindtags of the sub-canvas to include
# the parent canvas
bindtags = list(canvas.bindtags())
bindtags.insert(1, self.main)
canvas.bindtags(tuple(bindtags))
# add a callback for button events on the inner canvas
canvas.bind("<1>", self.on_sub_click)
def on_sub_click(self, event):
print "sub-canvas binding"
if event.widget.cget("background") == "pink":
return "break"
def on_main_click(self, event):
print "main widget binding"
if __name__ == "__main__":
root = tk.Tk()
Example(root).pack (fill="both", expand=True)
root.mainloop()

Python tkinter to modify only listbox with focus

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.

Categories