How to arrange panels in tkinter through classes? - python

I am relatively new in programming with python and I'm now trying to master the use of classes and inheritance in Tkinter a bit. In the code here I try to arrange two canvas panels above each other and to place data panel beside these two canvas panels. I tried to do that by defining a leftFrame in which the canvas panels are placed and a rightFrame for the data panel. However, it fails to show both canvas panels. I hope somebody can show me the right way.
import tkinter as tk
class Data():
def __init__(self):
self.borderSize = 8
class Frame():
def __init__(self, master):
self.leftFrame = tk.Frame(master)
self.leftFrame.grid(row=0, column=0)
self.rightFrame = tk.Frame(master)
self.rightFrame.grid(row=0, column=1)
class CanvasPanel(Frame):
def __init__(self,master, width, height, row, column, bg=None):
super().__init__(master)
self.borderFrame = tk.Frame(self.leftFrame, border = data.borderSize)
self.borderFrame.grid(row=row, column=column)
self.cWidth = width
self.cHeight = height
self.canvas = tk.Canvas(self.borderFrame, width=self.cWidth, height=self.cHeight,
borderwidth = 0, highlightthickness=0, bg=bg)
self.canvas.pack()
self.canvas.create_rectangle(0,0,width, height)
class DataPanel(Frame):
def __init__(self, master, width, height, row, column, bg = None):
super().__init__(master)
self.borderFrame = tk.Frame(self.rightFrame, border = data.borderSize)
self.borderFrame.grid(row=row, column=column)
self.dataFrame = tk.Frame(self.borderFrame, width = width, height = height,bg=bg)
self.dataFrame.pack()
data = Data()
root = tk.Tk()
root.title("PANELS")
canvas1 = CanvasPanel(root,600,300,0,0,'yellow')
canvas2 = CanvasPanel(root,600,300,1,0,'red')
dataPanel = DataPanel(root,100,600,0,0,'light grey')
root.mainloop()

The red and yellow frames in your code are overlapping with each other.
You dont need to write so many classes for such simple example. Placement of frames or other widgets in root window can be easily done via functions using single class. I have edited your code to illustrate the same -
import tkinter as tk
class Frame():
def __init__(self, master):
self.borderSize = 8
self.leftFrame = tk.Frame(master)
self.leftFrame.grid(row=0, column=0)
self.rightFrame = tk.Frame(master)
self.rightFrame.grid(row=0, column=1)
self.canvas_panel(600, 300, 0, 0, 'yellow')
self.canvas_panel(600, 300, 1, 0, 'red')
self.panel_frame(100, 600, 0, 0, 'light grey')
def canvas_panel(self, width, height, row, column, bg=None):
self.borderFrame = tk.Frame(self.leftFrame, border=self.borderSize)
self.borderFrame.grid(row=row, column=column)
self.cWidth = width
self.cHeight = height
self.canvas = tk.Canvas(self.borderFrame, width=self.cWidth, height=self.cHeight,
borderwidth=0, highlightthickness=0, bg=bg)
self.canvas.pack()
self.canvas.create_rectangle(0, 0, width, height)
def panel_frame(self, width, height, row, column, bg=None):
self.borderFrame = tk.Frame(self.rightFrame, border=self.borderSize)
self.borderFrame.grid(row=row, column=column)
self.dataFrame = tk.Frame(self.borderFrame, width=width, height=height, bg=bg)
self.dataFrame.pack()
root = tk.Tk()
root.title("PANELS")
frame = Frame(root)
root.mainloop()

The root of the problem lies in the fact that Frame is putting leftFrame and rightFrame in the master at fixed coordinates. Each time you create a new panel it overlays previously created panels because they all are placed at the same coordinates.
You don't need Frame. Instead, your panels should inherit from tkFrame, and the code that creates the panels should be responsible for putting them in the left or right frame.
For example, the CanvasPanel should look something like this:
class CanvasPanel(BasePanel):
def __init__(self,master, width, height, bg=None):
super().__init__(master)
self.borderFrame = tk.Frame(self, border = data.borderSize)
self.borderFrame.grid(row=row, column=column)
self.cWidth = width
self.cHeight = height
self.canvas = tk.Canvas(self.borderFrame, width=self.cWidth, height=self.cHeight,
borderwidth = 0, highlightthickness=0, bg=bg)
self.canvas.pack()
self.canvas.create_rectangle(0,0,width, height)
You should make similar changes to DataPanel (ie: placing borderFrame directly in self).
You can now use these classes like you would any other tkinter widget: you first create an instance, and then you add it to the window. You don't need to create a leftFrame or rightFrame, because your code is in control of where the widgets are placed.
root = tk.Tk()
canvas1 = CanvasPanel(root, width=600, height=300, bg='yellow')
canvas2 = CanvasPanel(root, width=600, height=300, bg='red')
dataPanel = DataPanel(root, width=100, height=600, bg='light grey')
canvas1.grid(row=0, column=0, sticky="nsew")
canvas2.grid(row=1, column=0, sticky="nsew")
dataPanel.grid(row=0, column=1, rowspan=2, sticky="nsew")

Related

create tkinter button with OOP on a same window: issue

I am a beginner in Python. I created a GUI with nice buttons. To do this I did a change of images: when the mouse hovers the button and when the mouse leaves the button. I did this with this pretty ugly code, but it works:
from tkinter import *
from PIL import Image, ImageTk
root = Tk()
root.title("My first Python GUI")
root.geometry("1130x800")
canvas = Canvas(root, bg="#a9dfbf")
canvas.pack(fill=BOTH, expand=True)
button_1_onHover = Image.open("Buttons/button1_hover.png")
button_1_onLeave = Image.open("Buttons/button1_leave.png")
button_2_onHover = Image.open("Buttons/button2_hover.png")
button_2_onLeave = Image.open("Buttons/button2_leave.png")
root.button_1_onLeave = ImageTk.PhotoImage(button_1_onLeave)
root.button_1_onHover = ImageTk.PhotoImage(button_1_onHover)
root.button_2_onLeave = ImageTk.PhotoImage(button_2_onLeave)
root.button_2_onHover = ImageTk.PhotoImage(button_2_onHover)
def on_enter(event):
button1.config(image=root.button_1_onHover)
def on_leave(leave):
button1.config(image=root.button_1_onLeave)
def on_enter2(event):
button2.config(image=root.button_2_onHover)
def on_leave2(leave):
button2.config(image=root.button_2_onLeave)
button1 = Button(root, image=root.button_1_onLeave, bg="#a9dfbf", width=400, height=150, bd=0, relief="sunken", activebackground="#a9dfbf")
button2 = Button(root, image=root.button_2_onLeave, bg="#a9dfbf", width=400, height=150, bd=0, relief="sunken", activebackground="#a9dfbf")
canvas.create_window(300, 150, window=button1)
canvas.create_window(300, 350, window=button2)
button1.bind("<Enter>", on_enter)
button1.bind("<Leave>", on_leave)
button2.bind("<Enter>", on_enter2)
button2.bind("<Leave>", on_leave2)
root.mainloop()
This is the visual result:
visual result of the ugly code (it work)
BUT...
The problem is that to make a single button, it takes 15 lines of code.
If I want to create 10 buttons, it becomes incredibly repetitive and unpleasant.
Being a beginner, I heard about object-oriented programming, and so I turned my code into a class that I called NewButton:
from tkinter import *
from PIL import Image, ImageTk
class NewButton:
def __init__(self, imageHover, imageLeave, width, height, hposition, vposition):
self.root = Tk()
self.root.title("My first Python GUI")
self.root.geometry("1130x800")
canvas = Canvas(self.root, bg="#a9dfbf")
canvas.pack(fill=BOTH, expand=True)
self.width = width
self.height = height
self.hposition = hposition
self.vposition = vposition
self.imageHover = Image.open(f"Buttons/{imageHover}.png")
self.imageLeave = Image.open(f"Buttons/{imageLeave}.png")
self.root.imageLeave = ImageTk.PhotoImage(self.imageLeave)
self.root.imageHover = ImageTk.PhotoImage(self.imageHover)
self.button = Button(self.root, image=self.root.imageLeave, bg="#a9dfbf", width=self.width, height=self.height, bd=0, relief="sunken", activebackground="#a9dfbf")
canvas.create_window(self.hposition, self.vposition, window=self.button)
def on_enter(event):
self.button.config(image=self.root.imageHover)
def on_leave(leave):
self.button.config(image=self.root.imageLeave)
self.button.bind("<Enter>", on_enter)
self.button.bind("<Leave>", on_leave)
self.root.mainloop()
NewButton("button1_hover","button1_leave",400,150,300,150)
NewButton("button2_hover","button2_leave",400,150,300,350)
In the constructor of my class, I define the image used on hover, the image used on leave, the width of the button, its height, as well as the position of this button (horizontal position and vertical position).
Still in the constructor, I placed my 2 functions which change the image according to the enter/leave state.
Then I create my buttons as NewButton objects and give the characteristics of the button.
When I run my code, python creates the buttons for me, but in a different window.
This is the visual result:
result with POO code (not working)
What I want is to put all the buttons that I create on the same window, and that's not what I get with my code.
Can you tell me what's wrong?
Thank you (and sorry for my frenglish!)
SOLUTION by acw1668 that i try (it doesn't work):
His suggestion: "You need to create root and canvas outside the class and pass canvas to the class instance instead."
what I have done:
from tkinter import *
from PIL import Image, ImageTk
root = Tk()
root.title("My first Python GUI")
root.geometry("1130x800")
canvas = Canvas(root, bg="#a9dfbf")
canvas.pack(fill=BOTH, expand=True)
class NewButton:
def __init__(self, canvas, imageHover, imageLeave, width, height, hposition, vposition):
self.canvas = canvas
self.width = width
self.height = height
self.hposition = hposition
self.vposition = vposition
self.imageHover = Image.open(f"Buttons/{imageHover}.png")
self.imageLeave = Image.open(f"Buttons/{imageLeave}.png")
self.canvas.imageLeave = ImageTk.PhotoImage(self.imageLeave)
self.canvas.imageHover = ImageTk.PhotoImage(self.imageHover)
self.button = Button(self.canvas, image=self.canvas.imageLeave, bg="#a9dfbf", width=self.width, height=self.height, bd=0, relief="sunken", activebackground="#a9dfbf")
canvas.create_window(self.hposition, self.vposition, window=self.button)
def on_enter(event):
self.button.config(image=self.canvas.imageHover)
def on_leave(leave):
self.button.config(image=self.canvas.imageLeave)
self.button.bind("<Enter>", on_enter)
self.button.bind("<Leave>", on_leave)
self.canvas.mainloop()
NewButton(canvas,"button1_hover","button1_leave",400,150,300,150)
NewButton(canvas,"button2_hover","button2_leave",400,150,300,350)
You need to create root and canvas outside the class and then pass canvas to the class:
from tkinter import *
from PIL import Image, ImageTk
class NewButton:
def __init__(self, canvas, imageHover, imageLeave, width, height, hposition, vposition):
self.canvas = canvas
self.width = width
self.height = height
self.hposition = hposition
self.vposition = vposition
imageHover = Image.open(f"Buttons/{imageHover}.png")
imageLeave = Image.open(f"Buttons/{imageLeave}.png")
self.imageLeave = ImageTk.PhotoImage(imageLeave)
self.imageHover = ImageTk.PhotoImage(imageHover)
self.button = Button(canvas, image=self.imageLeave, bg="#a9dfbf", width=self.width, height=self.height, bd=0, relief="sunken", activebackground="#a9dfbf")
self.button.bind("<Enter>", self.on_enter)
self.button.bind("<Leave>", self.on_leave)
self.item_id = canvas.create_window(self.hposition, self.vposition, window=self.button)
def on_enter(self, event):
self.button.config(image=self.imageHover)
def on_leave(self, event):
self.button.config(image=self.imageLeave)
root = Tk()
root.title("My first Python GUI")
root.geometry("1130x800")
canvas = Canvas(root, bg="#a9dfbf")
canvas.pack(fill=BOTH, expand=True)
NewButton(canvas,"button1_hover","button1_leave",400,150,300,150)
NewButton(canvas,"button2_hover","button2_leave",400,150,300,350)
root.mainloop()

Trying to change a scrolled canvas width with mouse wheel

I'm trying to control multiple canvases widths with the mouse wheel. What I have so far is this:
import tkinter as tk
class App(tk.Frame):
row_amount = 3
def __init__(self, root):
super(App, self).__init__(root)
self.root = root
self.main_frame = tk.Frame(root)
self.main_frame.pack(expand=True, fill=tk.BOTH)
self.row_collection = RowCollection(root, self.main_frame)
for i in range(App.row_amount): self.row_collection.row()
window_height = App.row_amount * 100
window_width = root.winfo_screenwidth() - 30
root.geometry(f'{window_width}x{window_height}+0+0')
self.row_collection.right_frame.grid_columnconfigure(0, weight=1)
self.row_collection.left_frame.grid_columnconfigure(0, weight=1)
self.pack()
class RowCollection:
"""Collection of rows"""
def __init__(self, root, frame):
self.row_list = []
self.root = root
self.frame = frame
self.right_frame = tk.Frame(self.frame, bg='red')
self.right_frame.pack(side=tk.RIGHT, expand=tk.YES, fill=tk.BOTH)
self.left_frame = tk.Frame(self.frame)
self.left_frame.pack(side=tk.LEFT, fill=tk.Y)
self.scrollbar = tk.Scrollbar(self.right_frame, orient=tk.HORIZONTAL)
self.scrollbar.config(command=self.scroll_x)
def row(self):
row = Row(self)
self.row_list.append(row)
return row
def scroll_x(self, *args):
for row in self.row_list:
row.canvas.xview(*args)
def zoomer(self, event=None):
print('zooming')
for row in self.row_list:
scale_factor = 0.1
curr_width = row.canvas.winfo_reqwidth()
print(f'event delta={event.delta}')
if event.delta > 0:
row.canvas.config(width=curr_width * (1 + scale_factor))
elif event.delta < 0:
row.canvas.config(width=curr_width * (1 - scale_factor))
row.canvas.configure(scrollregion=row.canvas.bbox('all'))
class Row:
"""Every row consists of a label on the left side and a canvas with a line on the right side"""
row_count = 0
label_width = 15
line_weight = 3
line_yoffset = 3
padx = 20
def __init__(self, collection):
self.frame = collection.frame
self.root = collection.root
self.collection = collection
self.canvas = None
self.label = None
self.text = f'Canvas {Row.row_count}'
self.height = 100
self.root.update()
self.label = tk.Label(self.collection.left_frame,
text=self.text,
height=1,
width=Row.label_width,
relief='raised')
self.label.grid(row=Row.row_count, column=0, sticky='ns')
# configure row size to match future canvas height
self.collection.left_frame.grid_rowconfigure(Row.row_count, minsize=self.height)
self.root.update()
self.canvas = tk.Canvas(self.collection.right_frame,
width=10000,
height=self.height,
bg='white',
highlightthickness=0)
self.canvas.grid(row=Row.row_count, column=0, sticky=tk.W)
self.root.update()
# draw line
self.line = self.canvas.create_rectangle(self.padx,
self.canvas.winfo_height() - Row.line_yoffset,
self.canvas.winfo_width() - self.padx,
self.canvas.winfo_height() - Row.line_yoffset + Row.line_weight,
fill='#000000', width=0, tags='line')
# config canvas
self.canvas.config(scrollregion=self.canvas.bbox('all'))
self.canvas.config(xscrollcommand=self.collection.scrollbar.set)
self.canvas.bind('<Configure>', lambda event: self.canvas.configure(scrollregion=self.canvas.bbox('all')))
self.canvas.bind('<MouseWheel>', self.collection.zoomer)
# Create point at canvas edge to prevent scrolling from removing padding
self.bounding_point = self.canvas.create_rectangle(0, 0, 0, 0, width=0)
self.bounding_point = self.canvas.create_rectangle(self.canvas.winfo_width(), self.canvas.winfo_width(),
self.canvas.winfo_width(), self.canvas.winfo_width(),
width=0)
Row.row_count += 1
self.collection.scrollbar.grid(row=Row.row_count, column=0, sticky='ew')
if __name__ == '__main__':
root = tk.Tk()
app = App(root)
root.mainloop()
The canvases themselves are inside right_frame, and the number of canvases is given by row_amount. The left_frame contains labels for each of the canvases. The canvases should be allowed to be pretty wide, so I initially set a width value of 10000. Because of that, they start partially visible, with the rest being accessible via a scrollbar.
What I would like is for the mouse wheel to control the size of the canvas as a whole (that is, both what is currently visible and what could be viewed using the scrollbar), similar to what would happen in an audio or video editing software timeline.
Right now, when I use the mouse wheel, what seems to get resized is not the whole canvas, but only the 'visible' portion. Resize it to be small enough and you can start to see it's frame background on the right portion of the window.
What am I missing here?
What am I missing here?
I think what you're missing is that the drawable area of the canvas is not at all related to the physical size of the canvas widget. You do not need to resize the canvas once it has been created. You can draw well past the borders of the widget.
If you want to be able to scroll elements into view that are not part of the visible canvas, you must configure the scrollregion to define the area of the virtual canvas that should be visible.
You said in a comment you're trying to create a timeline. Here's an example of a canvas widget that "grows" by adding a tickmark every second. Notice that the canvas is only 500,100, but the drawable area gets extended every second.
import tkinter as tk
root = tk.Tk()
canvas = tk.Canvas(root, width=500, height=100, bg="black")
vsb = tk.Scrollbar(root, orient="vertical", command=canvas.yview)
hsb = tk.Scrollbar(root, orient="horizontal", command=canvas.xview)
canvas.configure(yscrollcommand=vsb.set, xscrollcommand=hsb.set)
canvas.grid(row=0, column=0, sticky="nsew")
vsb.grid(row=0, column=1, sticky="ns")
hsb.grid(row=1, column=0, sticky="ew")
root.grid_rowconfigure(0, weight=1)
root.grid_columnconfigure(0, weight=1)
counter = 0
def add_tick():
global counter
# get the current state of the scrollbar. We'll use this later
# to determine if we should auto-scroll
xview = canvas.xview()
# draw a new tickmark
counter += 1
x = counter * 50
canvas.create_text(x, 52, anchor="n", text=counter, fill="white")
canvas.create_line(x, 40, x, 50, width=3, fill="red")
# update the scrollable region to include the new tickmark
canvas.configure(scrollregion=canvas.bbox("all"))
# autoscroll, only if the widget was already scrolled
# as far to the right as possible
if int(xview[1]) == 1:
canvas.xview_moveto(1.0)
canvas.after(1000, add_tick)
add_tick()
root.mainloop()

There is a way to remove this black line around my widget in tkinter?

So I want to make my custom widgets with Canvas, I started with this "radiobutton" thing. The problem is that there are two black lines that I can't make to disappear.
I tried the reliefs, borderwidths/ colors/ highlightthickness/ different widths and heights etc.. both on Canvas and the Frame but nothing seems to work.
Here is my code, I know it is a big mess and there are more simple solutions to make this exact custom button, but my coding skill is low. If there is a solution to make the lines disappear without changing a lot of things on my code, I would thank your help.
from tkinter import *
class CustomRadioButton(Frame):
def __init__(self, parent, text=None, textcolor='black', fontstyle=None, width=80, height=30, colorin='#ededed',
color='#e6e6e6', colorclick='#dedede', bordercolor='black', borderwidth=0, c_fill='black',
c_outlinewidth=1, c_outlinecolor='black', c_size=5, cursor='arrow'):
Frame.__init__(self, parent, bg=bordercolor, bd=borderwidth, cursor=cursor)
self.parent = parent
self.is_on = False
self.text = text
self.textcolor = textcolor
self.fontstyle = fontstyle
self.width = width
self.height = height
self.colorin = colorin
self.color = color
self.colorclick = colorclick
self.bordercolor = bordercolor
self.borderwidth = borderwidth
self.c_fill = c_fill
self.c_outlinewidth = c_outlinewidth
self.c_outlinecolor = c_outlinecolor
self.c_size = c_size
self.textposx = self.width/2
self.textposy = self.height/2
self.cposx = (self.width/6)
self.cposy = (self.height/2)
self.btn = Canvas(self, width=width, height=height, highlightthickness=0, borderwidth=0)
self.btn.pack(expand=True, side=TOP, fill=BOTH)
self.r = self.btn.create_rectangle(self.width, self.height, 0, 0, fill=color, outline=None)
self.i = self.btn.create_text((self.textposx, self.textposy), text=self.text, fill=textcolor, font=self.fontstyle, justify=RIGHT)
class MyFirstGUI(Frame):
def __init__(self, master):
Frame.__init__(self, master, bg='black')
self.master = master
master.title("..")
self.crb = CustomRadioButton(self, text='Test', c_fill='red', width=500, height=240)
self.crb.pack(side=TOP)
root = Tk()
my_gui = MyFirstGUI(root)
my_gui.pack(side=TOP, expand=True, fill=BOTH)
root.mainloop()
You need to set the outline color of the rectangle to be the same as the fill color. By setting it to None you're in effect telling tkinter you want the default color, which is black.
self.r = self.btn.create_rectangle(..., fill=color, outline=color)

Overlapping scrollbar in tkinter gui

I have a double scrollbar frame in a GUI based on tkinter but the right side of the horizontal scrollbar overlaps with the vertical bar and the right arrow is not visible.
I primarily used the code from the following link https://lucasg.github.io/2015/07/21/How-to-make-a-proper-double-scrollbar-frame-in-Tkinter/
As can be seen from the image in the figure on the bottom right the two scrollbars are overlapping and the right arrow of horizontal scrollbar is not visible.
Copying the code from the link mentioned as it serves as a minimal reproducible example
import tkinter as tk
from tkinter import ttk
class DoubleScrollbarFrame(ttk.Frame):
def __init__(self, master, **kwargs):
'''
Initialisation. The DoubleScrollbarFrame consist of :
- an horizontal scrollbar
- a vertical scrollbar
- a canvas in which the user can place sub-elements
'''
ttk.Frame.__init__(self, master, **kwargs)
# Canvas creation with double scrollbar
self.hscrollbar = ttk.Scrollbar(self, orient = tk.HORIZONTAL)
self.vscrollbar = ttk.Scrollbar(self, orient = tk.VERTICAL)
self.sizegrip = ttk.Sizegrip(self)
self.canvas = tk.Canvas(self, bd=0, highlightthickness=0,
yscrollcommand = self.vscrollbar.set,
xscrollcommand = self.hscrollbar.set)
self.vscrollbar.config(command = self.canvas.yview)
self.hscrollbar.config(command = self.canvas.xview)
def pack(self, **kwargs):
'''
Pack the scrollbar and canvas correctly in order to recreate the same look as MFC's windows.
'''
self.hscrollbar.pack(side=tk.BOTTOM, fill=tk.X, expand=tk.FALSE)
self.vscrollbar.pack(side=tk.RIGHT, fill=tk.Y, expand=tk.FALSE)
self.sizegrip.pack(in_ = self.hscrollbar, side = tk.BOTTOM, anchor = "se")
self.canvas.pack(side=tk.LEFT, padx=5, pady=5,
fill=tk.BOTH, expand=tk.TRUE)
ttk.Frame.pack(self, **kwargs)
def get_frame(self):
'''
Return the "frame" useful to place inner controls.
'''
return self.canvas
if __name__ == '__main__':
# Top-level frame
root = tk.Tk()
root.title( "Double scrollbar with tkinter" )
root.minsize(width = 600, height = 600)
frame = DoubleScrollbarFrame(root, relief="sunken")
# Add controls here
subframe = ttk.Frame( frame.get_frame() )
txt = ttk.Label(subframe, text="Add things here !")
#Packing everything
txt.pack(anchor = 'center', fill = tk.Y, expand = tk.Y)
subframe.pack(padx = 15, pady = 15, fill = tk.BOTH, expand = tk.TRUE)
frame.pack( padx = 5, pady = 5, expand = True, fill = tk.BOTH)
# launch the GUI
root.mainloop()
I want to know how can I modify the code so that I can see the right side of the horizontal scrollbar.

Python - Passing a frame containing tkinter widgets into a class

I'm tring to build a scrollable GUI window containing ttk.Entry and ttk.Label.
The only way doing so (as noted in many questions here )- is by creating a Canvas, with a frame that contains all that widgets.
So- my goal is to make such class- that gets as a parameter a frame containing all needed widgets, and display it in a window with horizontal and vertical scroll bars ( since I need it in many displays inside my code ).
After coding successfully - I tried to make a class, but it shows only empty green canvas.
Any ideas what am I doing wrong?
import tkinter as tk
from tkinter import ttk
class CanvasWidgets(ttk.Frame):
def __init__(self, master, frame_in, width=100, height=100):
ttk.Frame.__init__(self, master)
self.master = master
self.frame = frame_in
self.width, self.height = width, height
self.build_gui()
def build_gui(self):
self.canvas = tk.Canvas(self.master, self.width, self.height, bg='light green')
# self.frame = ttk.Frame(self.frame_in)
self.frame.bind("<Configure>", self.onFrameConfigure)
self.vsb = tk.Scrollbar(self.frame, orient="vertical", command=self.canvas.yview)
self.hsb = tk.Scrollbar(self.frame, orient="horizontal", command=self.canvas.xview)
self.canvas.configure(yscrollcommand=self.vsb.set, xscrollcommand=self.hsb.set)
self.vsb.grid(row=0, column=1, sticky=tk.N + tk.S + tk.W)
self.hsb.grid(row=1, column=0, sticky=tk.W + tk.N + tk.E)
self.canvas.create_window((4, 4), window=self.frame, anchor="nw")
self.canvas.grid(row=0, column=0)
def onFrameConfigure(self, event):
self.canvas.configure(scrollregion=self.canvas.bbox("all"))
root = tk.Tk()
frame = ttk.Frame(root)
rows, cols = 5, 5
for row in range(rows):
for col in range(cols):
ttk.Label(frame, text=[row, col], relief=tk.SUNKEN, width=5).grid(row=row, column=col, sticky=tk.E)
a = CanvasWidgets(root, frame)
a.grid()
root.mainloop()
The first problem is that you are placing the canvas in master when it needs to be in self. Think of the instance of CanvasWindow as a box in which you are going to put everything else.
The second problem is that, because the frame was created before the canvas, the frame has a lower stacking order than the canvas. You need to call lift on the frame to get it to be above the canvas.
The third problem is that you're putting the scrollbars in frame. You can't put them in the inner frame because they control the inner frame. Instead, they also need to be in self. Both the scrollbar and the canvas need to share a common parent.
The fourth problem is that the frame isn't a child of the canvas, so it won't be clipped by the borders of the canvas. It would be better if the CanvasWidgets created the frame, and then the caller can get the frame and add widgets to it.
For example:
a = CanvasWidgets(root)
rows, cols = 5, 5
for row in range(rows):
for col in range(cols):
label = ttk.Label(a.frame, text=[row, col], relief=tk.SUNKEN, width=5)
label.grid(row=row, column=col, sticky=tk.E)

Categories

Resources