TTKCalendar selection return Python - python-3.x

I have this ttk calendar and my program is meant to update a field when a date in the calendar widget is pressed.
Here are the start_date and end_date fields:
start_date = StringVar()
start_date = ttk.Entry(f2, width=15, textvariable=start_date)
start_date.grid(column=2, row=1, sticky=E)
ttk.Label(f2, text="Start date:", width=10).grid(column=1, row=1, sticky=E)
end_date = StringVar()
end_date = ttk.Entry(f2, width=15, textvariable=end_date)
end_date.grid(column=2, row=2, sticky=E)
ttk.Label(f2, text="End date:", width=10).grid(column=1, row=2, sticky=E)
Here's the function that the button triggers:
def callbackCal():
root2=Toplevel(f2)
ttkcal = ttkcalendar.Calendar(root2,firstweekday=calendar.SUNDAY)
ttkcal.pack(expand=1, fill='both')
root2.update()
root2.minsize(root2.winfo_reqwidth(), root2.winfo_reqheight())
Here's the button code:
b=ttk.Button(f2, width=4, text="Cal", command=callbackCal).grid(column=3,row=1, sticky=W)
Thanks to NorthCat's help, I was able to get this far. And I know the ttk calendar has the methods _pressed() , _show_selection() and selection(). But I have no idea how I can use them in order to show the selected date when it is clicked. And also, to close the calendar widget once that is done.
Thanks a lot! and sorry for these newbie questions.

I don't pretend to understand the code, but I found an answer to another question that suggested a few changes, the answer was from kalgasnik
Python tkinter with ttk calendar
Then I made this change :-
def __init__(self, master=None, selection_callback=None, **kw):
and added this in the init function
self.selection_callback = selection_callback
In the _pressed function I added
if self.selection_callback:
self.selection_callback(self.selection)
Basically adding a callback to get the values when a date is clicked.
My sample callback program was :-
import calendar
import tkinter as TK
import tkinter.font
from tkinter import ttk
from ttk_calendar import Calendar
import sys
class Test():
def __init__(self, root):
self.root = root
self.root.title('Ttk Calendar')
frame = ttk.Frame(self.root)
frame.pack()
quit_button = ttk.Button(frame, text="Calendar", command=self.use_calendar)
quit_button.pack()
self.calendarroot = None
def use_calendar(self):
if not self.calendarroot:
self.calendarroot=TK.Toplevel(self.root)
ttkcal = Calendar(master=self.calendarroot, selection_callback=self.get_selection, firstweekday=calendar.SUNDAY)
ttkcal.pack(expand=1, fill='both')
self.calendarroot.update()
self.calendarroot.minsize(self.calendarroot.winfo_reqwidth(), self.calendarroot.winfo_reqheight())
else:
self.calendarroot.deiconify() # Restore hidden calendar
def get_selection(self, selection):
print (selection)
self.calendarroot.withdraw() # Hide calendar - if that is what is required
if __name__ == '__main__':
root = tkinter.Tk()
x = Test(root)
root.mainloop()
I tried to destroy the TopLevel frame but got an error, hence I used withdraw and deiconify, not best, but at least I got something to work.
A bit of a muddled answer I realize, but you might be agle to figure out a better solution.

Related

Tkinter how update main window combobox values from Toplevel list python3.8

I have 3 modules (small, dont worry).
main_module = it has a combobox and a button. Comobobox list must be update each time a list (in module2) increases in number of names (combo values). Button calls the second window (module2)-->
myapp_second_window.py which has a entry box and another button. We write a name in the entry, push the button...voila..the list increases. In the origina app the list is created automatically when (2) is called.
Now I pass the list to a Pages.variable that is in -->
my_pages_to_connect_modules.
So, when app start I can populate combobox calling (2) to generate a Pages.variable list or populate combobox with json previously written.
The problem? --> how populate combobox while app is running. I mean, we go to (2) create a new name in entry come back to (1) and it is already there.
main_module
import tkinter as tk
from tkinter import*
from tkinter import ttk
import myapp_second_window
from myapp_second_window import SecondClass
root= Tk()
root.geometry("500x500")
root.title('myAPP_Main_Window')
class MainClass:
def __init__(self, parent,myapp_second_window):
self.parent = parent
self.my_widgets1()
def call_second_page (self):
Window2 = tk.Toplevel(root)
Window2.geometry('400x300')
myapp_second_window.SecondClass(Window2)
def my_widgets1(self):
self.field1_value = StringVar()
self.field1 = ttk.Combobox(self.parent, textvariable=self.field1_value)
self.field1['values'] = [1,2] # Pages.variable comes Here
self.field1.grid( row=0, column=0)
self.myButton = tk.Button(self.parent, text = "Call Second module", command = self.call_second_page)
self.myButton.grid(row=2, column=0)
if __name__ == '__main__':
app = MainClass(root, myapp_second_window)
root.mainloop()
myapp_second_window.py
import tkinter as tk
from tkinter import*
from tkinter import ttk
root= Tk()
root.minsize(550,450)
root.maxsize(560,460)
root.title('myAPP_Second_Window')
class SecondClass:
def init(self, parent):
self.parent = parent
self.my_widgets()
self.names = []
def my_widgets(self):
mylabel = Label(self.parent, text='Insert new name in next widget:')
mylabel.grid(column=0, row=0, sticky=W, pady=3)
button1 = tk.Button(self.parent, text="Click to enter Names in list", command=self.addToList)
button1.grid(column=3, row=0, sticky=W, pady=3)
self.name = StringVar()
valueEntry = tk.Entry(self.parent, textvariable= self.name)
valueEntry.grid(row=1, column=0, sticky=W, pady=3)
def addToList(self):
self.names.append(self.name.get())
print('listentries', self.names)
Pages.list_of_names = self.names
my_pages_to_connect_modules.
class Pages():
list_of_names = " "
It`s been challenging to me, every help is welcome. But please dont say just that I must update main window, I need to know how. Thanks to all of you.

Tkcalendar DateEntry - Allow Widget to Return No Selection as Blank

I have created a GUI where the user can select a date from a drop down using the tkcalendar DateEntry widget. I would like to allow the user the option of not selecting a date and leaving this widget blank. However, even if no date is selected the widget returns the current date.
Is there a way to configure the DateEntry to allow for no selection rather than defaulting to the current date if the user does not select a date?
Below is a subset of my code:
import pandas as pd
from tkinter import *
from tkinter.ttk import *
import tkinter as tk
from tkcalendar import DateEntry
class Window(Frame):
def __init__(self, master):
Frame.__init__(self,master)
master.title('Solar Master Project Tracking')
# create canvas for scrollable window
canvas = Canvas(root)
canvas.grid(row=1,column=0, columnspan=2)
# create vertical scrollbar and connect it to the canvas
scrollBar = tk.Scrollbar(root, orient='vertical', command = canvas.yview)
scrollBar.grid(row=1, column=2, sticky = 'ns')
canvas.configure(yscrollcommand=scrollBar.set)
def update_scroll_region(event):
canvas.configure(scrollregion=canvas.bbox("all"))
def _on_mousewheel(event):
canvas.yview_scroll(int(-1*(event.delta/120)), "units")
# create a frame for the widgets in the scrollable canvas
scroll_frame = Frame(canvas)
scroll_frame.bind("<Configure>", update_scroll_region)
canvas.create_window(0,0, anchor='nw', window = scroll_frame)
canvas.bind_all("<MouseWheel>", _on_mousewheel)
# Proposal Date
self.L18 = Label(scroll_frame, text="Proposal Date:",font=('TKDefaultFont', 8, 'bold'))
self.L18.grid(row=21, column=0, sticky=W)
self.prop_date_selection = DateEntry(scroll_frame, width = 25, background = 'LightCyan3',
foreground ='white',borderwidth=2)
self.prop_date_selection.grid(row=21, column=1,sticky=E)
self.prop_date_selection.delete(0,"end")
# SUBMIT INFORMATION
self.button = tk.Button(root, text="Insert / Update Project",font=('TKDefaultFont', 10, 'bold'),
relief=RAISED, command = self.store_user_inputs, bg = "gray80")
self.button.grid(row=25, column = 0, columnspan=8, sticky = 'EW')
# STORE USER INPUT
def store_user_inputs(self):
prop_date_selection = self.prop_date_selection.get_date()
global params
params = [prop_date_selection]
root.destroy()
if __name__ == "__main__":
root = Tk()
Window(root)
root.mainloop()
You can create a class inheriting from tkcalendar.DateEntry and modify the get_date() method to return None when the DateEntry is empty:
import tkcalendar
class DateEntry(tkcalendar.DateEntry):
def get_date(self):
if not self.get():
return None
self._validate_date()
return self.parse_date(self.get())
For those who find this post.
Modifying get_date() didn't change anything in my case. But the following did:
class MyDateEntry(tkcalendar.DateEntry):
def _validate_date(self):
if not self.get():
return True # IMPORTANT!!! Validation must return True/False otherwise it is turned off by tkinter engine
return super()._validate_date()

How to link Calendar.get_date() with a StringVar() in tkinter

I've been working on a GUI for a sort of "Rota manager" app that has a calendar object in the main window to allow the user to select a specific date on it and record beginning and end of his/her working shift.
In the main window I would like to have a tk.Label that display the date selected on the calendar and change every time the user clicks on a different day.
I found a lot of question related to something similar but I can't figure it how to make the label update itself after a click.
So far I could only display the first selection of the date (today's date by default).
Here the part of the code:
import tkinter as tk
from tkinter import StringVar
from tkcalendar import Calendar
class MainWindow:
def __init__(self, master):
self.master = master
master.title("Rota Manager")
master.geometry("400x550")
master.grid_rowconfigure(0, weight=1)
master.grid_columnconfigure(0, weight=1)
master.resizable(False, False)
self.cal = Calendar(self.master, font="Arial 14", selectmode='day', date_pattern='dd/mm/yy')
self.cal.grid(sticky='nsew', pady=10, padx=5)
self.v = StringVar()
self.dynamic_label = tk.Label(self.master, textvariable=self.v, font=('Arial', 10))
self.dynamic_label.grid(row=5, sticky='n')
self.v.set(f"Date selected: {self.cal.get_date()}")
if __name__ == "__main__":
root = tk.Tk()
app = MainWindow(root)
root.mainloop()
This is just the portion of the code with the task I'm asking about.
As usual, thanks a lot to whoever can help.
Another solution is to use Calendar's textvariable option to keep track of the selected day. So just connect both the label and the calendar to the same StringVar.
import tkinter as tk
from tkinter import StringVar
from tkcalendar import Calendar
class MainWindow:
def __init__(self, master):
self.master = master
master.title("Rota Manager")
master.geometry("400x550")
master.grid_rowconfigure(0, weight=1)
master.grid_columnconfigure(0, weight=1)
master.resizable(False, False)
self.v = StringVar(self.master, Calendar.date.today().strftime("%d/%m/%y"))
self.cal = Calendar(self.master, font="Arial 14", selectmode='day',
date_pattern='dd/mm/yy', textvariable=self.v)
self.cal.grid(sticky='nsew', pady=10, padx=5)
label_frame = tk.Frame(self.master) # put static label and dynamic label in single frame
tk.Label(label_frame, text="Date selected: ",
font=('Arial', 10)).pack(side="left") # static_label
self.dynamic_label = tk.Label(label_frame,
textvariable=self.v, font=('Arial', 10))
self.dynamic_label.pack(side="left")
label_frame.grid(row=5, sticky='n')
if __name__ == "__main__":
root = tk.Tk()
app = MainWindow(root)
root.mainloop()

tkinter catch value from lambda function

I'm trying to wrap my head around this problem.
Say I have a code like this:
def get_input(data_A, data_B):
all_data = [data_A.get(),dataB.get()]
return(all_data)
def the_gui():
root = Tk()
data_A = Entry(root)
data_B = Entry(root)
button = Button(root, text='Submit', command=lambda: get_input(data_A, data_B))
mainloop()
My goal is to get the value of data_A and data_B once I clicked the submit button.
I tried to use global variable and everything, but I kept failing to catch the value.
The only thing that works is when I put the whole get_input() function inside the_gui() function. However, I don't think that's a good practice to implement.
Any suggestions?
Here is a simple example of how you could write this to get the results you are looking for.
When using global is that all your root window and related fields are in a function. So you would have to define global in both function and this is not what you want to do.
Typically you will want to write the root window in the global namespace and not in a function or write it into a class so you can avoid global's all-together.
button = Button(...) may not be doing what you think it is. This does not return a value from the command once clicked. Tkinter buttons do not care about anything being returned. So you have to record that value elsewhere.
I am not sure how you code is working as you do not use geometry managers and mainloop() should be attached to the root window so I have added those in as well.
Example 1:
import tkinter as tk
def get_input():
global a_and_b
a_and_b = [data_a.get(), data_b.get()]
# If you want to keep a running record of all values submitted
# then you can do this instead:
# a_and_b.append([data_a.get(), data_b.get()])
def print_a_b():
print(a_and_b)
root = tk.Tk()
a_and_b = []
data_a = tk.Entry(root)
data_b = tk.Entry(root)
data_a.pack()
data_b.pack()
tk.Button(root, text='Submit', command=get_input).pack()
tk.Button(root, text='Print A/B List', command=print_a_b).pack()
root.mainloop()
Example 2 using OOP:
import tkinter as tk
class App(tk.Tk):
def __init__(self):
super().__init__()
self.a_and_b = []
self.data_a = tk.Entry(self)
self.data_b = tk.Entry(self)
self.data_a.pack()
self.data_b.pack()
tk.Button(self, text='Submit', command=self.get_input).pack()
tk.Button(self, text='Print A/B List', command=self.print_a_b).pack()
def get_input(self):
self.a_and_b = [self.data_a.get(), self.data_b.get()]
def print_a_b(self):
print(self.a_and_b)
if __name__ == '__main__':
App().mainloop()

Python Tkinter Check if Frame exists

I am trying to do the following:
Create a Tkinter App with a 'File' menu.
The File Menu has 2 options, Add and View.
The Add option adds a Frame and then adds a Label widget (Label 1) in the frame.
If I then select the View option form the file menu, it should print out whether or not a Frame widget already exists.
Following is my attempt at it, but I receive the error
AttributeError: 'Test' object has no attribute 'tk'
when I select the View option, can someone please help point out what I am missing here?
from tkinter import Tk, Menu, Label, Frame
class Test():
def __init__(self):
self.gui = Tk()
self.gui.geometry("600x400")
menu = Menu(self.gui)
new_item1 = Menu(menu)
menu.add_cascade(label='File', menu=new_item1)
new_item1.add_command(label='Add', command=self.addlbl)
new_item1.add_command(label='View', command=self.viewlbl)
self.gui.config(menu=menu)
self.gui.mainloop()
def addlbl(self):
f=Frame()
f.pack()
lbl1 = Label(f, text="Label 1").grid(row=0, column=0)
def viewlbl(self):
print(Frame.winfo_exists(self))
T=Test()
I replicated your problem. I got the code below to work using Python3.4 on Linux. f needs to become self.f. I named it self.frame. This enables the frame to be accessed outside of the method it is created in.
from tkinter import Tk, Menu, Label, Frame
class Test():
def __init__(self):
self.gui = Tk()
self.gui.geometry("600x400")
menu = Menu(self.gui)
new_item1 = Menu(menu)
menu.add_cascade(label='File', menu=new_item1)
new_item1.add_command(label='Add', command=self.addlbl)
new_item1.add_command(label='View', command=self.viewlbl)
self.gui.config(menu=menu)
self.gui.mainloop()
def addlbl(self):
self.frame = Frame(self.gui)
self.frame.pack()
lbl1 = Label(self.frame, text="Label 1")
lbl1.grid(row=0, column=0)
def viewlbl(self):
print('frame exists {}'.format(self.frame.winfo_exists()))
T=Test()

Resources