Posts: 1
Threads: 1
Joined: Jun 2022
Jun-14-2022, 03:22 PM
(This post was last modified: Jun-14-2022, 03:22 PM by Clich3.
Edit Reason: Moving Picture
)
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:
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()
Posts: 6,779
Threads: 20
Joined: Feb 2020
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/
Posts: 536
Threads: 0
Joined: Feb 2018
Jun-14-2022, 05:16 PM
(This post was last modified: Jun-14-2022, 05:16 PM by woooee.)
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()
Posts: 12,022
Threads: 484
Joined: Sep 2016
Jun-14-2022, 05:24 PM
(This post was last modified: Jun-16-2022, 09:40 PM by Larz60+.)
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()
Gribouillis likes this post
Posts: 1,144
Threads: 114
Joined: Sep 2019
Jun-16-2022, 06:50 PM
(This post was last modified: Jun-16-2022, 07:46 PM by menator01.)
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()
Posts: 453
Threads: 16
Joined: Jun 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/t...scrollbar/
Sig:
>>> import this
The UNIX philosophy: "Do one thing, and do it well."
"The danger of computers becoming like humans is not as great as the danger of humans becoming like computers." :~ Konrad Zuse
"Everything should be made as simple as possible, but not simpler." :~ Albert Einstein
|