Home > Software engineering >  Change main class label's text from different class using toplevel in tkinter
Change main class label's text from different class using toplevel in tkinter

Time:02-28

Just picked up tkinter recently

I have a program where when a user click a [...] button, it will display a toplevel window containing a calendar and [OK] button inside it.

When the user click the [OK] button, I want it to change [startDate] variable, and [labelStartDate] label in the main window. I need the [startDate] variable for my next data process. and [labelStartDate] label is to show user that the date is changed.

How to achieve that? I tried to use command=lambda or stringvar, but honestly I am kinda lost trying to apply it to my program.

enter image description here

Clicking [OK] button will change the variable and label's text

from datetime import date
from textwrap import fill
import tkinter as tk
from tkinter import ttk
from tkinter import Toplevel
from tkinter import font
from tkcalendar import Calendar
from turtle import color, width
    
# Define the GUI
class App(tk.Tk):
    def __init__(self):
        super().__init__()

        # root window
        self.title('Main Window')
        self.geometry('620x570')
        global startDate   #variable that I want to use for later data processing
        startDate = date.today().strftime("%Y/%m/%d/")

        #DATE MENU FRAME
        DateMenuBar = ttk.LabelFrame(self.master, borderwidth = 1, text='Setting')
        subFrame2 = tk.Frame(DateMenuBar, borderwidth = 1, relief = tk.FLAT, pady=0, padx=0)

        #SUB FRAME 2
        labelStart = tk.Label(subFrame2, text='Start',font=('meiryoui', 15))
        labelStartDate = tk.Label(subFrame2, text=startDate,font=('meiryoui', 15))
        btnOpenCalendar1 = tk.Button(subFrame2, height=1, background='#eeeeee', text='...', font=('meiryoui', 8), command=self.changeStartDate)

        labelStart.pack(side = tk.LEFT, ipadx=10)
        labelStartDate.pack(side = tk.LEFT, padx=(30,10))
        btnOpenCalendar1.pack(side = tk.LEFT)

        subFrame2.pack(fill = tk.X,padx=0, pady=10)
        DateMenuBar.pack(fill = tk.X,padx=20, ipadx=20, ipady=20)

    def changeStartDate(self):
        window = Window(self)
        window.grab_set() 


class Window(tk.Toplevel):
    def __init__(self, parent):
        super().__init__(parent)    

        self.title("Pick Date")
        self.geometry("250x250")
        
        def selectStartDate():
            startDate = cal.get_date()
            #I got stuck here, trying to figure out how to change the labelStartDate's text
        
        cal = Calendar(self, selectmode = 'day')
        cal.pack(padx=20, pady=10)
        frame = tk.Frame(self, borderwidth = 1, relief = tk.FLAT, pady=10, padx=20)
        btnOK = tk.Button(frame, height=2,width=8, background='#eeeeee', text='OK', font=('meiryoui', 9),command=selectStartDate)
        btnCancel = tk.Button(frame, height=2,width=8, background='#eeeeee', text='Cancel', font=('meiryoui', 9))

        btnOK.pack(side = tk.RIGHT, padx=(10,0))
        btnCancel.pack(side = tk.RIGHT)
        frame.pack(fill = tk.X)


if __name__ == "__main__":
    app = App()
    app.mainloop()

Edit Note: I added the missing code to my program so that it can be run by others :)

CodePudding user response:

You can first use tkinter.StringVar() and set the label textvariable to the same, inorder to be able to modify the label's text.

self.labelStartDateVar = tk.StringVar() # Initalizing the text variable
self.labelStartDateVar.set(startDate.start_date) # Setting initial value of the textvariable.
# Added textvariable as labelStartDateVar
self.labelStartDate = tk.Label(subFrame2, textvariable = labelStartDateVar, font = ('meiryoui', 15))

Further, using some knowledge from this post(of Observer Pattern), it is possible to call a function when a change in the startDate is detected. We do so by defining a new class and using a startDateData object as the global object, and to get the value of startDate itself, we simply need to access it's start_date property startDateData.start_date to set it the same property needs to be set like so -:

startDateData.start_date = cal.get_date()

The full code will look something like this -:

class startDate(object):
    def __init__(self):
        # Setting the default value as in the OP.
        self._start_date = date.today().strftime("%Y年 %m月 %d日")
        self._observers = []
        return

    @property
    def start_date(self):
        return self._start_date

    @global_wealth.setter
    def start_date(self, value):
        self._start_date = value
        for callback in self._observers:
            print('announcing change')
            callback(self._start_date)
        return

    def bind_to(self, callback):
        print('bound')
        self._observers.append(callback)

startDateData = startDate() # global startDateData object.

# Define the GUI
class App(tk.Tk):
    def __init__(self):
        super().__init__()

        # root window
        self.title('Main Window')
        self.geometry('620x570')
        global startDateData   #variable that I want to use for later data processing
        
        ###
        self.labelStartDateVar = tk.StringVar()
        self.labelStartDateVar.set(startDate.start_date)
        startDateData.bind_to(self.updateStartDate) # Binding the updateStartDate function to be called whenever value changes.
        ###
        
        #SUB FRAME 2
        self.labelStart = tk.Label(subFrame2, text='開始',font=('meiryoui', 15))
        
        # Added textvariable as labelStartDateVar
        self.labelStartDate = tk.Label(subFrame2, textvariable = labelStartDateVar, font = ('meiryoui', 15))
        self.btnOpenCalendar1 = tk.Button(subFrame2, height=1, background='#eeeeee', text='...', font=('meiryoui', 8), command=self.changeStartDate)

        self.labelStart.pack(side = tk.LEFT, ipadx=10)
        self.labelStartDate.pack(side = tk.LEFT, padx=(30,10))
        self.btnOpenCalendar1.pack(side = tk.LEFT)

        subFrame2.pack(fill = tk.X,padx=0, pady=10)

    def updateStartDate(self, startDate) :
        self.labelStartDateVar.set(startDate)
        return


class Window(tk.Toplevel):
    def __init__(self, parent):
        super().__init__(parent)    

        self.title("Pick Date")
        self.geometry("250x250")
        
        # Globally fetch the startDateData object.
        global startDateData
        
        def selectStartDate():
            # All binded callbacks will be called, when the value is changed here.
            startDateData.start_date = cal.get_date()
        
        cal = Calendar(self, selectmode = 'day')
        cal.pack(padx=20, pady=10)
        frame = tk.Frame(self, borderwidth = 1, relief = tk.FLAT, pady=10, padx=20)
        btnOK = tk.Button(frame, height=2,width=8, background='#eeeeee', text='OK', font=('meiryoui', 9),command=selectStartDate)
        btnCancel = tk.Button(frame, height=2,width=8, background='#eeeeee', text='Cancel', font=('meiryoui', 9))

        btnOK.pack(side = tk.RIGHT, padx=(10,0))
        btnCancel.pack(side = tk.RIGHT)
        frame.pack(fill = tk.X)

NOTE: As the code provided in the OP, was not adequate enough to be able to test whether this solution works. Further, as the initial code provided seemed to be incomplete, the full code given in the answer at the end may also seem incomplete but still implements all the features present in the code given in the OP.

EDIT: The previous placement of the line startDateData = startDate() was wrong as it was trying to construct an object of a class before it is defined, now the line has been shifted below the class definition of startDate.

  • Related