scrollable listbox within a grid using tkinter - python

I'm new to this place and tkinter. I am stuck at making a scrollable listbox or canvas. I have tried both widgets. Within this listbox or canvas, I have several entry and label widgets. The origin point is R0,C0. I used row/columnconfigure to stretch the listbox or canvas.
In the main window, I had 4 buttons on row four to column four (0,4->4,4). I placed the scrollbar on column 5. I attempted to use the grid method. The issue I am having is making the scrollbar functional.
Note: Turning the mainframe into a class is only one of the ways I have tried. Packing the scrollbar on the right has worked, with the listbox/canvas packed on the left. However, the listbox/canvas widget that the scrollbar is commanded to does not scroll the listbox/canvas. Also, adding many entry boxes does not cause the listbox/canvas to scroll. Help please.
from tkinter import *
from tkinter.ttk import *
Style().configure("B.TFrame", relief="flat",
background="blue")
Style().configure("R.TFrame", relief="flat",
background="red")
Style().configure("R.TLabel", background="red")
class Application(Frame):
def __init__(self, master=None):
Frame.__init__(self, master, style="B.TFrame")
self.grid(sticky=N+S+E+W)
self.mainframe()
def mainframe(self):
top=self.winfo_toplevel()
self.menuBar = Menu(top)
top["menu"] = self.menuBar
self.subMenu = Menu(self.menuBar, tearoff=0)
self.subMenu2 = Menu(self.menuBar, tearoff=0)
self.menuBar.add_cascade(label="File", menu=self.subMenu)
self.menuBar.add_cascade(label="About", menu=self.subMenu2)
self.subMenu.add_command(label="Open")
self.subMenu.add_command(label="Save")
self.subMenu.add_command(label="Exit")
self.subMenu2.add_command(label="About")
self.subMenu2.add_command(label="Help")
self.data = Listbox (self, bg='red')
scrollbar = Scrollbar(self.data, orient=VERTICAL)
self.add = Button(self, text="")
self.remove = Button(self, text="")
self.run = Button(self, text="")
self.stop = Button(self, text="")
self.data.grid (row=0, column=0, rowspan=4, columnspan=4, sticky=N+E+S+W)
self.data.columnconfigure(1, weight=1)
self.data.columnconfigure(3, weight=1)
self.add.grid(row=4,column=0,sticky=EW)
self.remove.grid(row=4,column=1,sticky=EW)
self.run.grid(row=4,column=2,sticky=EW)
self.stop.grid(row=4,column=3,sticky=EW)
scrollbar.grid(column=5, sticky=N+S)

Without any content in the listbox, there's nothing to scroll...
This seems to work though (shortened the example a bit). See also the example at the scrollbar documentation.
class Application(Frame):
def __init__(self, master=None):
Frame.__init__(self, master)
self.grid(sticky=N+S+E+W)
self.mainframe()
def mainframe(self):
self.data = Listbox(self, bg='red')
self.scrollbar = Scrollbar(self.data, orient=VERTICAL)
self.data.config(yscrollcommand=self.scrollbar.set)
self.scrollbar.config(command=self.data.yview)
for i in range(1000):
self.data.insert(END, str(i))
self.run = Button(self, text="run")
self.stop = Button(self, text="stop")
self.data.grid(row=0, column=0, rowspan=4,
columnspan=2, sticky=N+E+S+W)
self.data.columnconfigure(0, weight=1)
self.run.grid(row=4,column=0,sticky=EW)
self.stop.grid(row=4,column=1,sticky=EW)
self.scrollbar.grid(column=2, sticky=N+S)
a = Application()
a.mainframe()
a.mainloop()

You must define the command attribute to the scrollbar, and you must supply the yscrollcommand attribute to the listbox. These two attributes work together to make something scrollable.
The yscrollcommand option tells the listbox "when you are scrolled in the Y direction, call this command. This is usually the set method of a scrollbar, so that when the user scrolls via arrow keys, the scrollbar gets updated.
The command attribute of a scorllbar says "when the user moves you, call this command". This is usually the yview or xview method of a widget, which causes the widget to change its view parameters in the Y or X direction.
In your case, after creating the widgets you would do this:
self.data.config(yscrollcommand=self.scrollbar.set)
scrollbar.config(command=self.data.yview)

This thread is old but in case somebody else falls across it as I did, it needs a few precisions.
Junuxx's answer doesnt work as is, not only because there is an indentation problem due to difficulties in seizing code here (from "self.run" which is part of the "mainframe" function) but because it seems necessary to put the listbox and the scrollbar in their own frame.
Here is a working code for Python 2 and 3 :
#!/usr/bin/env python2
try:
# for Python2
from Tkinter import *
except ImportError:
# for Python3
from tkinter import *
class Application(Frame):
def __init__(self, master=None):
Frame.__init__(self, master)
self.grid(sticky=N+S+E+W)
self.mainframe()
def mainframe(self):
frame = Frame(self)
scrollbar = Scrollbar(frame, orient=VERTICAL)
data = Listbox(frame, yscrollcommand=scrollbar.set,
bg='red')
scrollbar.config(command=data.yview)
scrollbar.pack(side=RIGHT, fill=Y)
data.pack(side=LEFT, fill=BOTH, expand=1)
for i in range(1000):
data.insert(END, str(i))
self.run = Button(self, text="run")
self.stop = Button(self, text="stop")
frame.grid(row=0, column=0, rowspan=4,
columnspan=2, sticky=N+E+S+W)
frame.columnconfigure(0, weight=1)
self.run.grid(row=4,column=0,sticky=EW)
self.stop.grid(row=4,column=1,sticky=EW)
a = Application()
a.mainframe()
a.mainloop()
You may find further information here : https://www.effbot.org/tkinterbook/listbox.htm.
Hope this helps.

Related

Scrollable Tkinter GUI with shiftable pods

I am trying to make a GUI such as this with pods, each containing their own elements such as text, images and buttons.
My goal is to make it so that the so called pods can be added to the GUI window (a scrolling capable window) at any point in the code and updated in the window shifting the previous pod to the right or down to the next row if the current row is full like the image below.
I have never messed with Tkinter before so I was wondering if anyone could help me with what steps I would need to take to make such a GUI.
Implement a class that inherits from the Frame class. You can then create as many instances of this class that you want. Since you want the pods to wrap, you can use a Text widget to hold the pods since it's the only scrollable widget that natively supports wrapping.
The "pod" class might look something like this:
class Pod(tk.Frame):
def __init__(self, parent, title, subtitle, image):
super().__init__(parent, bd=2, relief="groove")
if isinstance(image, tk.PhotoImage):
self.image = image
else:
self.image = tk.PhotoImage(file=image_path)
self.title = tk.Label(self, text=title)
self.image_label = tk.Label(self, image=self.image, bd=1, relief="solid")
self.subtitle = tk.Label(self, text=subtitle)
self.b1 = tk.Button(self, text="Button 1")
self.b2 = tk.Button(self, text="Button 2")
self.grid_rowconfigure(1, weight=1)
self.grid_columnconfigure((0,1), weight=1)
self.title.grid(row=0, column=0, columnspan=2, sticky="ew")
self.image_label.grid(row=1, column=0, columnspan=2, sticky="nsew", padx=8, pady=8)
self.subtitle.grid(row=2, column=0, columnspan=2, sticky="ew")
self.b1.grid(row=3, column=0)
self.b2.grid(row=3, column=1)
You can create another class to manage these objects. If you base it on a Text widget you get the wrapping behavior for free. Though, you could also base it on a Frame or Canvas and manage the wrapping yourself.
It might look something like this:
class PodManager(tk.Text):
def __init__(self, parent, **kwargs):
super().__init__(parent, **kwargs)
self.configure(state="disabled", wrap="char")
self.pods = []
def add(self, pod):
self.pods.append(pod)
self.configure(state="normal")
self.window_create("end", window=pod)
self.configure(state="disabled")
To tie it all together, create one PodManager class, then pass one or more instances of Pod to its add method:
import tkinter as tk
...
root = tk.Tk()
pm = PodManager(root)
vsb = tk.Scrollbar(root, orient="vertical", command=pm.yview)
pm.configure(yscrollcommand=vsb.set)
vsb.pack(side="right", fill="y")
pm.pack(side="left", fill="both", expand=True)
for i in range(10):
image = tk.PhotoImage(width=200,height=100)
pod = Pod(pm, f"Title #{i+1}", "More Text", image)
pm.add(pod)
root.mainloop()

Python & tkinter: canvas.lift and canvas.lower on overlapping buttons does not work

I created two overlapping buttons on a canvas, using tkinter and python 3.4:
Now I would like to bring button1 to the front (the button you cannot see right now, because it is under button2)
self.canvas.lift(self.button1)
But for some reason this does not work. Just nothing happens. Also lowering button2 has no effect. Can you tell me why?
import tkinter as tk
class Example(tk.Frame):
def __init__(self, root):
tk.Frame.__init__(self, root)
self.canvas = tk.Canvas(self, width=400, height=400, background="bisque")
self.canvas.create_text(50,10, anchor="nw", text="Click to lift button1")
self.canvas.grid(row=0, column=0, sticky="nsew")
self.canvas.bind("<ButtonPress-1>", self.click_on_canvas)
self.button1 = tk.Button(self.canvas, text="button1")
self.button2 = tk.Button(self.canvas, text="button2")
x = 40
self.canvas.create_window(x, x, window=self.button1)
self.canvas.create_window(x+5, x+5, window=self.button2)
def click_on_canvas(self, event):
print("lifting", self.button1)
self.canvas.lift(self.button1)
self.canvas.lower(self.button2)
if __name__ == "__main__":
root = tk.Tk()
Example(root).pack(fill="both", expand=True)
root.mainloop()
Instead of calling lift() on the canvas, you need to call it on the widget instance directly:
def click_on_canvas(self, event):
print("lifting", self.button1)
self.button1.lift()
self.button2.lower() # Not necessary to both lift and lower
This is only true for widgets displayed via a window on your canvas.
If you were to draw objects such as lines or rectangles, you would use lift() or tag_raise() on the canvas instance as you were doing before.

Using scrollbars with canvas in tkinter

I'm trying to put together a window that displays a bunch of labels generated from a dict. I'm having trouble getting the scrollbars to work properly. They won't stick to the sides of the frame when I resize the window, and I can't get the canvas to respond to the scroll command. I need the window to support a large number of labels.
from Tkinter import *
from math import floor
bits = {}
#the dict is then built
class Bitbox(Canvas):
def __init__(self, parent, bitdict, *args, **kwargs):
Canvas.__init__(self, parent, background="black")
self.bitdict = bitdict
self.parent = parent
self.lbllist = []
n=0
for i in bitdict.keys():
label = Label(self, text=i, bg='black', fg='green')
n += 1
label.grid(row = ((n-1)%30), column=int(floor((n-1)/30)))
self.lbllist.append(label)
def main():
root = Tk()
frame = Frame(root)
frame.grid(sticky=N+S+E+W)
bts = Bitbox(frame, bits)
bts.grid(row=0, column=0)
vbar = Scrollbar(frame, orient=VERTICAL)
vbar.grid(row=0, column=1, sticky=N+S)
vbar.config(command=bts.yview)
hbar = Scrollbar(frame, orient=HORIZONTAL)
hbar.grid(row=1, column=0, columnspan=2, sticky=W+E)
bts.config(xscrollcommand=hbar.set)
hbar.config(command=bts.xview)
bts.config(yscrollcommand=vbar.set)
bts.config(scrollregion=(0,0,500,1000))
root.mainloop()
Clearly I'm new at all this. It's entirely possible I have a fundamental misunderstanding of how these widgets interact. Any help is much appreciated.
to get the scrollbar to react to the mouse bind the mouse to the scrollbar like this:
def on_mousewheel(event):
bts.yview_scroll(-1*(event.delta/120), "units")
def main():
global bts
#your code...
root.bind_all("<MouseWheel>",on_mousewheel)

Tkinter Python managing scrollbar inside the listbox in a canvas

I got the code below which contain a listbox inside a canvas, I'm trying to place the scrollbar only inside the listbox but what I've got is the scrollbar always set on the whole form. Can you pls help me to put the scrollbar inside the listbox only and not on the whole canvas.
def __init__(self):
self.form = Tk()
self.form.title("Admin");
self.form.geometry('608x620+400+50')
self.form.option_add("*font",("Monotype Corsiva",13))
self.form.overrideredirect(True)
self.form.resizable(width=FALSE, height=FALSE)
self.canvas = Canvas(self.form)
self.canvas.pack(expand=YES,fill=BOTH)
self.photo = PhotoImage(file='src/back3.gif')
self.canvas.create_image(-7,-8,image=self.photo,anchor=NW)
self.scrollbar = tk.Scrollbar(self.canvas, orient="vertical")
self.lb = tk.Listbox(self.canvas, width=78, height=15,yscrollcommand=self.scrollbar.set)
self.lb.place(relx=0.04,rely=0.17)
self.scrollbar.config(command=self.lb.yview)
self.scrollbar.pack(side="right", fill="none")
I suggest you to create a Frame to wrap the Listbox and the Scrollbar. Here it is a class I wrote to do the same thing, it does not fit exactly with your code - I use grid() instead of pack() -, but you get the idea.
class ScrollableListbox(tk.Listbox):
def __init__(self, master, *arg, **key):
self.frame = tk.Frame(master)
self.yscroll = tk.Scrollbar(self.frame, orient=tk.VERTICAL)
tk.Listbox.__init__(self, self.frame, yscrollcommand=self.yscroll.set, *arg, **key)
self.yscroll['command'] = self.yview
def grid(self, *arg, **key):
self.frame.grid(*arg, **key)
tk.Listbox.grid(self, row=0, column=0, sticky='nswe')
self.yscroll.grid(row=0, column=1, sticky='ns')

Expandable and contracting frame in Tkinter

Does anyone know if there is already a widget/class to handle expanding/contracting a frame based on a toggled button (checkbutton) in tkinter/ttk?
This question stems from my attempt to clean up a cluttered gui that has lots of options categorized by specific actions. I would like something along the lines of:
example found on google
However instead of just text, allow for buttons, entries, any of tkinter's widgets. If this doesn't already exist, would it be possible/useful to create a class that inherits the tkinter Frame:
import tkinter as tk
import ttk
class toggledFrame(tk.Frame):
def __init__(self):
self.show=tk.IntVar()
self.show.set(0)
self.toggleButton=tk.Checkbutton(self, command=self.toggle, variable=self.show)
self.toggleButton.pack()
self.subFrame=tk.Frame(self)
def toggle(self):
if bool(self.show.get()):
self.subFrame.pack()
else:
self.subFrame.forget()
Note: this code is untested, just presenting concept
I am actually surprised at how close I was to getting functioning code. I decided to work on it some more and have develop a simple little class to perform exactly what I wanted (comments and suggestions on the code are welcome):
import tkinter as tk
from tkinter import ttk
class ToggledFrame(tk.Frame):
def __init__(self, parent, text="", *args, **options):
tk.Frame.__init__(self, parent, *args, **options)
self.show = tk.IntVar()
self.show.set(0)
self.title_frame = ttk.Frame(self)
self.title_frame.pack(fill="x", expand=1)
ttk.Label(self.title_frame, text=text).pack(side="left", fill="x", expand=1)
self.toggle_button = ttk.Checkbutton(self.title_frame, width=2, text='+', command=self.toggle,
variable=self.show, style='Toolbutton')
self.toggle_button.pack(side="left")
self.sub_frame = tk.Frame(self, relief="sunken", borderwidth=1)
def toggle(self):
if bool(self.show.get()):
self.sub_frame.pack(fill="x", expand=1)
self.toggle_button.configure(text='-')
else:
self.sub_frame.forget()
self.toggle_button.configure(text='+')
if __name__ == "__main__":
root = tk.Tk()
t = ToggledFrame(root, text='Rotate', relief="raised", borderwidth=1)
t.pack(fill="x", expand=1, pady=2, padx=2, anchor="n")
ttk.Label(t.sub_frame, text='Rotation [deg]:').pack(side="left", fill="x", expand=1)
ttk.Entry(t.sub_frame).pack(side="left")
t2 = ToggledFrame(root, text='Resize', relief="raised", borderwidth=1)
t2.pack(fill="x", expand=1, pady=2, padx=2, anchor="n")
for i in range(10):
ttk.Label(t2.sub_frame, text='Test' + str(i)).pack()
t3 = ToggledFrame(root, text='Fooo', relief="raised", borderwidth=1)
t3.pack(fill="x", expand=1, pady=2, padx=2, anchor="n")
for i in range(10):
ttk.Label(t3.sub_frame, text='Bar' + str(i)).pack()
root.mainloop()
This code produces:
To my knowledge, Tkinter/ttk does no provide such widgets. You might mimic your example (expand/collapse label list) with a tkinter.ttk.Treeview.
It is perfectly acceptable1 to develop your own widgets, and your code seems a right start.

Categories

Resources