├── CTkDatePicker ├── __init__.py └── ctk_date_picker.py ├── README.md └── example.py /CTkDatePicker/__init__.py: -------------------------------------------------------------------------------- 1 | __version__ = "1.0" 2 | 3 | from .ctk_date_picker import CTkDatePicker 4 | -------------------------------------------------------------------------------- /CTkDatePicker/ctk_date_picker.py: -------------------------------------------------------------------------------- 1 | import tkinter as tk 2 | import customtkinter as ctk 3 | from datetime import datetime 4 | import calendar 5 | 6 | class CTkDatePicker(ctk.CTkFrame): 7 | def __init__(self, master=None, **kwargs): 8 | """ 9 | Initialize the CTkDatePicker instance. 10 | 11 | Parameters: 12 | - master: The parent widget. 13 | - **kwargs: Additional keyword arguments passed to the CTkFrame constructor. 14 | 15 | Initializes the date entry, calendar button, popup, and other related components. 16 | """ 17 | 18 | super().__init__(master, **kwargs) 19 | 20 | self.date_entry = ctk.CTkEntry(self) 21 | self.date_entry.grid(row=0, column=0, sticky="ew", padx=5, pady=5) 22 | 23 | self.calendar_button = ctk.CTkButton(self, text="▼", width=20, command=self.open_calendar) 24 | self.calendar_button.grid(row=0, column=1, sticky="ew", padx=5, pady=5) 25 | 26 | self.popup = None 27 | self.selected_date = None 28 | self.date_format = "%m/%d/%Y" 29 | self.allow_manual_input = True 30 | self.allow_change_month = True 31 | self.add_months = 0 32 | self.subtract_months = 0 33 | 34 | def set_date_format(self, date_format): 35 | """ 36 | Set the date format to be used in the date entry. 37 | 38 | Parameters: 39 | - date_format (str): The desired date format string, e.g., "%m/%d/%Y". 40 | 41 | Sets the format in which the selected date will be displayed. 42 | """ 43 | self.date_format = date_format 44 | 45 | def set_localization(self, localization): 46 | """ 47 | Set the localization for month and day names. 48 | 49 | Parameters: 50 | - localization (str): The desired localization string, e.g., "en_EN". 51 | 52 | Sets the language that month and day names will be displayed. 53 | """ 54 | import locale 55 | locale.setlocale(locale.LC_ALL, localization) 56 | locale.setlocale(locale.LC_NUMERIC, "C") 57 | 58 | def open_calendar(self): 59 | """ 60 | Open the calendar popup for date selection. 61 | 62 | Creates and displays a calendar widget allowing the user to select a date. 63 | The calendar appears just below the date entry field. 64 | """ 65 | 66 | if self.popup is not None: 67 | self.popup.destroy() 68 | self.popup = ctk.CTkToplevel(self) 69 | self.popup.title("Select Date") 70 | self.popup.geometry("+%d+%d" % (self.winfo_rootx(), self.winfo_rooty() + self.winfo_height())) 71 | self.popup.resizable(False, False) 72 | 73 | self.popup.after(500, lambda: self.popup.focus()) 74 | 75 | self.current_year = datetime.now().year 76 | self.current_month = datetime.now().month 77 | self.build_calendar() 78 | 79 | def build_calendar(self): 80 | """ 81 | Build and display the calendar in the popup. 82 | 83 | Constructs the calendar UI for the currently selected month and year. 84 | Includes navigation buttons for previous and next months if enabled. 85 | """ 86 | 87 | if hasattr(self, 'calendar_frame'): 88 | self.calendar_frame.destroy() 89 | 90 | self.calendar_frame = ctk.CTkFrame(self.popup) 91 | self.calendar_frame.grid(row=0, column=0) 92 | 93 | # Add months 94 | if self.add_months < 0: 95 | raise ValueError("add_months cannot be negative") 96 | for i in range(self.add_months): 97 | if self.current_month == 12: 98 | self.current_month = 1 99 | self.current_year += 1 100 | else: 101 | self.current_month += 1 102 | 103 | # Subtract months 104 | if self.subtract_months < 0: 105 | raise ValueError("subtract_months cannot be negative") 106 | for i in range(self.subtract_months): 107 | if self.current_month == 1: 108 | self.current_month = 12 109 | self.current_year -= 1 110 | else: 111 | self.current_month -= 1 112 | 113 | # Month and Year Selector 114 | month_label = ctk.CTkLabel(self.calendar_frame, text=f"{calendar.month_name[self.current_month].capitalize()}, {self.current_year}") 115 | month_label.grid(row=0, column=1, columnspan=5) 116 | 117 | if self.allow_change_month: 118 | prev_month_button = ctk.CTkButton(self.calendar_frame, text="<", width=5, command=self.prev_month) 119 | prev_month_button.grid(row=0, column=0) 120 | 121 | next_month_button = ctk.CTkButton(self.calendar_frame, text=">", width=5, command=self.next_month) 122 | next_month_button.grid(row=0, column=6) 123 | 124 | # Days of the week header 125 | days = [calendar.day_name[i][:3].capitalize() for i in range(7)] 126 | for i, day in enumerate(days): 127 | lbl = ctk.CTkLabel(self.calendar_frame, text=day) 128 | lbl.grid(row=1, column=i) 129 | 130 | # Days in month 131 | month_days = calendar.monthrange(self.current_year, self.current_month)[1] 132 | start_day = calendar.monthrange(self.current_year, self.current_month)[0] 133 | day = 1 134 | for week in range(2, 8): 135 | for day_col in range(7): 136 | if week == 2 and day_col < start_day: 137 | lbl = ctk.CTkLabel(self.calendar_frame, text="") 138 | lbl.grid(row=week, column=day_col) 139 | elif day > month_days: 140 | lbl = ctk.CTkLabel(self.calendar_frame, text="") 141 | lbl.grid(row=week, column=day_col) 142 | else: 143 | if ctk.get_appearance_mode() == "Light": 144 | btn = ctk.CTkButton(self.calendar_frame, text=str(day), width=3, command=lambda day=day: self.select_date(day), fg_color="transparent", text_color="black", hover_color="#3b8ed0") 145 | else: 146 | btn = ctk.CTkButton(self.calendar_frame, text=str(day), width=3, command=lambda day=day: self.select_date(day), fg_color="transparent") 147 | btn.grid(row=week, column=day_col) 148 | day += 1 149 | 150 | def prev_month(self): 151 | """ 152 | Navigate to the previous month in the calendar. 153 | 154 | Updates the calendar display to show the previous month's days. 155 | Adjusts the year if necessary. 156 | """ 157 | 158 | if self.current_month == 1: 159 | self.current_month = 12 160 | self.current_year -= 1 161 | else: 162 | self.current_month -= 1 163 | self.build_calendar() 164 | 165 | def next_month(self): 166 | """ 167 | Navigate to the next month in the calendar. 168 | 169 | Updates the calendar display to show the next month's days. 170 | Adjusts the year if necessary. 171 | """ 172 | 173 | if self.current_month == 12: 174 | self.current_month = 1 175 | self.current_year += 1 176 | else: 177 | self.current_month += 1 178 | self.build_calendar() 179 | 180 | def select_date(self, day): 181 | """ 182 | Select a date from the calendar. 183 | 184 | Parameters: 185 | - day (int): The day of the month selected by the user. 186 | 187 | Sets the selected date in the date entry field and closes the calendar popup. 188 | """ 189 | 190 | self.selected_date = datetime(self.current_year, self.current_month, day) 191 | # Temporarily enable the entry to set the date 192 | self.date_entry.configure(state='normal') 193 | self.date_entry.delete(0, tk.END) 194 | self.date_entry.insert(0, self.selected_date.strftime(self.date_format)) 195 | # Restore the disabled state if necessary 196 | if not self.allow_manual_input: 197 | self.date_entry.configure(state='disabled') 198 | self.popup.destroy() 199 | self.popup = None 200 | 201 | def get_date(self): 202 | """ 203 | Get the currently selected date as a string. 204 | 205 | Returns: 206 | - str: The date string in the format specified by self.date_format. 207 | """ 208 | 209 | return self.date_entry.get() 210 | 211 | def set_allow_manual_input(self, value): 212 | """ 213 | Enable or disable manual date input. 214 | 215 | Parameters: 216 | - value (bool): If True, manual input in the date entry is allowed; otherwise, it is disabled. 217 | 218 | Allows the user to manually enter a date if set to True; otherwise, restricts input to selection via the calendar. 219 | """ 220 | 221 | self.allow_manual_input = value 222 | if not value: 223 | self.date_entry.configure(state='disabled') 224 | else: 225 | self.date_entry.configure(state='normal') 226 | 227 | def set_allow_change_month(self, value): 228 | """ 229 | Enable or disable change month. 230 | 231 | Parameters: 232 | - value (bool): If False, user cannot change month in the calendar. 233 | 234 | Allows the user to change month if set to True; otherwise, removes the arrows to change month. 235 | """ 236 | 237 | self.allow_change_month = value 238 | 239 | def set_change_months(self, add_or_sub, value): 240 | """ 241 | Add or subract number of months when opening the calendar. 242 | 243 | Parameters: 244 | - add_or_sub (str): Either "add" or "sub". 245 | - value (int): Number of months. 246 | 247 | Set a number of months to add or subract when opening the calendar. 248 | """ 249 | 250 | if add_or_sub == "add": 251 | self.add_months = value 252 | elif add_or_sub == "sub": 253 | self.subtract_months = value 254 | else: 255 | raise ValueError("Invalid value for add_or_sub. Must be 'add' or 'sub'") 256 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # CTkDatePicker 2 | 3 | CTkDatePicker is a custom date picker widget built using the CustomTkinter library. It provides a user-friendly interface for selecting dates, with both a text entry and a calendar popup for easy date selection. 4 | 5 | ![grafik](https://github.com/user-attachments/assets/d51430c3-aea1-49bf-b790-550c58902773) 6 | 7 | 8 | ## Features 9 | 10 | - **Customizable Date Format**: Set the date format to display in the date entry. 11 | - **Calendar Popup**: Provides a visual calendar for selecting dates. 12 | - **Manual Date Entry**: Optionally allow users to manually enter a date. 13 | - **Previous and Next Month Navigation**: Easily navigate between months. 14 | 15 | ## Installation 16 | 17 | To use CTkDatePicker, you need to have CustomTkinter installed. You can install it using pip: 18 | 19 | ```bash 20 | pip install customtkinter 21 | ``` 22 | 23 | ## Usage 24 | 25 | ### Basic Example 26 | 27 | Here's a basic example of how to use CTkDatePicker in your application: 28 | 29 | ```python 30 | import tkinter as tk 31 | import customtkinter as ctk 32 | from CTkDatePicker import CTkDatePicker 33 | 34 | def main(): 35 | root = ctk.CTk() 36 | root.geometry("400x300") 37 | 38 | date_picker = CTkDatePicker(root) 39 | date_picker.set_allow_change_month(False) 40 | date_picker.set_change_months("sub", 5) 41 | date_picker.pack(pady=20) 42 | 43 | def print_date(): 44 | print(f"Selected Date: {date_picker.get_date()}") 45 | 46 | btn = ctk.CTkButton(root, text="Print Date", command=print_date) 47 | btn.pack(pady=20) 48 | 49 | root.mainloop() 50 | 51 | if __name__ == "__main__": 52 | main() 53 | ``` 54 | 55 | ### Customization 56 | 57 | - **Date Format**: You can set the date format using the `set_date_format` method. 58 | 59 | ```python 60 | date_picker.set_date_format("%d-%m-%Y") 61 | ``` 62 | 63 | - **Language**: You can set the language for days and months using the `set_localization` method. 64 | 65 | ```python 66 | date_picker.set_localization("en_EN") 67 | ``` 68 | 69 | - **Allow Manual Input**: Enable or disable manual date input using the `set_allow_manual_input` method. 70 | 71 | ```python 72 | date_picker.set_allow_manual_input(True) # Enable 73 | date_picker.set_allow_manual_input(False) # Disable 74 | ``` 75 | 76 | ## Methods 77 | 78 | ### `set_date_format(date_format)` 79 | 80 | - **Description**: Set the date format to be used in the date entry. 81 | - **Parameters**: 82 | - `date_format` (str): The desired date format string, e.g., "%m/%d/%Y". 83 | 84 | ### `set_localization(localization)` 85 | 86 | - **Description**: Set the localization for month and day names. 87 | - **Parameters**: 88 | - `localization` (str): The desired localization string, e.g., "en_EN". 89 | 90 | ### `open_calendar()` 91 | 92 | - **Description**: Open the calendar popup for date selection. 93 | 94 | ### `build_calendar()` 95 | 96 | - **Description**: Build and display the calendar in the popup. 97 | 98 | ### `prev_month()` 99 | 100 | - **Description**: Navigate to the previous month in the calendar. 101 | 102 | ### `next_month()` 103 | 104 | - **Description**: Navigate to the next month in the calendar. 105 | 106 | ### `select_date(day)` 107 | 108 | - **Description**: Select a date from the calendar. 109 | - **Parameters**: 110 | - `day` (int): The day of the month selected by the user. 111 | 112 | ### `get_date()` 113 | 114 | - **Description**: Get the currently selected date as a string. 115 | - **Returns**: 116 | - `str`: The date string in the format specified by `self.date_format`. 117 | 118 | ### `set_allow_manual_input(value)` 119 | 120 | - **Description**: Enable or disable manual date input. 121 | - **Parameters**: 122 | - `value` (bool): If True, manual input in the date entry is allowed; otherwise, it is disabled. 123 | 124 | ### `set_allow_change_month(value)` 125 | 126 | - **Description**: Enable or disable change month. 127 | - **Parameters**: 128 | - `value` (bool): If False, user cannot change month in the calendar. 129 | 130 | ### `set_change_months(value)` 131 | 132 | - **Description**: Add or subract number of months when opening the calendar. 133 | - **Parameters**: 134 | - `add_or_sub` (str): Either "add" or "sub". 135 | - `value` (int): Number of months. 136 | 137 | ## Contributing 138 | 139 | Contributions are welcome! Please fork the repository and submit a pull request with your improvements. 140 | 141 | ## License 142 | 143 | This project is licensed under the MIT License. See the [LICENSE](LICENSE) file for details. 144 | 145 | ## Acknowledgments 146 | 147 | - [CustomTkinter](https://github.com/TomSchimansky/CustomTkinter) for providing a great framework for creating modern and customizable GUI applications in Python. 148 | 149 | Feel free to customize this README to better suit your project's specific details and needs! 150 | -------------------------------------------------------------------------------- /example.py: -------------------------------------------------------------------------------- 1 | import customtkinter as ctk 2 | from CTkDatePicker import * 3 | 4 | if __name__ == "__main__": 5 | root = ctk.CTk() 6 | root.geometry("450x200") 7 | root.title("CTkDatePicker Example") 8 | 9 | date_picker = CTkDatePicker(root) 10 | date_picker.pack(padx=20, pady=20) 11 | date_picker.set_allow_manual_input(False) 12 | 13 | # get value 14 | # date.picker.get_date() 15 | 16 | root.mainloop() 17 | --------------------------------------------------------------------------------