![]() |
[Tkinter] Scrollable buttons with an add/delete button - Printable Version +- Python Forum (https://python-forum.io) +-- Forum: Python Coding (https://python-forum.io/forum-7.html) +--- Forum: GUI (https://python-forum.io/forum-10.html) +--- Thread: [Tkinter] Scrollable buttons with an add/delete button (/thread-37470.html) |
Scrollable buttons with an add/delete button - Clich3 - Jun-14-2022 I am trying to make a GUI to manage the sensors I am going to be putting in my aquariums, terrariums, and vivariums. I am going to have a main menu screen which will have a tab where I can see sensor info. My problem is I can not seem to figure out a way to make my sensor "rows" scrollable. What I have in mind is this: [attachment=1793] I have the two bottom buttons but I am stuck with the top canvas and not sure how to add my buttons. I am new to python and I have been trying to follow multiple tkinter guides online and have been looking around different forums but I can't seem to make them work with what I am trying to achieve. This is my code: import tkinter as tk from tkinter import scrolledtext class GUI(object): def __init__(self, root): self.root = root self.root.title('Sensors') windowWidth = 700 windowHeight = 500 # get the screen dimension screenWidth = root.winfo_screenwidth() # Current monitor is 1920 screenHeight = root.winfo_screenheight() # x 1080 # find the center point centerX = int(screenWidth/2 - windowWidth / 2) centerY = int(screenHeight/2 - windowHeight / 2) # set the position of the window to the center of the screen root.geometry(f'{windowWidth}x{windowHeight}+{centerX}+{centerY}') root.resizable(False,False) ### On main menu self.sensors = tk.Button(root, text='Sensors',height=3, width=10, font=('Helvetica',15), borderwidth=5, command=self.Sensors) self.options = tk.Button(root, text='Options',height=3, width=10, font=('Helvetica',15), borderwidth=5, command=self.Options) ### In sensor menu self.addSensorBtn = tk.Button(root, text='Add Sensor') self.ReturnMenu = tk.Button(root, text="Return to Main Menu", font=('Helvetica'), command=self.MainMenu) self.frame = tk.Frame(root).grid(row=0,column=0) self.canvas = tk.Canvas(self.frame,bg='gray75') self.vsb = tk.Scrollbar(self.frame, orient='vertical', command=self.canvas.yview) self.MainMenu() def GridConfig(self): root.rowconfigure(0,weight=3) root.rowconfigure(1,weight=1) root.columnconfigure((0,2), weight=1) def MainMenu(self): self.GridConfig() self.RemoveAll() self.sensors.grid(column=0, row=1, sticky='NE') self.options.grid(column=2, row=1, sticky='NW') def Sensors(self) : self.RemoveAll() self.canvas.grid(column=0,row=0,columnspan=3,sticky='NEWS') self.vsb.grid(row=0, column=3, sticky='NSE') self.canvas.configure(yscrollcommand=self.vsb.set, scrollregion=self.canvas.bbox('all')) self.addSensorBtn.grid(column=2,row=1, sticky='NEWS') self.ReturnMenu.grid(column=0, row=1,sticky='NEWS') def Options(self): self.RemoveAll() self.GridConfig() self.ReturnMenu.grid(column=1, row=1) def RemoveAll(self): self.vsb.grid_remove() self.canvas.grid_remove() self.addSensorBtn.grid_remove() self.sensors.grid_remove() self.options.grid_remove() self.ReturnMenu.grid_remove() if __name__ == '__main__': root = tk.Tk() myGUI = GUI(root) root.mainloop() RE: Scrollable buttons with an add/delete button - deanhystad - Jun-14-2022 Since you can scroll a canvas the common way of doing this is make a canvas, put a frame on the canvas, add controls to the frame. You might want to take a look at tkScrolledFrame. pypi.org/project/tkScrolledFrame/ RE: Scrollable buttons with an add/delete button - woooee - Jun-14-2022 This is an example from my toolbox. It uses 10 Labels, but you can use the code for buttons also. from tkinter import * class ScrolledCanvas(): def __init__(self, parent, color='brown'): canv = Canvas(parent, bg=color, relief=SUNKEN) canv.config(width=300, height=200) ##---------- scrollregion has to be larger than canvas size ## otherwise it just stays in the visible canvas canv.config(scrollregion=(0,0,300, 1000)) canv.config(highlightthickness=0) ybar = Scrollbar(parent) ybar.config(command=canv.yview) ## connect the two widgets together canv.config(yscrollcommand=ybar.set) ybar.pack(side=RIGHT, fill=Y) canv.pack(side=LEFT, expand=YES, fill=BOTH) for ctr in range(10): frm = Frame(parent,width=960, height=100,bg="#cfcfcf",bd=2) frm.config(relief=SUNKEN) Label(frm, text="Frame #"+str(ctr+1)).grid() canv.create_window(10,10+(100*ctr),anchor=NW, window=frm) if __name__ == '__main__': root=Tk() ScrolledCanvas(root) root.mainloop() RE: Scrollable buttons with an add/delete button - Larz60+ - Jun-14-2022 I'd suggest getting a copy of John Shipman's reference manual, it's old, but still the most comprehensive work on tkinter. You can get a copy here: Try running the code below which is very old, but surprisingly still functional look at lines 70-82 Note how sccrollbar is linked into the canvas Notice also, the link to canvas on line 80. Try running the code. """ cscroll.py This demonstration script creates a simple canvas that can be scrolled in two dimensions. June 17, 2005 """ import tkinter as Tk import template as A def i2c (i): return (str(i) + 'c') class Box: def __init__(self, master, cvs, i, j): self.master = master self.cvs = cvs x = i*3 - 10 y = j*3 - 10 self.text = '%d,%d' % (i, j) self.id_box = self.cvs.create_rectangle(i2c(x), i2c(y), i2c(x+2), i2c(y+2), outline='black', fill=self.master.bg, tags='box') self.id_text = self.cvs.create_text(i2c(x+1), i2c(y+1), text= self.text, anchor=Tk.CENTER, tags='text') self.cvs.tag_bind(self.id_box, '<Enter>', self.on_enter) self.cvs.tag_bind(self.id_box, '<Leave>', self.on_leave) self.cvs.tag_bind(self.id_text, '<Enter>', self.on_enter) self.cvs.tag_bind(self.id_box, '<1>', self.on_click) self.cvs.tag_bind(self.id_text, '<1>', self.on_click) def on_enter(self, event): self.cvs.itemconfigure(self.id_box, fill='SeaGreen1') def on_leave(self, event): self.cvs.itemconfigure(self.id_box, fill=self.master.bg) def on_click(self, event): self.master.echo.set(self.text) class Demo(A.Demo): """ a demo class """ def __init__(self, cmain): A.Demo.__init__(self, cmain, __file__) self.ini_frame() def ini_demo_called(self): """ This method should be defined""" self.ini_demo_called_0() self.ini_frame() def ini_frame(self): self.demo_main_frame.master.title("Form Demonstration") self.demo_main_frame.master.minsize(width=400, height=170) self.demo_main_frame.master.geometry("+50+50") A.Label(self.demo_frame, text= "This window displays a canvas widget that can be scrolled " "either using the scrollbars or by dragging with button 2 in the canvas. " "If you click button 1 on one of the rectangles, its indices will be printed on the lavel." , width=40, wraplength='9c') fa = Tk.Frame(self.demo_frame) fa.pack(fill=Tk.BOTH, expand=1, padx=1, pady=1) self.cvs = Tk.Canvas(fa, scrollregion=("-11c", "-11c", "50c", "20c"), relief=Tk.SUNKEN, borderwidth=2) self.cvs.grid(row=0, column=0, sticky= Tk.N+Tk.E+Tk.W+Tk.S) xscroll = Tk.Scrollbar(fa, orient=Tk.HORIZONTAL, command=self.cvs.xview) xscroll.grid(row=1, column=0, sticky=Tk.E+Tk.W) yscroll = Tk.Scrollbar(fa, orient=Tk.VERTICAL, command=self.cvs.yview) yscroll.grid(row=0, column=1, sticky=Tk.N+Tk.S) self.cvs.config(xscrollcommand=xscroll.set, yscrollcommand=yscroll.set) fa.grid_rowconfigure(0, weight=1, minsize=0) fa.grid_columnconfigure(0, weight=1, minsize=0) self.bg = self.cvs.cget('bg') self.echo = Tk.StringVar() for i in range(25): for j in range(10): Box(self, self.cvs, i, j) self.cvs.bind('<Button2-Motion>', self.on_motion) self.cvs.bind('<2>', self.on_press2) label = Tk.Label(self.demo_frame, textvariable=self.echo, width=12, relief=Tk.SUNKEN, borderwidth=2) label.pack(padx=10, pady=20) def on_press2(self, event): self.cvs.scan_mark(event.x, event.y) def on_motion(self, event): self.cvs.scan_dragto(event.x, event.y) ##------------------------------------------------------------ def demo(*av): """ function called by `index.py'""" d = Demo(False) d.demo_window.focus_set() if __name__ == '__main__': d = Demo(True) d.demo_main_frame.mainloop()EDIT June16 5:39 EDT I just realized you also need template.py which I have added below: template.py """ This is a template for demo codes of Tkinter. """ import sys import string import tkinter as Tk #from ScrolledText import ScrolledText from tkinter.scrolledtext import ScrolledText ## functions ------------------------------------- def read_contents(fname): """ read contens of `fname' """ f = file(fname) str = f.read() f.close() return str def newlist(n): ls = [] for i in range(n): ls.append(None) return ls def i_range(val, min, max): if(min and val < min): return(min) elif(max and val > max): return(max) else: return(val) def bottom_slide(str, dx): ls0 = str.split('+') ls1 = ls0[0].split('x') return ('+%d+%d' % (int(ls0[1]) + dx, int(ls1[1]) + int(ls0[2]) + 50)) def left_slide(str): ls0 = str.split('+') ls1 = ls0[0].split('x') return ('500x600+%d+%s' % (int(ls1[0]) + int(ls0[1]) + 50, ls0[2])) def str_same_p(str0, str1): ls0=string.split(str0) ls1=string.split(str1) for s0, s1 in zip(ls0, ls1): if s0!=s1: return False else: return True ## classes ---------------------------------------- class ShowVars: """ a function class to show variables' value in a separated window """ def __init__(self, demo_window, dx, *vars): self.demo_window = demo_window self.vars = vars self.toplevel = None self.dx = dx def __call__(self, *av): if self.toplevel: self.toplevel.focus_set() else: self.toplevel=Tk.Toplevel(self.demo_window) self.toplevel.title('Variable values') self.toplevel.geometry(bottom_slide( self.demo_window.winfo_geometry(), self.dx)) frame = Tk.Frame(self.toplevel) frame.pack(fill=Tk.BOTH, expand=1) l0 = Tk.Label(frame, text='Variable values: ', font=('Helvetica', '14')) l0.pack(padx=10, pady=10) f= Tk.Frame(frame) for i, (label, var) in enumerate(self.vars): l1 = Tk.Label(f, text=label, justify=Tk.LEFT, anchor=Tk.W, width=15) l1.grid(row=i, column=0, sticky=Tk.W) l2 = Tk.Label(f, textvariable=var, justify=Tk.LEFT, anchor=Tk.W, width=20) l2.grid(row=i, column=1, sticky=Tk.W) f.pack(fill=Tk.BOTH, anchor=Tk.W, padx=20, pady=20) b=Tk.Button(frame, text='OK', command=self.destroy_window) b.pack(side=Tk.BOTTOM, padx=10, pady=10) # self.demo_window.focus_set() def destroy_window(self): if self.toplevel: self.toplevel.destroy() self.toplevel= None class ButtonFrame(Tk.Frame): """ This is a Frame of two common buttons; dismess and (see code or return demo) """ def __init__(self, master, b0_text, b0_command, b1_text, b1_command): Tk.Frame.__init__(self, master, height=35) b0 = Tk.Button(self, text=b0_text, width=10, command=b0_command) b1 = Tk.Button(self, text=b1_text, width=10, command=b1_command) b0.pack(side=Tk.LEFT, padx=30, pady=5) b1.pack(side=Tk.LEFT, padx=30, pady=5) class Label(Tk.Label): """ a label class for the demo """ def __init__(self, master, **key): #justify=Tk.LEFT, font=("Helvetica", "12") key['justify'] = Tk.LEFT key['font'] = ("Helvetica", "12") Tk.Label.__init__(self, master, **key) self.pack(fill=Tk.X, padx=5, pady=5) class Demo: """ A class defining demo window and source code window. """ demo_window = None demo_main_frame = None demo_label = None demo_frame = None code_window = None def __init__(self, cmain, fname): self.fname = fname.split('.').pop(0) + '.py' self.cmain = cmain if cmain: self.demo_main_frame=Tk.Frame() else: self.demo_window = Tk.Toplevel() self.demo_main_frame=Tk.Frame(self.demo_window) self.ini_demo() def ini_demo(self): self.demo_frame = Tk.Frame(self.demo_main_frame) self.demo_buttons = ButtonFrame(self.demo_frame, "Dismiss", self.demo_destroy, "Show Code", self.show_code) self.demo_main_frame.pack(fill=Tk.BOTH, expand=1, padx=3, pady=3) self.demo_frame.pack(side=Tk.BOTTOM, fill=Tk.BOTH, expand=1) self.demo_buttons.pack(side=Tk.BOTTOM, expand=0, pady=5) def ini_demo_called_0(self): self.demo_window = Tk.Toplevel() self.demo_main_frame=Tk.Frame(self.demo_window) self.ini_demo() def ini_demo_called(self): pass def show_code(self): if not self.code_window: self.code_window = Tk.Toplevel() self.code_window.geometry(left_slide(self.demo_main_frame.master.winfo_geometry())) self.code_window.title(self.fname) self.code_frame = Tk.Frame(self.code_window) self.code_frame.pack(fill=Tk.BOTH, expand=1) self.scrolled_text = ScrolledText(self.code_frame, wrap=Tk.WORD) self.scrolled_text.pack(fill=Tk.BOTH, expand=1) self.content0 = read_contents(self.fname) self.scrolled_text.insert(Tk.END, self.content0) self.code_buttons = ButtonFrame(self.code_frame, "Dismiss", self.code_destroy, "Return Demo", self.return_demo) self.code_buttons.pack() self.code_window.focus_set() def code_destroy(self): self.code_window.destroy() self.code_window = None def demo_destroy(self): if self.cmain: sys.exit() else: self.demo_window.destroy() self.demo_window = None def return_demo(self): if self.cmain: self.demo_main_frame.focus_set() else: content = self.scrolled_text.get('1.0', Tk.END) if str_same_p(self.content0, content): if self.demo_window: self.demo_window.focus_set() else: self.ini_demo_called() else: if self.demo_window: self.demo_window.destroy() f=file('temp.py', 'w') f.write(content) f.close() mod = __import__('temp') reload(mod) self.code_window.destroy() d = mod.Demo(False) d.demo_window.focus_set() d.demo_window.master.after(20, d.show_code) ##---------------------------------------------------- if __name__ == '__main__': class De (Demo): def __init__(self): Demo.__init__(self, True, __file__) Label(self.demo_frame, text="test test test") a = De() a.demo_main_frame.mainloop() RE: Scrollable buttons with an add/delete button - menator01 - Jun-16-2022 My attempt. Much room for improvements though. The scrollbar doesn't seem to want to work as intended.(The area between arrows should shrink with more data introduced but, doesn't). There does not appear to be any bounds on the scrolling buttons. The scroll should stop at the button 0 and ending button but doesn't. import tkinter as tk class MyCanvas(tk.Canvas): def __init__(self, parent, *args, **kwargs): super().__init__(*args, **kwargs) self.columnconfigure(0, weight=1) self.rowconfigure(0, weight=1) self.container = tk.Frame(parent) self.container.grid(column=0, row=0, sticky='news') self.container.grid_columnconfigure(0, weight=3) self.canvas = tk.Canvas(self.container, bg='ivory') self.scrollbar = tk.Scrollbar(self.container, orient='vertical') self.canvas['yscrollcommand'] = self.scrollbar.set self.canvas['scrollregion'] = self.canvas.bbox('all') self.scrollbar['command'] = self.canvas.yview self.scrollbar.grid(column=1, row=0, sticky='ns') self.canvas.grid(column=0, row=0, sticky='news') self.frame = tk.Frame(self.canvas, bg='ivory') self.frame.grid(column=0, row=0, sticky='news') self.canvas.create_window(50, 10, window=self.frame, anchor='n') class MyButton(tk.Button): def __init__(self, parent, text, col, row, command=None, *args, **kwargs): self.parent = parent super().__init__(*args, **kwargs) self.button = tk.Button(parent) self.button['text'] = text self.button['command'] = command self.button.grid(column=col, row=row, sticky='new', pady=4, padx=2) root = tk.Tk() root.geometry('+250+250') canvas = MyCanvas(root) col = 0 row = 0 for i in range(100): MyButton(canvas.frame, f'Button {i}', col, row) if col >= 4: row += 1 col = 0 else: col += 1 root.mainloop() RE: Scrollable buttons with an add/delete button - rob101 - Jun-16-2022 I've gathered quite a lot of information pertaining to Tkinter and this link (below) shows a very simple example of the Scrollbar Widget, which would be my base for trying to do what you're doing. It could very well be that you've tried this and for whatever reason find it unsuitable. If so, please ignore this. https://www.pythontutorial.net/tkinter/tkinter-scrollbar/ |