I am reading, reading and reading but still unable to find the appropriate answer to my issue. I plan to create a 'Masterwindow' in which I implement several frames (currently a Navigation Panel to the left, a 'RootFrame', which shows up, when no process is running, a Statusbar at the bottom, which is for alerts and processings and a main frame. Running the Code below, everything just works fine, but the Frames do not adjust to the resize of the window.
Now, I know, I need to do some sticking in main- and subclasses and set grid_column- and rowconfigure to a weight of >0 but still nothing happens.
I cannot see the reason why. What am I missing? Do I need to put everything in another masterframe?? which I stick to the masterwindow? that can't be right since I am inheriting Frames everywhere...
Thanks for your effort and Input. rgds
p.s: oh by the way: can any1 tell me how to iterate through the rows in a grid method so that I can just simply say 'grid the widget in the next row' (relative) and dont have to use absolute integers?
# -*- coding: UTF-8 -*-
import tkinter.ttk
from tkinter import *
class MainApplication(tkinter.Frame):
#classmethod
def main(cls):
root = tkinter.Tk()
app = cls(root)
app.master.title('Sample')
root.resizable(True, True)
root.mainloop()
def __init__(self, parent=None, *args, **kwargs):
tkinter.Frame.__init__(self, parent, *args, **kwargs)
self.grid(sticky=N+E+S+W)
# Var-Declaration
self.h = 600
self.w = 1200
self.widget_fr_opts = dict(relief='groove', borderwidth=1, bg='#EFEFFB')
# Widget-Creation
self.rootframe = RootFrame(self)
self._visible_ = self.rootframe # internal updater what frame is visible - starting with rootframe on init
self.statusbar = Statusbar(self)
self.navbar = Navbar(self)
self.main_db = MainDB(self)
# Widget-Design
# Widget-Arrangement
self.grid_rowconfigure(0, minsize=self.h * 0.95)
self.grid_rowconfigure(1, minsize=self.h * 0.05)
self.grid_columnconfigure(0, minsize=self.w*0.15)
self.grid_columnconfigure(1, minsize=self.w*0.85)
self.navbar.grid(sticky=N+S+E+W, column=0, row=0)
self.main_db.grid(sticky=N+E+S+W, column=1, row=0)
self.rootframe.grid(sticky=N+E+S+W, column=1, row=0)
self.statusbar.grid(sticky=W+E, column=0, columnspan=2, row=1)
self.grid_columnconfigure(1, weight=1)
self.statusbar.columnconfigure(0, weight=1)
self.rootframe.columnconfigure(0, weight=1)
self.main_db.columnconfigure(0, weight=1)
self.grid_rowconfigure(0, weight=1)
self.navbar.rowconfigure(4, weight=1)
self.rootframe.rowconfigure(0, weight=1)
self.main_db.rowconfigure(0, weight=1)
self.rootframe.lift(self.main_db)
def visualize(self, master):
"""Lifts master to upper Level"""
master.lift(self.rootframe)
self._visible_ = master
def event_handler(self):
pass
def start_subapp(self, app):
self.visualize(app)
app.activate_content()
class RootFrame(tkinter.Frame):
"""General Launcher Frame as a Placeholder"""
def __init__(self, parent, *args, **kwargs):
tkinter.Frame.__init__(self, parent, *args, **kwargs)
# Var-Daclaration
self.widget_fr_opts = dict(relief='sunken', borderwidth=1)
self.widget_grid_opts = dict(sticky=N+E+S+W, padx=1, pady=1)
# Widget-Creation
self.Image = tkinter.ttk.Label(self, text='there would be a centered image right here', anchor='center')
# Widget-Design
self.configure(**self.widget_fr_opts)
# Widget-Arrangement
self.Image.grid(column=0, row=0, **self.widget_grid_opts)
self.grid_columnconfigure(0, weight=1)
self.grid_rowconfigure(0, weight=1)
class Navbar(tkinter.Frame):
"""vertical NavBar"""
def __init__(self, parent, *args, **kwargs):
tkinter.Frame.__init__(self, parent, *args, **kwargs)
# Var-Daclaration
self._info_ = tkinter.StringVar()
self.widget_fr_opts = dict(relief='groove', borderwidth=1)
self.widget_grid_opts = dict(sticky=N+E+S+W, padx=1, pady=1)
self.widget_grid_subopts = dict(sticky=W+N+E, padx=1, pady=1)
self._statusbar = parent.statusbar
# Widget-Creation
self._info_.set('Menu:\n...Menusentences:')
self.TextInfo = tkinter.ttk.Label(self, textvariable=self._info_)
self.Btn_Progress = tkinter.ttk.Button(self, text='Start Progress',
command=lambda: self.statusbar_input('Starting progress ...')) # some code being started
self.Btn_Database = tkinter.ttk.Button(self, text='Database',
command=lambda: parent.start_subapp(parent.main_db)) # Database window is lifted and content initialized
self.Btn_Exit = tkinter.ttk.Button(self, text='Exit', command=parent.quit)
# Widget-Design
self.configure(**self.widget_fr_opts)
# Widget-Arrangement
self.TextInfo.grid(column=0, row=1, **self.widget_grid_subopts)
self.Btn_Progress.grid(column=0, row=2, **self.widget_grid_subopts)
self.Btn_Database.grid(column=0, row=3, **self.widget_grid_subopts)
self.Btn_Exit.grid(column=0, row=4, **self.widget_grid_subopts)
self.grid_columnconfigure(0, weight=1)
self.grid_rowconfigure(0, weight=0)
self.grid_rowconfigure(1, weight=0)
self.grid_rowconfigure(2, weight=0)
self.grid_rowconfigure(3, weight=0)
self.grid_rowconfigure(4, weight=0)
def statusbar_input(self, comm: str):
self._statusbar.start()
self._statusbar._info_.set(comm)
class Statusbar(tkinter.Frame):
"""Status-Bar at the bottom"""
def __init__(self, parent, *args, **kwargs):
tkinter.Frame.__init__(self, parent, *args, **kwargs)
# Var-Daclaration
self.prgrvalue = tkinter.IntVar()
self._info_ = tkinter.StringVar()
self._user_ = 'some user'
self.widget_fr_opts = dict(relief='sunken', borderwidth=1)
self.widget_grid_opts = dict(padx=1, pady=1)
self.widget_grid_subopts = dict(padx=1, pady=1) # sticky=W + E,
# Widget-Creation
self._info_.set('Initializing ...')
self.prgrvalue.set(0)
self.TextInfo = tkinter.ttk.Label(self, textvariable=self._info_)
self.UserInfo = tkinter.ttk.Label(self, textvariable=self._user_)
self.progress_ = tkinter.ttk.Progressbar(self)
self.Btn_Move = tkinter.ttk.Button(self, text='Move it', command=lambda: self.start()) # just for initial testing, will be removed later
self.Btn_Stop = tkinter.ttk.Button(self, text='Stop it', command=lambda: self.stop())
# Widget-Design
self.configure(**self.widget_fr_opts)
self.progress_.configure(length=200, mode='determinate', orient=tkinter.HORIZONTAL)
# Widget-Arrangement
self.progress_.grid(sticky=W+E, column=0, row=0, **self.widget_grid_subopts)
self.TextInfo.grid(column=1, row=0, **self.widget_grid_subopts)
self.UserInfo.grid(column=2, row=0, padx=1, pady=1) # sticky=E,
self.Btn_Move.grid(column=3, row=0, **self.widget_grid_subopts)
self.Btn_Stop.grid(column=4, row=0, **self.widget_grid_subopts)
self.grid_columnconfigure(0, weight=1)
self.grid_columnconfigure(1, minsize=200)
self.grid_columnconfigure(2, minsize=200)
def start(self):
# just testing
self.progress_.start()
def stop(self):
# just testing
self.progress_.stop()
class MainDB(tkinter.Frame):
"""Frame for visualizing database."""
def __init__(self, parent, *args, **kwargs):
tkinter.Frame.__init__(self, parent, *args, **kwargs)
# Var-Daclaration
self._activated_ = False
self._source_ = None
self.combotext = tkinter.StringVar()
self.combotext.set('Please choose a tab...')
self.widget_fr_opts = dict(relief='sunken', borderwidth=1)
self.widget_grid_opts = dict(sticky=N+E+S+W, padx=1, pady=1)
# Widget-Creation
# CREATION OF TOOLS TO MANIPULATE DATABASE
self.toolframe = tkinter.Frame(self, width=100, height=50, relief='groove', borderwidth=1)
self.combo = tkinter.ttk.Combobox(self.toolframe, textvariable=self.combotext)
# more to come
# CREATION OF DATABASE'WINDOW
self.dbframe = tkinter.Frame(self, width=100, relief='groove', borderwidth=1)
self.db_treeview = tkinter.ttk.Treeview(self.dbframe, columns=('size', 'modified'), selectmode='extended')
# more to come
# Widget-Design
# Widget-Arrangement
self.toolframe.grid(column=0, row=0, sticky=N+E+W, padx=1, pady=1)
self.combo.grid(column=0, row=0, sticky=N+E, padx=1, pady=1)
self.dbframe.grid(column=0, row=1, sticky=NSEW, padx=1, pady=1)
self.db_treeview.grid(column=0, row=0, sticky=NSEW, padx=1, pady=1)
self.grid_columnconfigure(0, weight=1)
self.grid_rowconfigure(0, weight=0)
self.grid_rowconfigure(1, weight=1)
self.dbframe.grid_columnconfigure(0, weight=1)
self.dbframe.grid_rowconfigure(0, weight=1, minsize=600)
self.toolframe.grid_columnconfigure(0, weight=1)
self.toolframe.grid_rowconfigure(0, weight=1)
def activate_content(self):
# some contentloading and initializations
pass
def db_connector(self, comm: str, save=False) -> bool:
# some connection code
pass
if __name__ == '__main__':
UserScreen = MainApplication.main()
You are neglecting to give a weight to any rows or columns in the root window. Without it, tkinter doesn't know how to allocate extra space when you resize the window.
def main(cls):
...
root.grid_rowconfigure(0, weight=1)
root.grid_columnconfigure(0, weight=1)
Since you're only putting a single widget in the root window, I recommend using pack since you can accomplish everything in a single line.
self.pack(fill="both", expand=True)
Related
I have researched and tested a number of solutions on stackoverflow and other sites but I cannot get a scrollbar to work with my application.
I'm looking for a scrollbar capable of scrolling the SubFrame group that my application creates. I also need to delimit a minimum window height, and that this window can be extended, but without breaking the scrollbar.
Thank you in advance to all who will help me.
I admit not having understood which should go above the other, the Canvas or the Frame.
Here is a piece of code, with all the graphical widgets to visualize the scrollbar effect :
import tkinter as tk
from tkinter import ttk
class FrameStack(tk.Frame):
def __init__(self, parent):
super().__init__(parent)
self.subframes = []
self.topFrame = tk.Frame(root)
self.topFrame.pack(side="top", fill="x")
self.groupOfFrames = tk.Frame(root)
self.groupOfFrames.pack(side="top", fill="both", expand=True, pady=(32,0))
self.scrollbar = tk.Scrollbar(self.groupOfFrames, orient="vertical")
self.scrollbar.pack(side="right",fill="y")
#self.canvas = tk.Canvas(self.groupOfFrames, yscrollcommand=self.scrollbar.set)
#self.canvas.pack()
#self.canvas_window = self.canvas.create_window(0, 0, anchor="nw", window=self.groupOfFrames)
self.create_widget()
def create_widget(self):
tk.Label(self.topFrame, text="BUS").grid(row=0, column=0)
tk.Label(self.topFrame, text="IP").grid(row=1, column=0)
tk.Label(self.topFrame, text="REG").grid(row=2, column=0)
tk.Label(self.topFrame, text="SIZE").grid(row=3, column=0)
self.combobox_bus = ttk.Combobox(self.topFrame, values=list(dic.keys()), width=40, justify='center', state="readonly")
self.combobox_bus.bind('<<ComboboxSelected>>', self.getUpdateDataIP)
self.combobox_bus.grid(row=0, column=1, sticky="nsew")
self.combobox_ip = ttk.Combobox(self.topFrame, justify='center', width=40, state="readonly")
self.combobox_ip.bind('<<ComboboxSelected>>', self.getUpdateDataReg)
self.combobox_ip.grid(row=1, column=1, sticky="nsew")
self.button_read = tk.Button(self.topFrame, text="Read", command=self.read)
self.button_write = tk.Button(self.topFrame, text="Write", command=self.write)
self.button_read.grid(row=0, column=2, columnspan=2, sticky="nsew")
self.button_write.grid(row=1, column=2, columnspan=2, sticky="nsew")
self.button_hsid = tk.Button(self.topFrame, text="HSID")
self.button_hsid.grid(row=0, column=4, columnspan=2, sticky="nsew")
self.button_add = tk.Button(self.topFrame, text="+", command=self.add_frame)
self.button_add.grid(row=1, column=4, columnspan=2, sticky="nsew")
self.combobox_reg_dl = ttk.Combobox(self.topFrame, width=40, justify='center', state="readonly")
self.combobox_reg_dl.bind('<<ComboboxSelected>>', self.getUpdateDisplayReg)
self.combobox_reg_dl.grid(row=2, column=1, sticky="nsew")
self.button_dump = tk.Button(self.topFrame, text="Dump", command=self.dump)
self.button_load = tk.Button(self.topFrame, text="Load", command=self.load)
self.button_dump.grid(row=2, column=2, columnspan=2, sticky="nsew")
self.button_load.grid(row=2, column=4, columnspan=2, sticky="nsew")
self.select_size = tk.StringVar()
self.select_size.set("0")
self.entry_size = tk.Entry(self.topFrame, textvariable=self.select_size, justify='center')
self.entry_size.grid(row=3, column=1, sticky="nsew")
tk.Scale(self.topFrame, from_=0, to=1024, variable=self.select_size)
self.select_read_size = tk.IntVar()
self.select_read_size.set(8)
self.radio_8 = tk.Radiobutton(self.topFrame, text=" 8", variable=self.select_read_size, value=8)
self.radio_16 = tk.Radiobutton(self.topFrame, text="16", variable=self.select_read_size, value=16)
self.radio_32 = tk.Radiobutton(self.topFrame, text="32", variable=self.select_read_size, value=32)
self.radio_64 = tk.Radiobutton(self.topFrame, text="64", variable=self.select_read_size, value=64)
self.radio_8.grid(row=3, column=2)
self.radio_16.grid(row=3, column=3)
self.radio_32.grid(row=3, column=4)
self.radio_64.grid(row=3, column=5)
def getUpdateDataIP(self, event):
self.combobox_ip['values'] = list(dic[self.combobox_bus.get()].keys())
self.combobox_ip.set('')
self.combobox_reg_dl['values'] = ['']
self.combobox_reg_dl.set('')
for f in self.subframes:
f.combobox_reg_rw['values'] = ['']
f.combobox_reg_rw.set('')
def getUpdateDataReg(self, event):
self.combobox_reg_dl['values'] = list(dic[self.combobox_bus.get()][self.combobox_ip.get()].keys())
self.combobox_reg_dl.set('')
for f in self.subframes:
f.combobox_reg_rw['values'] = list(dic[self.combobox_bus.get()][self.combobox_ip.get()].keys())
f.combobox_reg_rw.set('')
def getUpdateDisplayReg(self, event):
self.combobox_reg_dl.set(self.combobox_reg_dl.get()+" ("+dic[self.combobox_bus.get()][self.combobox_ip.get()][self.combobox_reg_dl.get()]+")")
def delete_frame(self, frame):
self.subframes.remove(frame)
frame.destroy()
def add_frame(self):
f = SubFrame(parent=self.groupOfFrames, controller=self)
if self.combobox_bus.get()!="" and self.combobox_ip.get()!="":
f.combobox_reg_rw['values'] = list(dic[self.combobox_bus.get()][self.combobox_ip.get()].keys())
self.subframes.append(f)
f.pack(side="top", fill="x", pady=(0,5))
def read(self):
pass
def write(self):
pass
def dump(self):
pass
def load(self):
pass
class SubFrame(tk.Frame):
def __init__(self, parent, controller):
super().__init__(parent)
self.parent = parent
self.controller = controller
self.create_widget()
def create_widget(self):
self.combobox_reg_rw = ttk.Combobox(self, width=47, justify='center', state="readonly")
self.combobox_reg_rw.bind('<<ComboboxSelected>>', self.getUpdateDisplayReg)
self.select_val = tk.StringVar()
self.entry_value = tk.Entry(self, bd=1.5, width=20, textvariable=self.select_val, justify='center')
self.button_write = tk.Button(self, text="Write", command=self.write)
self.button_read = tk.Button(self, text="Read", command=self.read)
self.button_help = tk.Button(self, text="?", command=self.help)
self.button_remove = tk.Button(self, text="-", command=self.remove)
self.combobox_reg_rw.grid(row=0, column=0, columnspan=2, sticky="ew")
self.entry_value.grid(row=0, column=2, columnspan=2, sticky="ew")
self.button_write.grid(row=1, column=0, sticky="ew")
self.button_read.grid(row=1, column=1, sticky="ew")
self.button_help.grid(row=1, column=2, sticky="nsew")
self.button_remove.grid(row=1, column=3, sticky="nsew")
def remove(self):
self.controller.delete_frame(self)
def getUpdateDisplayReg(self, event):
self.combobox_reg_rw.set(self.combobox_reg_rw.get()+" ("+dic[self.controller.combobox_bus.get()][self.controller.combobox_ip.get()][self.combobox_reg_rw.get()]+")")
def write(self):
print(self.entry_value.get())
def read(self):
print(self.combobox_reg_rw.get())
def help(self):
pass
if __name__ == "__main__":
#dic = extractor.main()
dic = {'1': {'1.1': {'1.1.1': 'A', '1.1.2': 'B'}, '1.2': {'1.2.1': 'C', '1.2.2': 'D'}}, '2': {'2.1': {'2.1.1': 'E', '2.2.2': 'F'}, '2.2': {'2.2.1': 'G', '2.2.2': 'H'}}}
root = tk.Tk()
root.title("XXXXXX")
root.resizable(False, True)
fs = FrameStack(root)
fs.pack(fill="both", expand=True)
root.mainloop()
It is the canvas that has the ability to scroll, so your self.groupOfFrames needs to go inside the canvas. And since you want the scrollbar and canvas to appear as a single complex object, they should go in a frame.
You need to make sure that when the window is resized, the frame inside the canvas is resized as well. And you also need to make sure that when you add something to self.groupOfFrames you also update the scrollregion. These can be done by binding to the <Configure> event of each widget.
Thus, I would create the FrameStack like the following. Please note that self.topFrame and self.bottomFrame are children of self rather than root. That is a mistake I didn't catch in the code I gave you in your previous question.
class FrameStack(tk.Frame):
def __init__(self, parent):
super().__init__(parent)
self.subframes = []
self.topFrame = tk.Frame(self)
self.bottomFrame = tk.Frame(self)
self.topFrame.pack(side="top", fill="x")
self.bottomFrame.pack(side="bottom", fill="both", expand=True)
self.canvas = tk.Canvas(self.bottomFrame, bd=0)
vsb = tk.Scrollbar(self.bottomFrame, command=self.canvas.yview)
self.canvas.configure(yscrollcommand=vsb.set)
vsb.pack(side="right", fill="y")
self.canvas.pack(side="left", fill="both", expand=True)
self.groupOfFrames = tk.Frame(self.canvas)
self.canvas.create_window(0, 0, anchor="nw", window=self.groupOfFrames, tags=("inner",))
self.canvas.bind("<Configure>", self._resize_inner_frame)
self.groupOfFrames.bind("<Configure>", self._reset_scrollregion)
def _reset_scrollregion(self, event):
self.canvas.configure(scrollregion=self.canvas.bbox("all"))
def _resize_inner_frame(self, event):
self.canvas.itemconfig("inner", width=event.width)
I was trying to program a kind of calculator in Python using the Tkinter library. My problem is that I watch some pages that says that the way to set de heidth and weidth of a button is using rowconfigure and columnconfigure. The problem is that when I run the script it doesn't work. I don't know what I'm doing bad so pls help me
Here is the code
import tkinter as tk
def insert_number(variable, entry):
result = variable + entry
return result
conjunt = ""
class MainWindow(tk.Frame):
def __init__(self, parent, *args, **kwargs):
tk.Frame.__init__(self, parent, *args, **kwargs)
self.parent = parent
self.grid()
self.config(bg="blue")
self.button1 = tk.Button(text="1", command=insert_number(conjunt, "1")).grid(
column=0, row=0)
self.button1 = tk.Button(text="2", command=insert_number(conjunt, "2")).grid(
column=1, row=0)
self.button1 = tk.Button(text="3", command=insert_number(conjunt, "3")).grid(
column=2, row=0)
self.button1 = tk.Button(text="4", command=insert_number(conjunt, "1")).grid(
column=0, row=1)
self.button1 = tk.Button(text="5", command=insert_number(conjunt, "1")).grid(
column=1, row=1)
self.button1 = tk.Button(text="6", command=insert_number(conjunt, "1")).grid(
column=2, row=1)
self.button1 = tk.Button(text="7", command=insert_number(conjunt, "1")).grid(
column=0, row=2)
self.button1 = tk.Button(text="8", command=insert_number(conjunt, "1")).grid(
column=1, row=2)
self.button1 = tk.Button(text="9", command=insert_number(conjunt, "1")).grid(
column=2, row=2)
self.button1 = tk.Button(text="0", command=insert_number(conjunt, "1")).grid(
column=1, row=3)
self.columnconfigure(0, weight=1)
self.columnconfigure(1, weight=1)
self.rowconfigure(0, weight=1)
self.rowconfigure(1, weight=1)
def give_result(self):
pass
def main():
root = tk.Tk()
root.title("Calculadora")
buttons_frame = MainWindow(root)
buttons_frame.grid()
root.mainloop()
if __name__ == "__main__":
main()
I'm trying to create a canvas widget with a number of widgets embedded within it. Since there will frequently be too many widgets to fit in the vertical space I have for the canvas, it'll need to be scrollable.
import tkinter as tk # for general gui
import tkinter.ttk as ttk # for notebook (tabs)
class instructionGeneratorApp():
def __init__(self, master):
# create a frame for the canvas and scrollbar
domainFrame = tk.LabelFrame(master)
domainFrame.pack(fill=tk.BOTH, expand=1)
# make the canvas expand before the scrollbar
domainFrame.rowconfigure(0,weight=1)
domainFrame.columnconfigure(0,weight=1)
vertBar = ttk.Scrollbar(domainFrame)
vertBar.grid(row=0, column=1, sticky=tk.N + tk.S)
configGridCanvas = tk.Canvas(domainFrame,
bd=0,
yscrollcommand=vertBar.set)
configGridCanvas.grid(row=0, column=0, sticky=tk.N + tk.S + tk.E + tk.W)
vertBar.config(command=configGridCanvas.yview)
# add widgets to canvas
l = tk.Label(configGridCanvas, text='Products')
l.grid(row=1, column=0)
r = 2
for product in ['Product1','Product2','Product3','Product4','Product5','Product6','Product7','Product8','Product9','Product10','Product11','Product12','Product13','Product14','Product15','Product16','Product17','Product18','Product19','Product20']:
l = tk.Label(configGridCanvas, text=product)
l.grid(row=r, column=0)
c = tk.Checkbutton(configGridCanvas)
c.grid(row=r, column=1)
r += 1
ButtonFrame = tk.Frame(domainFrame)
ButtonFrame.grid(row=r, column=0)
removeServerButton = tk.Button(ButtonFrame, text='Remove server')
removeServerButton.grid(row=0, column=0)
# set scroll region to bounding box?
configGridCanvas.config(scrollregion=configGridCanvas.bbox(tk.ALL))
root = tk.Tk()
mainApp = instructionGeneratorApp(root)
root.mainloop()
As best as I can tell, I'm following the effbot pattern for canvas scrollbars, but I end up with either a scrollbar that isn't bound to the canvas, or a canvas that is extending beyond the edges of its master frame:
I've attempted the solutions on these questions, but there's still something I'm missing:
resizeable scrollable canvas with tkinter
Tkinter, canvas unable to scroll
Any idea what I'm doing wrong?
I have added some comments to #The Pinapple 's solution for future reference.
from tkinter import *
class ProductItem(Frame):
def __init__(self, master, message, **kwds):
Frame.__init__(self, master, **kwds)
self.grid_rowconfigure(1, weight=1)
self.grid_columnconfigure(0, weight=1)
self.text = Label(self, text=message, anchor='w')
self.text.grid(row=0, column=0, sticky='nsew')
self.check = Checkbutton(self, anchor='w')
self.check.grid(row=0, column=1)
class ScrollableContainer(Frame):
def __init__(self, master, **kwargs):
#our scrollable container is a frame, this frame holds the canvas we draw our widgets on
Frame.__init__(self, master, **kwargs)
#grid and rowconfigure with weight 1 are used for the scrollablecontainer to utilize the full size it can get from its parent
self.grid_rowconfigure(0, weight=1)
self.grid_columnconfigure(0, weight=1)
#canvas and scrollbars are positioned inside the scrollablecontainer frame
#the scrollbars take a command parameter which is used to position our view on the canvas
self.canvas = Canvas(self, bd=0, highlightthickness=0)
self.hScroll = Scrollbar(self, orient='horizontal',
command=self.canvas.xview)
self.hScroll.grid(row=1, column=0, sticky='we')
self.vScroll = Scrollbar(self, orient='vertical',
command=self.canvas.yview)
self.vScroll.grid(row=0, column=1, sticky='ns')
self.canvas.grid(row=0, column=0, sticky='nsew')
#We do not only need a command to position but also one to scroll
self.canvas.configure(xscrollcommand=self.hScroll.set,
yscrollcommand=self.vScroll.set)
#This is the frame where the magic happens, all of our widgets that are needed to be scrollable will be positioned here
self.frame = Frame(self.canvas, bd=2)
self.frame.grid_columnconfigure(0, weight=1)
#A canvas itself is blank, we must tell the canvas to create a window with self.frame as content, anchor=nw means it will be positioned on the upper left corner
self.canvas.create_window(0, 0, window=self.frame, anchor='nw', tags='inner')
self.product_label = Label(self.frame, text='Products')
self.product_label.grid(row=0, column=0, sticky='nsew', padx=2, pady=2)
self.products = []
for i in range(1, 21):
item = ProductItem(self.frame, ('Product' + str(i)), bd=2)
item.grid(row=i, column=0, sticky='nsew', padx=2, pady=2)
self.products.append(item)
self.button_frame = Frame(self.frame)
self.button_frame.grid(row=21, column=0)
self.remove_server_button = Button(self.button_frame, text='Remove server')
self.remove_server_button.grid(row=0, column=0)
self.update_layout()
#If the widgets inside the canvas / the canvas itself change size,
#the <Configure> event is fired which passes its new width and height to the corresponding callback
self.canvas.bind('<Configure>', self.on_configure)
def update_layout(self):
#All pending events, callbacks, etc. are processed in a non-blocking manner
self.frame.update_idletasks()
#We reconfigure the canvas' scrollregion to fit all of its widgets
self.canvas.configure(scrollregion=self.canvas.bbox('all'))
#reset the scroll
self.canvas.yview('moveto', '1.0')
#fit the frame to the size of its inner widgets (grid_size)
self.size = self.frame.grid_size()
def on_configure(self, event):
w, h = event.width, event.height
natural = self.frame.winfo_reqwidth() #natural width of the inner frame
#If the canvas changes size, we fit the inner frame to its size
self.canvas.itemconfigure('inner', width=w if w > natural else natural)
#dont forget to fit the scrollregion, otherwise the scrollbar might behave strange
self.canvas.configure(scrollregion=self.canvas.bbox('all'))
if __name__ == "__main__":
root = Tk()
root.grid_rowconfigure(0, weight=1)
root.grid_columnconfigure(0, weight=1)
sc = ScrollableContainer(root, bd=2)
sc.grid(row=0, column=0, sticky='nsew')
root.mainloop()
From what I can tell you are looking for something like this..
from tkinter import *
class ProductItem(Frame):
def __init__(self, master, message, **kwds):
Frame.__init__(self, master, **kwds)
self.grid_rowconfigure(1, weight=1)
self.grid_columnconfigure(0, weight=1)
self.text = Label(self, text=message, anchor='w')
self.text.grid(row=0, column=0, sticky='nsew')
self.check = Checkbutton(self, anchor='w')
self.check.grid(row=0, column=1)
class ScrollableContainer(Frame):
def __init__(self, master, **kwargs):
Frame.__init__(self, master, **kwargs) # holds canvas & scrollbars
self.grid_rowconfigure(0, weight=1)
self.grid_columnconfigure(0, weight=1)
self.canvas = Canvas(self, bd=0, highlightthickness=0)
self.hScroll = Scrollbar(self, orient='horizontal',
command=self.canvas.xview)
self.hScroll.grid(row=1, column=0, sticky='we')
self.vScroll = Scrollbar(self, orient='vertical',
command=self.canvas.yview)
self.vScroll.grid(row=0, column=1, sticky='ns')
self.canvas.grid(row=0, column=0, sticky='nsew')
self.canvas.configure(xscrollcommand=self.hScroll.set,
yscrollcommand=self.vScroll.set)
self.frame = Frame(self.canvas, bd=2)
self.frame.grid_columnconfigure(0, weight=1)
self.canvas.create_window(0, 0, window=self.frame, anchor='nw', tags='inner')
self.product_label = Label(self.frame, text='Products')
self.product_label.grid(row=0, column=0, sticky='nsew', padx=2, pady=2)
self.products = []
for i in range(1, 21):
item = ProductItem(self.frame, ('Product' + str(i)), bd=2)
item.grid(row=i, column=0, sticky='nsew', padx=2, pady=2)
self.products.append(item)
self.button_frame = Frame(self.frame)
self.button_frame.grid(row=21, column=0)
self.remove_server_button = Button(self.button_frame, text='Remove server')
self.remove_server_button.grid(row=0, column=0)
self.update_layout()
self.canvas.bind('<Configure>', self.on_configure)
def update_layout(self):
self.frame.update_idletasks()
self.canvas.configure(scrollregion=self.canvas.bbox('all'))
self.canvas.yview('moveto', '1.0')
self.size = self.frame.grid_size()
def on_configure(self, event):
w, h = event.width, event.height
natural = self.frame.winfo_reqwidth()
self.canvas.itemconfigure('inner', width=w if w > natural else natural)
self.canvas.configure(scrollregion=self.canvas.bbox('all'))
if __name__ == "__main__":
root = Tk()
root.grid_rowconfigure(0, weight=1)
root.grid_columnconfigure(0, weight=1)
sc = ScrollableContainer(root, bd=2)
sc.grid(row=0, column=0, sticky='nsew')
root.mainloop()
I'm trying to separate two frames with a third one, which should look like a vertical line. Using pack manager it always shows up on the very left or right, no matter how I shuffle the order of packing and/or side as 'left' or 'right'. When I use grid it doesn't show at all. Below is my code:
EDIT:
I added Import/Export Section definition, so the code is complete working example.
class ImportSection(tk.Frame):
def __init__(self, parent, *args, **kwargs):
tk.Frame.__init__(self, parent, *args, **kwargs)
self.lbl_import = tk.Label(self, text='IMPORT', width=20)
self.lbl_import.grid()
class ExportSection(tk.Frame):
def __init__(self, parent, *args, **kwargs):
tk.Frame.__init__(self, parent, *args, **kwargs)
self.lbl_export = tk.Label(self, text='EXPORT', width=20)
self.lbl_export.grid()
class Main(tk.Frame):
def __init__(self, parent, *args, **kwargs):
tk.Frame.__init__(self, parent, *args, **kwargs)
self.import_section = ImportSection(self)
self.export_section = ExportSection(self)
self.sep = tk.Frame(width=2, bd=1, relief='sunken')
# I tried to shuffle the order and experimented with left/right with no luck.
# the line is always on the very right or left
# self.import_section.pack(side='left', padx=5, pady=5, anchor='n')
# self.export_section.pack(side='left', padx=5, pady=5, anchor='n')
# self.sep.pack(side='left', fill='y', padx=5, pady=5)
# another attempt with grid, but the line does not show at all
self.import_section.grid(row=0, column=0, padx=5, pady=5, sticky='n')
self.sep.grid( row=0, column=1, padx=5, pady=5, sticky='ns')
self.export_section.grid(row=0, column=2, padx=5, pady=5, sticky='n')
if __name__ == '__main__':
root = tk.Tk()
app = Main(root)
# app.pack(side='top', fill='both', expand=True) - I used this version with pack
app.grid()
root.mainloop()
You can maybe use ttk.Separator:
import tkinter as tk
from tkinter import ttk
class Main(tk.Frame):
def __init__(self, parent, *args, **kwargs):
tk.Frame.__init__(self, parent, *args, **kwargs)
self.import_section = tk.Frame(self)
tk.Canvas(self.import_section, width=200, height=400, bg='cyan').grid(column=0, row=0)
self.export_section = tk.Frame(self)
tk.Canvas(self.export_section, width=200, height=400, bg='lightgreen').grid(column=0, row=0)
self.sep = ttk.Separator(self, orient=tk.VERTICAL)
self.import_section.grid(row=0, column=0, padx=5, pady=5, sticky='n')
self.sep.grid( row=0, column=1, padx=5, pady=5, sticky='ns')
self.export_section.grid(row=0, column=2, padx=5, pady=5, sticky='n')
if __name__ == '__main__':
root = tk.Tk()
app = Main(root)
app.grid()
root.mainloop()
The problem is that the frame you are trying to use as separator is not in the same frame as the ImportSection and ExportSection because you don't specify its parent. When you don't specify a parent, tkinter will make the widget a child of the root window. This is also the reason why you can't pack app into the root window: self.sep is already put into root with grid.
Change
self.sep = tk.Frame(width=2, bd=1, relief='sunken')
to
self.sep = tk.Frame(self, width=2, bd=1, relief='sunken')
I'm trying to create a chemistry moles calculator using Python and the Chemspider API. I got this code working, which searches the database by name:
===THIS WORKS FINE===
import tkinter as tk
from tkinter import ttk
import chemspipy
from chemspipy import ChemSpider
cs = ##MY CHEMSPIDER ID
def NameSearch(*args, **kwargs):
namesearch = tk.Tk()
namesearch.title("Search via Name")
def ChemSearch(*args, **kwargs):
try:
ChemName = ChemicalName.get()
ChemName = cs.search(ChemName)
if len(ChemName) >= 1:
ChemicalID.set(ChemName[0])
print(ChemicalID)
else:
popupmsg("Sorry, we could not find that chemical. Please try again")
except ValueError:
pass
searchframe = ttk.Frame(namesearch)
searchframe.grid(column=0, row=0, sticky="nsew")
searchframe.columnconfigure(0, weight=1)
searchframe.rowconfigure(0, weight=1)
ChemicalName = tk.StringVar()
ChemicalID = tk.StringVar()
Chemical_entry = ttk.Entry(searchframe, width=7, textvariable=ChemicalName)
Chemical_entry.grid(column=3, row=1, sticky="we")
ttk.Label(searchframe, textvariable=ChemicalID).grid(column=2, row=3, sticky=("ew"))
ttk.Button(searchframe, text="Search", command=ChemSearch).grid(column=3, row=4, sticky="w")
ttk.Label(searchframe, text="Chemical Name").grid(column=1, row=1, sticky="w")
ttk.Label(searchframe, text="The ID of this chemical is: ").grid(column=1, row=3, sticky="e")
for child in searchframe.winfo_children(): child.grid_configure(padx=5, pady=5)
Chemical_entry.focus()
namesearch.bind("<Return>", ChemSearch)
NameSearch()
I then decided to implement a menu for my program using tkinter like this:
cs = ##MY CHEMSPIDER ID
def popupmsg(msg):
popup = tk.Tk()
def leavemini():
popup.destroy()
popup.wm_title("!")
label = ttk.Label(popup, text=msg)
label.pack(side="top", fill="x", pady=10)
B1 = ttk.Button(popup, text="Okay", command = leavemini)
B1.pack()
def NameSearch(cs, *args, **kwargs):
namesearch = tk.Tk()
namesearch.title("Search via Name")
def ChemSearch(*args, **kwargs):
try:
ChemName = ChemicalName.get()
ChemName = cs.search(ChemName)
if len(ChemName) >= 1:
ChemicalID.set(ChemName[0])
else:
popupmsg("Sorry, we could not find that chemical. Please try again")
print(ChemicalID)
except ValueError:
print("a")
pass
searchframe = ttk.Frame(namesearch)
searchframe.grid(column=0, row=0, sticky="nsew")
searchframe.columnconfigure(0, weight=1)
searchframe.rowconfigure(0, weight=1)
ChemicalName = tk.StringVar()
ChemicalID = tk.StringVar()
Chemical_entry = ttk.Entry(searchframe, width=7, textvariable=ChemicalName)
Chemical_entry.grid(column=3, row=1, sticky="we")
ttk.Label(searchframe, textvariable=ChemicalID).grid(column=2, row=3, sticky=("ew"))
ttk.Button(searchframe, text="Search", command=ChemSearch).grid(column=3, row=4, sticky="w")
ttk.Label(searchframe, text="Chemical Name").grid(column=1, row=1, sticky="w")
ttk.Label(searchframe, text="The ID of this chemical is: ").grid(column=1, row=3, sticky="e")
for child in searchframe.winfo_children(): child.grid_configure(padx=5, pady=5)
Chemical_entry.focus()
namesearch.bind("<Return>", ChemSearch)
class ChemApp(tk.Tk):
def __init__(self, *args, **kwargs):
tk.Tk.__init__(self, *args, **kwargs)
tk.Tk.wm_title(self, "Chemistry App")
container = tk.Frame(self)
container.pack(side="top", fill="both", expand = True)
container.grid_rowconfigure(0, weight=1)
container.grid_columnconfigure(0, weight=1)
menubar = tk.Menu(container)
searchmenu = tk.Menu(menubar, tearoff=0)
searchmenu.add_command(label="Search by Name", command=lambda: NameSearch(cs))
searchmenu.add_separator()
searchmenu.add_command(label="Exit", command=quit)
menubar.add_cascade(label="Search", menu=searchmenu)
tk.Tk.config(self, menu=menubar)
self.frames = {}
for F in (StartPage,):
frame = F(container, self)
self.frames[F] = frame
frame.grid(row=0, column=0, sticky="nsew")
self.show_frame(StartPage)
def show_frame(self, cont):
frame = self.frames[cont]
frame.tkraise()
class StartPage(tk.Frame):
def __init__(self, parent, controller):
tk.Frame.__init__(self,parent)
label = ttk.Label(self, text="Start Page")
label.pack(pady=10,padx=10)
button = ttk.Button(self, text="Search for a Chemical",
command=lambda: controller.show_frame(NameSearch))
button.pack()
app = ChemApp()
app.geometry("1280x720")
app.mainloop()
However when run through a function triggered by the pressing of a tkinter menu button (under search in the GUI), it refuses to return a correct value from the database. As far as I'm aware, I've passed all the required variables, and it is running in an identical environment to before, it just won't work.
Any help would be greatly appreciated as it has been troubling
You can't have more than once instance of Tk in an application. You need to call Tk() exactly once. If you need more windows, create instances of Toplevel.