0

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 :)

Mario Ariyanto
  • 157
  • 1
  • 1
  • 12

1 Answers1

1

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(startDateData.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

    @start_date.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(startDateData.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 = self.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.

EDIT 2: Fixed some of the typos, as mentioned in the comments by @Mario Ariyanto.

typedecker
  • 1,351
  • 2
  • 13
  • 25
  • in line startDateData = startDate() # global startDateData object. it said [name 'startDate' is not defined] – Mario Ariyanto Feb 27 '22 at 23:54
  • my bad, this line must follow `startDate`'s class definition, currently it is not able to pick up the class for it to be able to construct it's object. – typedecker Feb 28 '22 at 02:52
  • 1
    I think there's some typo in this 3 parts: 1. self.labelStartDateVar.set(startDate.start_date) →   self.labelStartDateVar.set(startDateData.start_date) 2. @@global_wealth.setter → @@start_date.setter 3. self.labelStartDate = tk.Label(subFrame2, textvariable = labelStartDateVar, font = ('meiryoui', 15)) → self.labelStartDate = tk.Label(subFrame2, textvariable = self.labelStartDateVar, font=('meiryoui', 15)) After I fixed that typo, it is working as I intended – Mario Ariyanto Feb 28 '22 at 04:20
  • @MarioAriyanto Yes, thanks for pointing that out, I will make the suggested changes, so its easier for even future viewers to understand! :D – typedecker Feb 28 '22 at 04:35