When I expand my window, the Canvas expands properly. However, when I shrink the window, the Canvas does not shrink back like I want it to. At the very least, I want to make absolutely sure that the controls at the bottom do not get cut off and that the scroll bar activates if the contents of the Canvas don't all fit on the screen.
The only solution I could think of was binding to the <Configure> event of the Toplevel and having the program manually preform all of the calculations to figure out exactly how big the canvas should be to fit all of the items, but the canvas refused to shrink to the size I assigned to it.
Can anyone help me fix this?
Here is an image of what I mean:
Here is the relevant code:
import sys
if sys.version_info[0] < 3:
import Tkinter as tk
import ttk
else:
import tkinter as tk
from tkinter import ttk
class ResultsWindow(tk.Toplevel):
"""
Window that displays the results of the search.
"""
def __init__(self, master, results, *args, **kwargs):
tk.Toplevel.__init__(self, master, *args, **kwargs)
self.__master = master
self.__lg = ListsGroup(self, results)
self.__controlsFrame = ttk.Frame(self)
self.__okButton = ttk.Button(self.__controlsFrame, text = 'Delete Selection')
self.__cancelButton = ttk.Button(self.__controlsFrame, text = 'Cancel')
self.__lg.pack(side = 'top', expand = True, fill = 'both')
self.__controlsFrame.pack(side = 'bottom')
self.__okButton.pack(side = 'left')
self.__cancelButton.pack(side = 'right')
class ListsGroup(ttk.Frame): # TODO: Come up with a better name
"""
The grouping thing that contains the results list.
"""
def __init__(self, master, results, *args, **kwargs):
ttk.Frame.__init__(self, master, *args, **kwargs)
self.__sb = ttk.Scrollbar(self, orient = tk.VERTICAL)
self.__can = tk.Canvas(self, height = 0, yscrollcommand = self.__sb.set)
self.__sb.configure(command = self.__can.yview)
self.grid_columnconfigure(0, weight = 1)
self.grid_columnconfigure(1, weight = 0)
self.grid_rowconfigure(0, weight = 1)
self.__can.grid(column = 0, row = 0, sticky = 'nsew')
self.__sb.grid(column = 1, row = 0, sticky = 'nsew')
self.__packable = []
self.__entries = []
for x in results:
self.__entries.append(ResultEntry(self.__can, x))
self.__entries[-1].pack(side = 'top', expand = False, fill = 'x')
class ResultEntry(ttk.Frame):
"""
A single result group.
"""
def __init__(self, master, group, *args, **kwargs):
ttk.Frame.__init__(self, master, *args, **kwargs)
self.__listbox = tk.Listbox(self, selectmode = 'extended', height = len(group), borderwidth = 1)
self.__statusbox = tk.Canvas(self, width = 20, height = 0, bg = 'yellow', borderwidth = 0)
for x in group:
self.__listbox.insert('end', x)
self.__listbox.pack(side = 'left', expand = True, fill = 'both')
self.__statusbox.pack(side = 'right', expand = False)
self.__len = len(group)
self.bind('<Configure>', self.fix_size)
def fix_size(self, event = None):
"""
Function to fix the size of the canvas to the size of the tree view.
"""
self.__statusbox.configure(height = self.winfo_height() - 4)
Here is the function I used to test it and that created the picture:
def rtest():
a = tk.Tk()
b = ResultsWindow(a, [['1','2','3','4'], ['2', 'a'], ['h', '5']])
a.mainloop()
Apparently my problem was that I was using the Pack geometry manager inside the canvas as opposed to the Grid geometry manager. Changing the geometry manager fixed all of my problems.
Thank you to #BryanOakley for pointing out that you can't scroll with the pack manager. I had absolutely no idea.
Related
I have an object which I want to pass to a new frame through a method in another frame class. I found a solution that looked similar to what I was after and tried it, however, the object doesn't get passed. In fact, I got an error message saying "Value after * must be an iterable, not PackingList", where PackingList is the class which the object is made out of. I tried to instead get the "name" attribute of the object and pass it to the next frame, but it just returns an empty tuple. I am really stuck and I appreciate some help.
class App(tk.Tk):
def __init__(self, *args, **kwargs):
tk.Tk.__init__(self, *args, **kwargs)
container = tk.Frame(self)
container.pack(side = "top", fill = "both", expand = True)
container.grid_rowconfigure(0, weight = 1)
container.grid_columnconfigure(0, weight = 1)
self.frames = {}
for F in (Homescreen, Menuscreen, Create, ShowUpcoming, ShowAll, OpenList, Search, Edit):
frame = F(container, self, *args, **kwargs)
self.frames[F] = frame
frame.grid(row = 0, column = 0, sticky = "nsew")
self.show_frame(Homescreen)
def show_frame(self, container, *args, **kwargs):
frame = self.frames[container]
frame.tkraise()
...
class ShowAll(tk.Frame):
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent)
tk.Label(self, text = "Alla packningslistor", font = ("Times new roman", 30)).place(x = 110, y = 0)
self.controller = controller
objectList = PK.main()
objectOpen = PK.showAll(objectList)
self.radiobutton_list = []
self.objectList = objectList
for i in range(len(objectOpen)):
radiobutton = tk.Radiobutton(self, text = objectOpen[i], command = functools.partial(self.objectToOpen, idx = i)).place(x = 150, y = 100 + i*30)
self.radiobutton_list.append(radiobutton)
def objectToOpen(self, idx):
objectID = self.objectList[idx]
return self.controller.show_frame(OpenList, *objectID)
class OpenList(tk.Frame):
def __init__(self, parent, controller, *objectID):
tk.Frame.__init__(self, parent)
#lala = getattr(objectID, "name")
#print(lala)
As I said I tried to pass just the name of the object but it prints out as an empty tuple in the next frame class.
I did not understand your question. But here are my corrections to your App class. I hope these corrections can help your understanding of Python and tkinter and debug the rest of your codes.
If you need more detailed help, it will be helpful if you can more specific by stating in the comment section what you want to do with classes App, ShowAll, and OpenList (or their method), and I will see how I can help you by elaborating my answer further.
import tkinter as tk
class App(tk.Tk):
def __init__(self, *args, **kwargs):
super().__init__()
self.container = tk.Frame(self)
self.container.pack(side="top", fill="both", expand=True)
self.frames = {}
colors = ('white', 'black', 'red', 'green', 'blue', 'cyan', 'yellow', 'magenta')
frames = ("Homescreen", "Menuscreen", "Create", "ShowUpcoming", "ShowAll", "OpenList", "Search", "Edit")
col=0
for C, F in zip(colors, frames):
print(f'{F=}')
self.frames[F] = tk.Frame(self.container, background=C, width=100, height=50)
self.frames[F].grid(row=0, column=col, sticky="nsew")
# col += 1 # uncomment this to see all frames
self.show_frame('Create') # you should see red
def show_frame(self, container):
self.frames[container].tkraise()
if __name__ == "__main__":
app = App()
app.mainloop()
import tkinter as tk
root = tk.Tk()
my_paned_window = tk.PanedWindow(master=root)
frame1 = tk.Frame(master=my_paned_window, bg='snow')
frame2 = tk.Frame(master=my_paned_window)
tk.Label(master=frame1, text='frame1').pack()
tk.Label(master=frame2, text='frame2').pack()
my_paned_window.add(frame1)
my_paned_window.add(frame2)
my_paned_window.pack(fill=tk.BOTH)
root.mainloop()
In the above code, I don't want the frame1 to expand too much when dragged. How can I set a limit for this?
There are no straightforward ways to do this. You can achieve this either by setting minsize for frame2.
Something like this:
...
my_paned_window.add(frame1)
my_paned_window.add(frame2, minsize=550)
...
Another way is to return "break" when the sash position is greater than the max-width so it no longer can be moved.
minimal example:
import tkinter as tk
class PanedWindow(tk.PanedWindow):
def __init__(self, *args, **kwargs):
super(PanedWindow, self).__init__(*args, **kwargs)
self.max_width = {}
self.bind("<B1-Motion>", self.check_width)
self.bind("<ButtonRelease-1>", self.set_width)
def add(self, child, max_width=None, *args):
super(PanedWindow, self).add(child, *args)
self.max_width[child] = max_width
def check_width(self, event):
for widget, width in self.max_width.items():
if width and widget.winfo_width() >= width:
self.paneconfig(widget, width=width)
return "break"
def set_width(self, event):
for widget, width in self.max_width.items():
if width and widget.winfo_width() >= width:
self.paneconfig(widget, width=width-1)
root = tk.Tk()
my_paned_window = PanedWindow(master=root, sashwidth=5)
max_width = 500
frame1 = tk.Frame(master=my_paned_window, bg='red')
frame2 = tk.Frame(master=my_paned_window, bg='blue')
frame3 = tk.Frame(master=my_paned_window, bg="green")
tk.Label(master=frame1, text='frame1').pack()
tk.Label(master=frame2, text='frame2').pack()
my_paned_window.add(frame1, max_width=500)
my_paned_window.add(frame2)
my_paned_window.pack(fill=tk.BOTH, expand=True)
root.mainloop()
I'm writing a class that adds a scrolling frame. It detects when the frame's contents exceed its height, and then configures the scrollbar. The problem is that when I scroll down, the items in the frame scroll outside of the top of the frame and appear above it.
I've tested it out using plain labels, and it worked fine, but I'm using a class object that has some nested frames, and the child objects are what show up above the scrolling frame.
This is the gist of the code that's giving me problems (please note that the layout doesn't match the full project. I used this as my reference for the ScrollFrame() class.)
Just running this, pressing the button, and scrolling down will show you what's wrong with it.
import tkinter as tk
import tkinter.simpledialog as sd
class ScrollFrame(tk.Frame):
def __init__(self, master):
tk.Frame.__init__(self, master)
### setting up the objects used ###
self.canvas = tk.Canvas(master)
self.frame = tk.Frame(self.canvas)
self.scrollbar = tk.Scrollbar(master, orient = 'vertical',
command = self.canvas.yview)
### scrollbar moves with current canvas scrollamount ###
self.canvas.configure(yscrollcommand = self.scrollbar.set)
self.scrollbar.pack(side = 'right', fill = 'y')
self.canvas.pack(side = 'left', fill = 'both', expand = True)
### creating frame to pack widgets onto ###
self.canvas.create_window((4, 4), window = self.frame,
anchor = 'nw', tags = 'self.frame')
### setting scrollbar height on load ###
self.frame.bind('<Configure>', self.frameConfig)
### scroll when a user's mouse wheel is used inside the canvas ###
def scrollCanvas(event):
self.canvas.yview_scroll(-1*(event.delta//120), 'units')
self.canvas.bind('<MouseWheel>', scrollCanvas)
### set the scrollregion of the canvas ###
def frameConfig(self, event):
self.canvas.configure(scrollregion = self.canvas.bbox('all'))
class OptionSet(tk.Frame):
def __init__(self, master, **kwargs):
super().__init__()
self.all = tk.Frame(master)
self.all.configure(bd = 1, relief = 'solid')
# independent label
self.text = '' if not kwargs['text'] else kwargs['text']
self.label = tk.Label(text = self.text)
# list of all buttons
self.buttons = tk.Frame()
buttons = [] if not kwargs['buttons'] else kwargs['buttons']
self.button_list = []
if buttons:
for button in buttons:
self.button_list.append(
tk.Button(self.buttons, text = button)
)
self.style()
def style(self, default = 1, **kwargs):
if default:
self.label.pack(in_ = self.all, side = 'left')
for button in self.button_list:
button.pack(side = 'left')
self.buttons.pack(in_ = self.all, side = 'right')
root = tk.Tk()
list_items = []
current = {
'month': 'August'
# ...
}
def btn_fcn(num):
for i in list_items:
i.grid_forget()
'''
# get event as input
event = sd.askstring('Event Input',
f"What is happening on {current['month']} {num}?")
# insert new list_item
list_items.append(OptionSet(event_list.frame, text = event,
buttons = ['Edit', 'Delete']))
print(event)
'''
for i in range(10):
list_items.append(OptionSet(event_list.frame, text = 'test',
buttons = ['Edit', 'Delete']))
for i in list_items:
i.all.grid(sticky = 'we')
tk.Button(root, text = 'Add', command = lambda: btn_fcn(22)).pack()
event_list = ScrollFrame(root)
event_list.pack()
root.mainloop()
I want the buttons and labels to cut off outside of the ScrollFrame. I don't know whether they're overflowing from the frame or the canvas, but they should cut off normally if all goes according to plan.
Thanks.
The problem is that you're being very sloppy with where you create your widgets. For example, you aren't putting the canvas and scrollbar in the ScrollFrame, you're putting it in master. Every widget defined in ScrolLFrame needs to be in self or a child of self.
The same is true with OptionSet - you're putting the inner frame (self.all) a d the other widgets in master rather than inside the OptionSet itself.
My recommendation is to temporarily remove all of the OptionSet code (or just don't use it) and instead, just add labels to the scroll frame. Focus on getting that working without the complexity of a custom class being added to a custom class. Once you are able to scroll just labels, the you can add back in the OptionSet code.
Here my code for a very simple gui:
from Tkinter import *
class my_gui(Frame):
def __init__(self):
# main tk object
self.root = Tk()
# init Frame
Frame.__init__(self, self.root)
# create frame (gray window)
self.frame=Frame(self.root,width=100,height=100)
self.frame.grid(row=0,column=0)
self.__add_scroll_bars()
self.__create_canvas()
self.__add_plot()
def __create_canvas(self):
# create white area in the window for plotting
# width and height are only the visible size of the white area, scrollregion is the area the user can see by scrolling
self.canvas = Canvas(self.frame,bg='#FFFFFF',width=300,height=300,scrollregion=(0,0,500,500))
# with this command the window is filled with the canvas
self.canvas.pack(side=LEFT,expand=True,fill=BOTH)
# position and size of the canvas is used for configuration of the scroll bars
self.canvas.config(xscrollcommand=self.hbar.set, yscrollcommand=self.vbar.set)
# add command to the scroll bars to scroll the canvas
self.hbar.config(command = self.canvas.xview)
self.vbar.config(command = self.canvas.yview)
def __add_scroll_bars(self):
# add scroll bars
self.hbar=Scrollbar(self.frame,orient=HORIZONTAL)
self.hbar.pack(side=BOTTOM,fill=X)
self.vbar=Scrollbar(self.frame,orient=VERTICAL)
self.vbar.pack(side=RIGHT,fill=Y)
def __add_plot(self):
# create a rectangle
self.canvas.create_polygon(10, 10, 10, 150, 200, 150, 200, 10, fill="gray", outline="black")
def mainLoop(self):
# This function starts an endlos running thread through the gui
self.root.mainloop()
def __quit(self):
# close everything
self.root.quit()
def mainLoop(self):
# This function starts an endlos running thread through the gui
self.root.mainloop()
# init gui
my_gui = my_gui()
# execute gui
my_gui.mainLoop()
I have two questions:
1) I want if I resize the gui, that then the scrollbars are always on the Ends of the gui and I resize the canvas.
2) If I resize the GUI and the canvas, then the rectangle in the canvas shall be resized (for example if the new size of gui and canvas is four times the old size, then the new size of rectangle is twize the old size).
I search a solution for the first problem and for the second problem seperately.
Thanks for help.
You could use the following way to integrate my frame into your gui class:
from Tkinter import *
class ScrollableFrame(Frame):
def __init__(self, parent, *args, **kw):
'''
Constructor
'''
Frame.__init__(self, parent, *args, **kw)
# create a vertical scrollbar
vscrollbar = Scrollbar(self, orient = VERTICAL)
vscrollbar.pack(fill = Y, side = RIGHT, expand = FALSE)
# create a horizontal scrollbar
hscrollbar = Scrollbar(self, orient = HORIZONTAL)
hscrollbar.pack(fill = X, side = BOTTOM, expand = FALSE)
#Create a canvas object and associate the scrollbars with it
self.canvas = Canvas(self, bd = 0, highlightthickness = 0, yscrollcommand = vscrollbar.set, xscrollcommand = hscrollbar.set)
self.canvas.pack(side = LEFT, fill = BOTH, expand = TRUE)
#Associate scrollbars with canvas view
vscrollbar.config(command = self.canvas.yview)
hscrollbar.config(command = self.canvas.xview)
# set the view to 0,0 at initialization
self.canvas.xview_moveto(0)
self.canvas.yview_moveto(0)
# create an interior frame to be created inside the canvas
self.interior = interior = Frame(self.canvas)
interior_id = self.canvas.create_window(0, 0, window=interior,
anchor=NW)
# track changes to the canvas and frame width and sync them,
# also updating the scrollbar
def _configure_interior(event):
# update the scrollbars to match the size of the inner frame
size = (interior.winfo_reqwidth(), interior.winfo_reqheight())
self.canvas.config(scrollregion='0 0 %s %s' % size)
if interior.winfo_reqwidth() != self.canvas.winfo_width():
# update the canvas's width to fit the inner frame
self.canvas.config(width = interior.winfo_reqwidth())
interior.bind('<Configure>', _configure_interior)
class my_gui(Frame):
def __init__(self):
# main tk object
self.root = Tk()
# init Frame
Frame.__init__(self, self.root)
# create frame (gray window)
self.frame = ScrollableFrame(self.root)
self.frame.pack(fill=BOTH, expand=YES)
#self.__add_scroll_bars()
#self.__create_canvas()
self.__add_plot()
def __add_plot(self):
# create a rectangle
self.frame.canvas.create_polygon(10, 10, 10, 150, 200, 150, 200, 10, fill="gray", outline="black")
def mainLoop(self):
# This function starts an endlos running thread through the gui
self.root.mainloop()
def __quit(self):
# close everything
self.root.quit()
# init gui
my_gui = my_gui()
# execute gui
my_gui.mainLoop()
This should essentially solve your first problem. As for the second problem you'll need to create a function to re-render the canvas every time you resize it. In a way similar to the _configure_interior function.
You could use this following example, or integrate it in your class.
You could create a frame like this by calling.
self.frame = ScrollableFrame(self.root)
self.frame.pack(fill=BOTH, expand=YES)
Create a class like this for your frame:
from Tkinter import *
class ScrollableFrame(Frame):
'''
Creates a scrollable frame
'''
def __init__(self, parent, *args, **kw):
'''
Constructor
'''
Frame.__init__(self, parent, *args, **kw)
# create a vertical scrollbar
vscrollbar = Scrollbar(self, orient = VERTICAL)
vscrollbar.pack(fill = Y, side = RIGHT, expand = FALSE)
# create a horizontal scrollbar
hscrollbar = Scrollbar(self, orient = HORIZONTAL)
hscrollbar.pack(fill = X, side = BOTTOM, expand = FALSE)
#Create a canvas object and associate the scrollbars with it
canvas = Canvas(self, bd = 0, highlightthickness = 0, yscrollcommand = vscrollbar.set, xscrollcommand = hscrollbar.set)
canvas.pack(side = LEFT, fill = BOTH, expand = TRUE)
#Associate scrollbars with canvas view
vscrollbar.config(command = canvas.yview)
hscrollbar.config(command = canvas.xview)
# set the view to 0,0 at initialization
canvas.xview_moveto(0)
canvas.yview_moveto(0)
# create an interior frame to be created inside the canvas
self.interior = interior = Frame(canvas)
interior_id = canvas.create_window(0, 0, window=interior,
anchor=NW)
# track changes to the canvas and frame width and sync them,
# also updating the scrollbar
def _configure_interior(event):
# update the scrollbars to match the size of the inner frame
size = (interior.winfo_reqwidth(), interior.winfo_reqheight())
canvas.config(scrollregion='0 0 %s %s' % size)
if interior.winfo_reqwidth() != canvas.winfo_width():
# update the canvas's width to fit the inner frame
canvas.config(width = interior.winfo_reqwidth())
interior.bind('<Configure>', _configure_interior)
You could use this to obtain the result you want. Horizontal and Vertical scrolling are both enabled for this frame and scrollbar positions can be set using 'side' field.
For the second part of your question, could you elucidate further.
Reference: Gonzo's answer
Python Tkinter scrollbar for frame
This works very well, to get what I want with the minimal scrollable canvas size. But there is still the bug, when the gui was made larger and when it seems so, that one can not scroll, there is the possibility to click on the left or upper arrow of the scroll bars and so to scroll the canvas, what sould not be possible.
from Tkinter import *
class ScrollableFrame(Frame):
def __init__(self, parent, minimal_canvas_size, *args, **kw):
'''
Constructor
'''
Frame.__init__(self, parent, *args, **kw)
self.minimal_canvas_size = minimal_canvas_size
# create a vertical scrollbar
vscrollbar = Scrollbar(self, orient = VERTICAL)
vscrollbar.pack(fill = Y, side = RIGHT, expand = FALSE)
# create a horizontal scrollbar
hscrollbar = Scrollbar(self, orient = HORIZONTAL)
hscrollbar.pack(fill = X, side = BOTTOM, expand = FALSE)
#Create a canvas object and associate the scrollbars with it
self.canvas = Canvas(self, bd = 0, highlightthickness = 0, yscrollcommand = vscrollbar.set, xscrollcommand = hscrollbar.set)
self.canvas.pack(side = LEFT, fill = BOTH, expand = TRUE)
#Associate scrollbars with canvas view
vscrollbar.config(command = self.canvas.yview)
hscrollbar.config(command = self.canvas.xview)
# set the view to 0,0 at initialization
self.canvas.xview_moveto(0)
self.canvas.yview_moveto(0)
self.canvas.config(scrollregion='0 0 %s %s' % self.minimal_canvas_size)
# create an interior frame to be created inside the canvas
self.interior = interior = Frame(self.canvas)
interior_id = self.canvas.create_window(0, 0, window=interior,
anchor=NW)
# track changes to the canvas and frame width and sync them,
# also updating the scrollbar
def _configure_interior(event):
# update the scrollbars to match the size of the inner frame
size = (max(interior.winfo_reqwidth(), self.minimal_canvas_size[0]), max(interior.winfo_reqheight(), self.minimal_canvas_size[1]))
self.canvas.config(scrollregion='0 0 %s %s' % size)
if interior.winfo_reqwidth() != self.canvas.winfo_width():
# update the canvas's width to fit the inner frame
self.canvas.config(width = interior.winfo_reqwidth())
interior.bind('<Configure>', _configure_interior)
class my_gui(Frame):
def __init__(self):
# main tk object
self.root = Tk()
# init Frame
Frame.__init__(self, self.root)
minimal_canvas_size = (500, 500)
# create frame (gray window)
self.frame = ScrollableFrame(self.root, minimal_canvas_size)
self.frame.pack(fill=BOTH, expand=YES)
self.__add_plot()
def __add_plot(self):
# create a rectangle
self.frame.canvas.create_polygon(10, 10, 10, 150, 200, 150, 200, 10, fill="gray", outline="black")
def mainLoop(self):
# This function starts an endlos running thread through the gui
self.root.mainloop()
def __quit(self):
# close everything
self.root.quit()
# init gui
my_gui = my_gui()
# execute gui
my_gui.mainLoop()
Trying to build a GUI in Python at the moment, and I'm stuck at this part in particular. Every time I try to run my code it just throws the error TypeError: __init__() got multiple values for argument 'master'. I can't seem to find where I'm passing it more than one value, and it's got me scratching my head. I tried searching the error but the fixes other people had listed I can't see how to make them work with this one. Any guidance would be much appreciated. See code sample below:
class Plotter(tk.Canvas):
"""Creates a canvas for use in a GUI
Plotter() -> Canvas
"""
def __init__(self, master, **kwargs):
super().__init__(self, master = master, **kwargs)
self.bind("<Configure>", self.on_resize)
self.height = self.winfo_reqheight()
self.width = self.winfo_reqwidth()
self.bg = 'white'
self.relief = 'raised'
class AnimalDataPlotApp(object):
"""This is the top level class for the GUI, and is hence responsible for
creating and maintaining instances of the above glasses
"""
def __init__(self, master):
"""Initialises the window and creates the base window for the GUI.
__init__() -> None
"""
master.title('Animal Data Plot App')
self._master = master
self._text = tk.Text(master)
self._text.pack
menubar = tk.Menu(master)
master.config(menu = menubar)
filemenu = tk.Menu(menubar) #puts filemenu into the menubar
menubar.add_cascade(label = 'File', menu = filemenu)
filemenu.add_command(label = 'Open', command = self.open_file)
#frame for canvas
plotter_frame = tk.Frame(master, bg = 'red')
plotter_frame.pack(side = tk.RIGHT, anchor = tk.NW, fill = tk.BOTH, expand = True)
#frame for buttons
button_frame = tk.Frame(master, bg = 'yellow')
button_frame.pack(side=tk.TOP, anchor=tk.NW, ipadx=50, fill = tk.X)
#Label on the top left
left_label = tk.Label(button_frame, text='Animal Data Sets', bg='orange')
left_label.pack(side=tk.TOP, anchor=tk.N, fill=tk.X)
#second frame, for selection list
selection_frame = tk.Frame(master, bg = 'blue')
selection_frame.pack(side = tk.LEFT, anchor=tk.NW, fill = tk.BOTH, expand = True)
#draw buttons in frame
select = tk.Button(button_frame, text ='Select')
select.pack(side=tk.TOP, anchor=tk.N)
deselect = tk.Button(button_frame, text='Deselect')
deselect.pack(side=tk.TOP, anchor=tk.N)
self.selectionbox = SelectionBox(selection_frame)
self.selectionbox.pack(side = tk.TOP, expand = True, fill=tk.BOTH)
#self._selectionbox.show_animals(self._data)
self.plotter = Plotter(plotter_frame)
self.plotter.pack(side = tk.TOP, expand = True, fill=tk.BOTH)
super().__init__(self, master = master, **kwargs)
If you're using super(), you don't need to specify self explicitly.
You are getting that error because self is being interpreted as the argument for master. So it's like if you were calling __init__(master=self, master=master, **kwargs).
The issue is in the following line in Plotter.__init__() -
super().__init__(self, master = master, **kwargs)
When calling the parent's __init__ method, you do not need to pass the self argument, when you do that, it is getting treated as the master argument for the parent, and then when you try to pass master as master=master , it causes your error.
You should simply do -
super().__init__(master = master, **kwargs)