I feel like my question is pretty straightforward, but I just cant seem to figure out how to do this. I'm currently creating a GUI project with CustomTkInter and trying to make a button that navigates through to the next page, but I'm not sure how to do this? I'm trying to link different pages together, essentially just trying to have a multi-page project, I've tried to apply the solution for the normal tkinter to my customtkinter - but its not worked so far and its throwing me some errors (specifically an error about a master not being defined)
This is what I have so far
import tkinter
import tkinter.messagebox
import customtkinter
from PIL import Image
import os
customtkinter.set_appearance_mode("Dark") # Modes: "System" (standard), "Dark", "Light"
customtkinter.set_default_color_theme("green") # Themes: "blue" (standard), "green", "dark-blue"
class Page(customtkinter.CTkFrame):
def __init__(self):
customtkinter.CTkFrame.__init__()
def show(self):
self.lift()
class Page1(Page):
def __init__(self):
Page.__init__()
class HomePage(customtkinter.CTkFrame):
def __init__(self):
super().__init__()
self.title("IMS")
self.geometry(f"{1300}x{800}")
self.rowconfigure((0), weight=1)
self.columnconfigure((0), weight=1)
self.rowconfigure((1, 2), weight=1)
self.columnconfigure((1,2), weight=1)
self.rowconfigure((3), weight=1)
image_path = os.path.join(os.path.dirname(os.path.realpath(__file__)), "images")
self.logoImage = customtkinter.CTkImage(Image.open(os.path.join(image_path, "logo.png")), size=(150, 66))
self.titleLogo = customtkinter.CTkLabel(master=self, text="", image=self.logoImage)
self.titleLogo.grid(row=0, column=1, padx=0, pady=0)
self.categoriesButton = customtkinter.CTkButton(master=self, border_width=2, text_color=("gray10", "#DCE4EE"),font=("",33), width=150, height=50, text="Categories", command=Page1.show())
self.categoriesButton.grid(row=2, column=0, padx=(50, 50), pady=(0, 40), sticky="nsew", columnspan=3)
if __name__ == "__main__":
app = HomePage()
app.mainloop()
Would appreciate any help, ty :)
here's a great CTk doc that could help with your problem https://felipetesc.github.io/CtkDocs/#/multiple_frames
i made some minor changes because i was getting some error running sample 1 with python 3.9.13
import tkinter
import customtkinter
DARK_MODE = "dark"
customtkinter.set_appearance_mode(DARK_MODE)
customtkinter.set_default_color_theme("dark-blue")
class App(customtkinter.CTk):
frames = {"frame1": None, "frame2": None}
def frame1_selector(self):
App.frames["frame2"].pack_forget()
App.frames["frame1"].pack(in_=self.right_side_container,side=tkinter.TOP, fill=tkinter.BOTH, expand=True, padx=0, pady=0)
def frame2_selector(self):
App.frames["frame1"].pack_forget()
App.frames["frame2"].pack(in_=self.right_side_container,side=tkinter.TOP, fill=tkinter.BOTH, expand=True, padx=0, pady=0)
def __init__(self):
super().__init__()
# self.state('withdraw')
self.title("Change Frames")
self.geometry("{0}x{0}+0+0".format(self.winfo_screenwidth(), self.winfo_screenheight()))
# contains everything
main_container = customtkinter.CTkFrame(self)
main_container.pack(fill=tkinter.BOTH, expand=True, padx=10, pady=10)
# left side panel -> for frame selection
left_side_panel = customtkinter.CTkFrame(main_container, width=150)
left_side_panel.pack(side=tkinter.LEFT, fill=tkinter.Y, expand=False, padx=10, pady=10)
# buttons to select the frames
bt_frame1 = customtkinter.CTkButton(left_side_panel, text="Frame 1", command=self.frame1_selector)
bt_frame1.grid(row=0, column=0, padx=20, pady=10)
bt_frame2 = customtkinter.CTkButton(left_side_panel, text="Frame 2", command=self.frame2_selector)
bt_frame2.grid(row=1, column=0, padx=20, pady=10)
# right side panel -> to show the frame1 or frame 2
self.right_side_panel = customtkinter.CTkFrame(main_container)
self.right_side_panel.pack(side=tkinter.LEFT, fill=tkinter.BOTH, expand=True, padx=10, pady=10)
self.right_side_container = customtkinter.CTkFrame(self.right_side_panel,fg_color="#000811")
self.right_side_container.pack(side=tkinter.LEFT, fill=tkinter.BOTH, expand=True, padx=0, pady=0)
App.frames['frame1'] = customtkinter.CTkFrame(main_container,fg_color="red")
bt_from_frame1 = customtkinter.CTkButton(App.frames['frame1'], text="Test 1", command=lambda:print("test 1") )
bt_from_frame1.place(relx=0.5, rely=0.5, anchor=tkinter.CENTER)
App.frames['frame2'] = customtkinter.CTkFrame(main_container,fg_color="blue")
bt_from_frame2 = customtkinter.CTkButton(App.frames['frame2'], text="Test 2", command=lambda:print("test 2") )
bt_from_frame2.place(relx=0.5, rely=0.5, anchor=tkinter.CENTER)
a = App()
a.mainloop()
i made a very basic template that you could use and modify as you want or just take the principle out of it.
import tkinter
import customtkinter
DARK_MODE = "dark"
customtkinter.set_appearance_mode(DARK_MODE)
customtkinter.set_default_color_theme("dark-blue")
class App(customtkinter.CTk):
def __init__(self):
super().__init__()
self.title("Change Frames")
# remove title bar , page reducer and closing page !!!most have a quit button with app.destroy!!! (this app have a quit button so don't worry about that)
self.overrideredirect(True)
# make the app as big as the screen (no mater wich screen you use)
self.geometry("{0}x{1}+0+0".format(self.winfo_screenwidth(), self.winfo_screenheight()))
# root!
self.main_container = customtkinter.CTkFrame(self, corner_radius=10)
self.main_container.pack(fill=tkinter.BOTH, expand=True, padx=10, pady=10)
# left side panel -> for frame selection
self.left_side_panel = customtkinter.CTkFrame(self.main_container, width=150, corner_radius=10)
self.left_side_panel.pack(side=tkinter.LEFT, fill=tkinter.Y, expand=False, padx=5, pady=5)
self.left_side_panel.grid_columnconfigure(0, weight=1)
self.left_side_panel.grid_rowconfigure((0, 1, 2, 3), weight=0)
self.left_side_panel.grid_rowconfigure((4, 5), weight=1)
# self.left_side_panel WIDGET
self.logo_label = customtkinter.CTkLabel(self.left_side_panel, text="Welcome! \n", font=customtkinter.CTkFont(size=20, weight="bold"))
self.logo_label.grid(row=0, column=0, padx=20, pady=(20, 10))
self.scaling_label = customtkinter.CTkLabel(self.left_side_panel, text="UI Scaling:", anchor="w")
self.scaling_label.grid(row=7, column=0, padx=20, pady=(10, 0))
self.scaling_optionemenu = customtkinter.CTkOptionMenu(self.left_side_panel, values=["80%", "90%", "100%", "110%", "120%"],
command=self.change_scaling_event)
self.scaling_optionemenu.grid(row=8, column=0, padx=20, pady=(10, 20), sticky = "s")
self.bt_Quit = customtkinter.CTkButton(self.left_side_panel, text="Quit", fg_color= '#EA0000', hover_color = '#B20000', command= self.close_window)
self.bt_Quit.grid(row=9, column=0, padx=20, pady=10)
# button to select correct frame IN self.left_side_panel WIDGET
self.bt_dashboard = customtkinter.CTkButton(self.left_side_panel, text="Dashboard", command=self.dash)
self.bt_dashboard.grid(row=1, column=0, padx=20, pady=10)
self.bt_statement = customtkinter.CTkButton(self.left_side_panel, text="Statement", command=self.statement)
self.bt_statement.grid(row=2, column=0, padx=20, pady=10)
self.bt_categories = customtkinter.CTkButton(self.left_side_panel, text="Manage Categories", command=self.categories)
self.bt_categories.grid(row=3, column=0, padx=20, pady=10)
# right side panel -> have self.right_dashboard inside it
self.right_side_panel = customtkinter.CTkFrame(self.main_container, corner_radius=10, fg_color="#000811")
self.right_side_panel.pack(side=tkinter.LEFT, fill=tkinter.BOTH, expand=True, padx=5, pady=5)
self.right_dashboard = customtkinter.CTkFrame(self.main_container, corner_radius=10, fg_color="#000811")
self.right_dashboard.pack(in_=self.right_side_panel, side=tkinter.TOP, fill=tkinter.BOTH, expand=True, padx=0, pady=0)
# self.right_dashboard ----> dashboard widget
def dash(self):
self.clear_frame()
self.bt_from_frame1 = customtkinter.CTkButton(self.right_dashboard, text="dash", command=lambda:print("test dash") )
self.bt_from_frame1.grid(row=0, column=0, padx=20, pady=(10, 0))
self.bt_from_frame2 = customtkinter.CTkButton(self.right_dashboard, text="dash 1", command=lambda:print("test dash 1" ) )
self.bt_from_frame2.grid(row=1, column=0, padx=20, pady=(10, 0))
# self.right_dashboard ----> statement widget
def statement(self):
self.clear_frame()
self.bt_from_frame3 = customtkinter.CTkButton(self.right_dashboard, text="statement", command=lambda:print("test statement") )
self.bt_from_frame3.grid(row=0, column=0, padx=20, pady=(10, 0))
# self.right_dashboard ----> categories widget
def categories(self):
self.clear_frame()
self.bt_from_frame4 = customtkinter.CTkButton(self.right_dashboard, text="categories", command=lambda:print("test cats") )
self.bt_from_frame4.grid(row=0, column=0, padx=20, pady=(10, 0))
# Change scaling of all widget 80% to 120%
def change_scaling_event(self, new_scaling: str):
new_scaling_float = int(new_scaling.replace("%", "")) / 100
customtkinter.set_widget_scaling(new_scaling_float)
# close the entire window
def close_window(self):
App.destroy(self)
# CLEAR ALL THE WIDGET FROM self.right_dashboard(frame) BEFORE loading the widget of the concerned page
def clear_frame(self):
for widget in self.right_dashboard.winfo_children():
widget.destroy()
a = App()
a.mainloop()
Related
I created this test code to simulate a Tk window with a left panel that is resizable. How it works:
When the mouse pointer move over the ttk.Separator, a resizable arrow indicator will appear where the mouse pointer is.
Pressing the left mouse button and moving the mouse pointer, the width of the left panel will resize corresponding to the x position of the mouse pointer.
The resizable arrow indicator should also move in sync with the mouse pointer.
I am able to perform steps 1 & 2. However, for step 3, I have an issue. The resizable arrow indicator in step 1 does not disappear while the resizable arrow indicator position in step 3 does follow the mouse pointer occasionally: there appears to be a competition btw these two steps.
How do I fix this issue?
Test code:
#!/usr/bin/python3
# -*- coding: utf-8 -*-
import tkinter as tk
import tkinter.ttk as ttk
class App(ttk.Frame):
def __init__(self, master):
self.master = master
self.master.title('App')
self.master.geometry('1500x140')
self.mouse_pointer_x = tk.IntVar()
self.mouse_pointer_y = tk.IntVar()
super().__init__(master, style='App.TFrame', borderwidth=20)
self._set_style()
self._create_widgets()
self.bind('<Motion>', self._store_mouse_pointer_coordinate)
def _set_style(self):
self.style = ttk.Style()
self.style.configure('App.TFrame', background='pink')
self.style.configure('lframe.TFrame', background='green')
self.style.configure('rframe.TFrame', background='orange')
self.style.configure('TSeparator', background='red')
def _create_widgets(self):
self.lframe = ttk.Frame(self, style='lframe.TFrame', borderwidth=20)
self.rframe = ttk.Frame(self, style='rframe.TFrame', borderwidth=20)
self.divider = ttk.Separator(self, orient=tk.VERTICAL)
self.lframe.grid(row=0, column=0, sticky='nsew')
self.divider.grid(row=0, column=1, sticky='nsew', padx=20)
self.rframe.grid(row=0, column=2, sticky='nsew')
harrow = './resize-arrow-24.png'
self.icon_harrow = tk.PhotoImage(file=harrow)
self.harrow = ttk.Label(self, image=self.icon_harrow)
self.harrow.place(x=0, y=0)
self.harrow.place_forget()
self.ltv = self._create_treeview(self.lframe)
self.rtv = self._create_treeview(self.rframe)
self.ltv.grid(row=0, column=0, sticky='nsew')
self.rtv.grid(row=0, column=0, sticky='nsew')
self.divider.bind('<Enter>', self._show_divider)
self.divider.bind('<Leave>', self._hide_divider)
self.divider.bind("<B1-Motion>", self._button1_press_move)
def _create_treeview(self, parent):
# Create Treeview
SearchCols = ('#01', '#02', '#03', '#04', '#05', '#06')
tv = ttk.Treeview(parent, columns=SearchCols, height=2,
displaycolumn=['#05', '#06', '#01',
'#02', '#03', '#04'],
style='search.Treeview',
selectmode='extended', takefocus=True)
# Setup column & it's headings
tv.column('#0', stretch=0, minwidth=100, width=100, anchor='w')
tv.column('#01', stretch=0, anchor='n', width=70)
tv.column('#02', stretch=0, anchor='n', width=80)
tv.column('#03', stretch=0, anchor='n', width=75)
tv.column('#04', stretch=0, anchor='w')
tv.column('#05', stretch=0, anchor='e', width=80)
tv.column('#06', stretch=0, anchor='n', width=70)
tv.heading('#0', text=' Directory ', anchor='w')
tv.heading('#01', text='#01', anchor='center')
tv.heading('#02', text='#02', anchor='center')
tv.heading('#03', text='#03', anchor='center')
tv.heading('#04', text='#04', anchor='w')
tv.heading('#05', text='#05', anchor='center')
tv.heading('#06', text='#06', anchor='center')
# #0, #01, #02 denotes the 0, 1st, 2nd columns
return tv
# Event Handlers
def _store_mouse_pointer_coordinate(self, event):
self.mouse_pointer_x.set(event.x)
self.mouse_pointer_y.set(event.y)
print(self.mouse_pointer_x.get(), self.mouse_pointer_y.get())
def _show_divider(self, event):
x = self.mouse_pointer_x.get()
y = self.mouse_pointer_y.get()
self.harrow.place_configure(x=x-31, y=y-36)
self.harrow.lower(belowThis=self.divider)
def _hide_divider(self, event):
self.harrow.place_forget()
def _button1_press_move(self, event):
# Configure self.lframe
new_width = self.lframe.winfo_pointerx() - self.lframe['borderwidth']*2
self.lframe['width'] = new_width
print(f'self.lframe["width"]={self.lframe["width"]}')
self.lframe.grid_propagate(0)
# Configure self.harrow
new_height = event.y
self.harrow.place_forget()
self.harrow.place_configure(x=new_width+9, y=new_height-4)
self.harrow.lower(belowThis=self.divider)
self.update_idletasks()
if __name__ == '__main__':
root = tk.Tk()
root.resizable(width=False, height=False)
root.title('App')
root.geometry('1300x400+0+24')
root.rowconfigure(0, weight=1)
root.columnconfigure(0, weight=1)
app = App(root)
app.grid(row=0, column=0, sticky='nsew')
root.mainloop()
resize-arrow-24.png:
To resolve my resizable arrow indicator issue, I had to introduce event handlers to unbind and rebind events <Enter> and <Leave> when B1 is pressed and released on the ttk.Separator widget. See revised test code below. See Revised test code.
An enhancement to this script is to transform the mouse pointer appearance into a resizable arrow indicator when it enters the widget ttk.Separator as mentioned by #Atlas435 in the comment section of my question. This can be done by using the cursor option of the ttk.Separator widget. This approach also eliminates needing to implement the solution mentioned above and makes the code more concise. See Improved revised test code
From hindsight, I released I had written a python class to create an alternative vertically oriented ttk.PanedWindow widget using ttk.Frame and ttk.Separator widgets. #BryanOakley and #HenryYik thanks for pointing this fact to me.
Revised test code:
#!/usr/bin/python3
# -*- coding: utf-8 -*-
import tkinter as tk
import tkinter.ttk as ttk
class App(ttk.Frame):
def __init__(self, master):
self.master = master
self.mouse_pointer_x = tk.IntVar()
self.mouse_pointer_y = tk.IntVar()
super().__init__(master, style='App.TFrame', borderwidth=20)
self._set_style()
self._create_widgets()
self.bind('<Motion>', self._store_mouse_pointer_coordinate)
def _set_style(self):
self.style = ttk.Style()
self.style.configure('App.TFrame', background='pink')
self.style.configure('lframe.TFrame', background='green')
self.style.configure('rframe.TFrame', background='orange')
self.style.configure('TSeparator', background='red')
def _create_widgets(self):
self.lframe = ttk.Frame(self, style='lframe.TFrame', borderwidth=20)
self.rframe = ttk.Frame(self, style='rframe.TFrame', borderwidth=20)
self.divider = ttk.Separator(self, orient=tk.VERTICAL)
self.lframe.grid(row=0, column=0, sticky='nsew')
self.divider.grid(row=0, column=1, sticky='nsew', padx=20)
self.rframe.grid(row=0, column=2, sticky='nsew')
harrow = './resize-arrow-24.png'
self.icon_harrow = tk.PhotoImage(file=harrow)
self.harrow = ttk.Label(self, image=self.icon_harrow)
self.harrow.place(x=0, y=0)
self.harrow.place_forget()
self.ltv = self._create_treeview(self.lframe)
self.rtv = self._create_treeview(self.rframe)
self.ltv.grid(row=0, column=0, sticky='nsew')
self.rtv.grid(row=0, column=0, sticky='nsew')
self.divider_bind_enter = self.divider.bind('<Enter>',
self._show_divider)
self.divider_bind_leave = self.divider.bind('<Leave>',
self._hide_divider)
self.divider.bind("<ButtonPress-1>", self._divider_B1_press)
self.divider.bind("<ButtonRelease-1>", self._divider_B1_release)
self.divider.bind("<B1-Motion>", self._divider_B1_press_move)
def _create_treeview(self, parent):
# Create Treeview
SearchCols = ('#01', '#02', '#03', '#04', '#05', '#06')
tv = ttk.Treeview(parent, columns=SearchCols, height=2,
displaycolumn=['#05', '#06', '#01',
'#02', '#03', '#04'],
style='search.Treeview',
selectmode='extended', takefocus=True)
# Setup column & it's headings
tv.column('#0', stretch=0, minwidth=100, width=100, anchor='w')
tv.column('#01', stretch=0, anchor='n', width=70)
tv.column('#02', stretch=0, anchor='n', width=80)
tv.column('#03', stretch=0, anchor='n', width=75)
tv.column('#04', stretch=0, anchor='w')
tv.column('#05', stretch=0, anchor='e', width=80)
tv.column('#06', stretch=0, anchor='n', width=70)
tv.heading('#0', text=' Directory ', anchor='w')
tv.heading('#01', text='#01', anchor='center')
tv.heading('#02', text='#02', anchor='center')
tv.heading('#03', text='#03', anchor='center')
tv.heading('#04', text='#04', anchor='w')
tv.heading('#05', text='#05', anchor='center')
tv.heading('#06', text='#06', anchor='center')
# #0, #01, #02 denotes the 0, 1st, 2nd columns
return tv
# Event Handlers
def _store_mouse_pointer_coordinate(self, event):
self.mouse_pointer_x.set(event.x)
self.mouse_pointer_y.set(event.y)
print(self.mouse_pointer_x.get(), self.mouse_pointer_y.get())
def _show_divider(self, event):
x = self.mouse_pointer_x.get()
y = self.mouse_pointer_y.get()
self.harrow.place_configure(x=x-31, y=y-33)
self.harrow.lower(belowThis=self.divider)
def _hide_divider(self, event):
self.harrow.place_forget()
def _divider_B1_press(self, event):
print('unbind self.divider <Enter> & <Leave> events')
self.divider.unbind('<Enter>', self.divider_bind_enter)
self.divider.unbind('<Leave>', self.divider_bind_leave)
def _divider_B1_release(self, event):
print('bind self.divider <Enter> & <Leave> events')
self.divider_bind_enter = self.divider.bind('<Enter>',
self._show_divider)
self.divider_bind_leave = self.divider.bind('<Leave>',
self._hide_divider)
def _divider_B1_press_move(self, event):
# Configure self.lframe
new_width = self.lframe.winfo_pointerx() - self.lframe['borderwidth']*2
self.lframe['width'] = new_width
print(f'self.lframe["width"]={self.lframe["width"]}')
self.lframe.grid_propagate(0)
# Configure self.harrow
new_height = event.y
self.harrow.place_forget()
self.harrow.place_configure(x=new_width+9, y=new_height-6)
self.harrow.lower(belowThis=self.divider)
self.update_idletasks()
if __name__ == '__main__':
root = tk.Tk()
#root.resizable(width=False, height=False)
root.title('App')
root.geometry('1500x140+0+24')
root.rowconfigure(0, weight=1)
root.columnconfigure(0, weight=1)
app = App(root)
app.grid(row=0, column=0, sticky='nsew')
root.mainloop()
Improved revised test code (a VerticalPanedWindow widget):
#!/usr/bin/python3
# -*- coding: utf-8 -*-
import tkinter as tk
import tkinter.ttk as ttk
class VerticalPanedWindow(ttk.Frame):
'''A ttk styled Vertical PanedWindow.'''
def __init__(self, master, bg='pink', borderwidth=0,
divider_fg='red', divider_padx=1,
lframe_bg='green', lframe_borderwidth=0,
rframe_bg='orange', rframe_borderwidth=0, ):
self.master = master
self.bg = bg
self.borderwidth = borderwidth
self.divider_fg = divider_fg
self.divider_padx = divider_padx
self.lframe_bg = lframe_bg
self.lframe_borderwidth = lframe_borderwidth
self.rframe_bg = rframe_bg
self.rframe_borderwidth = rframe_borderwidth
self.mouse_pointer_x = tk.IntVar()
self.mouse_pointer_y = tk.IntVar()
super().__init__(master, style='App.TFrame', borderwidth=borderwidth)
self._set_style()
self._create_widgets()
self.bind('<Motion>', self._store_mouse_pointer_coordinate)
def _set_style(self):
self.style = ttk.Style()
self.style.configure('App.TFrame', background=self.bg)
self.style.configure('lframe.TFrame', background=self.lframe_bg)
self.style.configure('rframe.TFrame', background=self.rframe_bg)
self.style.configure('TSeparator', background=self.divider_fg)
def _create_widgets(self):
self.lframe = ttk.Frame(self, style='lframe.TFrame',
borderwidth=self.lframe_borderwidth)
self.rframe = ttk.Frame(self, style='rframe.TFrame',
borderwidth=self.rframe_borderwidth)
self.divider = ttk.Separator(self, orient=tk.VERTICAL,
cursor='sb_h_double_arrow')
self.lframe.grid(row=0, column=0, sticky='nsew')
self.rframe.grid(row=0, column=2, sticky='nsew')
self.divider.grid(row=0, column=1, sticky='nsew',
padx=self.divider_padx)
self.divider.bind("<B1-Motion>", self._divider_B1_press_move)
# Event Handlers
def _store_mouse_pointer_coordinate(self, event):
self.mouse_pointer_x.set(event.x)
self.mouse_pointer_y.set(event.y)
print(self.mouse_pointer_x.get(), self.mouse_pointer_y.get())
def _divider_B1_press_move(self, event):
# Configure self.lframe
eventx = event.x
mpx = self.mouse_pointer_x.get()
new_x = mpx + eventx
self.mouse_pointer_x.set(new_x)
self.lframe['width'] = new_x
self.lframe.grid_propagate(0)
print(f' {eventx} {mpx} {new_x} {self.lframe["width"]}')
def _create_treeview(parent):
# Create Treeview
SearchCols = ('#01', '#02', '#03', '#04', '#05', '#06')
tv = ttk.Treeview(parent, columns=SearchCols, height=2,
displaycolumn=['#05', '#06', '#01',
'#02', '#03', '#04'],
style='search.Treeview',
selectmode='extended', takefocus=True)
# Setup column & it's headings
tv.column('#0', stretch=0, minwidth=100, width=100, anchor='w')
tv.column('#01', stretch=0, anchor='n', width=70)
tv.column('#02', stretch=0, anchor='n', width=80)
tv.column('#03', stretch=0, anchor='n', width=75)
tv.column('#04', stretch=0, anchor='w')
tv.column('#05', stretch=0, anchor='e', width=80)
tv.column('#06', stretch=0, anchor='n', width=70)
tv.heading('#0', text=' Directory ', anchor='w')
tv.heading('#01', text='#01', anchor='center')
tv.heading('#02', text='#02', anchor='center')
tv.heading('#03', text='#03', anchor='center')
tv.heading('#04', text='#04', anchor='w')
tv.heading('#05', text='#05', anchor='center')
tv.heading('#06', text='#06', anchor='center')
# #0, #01, #02 denotes the 0, 1st, 2nd columns
return tv
if __name__ == '__main__':
root = tk.Tk()
# root.resizable(width=False, height=False)
root.title('VerticalPanedWindow')
root.geometry('1500x140')
root.rowconfigure(0, weight=1)
root.columnconfigure(0, weight=1)
# Use customs colors and borderwidth values
# color = '#240240237'
# app = VerticalPanedWindow(
# root, bg=color, borderwidth=20,
# divider_fg=color, divider_padx=20,
# lframe_bg=color, lframe_borderwidth=20,
# rframe_bg=color, rframe_borderwidth=20, )
# Use customs borderwidth values
app = VerticalPanedWindow(root, borderwidth=20, divider_padx=20,
lframe_borderwidth=20, rframe_borderwidth=20)
# Use default options value
# app = VerticalPanedWindow(root)
app.grid(row=0, column=0, sticky='nsew')
ltv = _create_treeview(app.lframe)
rtv = _create_treeview(app.rframe)
ltv.grid(row=0, column=0, sticky='nsew')
rtv.grid(row=0, column=0, sticky='nsew')
root.mainloop()
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 just got into Python and have been using tkinter to design GUIs which has been fun so far. I've been experimenting with the frames, and I was trying to separate the screen into 3 'panes' if you will. For some reason, even though the combined widths are less than the total width, it still extends past the bounds of the screen.
For the life of me I can't figure out why the purple frame is cut off on the right.
I am somewhat suspicious of the amount of times I've used padx and pady. Also am curious if it's related to grid_propagate or pack_propagate, which is why I have used it so many times.
Any help is appreciated.
RESULT
import tkinter as tk
from tkinter import *
from tkinter import filedialog
import matplotlib
matplotlib.use("TkAgg")
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg, NavigationToolbar2Tk
from matplotlib.figure import Figure
def close():
exit()
#--------------------------------------------------------------
# Root Stuff
root = tk.Tk()
root.title('rough frame gui test v1')
root.geometry('1920x1080')
root.attributes('-fullscreen', False)
root.state('zoomed')
root.iconphoto(False, tk.PhotoImage(file='C:/Users/Trevor/OneDrive - Providence College/Research/Summer 2021/Single Py Files/spec_icon.png'))
#--------------------------------------------------------------
# A couple Random Functions
def donothing():
print('nothing')
def close():
root.destroy()
#---------------------------------------------------------------
# 0 - Main Frame
#root = Frame(root)
#root.grid(row=0, column=0, sticky="nswe")
#---------------------------------------------------------------
# 1 - Navigation Frame
frameNav = Frame(root, bg="blue", height=500, width=480)
frameNav.grid(row=0, column=0, sticky=N)
frameNav.grid_propagate(True)
global canvasNav
canvasNav = Canvas(frameNav, bg="white", height=500, width=480, bd=2, relief=SUNKEN)
canvasNav.grid(row=0, column=0, sticky="nswe", padx=5, pady=5)
canvasNav.grid_propagate(False)
navTitle = Label(canvasNav, bg='white', text="Current Database:", bd=2, anchor=CENTER)
navTitle.grid(row=0, column=0, padx=5, pady=5)
navPane = Label(canvasNav, bg='white', text="NAVIGATION PANE", bd=2, anchor=CENTER)
navPane.grid(row=2, column=0, padx=5, pady=5)
#---------------------------------------------------------------
# 2 - Graph Frame
frameGraph = Frame(root, bg="green", height=500, width=960)
frameGraph.grid(row=0, column=1, sticky=N)
frameGraph.grid_propagate(True)
global canvasGraph
canvasGraph = Canvas(frameGraph, bg="white", height=500, width=960, bd=2, relief=SUNKEN)
canvasGraph.grid(row=0, column=0, sticky="nswe", padx=5, pady=5)
canvasGraph.grid_propagate(False)
loadGraph = Button(canvasGraph, text="Load Graph", bd=2, anchor=CENTER)
loadGraph.grid(row=0, column=0, sticky=S, padx=5, pady=5)
#---------------------------------------------------------------
# 3 - Tasks Frame
frameTasks = Frame(root, bg="purple", height=500, width=400)
frameTasks.grid(row=0, column=2, sticky=N)
frameTasks.grid_propagate(True)
global bFrameTasks
bFrameTasks = Canvas(frameTasks, bg="white", height=500, width=400, bd=2, relief=SUNKEN)
bFrameTasks.grid(row=0, column=0, sticky="nswe", padx=5, pady=5)
bFrameTasks.grid_propagate(True)
#---------------------------------------------------------------
# FUNCTION TO HIDE WINDOWS (TASKS)
#
def toggleTasks():
try:
global bFrameTasks
print(bFrameTasks.winfo_exists())
if bFrameTasks.winfo_exists() == 1:
print("Destroying package...")
bFrameTasks.destroy()
else:
bFrameTasks = tk.Frame(frameTasks, bg="white", height=500, width=480, bd=2, relief=SUNKEN)
bFrameTasks.pack(padx=5, pady=5)
except Exception as e:
print(e)
print('An error has occurred...')
#---------------------------------------------------------------
#end
#---------------------------------------------------------------
# FUNCTION TO HIDE WINDOWS (GRAPH)
#
def toggleGraph():
try:
global canvasGraph
print(canvasGraph.winfo_exists())
if canvasGraph.winfo_exists() == 1:
print('Destroying package...')
canvasGraph.destroy()
else:
canvasGraph = tk.Canvas(frameGraph, bg="white", height=500, width=960, bd=2, relief=SUNKEN)
canvasGraph.pack(padx=5,pady=5)
except Exception as e:
print(e)
print('An error has occurred...')
#---------------------------------------------------------------
#end
#---------------------------------------------------------------
# FUNCTION TO HIDE WINDOWS (NAVIGATION)
#
def toggleNav():
try:
global canvasNav
print(canvasNav.winfo_exists())
if canvasNav.winfo_exists():
print('Destroying...')
canvasNav.destroy()
else:
canvasNav = tk.Canvas(frameNav, bg="white", height=500, width=480, bd=2, relief=SUNKEN)
canvasNav.pack(padx=5,pady=5)
except Exception as e:
print(e)
print('An error has occurred')
#---------------------------------------------------------------
#end
def fileExplore():
dbFolder = filedialog.askdirectory(initialdir = '/', title = 'Select Database Folder')
print(dbFolder) #working! just need to get it to display to a widget
folderName = tk.Label(canvasNav, bg='gray', text=dbFolder)
folderName.grid(row=1, column=0, sticky=N, padx=5, pady=5)
#---------------------------------------------------------------
# MENU
menuBar = Menu(root)
fileMenu = Menu(menuBar, tearoff=0)
menuBar.add_cascade(label="File", menu=fileMenu)
fileMenu.add_command(label="Open Database Folder", command=fileExplore)
fileMenu.add_command(label="Save", command=donothing)
fileMenu.add_command(label="Save as...", command=donothing)
fileMenu.add_separator()
fileMenu.add_command(label="Exit...", command=close)
hideMenu = Menu(menuBar, tearoff=0)
menuBar.add_cascade(label="Hide", menu=hideMenu)
hideMenu.add_checkbutton(label="Task Bar", command=toggleTasks)
hideMenu.add_checkbutton(label="Graph", command=toggleGraph)
hideMenu.add_checkbutton(label="Navigation", command=toggleNav)
#---------------------------------------------------------------
root.config(menu=menuBar)
root.mainloop()
I think Python's not yet aware of the correct screen resolution even though you're setting it with geometry(). Based on :
When should I use root.update() in tkInter for python
Why does the "geometry()" method work with a delay? :
I'd say add the line for root.update_idletasks() after the root.state('zoomed') to see if that helps to change it.
If you don't want to hardcode the geometry each time you can try root.geometry(f"{root.winfo_width()}x{root.winfo_height()}". I think you may want to adjust the widths/height of the other Frames after this as well in consideration of borders taking up some pixels and the space the menu bar would take on the height
I have a layout with Frame and pack some widwets inside. This works so fare. But for the treeview widget it doesn't work and gives a strange error:
Display Names in the Treeview doesn't work with pack tkinter.TclError:
cannot use geometry manager pack inside . which already has slaves
managed by grid
Need help from experienced tkinter user.
Here my layout:
here, if I try the same with a treeview instead of a label widget:
here is my program:
import tkinter as tk
from tkinter import ttk
# Main App
win = tk.Tk()
win.title('Layout Test')
win.geometry('1200x720+300+300')
win.resizable(True, True)
# Frame Design
top_frame = tk.Frame(win, background="#FFF0C1", bd=1, relief="sunken")
left_frame = tk.Frame(win, background="#D2E2FB", bd=1, relief="sunken")
center_frame = tk.Frame(win, background="#CCE4CA", bd=1, relief="sunken")
right_frame = tk.Frame(win, background ='lightblue', bd=1, relief='sunken')
bottom_frame = tk.Frame(win, background="#F5C2C1", bd=1, relief="sunken")
top_frame.grid(row=0, column=0, columnspan=3, sticky="nsew", padx=2, pady=2)
left_frame.grid(row=1, column=0, sticky="nsew", padx=2, pady=2)
center_frame.grid(row=1, column=1, sticky="nsew", padx=2, pady=2)
right_frame.grid(row=1, column=2, sticky="nsew", padx=2, pady=2)
bottom_frame.grid(row=3, column=0, columnspan=3, sticky="nsew", padx=2, pady=2)
win.grid_rowconfigure(0, weight=6)
win.grid_rowconfigure(1, weight=40)
win.grid_rowconfigure(3, weight=1)
win.grid_columnconfigure(0, weight=1)
win.grid_columnconfigure(1, weight=2)
win.grid_columnconfigure(2, weight=4)
# Display Names in the Treeview doesn't work with pack
# tkinter.TclError: cannot use geometry manager pack inside .
# which already has slaves managed by grid
treeview = ttk.Treeview(win)
treeview.pack(side=tk.LEFT, fill=tk.BOTH, expand=1)
treeview.insert('center_frame','0','item1', text = 'First item',tag='T' )
treeview.insert('center_frame','1','item2', text = 'Second item',tag='T' )
treeview.insert('center_frame','2','item3', text = 'Third item',tag='T' )
treeview.insert('center_frame','3','item4', text = 'Forth item',tag='T' )
treeview.insert('center_frame','end','item5', text = 'Five item',tag='T' )
treeview.insert('item1','end','item6', text = 'Sechster Text',tag='T' )
treeview.tag_configure('T', font=('Calibre', 15))
"""
# Test works well with pack
MyLabel = tk.Label(center_frame,text="Label inside Frame1")
MyLabel.pack(side=tk.LEFT, fill=tk.BOTH, expand=1)
"""
# Program here
# Status Bar at bottom_frame works well with pack
statustext = tk.StringVar()
statustext.set(' ... choose your avm_xml file') # will be changed from file dialoge
status = ttk.Label(bottom_frame, textvariable=statustext, borderwidth='25', relief=tk.SUNKEN, anchor=tk.W)
status.pack(side=tk.BOTTOM, fill=tk.X) #fill='both', expand=False, padx=8, pady=8
win.mainloop()
treeview has win as master, so when you try to pack it you get an error since you used grid for the other widgets in win. This is because the layout managers pack and grid cannot be used simultaneously in the same master widget.
On the other hand, your test label's master is center_frame, so you can pack it inside. If you change your treeview's master to center_frame, you will be able to pack it like the label.
import tkinter as tk
from tkinter import ttk
# Main App
win = tk.Tk()
win.title('Layout Test')
win.geometry('1200x720+300+300')
win.resizable(True, True)
# Frame Design
top_frame = tk.Frame(win, background="#FFF0C1", bd=1, relief="sunken")
left_frame = tk.Frame(win, background="#D2E2FB", bd=1, relief="sunken")
center_frame = tk.Frame(win, background="#CCE4CA", bd=1, relief="sunken")
right_frame = tk.Frame(win, background ='lightblue', bd=1, relief='sunken')
bottom_frame = tk.Frame(win, background="#F5C2C1", bd=1, relief="sunken")
top_frame.grid(row=0, column=0, columnspan=3, sticky="nsew", padx=2, pady=2)
left_frame.grid(row=1, column=0, sticky="nsew", padx=2, pady=2)
center_frame.grid(row=1, column=1, sticky="nsew", padx=2, pady=2)
right_frame.grid(row=1, column=2, sticky="nsew", padx=2, pady=2)
bottom_frame.grid(row=3, column=0, columnspan=3, sticky="nsew", padx=2, pady=2)
win.grid_rowconfigure(0, weight=6)
win.grid_rowconfigure(1, weight=40)
win.grid_rowconfigure(3, weight=1)
win.grid_columnconfigure(0, weight=1)
win.grid_columnconfigure(1, weight=2)
win.grid_columnconfigure(2, weight=4)
treeview = ttk.Treeview(center_frame) # <-- changed master from win to center_frame
treeview.pack(side=tk.LEFT, fill=tk.BOTH, expand=1) # <-- pack works now
# Program here
# Status Bar at bottom_frame works well with pack
statustext = tk.StringVar()
statustext.set(' ... choose your avm_xml file') # will be changed from file dialoge
status = ttk.Label(bottom_frame, textvariable=statustext, borderwidth='25', relief=tk.SUNKEN, anchor=tk.W)
status.pack(side=tk.BOTTOM, fill=tk.X) #fill='both', expand=False, padx=8, pady=8
win.mainloop()
As far as I am concerned, if you change treeview.pack() to treeview.grid(sticky=W)etc it might work. You can't have both pack and grid in the same widget.
I am newbie in python tkinter,I want to have some 3 textbox of each having some 4 lines to print text on a single GUI.
I tried a code which creates just only one text box that wraps to the entire screen.i also wanted to number the 3 textboxes as 1,2,3 on its left side as label.Please help me to complete my code!
import tkinter as tki # Tkinter -> tkinter in Python3
class App(object):
def __init__(self):
self.root = tki.Tk()
# create a Frame for the Text and Scrollbar
txt_frm = tki.Frame(self.root, width=600, height=600)
txt_frm.pack(fill="both", expand=True)
# ensure a consistent GUI size
txt_frm.grid_propagate(False)
# implement stretchability
txt_frm.grid_rowconfigure(0, weight=1)
txt_frm.grid_columnconfigure(0, weight=1)
# create a Text widget
self.txt = tki.Text(txt_frm, borderwidth=3, relief="sunken")
self.txt.config(font=("consolas", 12), undo=True, wrap='word')
self.txt.grid(row=0, column=0, sticky="nsew", padx=2, pady=2)
# create a Scrollbar and associate it with txt
scrollb = tki.Scrollbar(txt_frm, command=self.txt.yview)
scrollb.grid(row=0, column=1, sticky='nsew')
self.txt['yscrollcommand'] = scrollb.set
app = App()
app.root.mainloop()
Try this
import tkinter as tki # Tkinter -> tkinter in Python3
class App(object):
def __init__(self,root):
self.root = root
# create a Frame for the Text and Scrollbar
txt_frm = tki.Frame(self.root, width=600, height=400)
txt_frm.pack(fill="both", expand=True)
# ensure a consistent GUI size
txt_frm.grid_propagate(False)
# create first Text label, widget and scrollbar
self.lbl1 = tki.Label(txt_frm, text="1")
self.lbl1.grid(row=0,column=0,padx=2,pady=2)
self.txt1 = tki.Text(txt_frm, borderwidth=3, relief="sunken", height=4,width=55)
self.txt1.config(font=("consolas", 12), undo=True, wrap='word')
self.txt1.grid(row=0, column=1, sticky="nsew", padx=2, pady=2)
scrollb1 = tki.Scrollbar(txt_frm, command=self.txt1.yview)
scrollb1.grid(row=0, column=2, sticky='nsew')
self.txt1['yscrollcommand'] = scrollb1.set
# create second Text label, widget and scrollbar
self.lbl2 = tki.Label(txt_frm, text="2")
self.lbl2.grid(row=1,column=0,padx=2,pady=2)
self.txt2 = tki.Text(txt_frm, borderwidth=3, relief="sunken",height=4,width=55)
self.txt2.config(font=("consolas", 12), undo=True, wrap='word')
self.txt2.grid(row=1, column=1, sticky="nsew", padx=2, pady=2)
scrollb2 = tki.Scrollbar(txt_frm, command=self.txt2.yview)
scrollb2.grid(row=1, column=2, sticky='nsew')
self.txt2['yscrollcommand'] = scrollb2.set
# create third Text label, widget and scrollbar
self.lbl3 = tki.Label(txt_frm, text="3")
self.lbl3.grid(row=2,column=0,padx=2,pady=2)
self.txt3 = tki.Text(txt_frm, borderwidth=3, relief="sunken",height=4,width=55)
self.txt3.config(font=("consolas", 12), undo=True, wrap='word')
self.txt3.grid(row=2, column=1, sticky="nsew", padx=2, pady=2)
scrollb3 = tki.Scrollbar(txt_frm, command=self.txt3.yview)
scrollb3.grid(row=2, column=2, sticky='nsew')
self.txt3['yscrollcommand'] = scrollb3.set
root = tki.Tk()
app = App(root)
root.mainloop()