Python tkinter scrollbar active state - python

Not sure what else to call 'active'. Is it possible to have the scrollbar, once clicked on to remain 'active'? Another words once I click on the scrollbar I would like to be able to move the scrollbar with the keyboard(left/right arrow keys) or the mouse. Is this possible? If so what do I have to do to accomplish it?

Technically it should be possible, by giving the scrollbar the keyboard focus and then adding some custom bindings. That's a fairly unusual thing to do. Since the scrollbars are drawn with native widgets on Windows and the Mac, it might be impossible on those platforms.
What you probably want to do instead is set some bindings on the application as a whole, or on some sort of widget that typically gets focus such as a canvas or text widget. Your bindings can call the xview and yview commands and give it arguments to tell it how to scroll, which is exactly what the scrollbar does.

You can achieve this like this for example:
from Tkinter import *
root = Tk()
scrollbar = Scrollbar(root, takefocus = True)
scrollbar.pack( side = RIGHT, fill=Y )
mylist = Listbox(root, yscrollcommand = scrollbar.set )
scrollbar.bind("<Down>", lambda event: mylist.yview_scroll(1, "units"))
scrollbar.bind("<Up>", lambda event: mylist.yview_scroll(-1, "units"))
scrollbar.bind("<Button-1>", lambda event: scrollbar.focus_set())
for line in range(100):
mylist.insert(END, "This is line number " + str(line))
mylist.pack( side = LEFT, fill = BOTH )
scrollbar.config( command = mylist.yview )
mainloop()
The takefocus flag allows the scrollbar to maintain the focus. The focus is set with "<Button-1>" event. Key presses are also handled by the events and invoke the desired scroll behaviour on scrollable object.

Related

Add ScrollBar to a window of labels [duplicate]

This question already has answers here:
Adding a scrollbar to a group of widgets in Tkinter
(3 answers)
Closed 4 years ago.
My code is working well in itself, but doesn't scroll through the Labels (which is what i'm trying to achieve).
I don't want to use canvas or listbox or anything.
import tkinter as tk
master = tk.Tk()
scrollbar = tk.Scrollbar(master).pack(side=tk.RIGHT, fill=tk.Y,command=tk.yview)
label = tk.Label(text="llklkl")
label.place(x=100,y=500)
label2 = tk.Label(text="llklkl")
label2.place(x=1000,y=5000)
tk.mainloop()
Hello and welcome to SO.
The tkinter Scrollbar widget sadly can not be used on a _tkinter.tkapp object, i.e. your main window called master. From effbot.org:
This widget is used to implement scrolled listboxes, canvases, and text fields.
and
The Scrollbar widget is almost always used in conjunction with a Listbox, Canvas, or Text widget. Horizontal scrollbars can also be used with the Entry widget.
That means that you absolutely HAVE to create some widget inside your main window in order to be able to scroll anything, you can`t just scroll the window itself.
That being said, if you wanted to add a Scrollbar to, let's say, a Listbox, that's how you would do it (also taken from the above mentioned website, you should really check it out):
First of all, you have to set the widget’s yscrollcommand callbacks to the set method of the scrollbar.
Secondly, you have to set the scrollbar’s command to the yview method of the widget, much like you did already, but like name_of_object.yview, not tk.yview.
import tkinter as tk
master = tk.Tk()
scrollbar = tk.Scrollbar(master)
scrollbar.pack(side=tk.RIGHT, fill=tk.Y)
listbox = tk.Listbox(master, yscrollcommand=scrollbar.set)
for i in range(20):
listbox.insert(tk.END, str(i))
listbox.pack(side=tk.LEFT, fill=tk.BOTH)
scrollbar.config(command=listbox.yview)
master.mainloop()
Also, pack the scrollbar in a seperate line. This will produce a window with numbers from 1 to 50 in a scrollable Listbox widget. If i get you right, you want to be able to scroll between your labels? well, i guess you'll have to use some kind of wrapping widget for that, i would recommend a Canvas. But that's really up to you and i'm sure you'll figure it out yourself. If you need any more help, let me know - but please read the docs before asking ;-)

How can I set the focus to a widget (that isn't an entry) by clicking on it?

The reason I need to do this is because I need to change the text of a label without having to use entries; I want to use events instead.
I have tried this:
import tkinter as tk
root = tk.Tk()
root.bind("<Button-1>", lambda _: root.focus())
l = tk.Label(root, width=50, height=50, bg="white")
l.bind("<Button-1>", lambda _: l.focus())
l.bind("1", lambda _: l.config(bg="yellow"))
l.bind("2", lambda _: l.config(bg="white"))
l.pack()
root.mainloop()
When I ran the program, I expected to be able to change the colour of the label l to yellow by clicking on it (which I thought would set the focus to it) then pressing 1, and changing it back to white by pressing 2; provided that I didn't click outside of the label and set the focus to the root widget (where the keys 1 and 2 weren't bound to any callback).
I know that you can bind keys to callbacks (tested it), and I also know that it is possible to set the focus to widgets which aren't entries (tested that too), yet this doesn't seem to work.
Can anybody help me?
The problem is that you have two bindings for a button click: one on the label widget itself and one on the root window. Because of the way that events are processed, the binding on the root window will fire after the event on the label. That means that whatever focus you set on the label binding will get undone with the binding on the root window.
One solution is to change your binding on the click to set the focus to whatever was clicked on. With that, you don't need to set a binding on the label widget for a click.
root.bind("<Button-1>", lambda event: event.widget.focus_set())
Another solution would be to modify your binding on the label to prevent the binding on the root window from firing. You can do that by returning the string "break" from the function that is called.
def callback(event):
l.focus()
return "break"
l.bind("<Button-1>", callback)

How can I disable horizontal scrolling in a Tkinter listbox? (Python 3)

Say in Tkinter you have a listbox of a certain size within a window. Then let's say you add a string to that listbox that is larger than that size. If you highlight this element and drag to the side the listbox will automatically "scroll" itself so that you can see the full element. Is there anyway to disable this short of running a thread that repeatedly attempts to set the scroll to 0?
import tkinter
root = tkinter.Tk()
listbox = tkinter.Listbox(root)
listbox.insert("end", "Minimal, Complete, and Verifiable example")
listbox.pack()
root.mainloop()
quit()
The auto-scrolling is triggered by the mouse leaving the listbox while the button is pressed. Perhaps the simplest solution is to prevent that behavior by creating your own binding that returns "break":
listbox.bind("<B1-Leave>", lambda event: "break")
Note: this will also prevent the vertical auto-scrolling. If you want to keep the vertical auto-scrolling than you'll have to write a more complex function that will only return "break" if the mouse is to the left or right of the listbox.

Simple bind function doesn't work in python

I'm currently creating an adventure game and I want to bind alt+a to my callback. It doesn't do what I want, so I have two questions:
Is it possible to bind a function to a Label, too?
Why does this (simplyfied) code doesn't work?
Here is the code:
import tkinter as tk
dw = tk.Tk()
dw.title('Hearts')
def play(event):
print('This is the test.')
areal = tk.Frame(master=dw, width=1200, height=600, bg='blue')
areal.pack_propagate(0)
areal.pack(fill=tk.BOTH, expand=bool(dw))
areal.bind("<Alt-A>", play)
dw.mainloop()
It doesn't give me an error, but it doesn't do anything when I click the Frame and afterwards press alt+a. What is wrong here?
EDIT:
import tkinter as tk
def go_fwd(event):
areal.focus_set()
print(event.x, event.y)
dw = tk.Tk()
dw.title('Adventure')
areal = tk.Frame(master=dw, width=20000, height=600, bg='blue')
areal.pack_propagate(0)
areal.pack(fill=tk.BOTH, expand=bool(dw)-100)
areal.focus_set()
dw.bind("<Alt-A>", go_fwd)
enter = tk.Frame(master=dw, width=20000, height=100, bg='cyan')
enter.pack(fill=tk.X)
enterentry = tk.Text(master=enter, width=100, height=4, bg='white')
enterentry.pack()
enterbutton = tk.Button(master=enter, text='Senden', bg='red')
enterbutton.pack()
dw.mainloop()
Here is the complete code.
Is it possible to bind a function to a Label, too?
You can bind to any widget you want. However, if you bind key events, the bindings will only work if the widget has focus. By default, most widgets other than Entry and Text don't get focus unless you explicitly set the focus to them.
Note: only one widget can have keyboard focus at a time.
You can also set a binding to the root window, which will cause it to fire no matter what widget has focus.
For a more thorough explanation of how key bindings are processed, see this answer: https://stackoverflow.com/a/11542200/7432
Why does this (simplyfied) code doesn't work?
It doesn't work the way you expect because the binding is on a Frame widget, but that widget doesn't have the keyboard focus. You could give it focus with something like this:
areal.focus_set()
Or, you could only give it focus after you click on the frame, by creating a binding on a mouse click:
areal.bind("<1>", lambda event: areal.focus_set())
Note: you are binding to a capital "A", so make sure when you test that you're pressing control-alt-a
You need to bind to dw instead of your frame.
So, you can do dw.bind("<Alt-A>", play).
A minor note, Alt-A will bind to the uppercase A as expected, so you'd have to click Alt+Shift+A on your keyboard. Doing Alt+A on your keyboard won't work, you'd have to bind to Alt-a for this to work.
The main window has keyboard focus. Or, alternatively you can leave the bind on the frame and just do areal.focus_set() to set the focus to the frame.

In Tkinter how do i remove focus from a widget?

I'd like to remove focus from a widget manually.
You can focus to another dummy widget.
Edit
from Tkinter import *
def callback():
print master.focus()
master = Tk()
e = Entry(master)
e.pack()
e.focus()
b = Button(master, text="get", width=10, command=callback)
b.pack()
master.mainloop()
Focusing on a non-'focusable' widget will remove focus from another widget.
Set focus to another widget to remove focus from the target widget is a good idea. There are two methods for this: w.focus_set() and w.focus_force(). However, method w.focus_force() is impolite. It's better to wait for the window manager to give you the focus. Setting focus to parent widget or to the root window removes focus from the target widget.
Some widgets have takefocus option. Set takefocus to 0 to take your widget out of focus traversal (when user hits <Tab> key).
My solution is root.focus() it will remove widget focus.
If the dummy widget is Canvas then c.focus() will not work.
use c.focus_set() or c.tk.call('focus',c) to first focus on the canvas window itself.
That's because
c.focus()
... returns the id for the item that currently has the focus, or an empty string if no item has the focus. Reference
c.focus(id_) will focus on the item having id id_ within the canvas.
c.focus("") will remove the focus from any item in the canvas.
Hence (within some callback)
c.config(highlightthickness = 0) # to remove the highlight border on focus
c.foucs_set()
c.focus("") # just to be sure
The reason c.focus() functions differently is that within Tcl/Tk's Commands there's the "Primary" Command focus
as well as the Canvas-specific Widget Command focus
That's not an issue within the Tcl/Tk syntax but in the tkinter module c.focus() will call the underlying canvas-specific foucs.
From tkinter.py within the Canvas class Line 2549
def focus(self, *args):
"""Set focus to the first item specified in ARGS."""
return self.tk.call((self._w, 'focus') + args)
So the question may be a duplicate here, but the answer from #Bryan Oakley works perfectly for me in Python 3.8
root.focus_set()
Too easy...
If you use ttk widgets you can "remove" the focus ring by removing the color; for example on a button:
style = ttk.Style()
style.configure('TButton', focuscolor='')

Categories