pythontkinter

Moving a function from a frame class to a commands class in tkinter


I am learning OO programming by converting an existing app programmed in straight tkinter. I need to be able to access an entrybox in top_frame from a button in left_frame. I have managed to achieve this by passing an instance of top_frame to Left_frame and with the program as listed here it works. However I wanted to put all of the methods into a Commands class for clarity so that the frame classes set the UI and the Commands class does the work. In the code I can call methods in the commands class from left_frame and in simple terms they work. My problem is that I need to use the contents of top_frame.g_round in the list golfers method in the Commands class. The code as shown works if the command for my_list_btn is "self.list_golfers" (using the left_frame method) but if I try to use the Commands class version by calling "Commands.list_golfers" I get errors when trying to pass the required attributes. I have tried various answers but each give me errors. Could anyone help by showing me how to pass the necessary attributes to the method in the Commands class. I have gone through many videos/texts to try to work it out but clearly I have missed something. Thanks

from tkinter import *
import tkinter as tk
from ttkbootstrap.constants import *
import ttkbootstrap as tb
from ttkbootstrap import Style
from ttkbootstrap.scrolled import ScrolledText, ScrolledFrame
#import sqlite3
#from sqlite3 import Error
from ttkbootstrap.dialogs import Messagebox


class Commands(tb.Frame):
    def __init__(self,parent, left_frame_instance):
        super().__init__(parent)        
        l_frame = left_frame_instance
    def list_golfers(self):
        x= self.l_frame.tframe1.g_round.get()
        print('list_golfers')
        print(x)     

    def new_golfer():
        print('new_golfer')

    def report():
        print('report')

    def select():
        print('select')


class Top_Frame(tb.Frame):
    
    def __init__(self, parent):       # Create widgets
        super().__init__(parent,width=900, height=200, bootstyle ='light')

        self.pack(padx=10, pady=5, fill=BOTH)

        self.g_round = tb.Entry(self,width=3, font=("Courier",10))
        self.g_round.grid(row = 0, column=1,padx=10,pady=10)
        self.round_label = tb.Label(self, text = "You must enter the round number before proceeding -->  ", font=('Courier', 10))
        self.round_label.grid(row=0,column=0,padx=10,pady=10)
        
   

class Left_Frame(tb.Frame):
    def __init__(self,parent, top_frame_instance):
        super().__init__(parent,width=400, height=600, bootstyle ='light')
        self.tframe1 = top_frame_instance  # Store reference to Frame1 instance

        
        self.pack(side='left',padx=10, pady=5, fill=BOTH, expand= True)
        
        self.create_Lwidgets()
    
        
    def list_golfers(self):
        x= self.tframe1.g_round.get()
        print('list_golfers')
        print(x) 

    def create_Lwidgets(self):
        my_list_btn=tb.Button(self,text='list',bootstyle='success', command= self.list_golfers)
        my_list_btn.grid(row=0, column=0,padx=5,pady=5)

        list_frame = tb.Frame(self,width=200, height=800,bootstyle='primary')
        list_frame.grid(row=1, column=0,padx=5,pady=5, rowspan=18)

        my_listbox=Listbox(list_frame)
        my_listbox.pack(padx=0,pady=15, side = LEFT,fill='both')

        my_scrollbar = Scrollbar(list_frame)
        my_scrollbar.pack(side = RIGHT, fill = BOTH)

        my_listbox.config(yscrollcommand = my_scrollbar.set)
        my_scrollbar.config(command = my_listbox.yview)

        golfer_button= tb.Button(self, text='New Golfer',bootstyle ='success',command = Commands.new_golfer)
        golfer_button.grid(row=21, column=0,padx=10,pady=10)

        report_button= tb.Button(self, text='Generate Report',bootstyle ='success',command = Commands.report)
        report_button.grid(row=22, column=0,padx=10,pady=10)

        select_btn = tb.Button(self, text="Select", bootstyle = SUCCESS,command = Commands.select)
        select_btn.grid(row=4, column=1, pady=5, padx=5)

        id_label=tb.Label(self,text = 'id')
        id_label.grid(row=2,column=2,padx=10)

        g_id = tb.Entry(self,width=3, font=("Courier",10), state='disabled')
        g_id.grid(row = 2, column=3,padx=5,pady=5)

    



class Right_Frame(tb.Frame):
    def __init__(self,parent):
        super().__init__(parent,width=400, height=600, bootstyle ='light')
        self.pack(side= 'right',padx=10, pady=5, fill=BOTH, expand=True)

        self.create_Rwidgets()
        #create widgets
    def create_Rwidgets(self):
        my_label = tb.Label(self, text = "Batch Scores", font=('Courier', 10))
        my_label.grid(row=0,column=0,padx=10,pady=10)

        Ts = Listbox(self, height = 30, width = 30)
        Ts.grid(row= 2, column= 0, sticky= N, columnspan = 5, padx= 10, pady= 15)

class MyApp(tb.Window):
    def __init__(self):
        super().__init__(themename = 'terry')
       # self.root = root
        self.title("TTKBootstrap OOP Example")
        self.geometry("1000x1000")


        top_frame = Top_Frame(self)
        left_frame = Left_Frame(self,top_frame)
        right_frame = Right_Frame(self)
        commands = Commands(self, left_frame)

if __name__ == "__main__":
    app = MyApp()  # Create an instance of MyApp
    app.mainloop()

Solution

  • Problem is that you mix "methods of instance" with "static methods"

    Some of your example functions don't need self so they can be used as Command.function (static methods) but list_golfers needs self to access self.l_frame and it needs to create command = Command() and later use command.list_golfers

    But I found other problem with this - class Left_Frame() needs access to variable command which is created later, but class Command() needs access to variable left_frame - and this makes conflict - you can't put Command() before Left_Frame because conflict still exists.

    I would remove left_frame from Command() and use self.parent.left_frame (or rather self.master.left_frame) to access left_frame.
    Of course it needs to use self. when you create frame - self.left_frame = Left_Frame(..)


    My full working code. I keep instance of Commands() as self.commands and Frames use self.master.command to access functions in this instance.

    command=self.master.commands.left_golfers
    

    Frankly I would move all functions to MyApp and then it would need self.master

    command=self.master.left_golfers
    

    And in all functions in Command() I use self because sooner or later they may need access to self.parent

    #from tkinter import *  # PEP8: `import *` is not preferred
    import tkinter as tk
    #from ttkbootstrap.constants import *  # PEP8: `import *` is not preferred
    import ttkbootstrap as tb
    from ttkbootstrap import Style
    from ttkbootstrap.scrolled import ScrolledText, ScrolledFrame
    #import sqlite3
    #from sqlite3 import Error
    from ttkbootstrap.dialogs import Messagebox
    
    
    class Commands():
        def __init__(self, parent):
            super().__init__()
            self.parent = parent
    
        def list_golfers(self):
            x = self.parent.left_frame.top_frame.g_round.get()
            print('list_golfers')
            print(x)
    
        def new_golfer(self):
            print('new_golfer')
    
        def report(self):
            print('report')
    
        def select(self):
            print('select')
    
    
    class Top_Frame(tb.Frame):
    
        def __init__(self, parent):       # Create widgets
            super().__init__(parent, width=900, height=200, bootstyle='light')
    
            self.pack(padx=10, pady=5, fill="both")
    
            self.g_round = tb.Entry(self, width=3, font=("Courier",10))
            self.g_round.grid(row = 0, column=1, padx=10, pady=10)
            self.round_label = tb.Label(self, text="You must enter the round number before proceeding -->  ", font=('Courier', 10))
            self.round_label.grid(row=0, column=0, padx=10, pady=10)
    
    
    class Left_Frame(tb.Frame):
        def __init__(self, parent, top_frame):
            super().__init__(parent, width=400, height=600, bootstyle='light')
            self.top_frame = top_frame  # Store reference to Frame1 instance
    
            self.pack(side='left',padx=10, pady=5, fill="both", expand=True)
    
            self.create_left_widgets()
    
        def list_golfers(self):
            x = self.top_frame.g_round.get()
            print('list_golfers')
            print(x)
    
        def create_left_widgets(self):
            my_list_btn = tb.Button(self, text='list', bootstyle='success', command=self.master.commands.list_golfers)
            my_list_btn.grid(row=0, column=0, padx=5, pady=5)
    
            list_frame = tb.Frame(self,width=200, height=800, bootstyle='primary')
            list_frame.grid(row=1, column=0, padx=5, pady=5, rowspan=18)
    
            my_listbox = tk.Listbox(list_frame)
            my_listbox.pack(padx=0, pady=15, side="left", fill='both')
    
            my_scrollbar = tk.Scrollbar(list_frame)
            my_scrollbar.pack(side="right", fill="both")
    
            my_listbox.config(yscrollcommand=my_scrollbar.set)
            my_scrollbar.config(command=my_listbox.yview)
    
            golfer_button = tb.Button(self, text='New Golfer', bootstyle='success', command=self.master.commands.new_golfer)
            golfer_button.grid(row=21, column=0, padx=10, pady=10)
    
            report_button = tb.Button(self, text='Generate Report', bootstyle='success', command=self.master.commands.report)
            report_button.grid(row=22, column=0, padx=10, pady=10)
    
            select_btn = tb.Button(self, text="Select", bootstyle="success", command=self.master.commands.select)
            select_btn.grid(row=4, column=1, pady=5, padx=5)
    
            id_label = tb.Label(self, text='id')
            id_label.grid(row=2, column=2, padx=10)
    
            g_id = tb.Entry(self, width=3, font=("Courier", 10), state='disabled')
            g_id.grid(row=2, column=3, padx=5, pady=5)
    
    
    class Right_Frame(tb.Frame):
        def __init__(self,parent):
            super().__init__(parent, width=400, height=600, bootstyle='light')
            self.pack(side='right', padx=10, pady=5, fill="both", expand=True)
    
            self.create_right_widgets()
            #create widgets
    
        def create_right_widgets(self):
            my_label = tb.Label(self, text="Batch Scores", font=('Courier', 10))
            my_label.grid(row=0, column=0, padx=10, pady=10)
    
            ts = tk.Listbox(self, height=30, width=30)
            ts.grid(row=2, column=0, sticky="n", columnspan=5, padx=10, pady=15)
    
    class MyApp(tb.Window):
        def __init__(self):
            super().__init__() #themename = 'terry')
           # self.root = root
            self.title("TTKBootstrap OOP Example")
            self.geometry("1000x1000")
    
            self.commands = Commands(self)
    
            self.top_frame = Top_Frame(self)
            self.left_frame = Left_Frame(self, self.top_frame)
            self.right_frame = Right_Frame(self)
    
    if __name__ == "__main__":
        app = MyApp()  # Create an instance of MyApp
        app.mainloop()
    

    PEP 8 -- Style Guide for Python Code