0

Trying to develop an application for work and I'd like to keep the UI and main controller class code separate. How should i approach this?

I have my MainUi class created.

class MainUi:
store_number = ''
tld_date = ''

def __init__(self, root):
    self.root = root
    root.title("TacoBell TLD Tool")
    self.store_number = IntVar()
    self.tld_date = IntVar()

    self.lblstore_number = Label(root, text="Store Number (XXXXXX): ")
    self.lbltld_date = Label(root, text="TLD Date(MM/DD/YYYY): ")

    self.lblstore_number.grid(row=0, sticky=E)  # Sticky align text North(N), East(E), South(S), West(W0
    self.lbltld_date.grid(row=1, sticky=E)

    self.entStore = Entry(self.root, textvariable=self.store_number)
    self.entDate = Entry(self.root, textvariable=self.tld_date)

    self.entStore.grid(row=0, column=1)
    self.entDate.grid(row=1, column=1)

Do I need to define getters and setters? Id like these entrys to be store in two variables in my mainclass so i can pass the variables in my main class to other functions.

import paramiko
import os
import csv
import tools
from ui import MainUi
from tkinter import *


root = Tk()
ui = MainUi(root)
# get store number and date data from UI.py and store them as variables here 
root.mainloop()

2 Answers 2

1

Yes you can keep them separate by changing the class variables store_number & tld_date. To do this, you need to get the numbers from the entryboxes and save them as variables. We can conveniently do this by calling self.store_number = self.entStore.get() or self.tld_date = self.entDate.get()

MainUI.py

from tkinter import *

class LaunchMainUi:
    store_number = ''
    tld_date = ''

    def __init__(self, root):
        self.root = root
        root.title("TacoBell TLD Tool")
        self.store_number = IntVar()
        self.tld_date = IntVar()

        Label(root, text="Store Number (XXXXXX): ").grid(row=0, sticky=E)  # Sticky align text North(N), East(E), South(S), West(W)
        Label(root, text="TLD Date(MM/DD/YYYY): ").grid(row=1, sticky=E)

        self.entStore = Entry(self.root)
        self.entDate = Entry(self.root)

        self.entStore.grid(row=0, column=1)
        self.entDate.grid(row=1, column=1)

        self.confirm = Button(root, text="Confirm", command=self.save)
        self.confirm.grid(row=2, column=1)

    def save(self, event=None):
        storeNumber = self.entStore.get()
        tldDate = self.entDate.get()

        #Saved as method variables ^ in order to perform necessary validation checks easier

        #If entered items are valid:
        self.store_number = storeNumber
        self.tld_date = tldDate
        self.root.destroy()

I firstly changed the name of the class so that the file name and the class name do not clash. I then created a button called self.confirm and set it's command to self.save. This function stores the two variables as storeNumber and tldDate, before setting the class variables to what as been entered and destroying the root window.

Controller.py

import MainUI
from tkinter import *

root = Tk()

ui = MainUI.LaunchMainUi(root)
root.mainloop() #Keeps the ui instance running until closed, then the rest of the code is run

storeNum = ui.store_number
tldDate = ui.tld_date

print('Store Number: {}\nTLD Date: {}'.format(storeNum, tldDate)) #This line is to show that the variables can be accessed

Because we destroyed the root instance, the mainloop() function is returned meaning that any code underneath it will run. As long as the UI is open, the code under it won't be run. So when the root window is closed the code underneath it is run saving the two variables for later use. You should add some error-checking code where it is commented out to.


Using .pack()

I personally stay away from using .grid() because is can be a bit confusing. Instead I like to use .pack() and encapsulation.enter image description here If you'd want to use .pack() instead, here is another __init__ that works just the same. I also made the GUI look a little less standard on this __init__ and with some TacoBell colours :)

def __init__(self, root):
    self.root = root
    self.root.title("TacoBell TLD Tool")
    self.root.geometry('600x300')

    font20 = 'Calibri 20'
    bfont20 = font20 + ' bold'

    Label(self.root, text='TacoBell TLD Tool', font=bfont20, fg='purple').pack()

    topItems = Frame(self.root)
    Label(topItems, text="Store Number (XXXXXX): ", font=font20).pack(side=LEFT)  # Sticky align text North(N), East(E), South(S), West(W)
    self.entStore = Entry(topItems, font=font20)
    self.entStore.pack(side=RIGHT)
    topItems.pack(pady=20)

    lowItems = Frame(self.root)
    Label(lowItems, text="TLD Date(MM/DD/YYYY): ", font=font20).pack(side=LEFT)
    self.entDate = Entry(lowItems, font=font20)
    self.entDate.pack(side=RIGHT)
    lowItems.pack(pady=20)

    self.confirm = Button(root, text="Confirm", bg='purple', fg='white', font=bfont20, width=30, command=self.save)
    self.confirm.pack()
Sign up to request clarification or add additional context in comments.

Comments

0

The best approach is it use components. With tkinter, you should define your components by extending the Frame class. So your code should look like this:

class StoreForm(tkinter.Frame):
    store_number = ''
    tld_date = ''

    def __init__(self, root, **kw):
        super().__init__(**kw)
        root.title("TacoBell TLD Tool")
        self.store_number = tkinter.IntVar()
        self.tld_date = tkinter.IntVar()

        self.lblstore_number = tkinter.Label(self, text="Store Number (XXXXXX): ")
        self.lbltld_date = tkinter.Label(self, text="TLD Date(MM/DD/YYYY): ")

        self.lblstore_number.grid(row=0, sticky=E)  # Sticky align text North(N), East(E), South(S), West(W0
        self.lbltld_date.grid(row=1, sticky=E)

        self.entStore = tkinter.Entry(self, textvariable=self.store_number)
        self.entDate = tkinter.Entry(self, textvariable=self.tld_date)

        self.entStore.grid(row=0, column=1)
        self.entDate.grid(row=1, column=1)

Beware, the root parameter is not your root but the expected container for this frame.

This way, you have a stand-alone reusable graphic component. This component manage the view logic (displaying the widgets, interaction between them if needed) but not your application logic which is the job of your controller. But to use that controller, you should be able to get the data from this component and/or provide some. For that purpose, you should add methods. I would not call them getters and setters even if I use similar names…

    def get_data(self):
        return self.store_number.get(), self.tld_date.get()

    def set_data(self, store_number_value, tld_date_value):
        self.store_number.set(store_number_value)
        self.tld_date.set(tld_date_value)

Now, in your main module, you can add something like

MAIN_UI = tkinter.Tk()
form = StoreForm(MAIN_UI).pack()

I let you choose if this should take place at the root of the module or inside a class. And this is the place where the controller should be. The controller is the component with your business logic outside the graphic component.

Using Frames let you:

  • separate the UI logic from your business logic
  • mix layouts (grid in the frame for the form, pack outside for a components flow)

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.