How to create a wx.DatePickerCtrl with a customisable format (Phoenix)
Keywords : wx.DatePickerCtrl, wx.adv.CAL_MONDAY_FIRST, wx.DateTime, wx.pydate2wxdate, wx.adv.GenericCalendarCtrl, wx.PopupTransientWindow, wx.adv.CAL_SHOW_WEEK_NUMBERS.
Contents
Introduction :
DatePickerCtrl is seemingly stuck with a MM/DD/YYYY format, great for our North American chums but which makes it virtually useless for the rest of us. Hopefully others will find this useful as you can decide the date format you wish to use and elect to use a calendar in your own language too.
Demonstrating :
Tested py3.8.10, wx4.x and Linux.
Tested py3.10.5, wx4.2.1 and Win11.
Are you ready to use some samples ?
Test, modify, correct, complete, improve and share your discoveries !
First example
1 """
2 MiniDatePicker.py
3
4 A custom class that looks like the wx.DatePickerCtrl but with the ability to customise
5 the calendar and the output format. (DatePickerCtrl is seemingly stuck with MM/DD/YYYY format)
6 Works with wx.DateTime or python datetime values
7 With or without an activating button
8 Uses wx.adv.GenericCalendarCtrl
9 Uses locale to enable different languages for the calendar
10
11 MiniDatePicker(parent, id=wx.ID_ANY, pos=wx.DefaultPosition, size=wx.DefaultSize,
12 style=wx.BORDER_SIMPLE, name="MiniDatePicker", date=0, formatter=''):
13
14 @param parent: Parent window. Must not be None.
15 @param id: identifier. A value of -1 indicates a default value.
16 @param pos: MiniDatePicker position. If the position (-1, -1) is specified
17 then a default position is chosen.
18 @param size: If the default size (-1, -1) is specified then a default size is calculated.
19 Size should be able to accomodate the specified formatter string + button
20 @param style: Alignment (Left,Middle,Right).
21 @param name: Widget name.
22 @param date: Initial date (an invalid date = today)
23 @param formatter A date formatting string in the form of a lambda function
24 default lambda dt: dt.FormatISODate()
25 = ISO 8601 format "YYYY-MM-DD".
26 or a lambda function with a format string e.g.:
27 lambda dt: (f'{dt.Format("%a %d-%m-%Y")}')
28 e.g.:
29 format = lambda dt: (f'{dt.Format("%a %d-%m-%Y")}')
30 format = lambda dt: (f'{dt.Format("%A %d %B %Y")}')
31 or
32 fmt = "%Y/%m/%d"
33 format = lambda dt: (dt.Format(fmt))
34 format = lambda dt: (dt.Format("%Y/%m/%d"))
35 format = lambda dt: (dt.FormatISODate())
36
37 TextCtrl Styles: wx.TE_READONLY (Default)
38 wx.TE_RIGHT
39 wx.TE_LEFT
40 wx.TE_CENTRE
41
42 wx.BORDER_NONE is always applied to the internal textctrl
43 wx.BORDER_SIMPLE is the default border for the control itself
44
45 Events: EVT_DATE_CHANGED A date change occurred in the control
46
47 Event Functions:
48 GetValue() Returns formatted date in the event as a string
49
50 GetDate() Returns wxDateTime date in the event
51
52 GetDateTime() Returns python datetime of date in the event
53
54 GetTimeStamp() Returns seconds since Jan 1, 1970 UTC for current date
55
56 Functions:
57 GetValue() Returns formatted date in the event as a string
58
59 GetDate() Returns wxDateTime date in the control
60
61 GetDateTimeValue() Returns python datetime of date in the control
62
63 GetTimeStamp() Returns seconds since Jan 1, 1970 UTC for selected date
64
65 GetLocale() Returns tuple of current language code and encoding
66
67 SetValue(date) Sets the date in the control
68 expects a wx.DateTime, a python datetime datetime or a timestamp
69 Any invalid date defaults to wx.DateTime.Today()
70
71 SetFormatter(formatter) Date format in the form of a lambda
72 default: lambda dt: dt.FormatISODate()
73
74 SetButton(Boolean) Shows or Hides Ctrl Button
75
76 SetLocale(locale) Set the locale for Calendar day and month names
77 e.g. 'de_DE.UTF-8' German
78 'es_ES.UTF-8' Spanish
79 depends on the locale being available on the machine
80
81 SetCalendarStyle(style)
82 wx.adv.CAL_SUNDAY_FIRST: Show Sunday as the first day in the week (not in wxGTK)
83 wx.adv.CAL_MONDAY_FIRST: Show Monday as the first day in the week (not in wxGTK)
84 wx.adv.CAL_SHOW_HOLIDAYS: Highlight holidays in the calendar (only generic)
85 wx.adv.CAL_NO_YEAR_CHANGE: Disable the year changing (deprecated, only generic)
86 wx.adv.CAL_NO_MONTH_CHANGE: Disable the month (and, implicitly, the year) changing
87 wx.adv.CAL_SHOW_SURROUNDING_WEEKS: Show the neighbouring weeks in the previous and next months
88 wx.adv.CAL_SEQUENTIAL_MONTH_SELECTION: more compact, style for the month and year selection controls.
89 wx.adv.CAL_SHOW_WEEK_NUMBERS
90
91 SetCalendarHighlights(colFg, colBg)
92
93 SetCalendarHeaders(colFg, colBg)
94
95 SetCalendarFg(colFg) Calendar ForegroundColour
96
97 SetCalendarBg(colBg) Calendar & Ctrl BackgroundColour
98
99 Default Values:
100 date - Today
101 style - READ_ONLY
102
103 Author: J Healey
104 Created: 04/12/2022
105 Copyright: J Healey - 2022
106 License: GPL 3 or any later version
107 Email: <rolfofsaxony@gmx.com>
108
109 Usage example:
110
111 import wx
112 import minidatepicker as MDP
113 class Frame(wx.Frame):
114 def __init__(self, parent):
115 wx.Frame.__init__(self, parent, -1, "MiniDatePicker Demo")
116 format = (lambda dt: (f'{dt.Format("%A %d-%m-%Y")}'))
117 panel = wx.Panel(self)
118 mdp = MDP.MiniDatePicker(panel, -1, pos=(50, 50), size=(-1,-1), style=0, date=0, formatter=format)
119 self.Show()
120
121 app = wx.App()
122 frame = Frame(None)
123 app.MainLoop()
124
125 """
126
127 import wx
128 import wx.adv
129 from wx.lib.embeddedimage import PyEmbeddedImage
130 import datetime
131 import locale
132
133 img = PyEmbeddedImage(
134 b'iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAABg2lDQ1BJQ0MgcHJvZmlsZQAA'
135 b'KJF9kT1Iw0AcxV9TS0UiDu0g4pChOlkQFXHUKhShQqgVWnUwufQLmjQkKS6OgmvBwY/FqoOL'
136 b's64OroIg+AHi6OSk6CIl/i8ptIj14Lgf7+497t4BQqPCdLtnHNANx0onE1I2tyqFXyEihAhi'
137 b'EBVmm3OynELX8XWPAF/v4jyr+7k/R7+WtxkQkIhnmWk5xBvE05uOyXmfOMpKikZ8Tjxm0QWJ'
138 b'H7mu+vzGueixwDOjViY9TxwlloodrHYwK1k68RRxTNMNyheyPmuctzjrlRpr3ZO/UMwbK8tc'
139 b'pzmMJBaxBBkSVNRQRgUO4rQapNhI036ii3/I88vkUslVBiPHAqrQoXh+8D/43a1dmJzwk8QE'
140 b'EHpx3Y8RILwLNOuu+33sus0TIPgMXBltf7UBzHySXm9rsSNgYBu4uG5r6h5wuQMMPpmKpXhS'
141 b'kKZQKADvZ/RNOSByC/St+b219nH6AGSoq9QNcHAIjBYpe73Lu3s7e/v3TKu/H0FDcpMpQj/v'
142 b'AAAACXBIWXMAAAsTAAALEwEAmpwYAAACk0lEQVRIx43W3+unQxQH8Nc83w9tVutHNj9v5M4q'
143 b'tty7oC2u2Fy58KOV9g/AlRJygQvlRrIlpbhSW7uJorCKlKQoJand7AVJYm3Z7zMu9v18zWfM'
144 b'J6aeZs7MmTNnznm/zzwFE6p/WunkLczNXEnfyhO2Rza2rLfpP4xPG4zPm2xsNR6NPN/uNuq8'
145 b'nAa3W4tGaRQrrsZFkX/FIbyO3dG7PHq/RP4d9+NIs/YHTi+OrzKYcS2OZtNpvIqbcQfuyqFv'
146 b'pH80e45hP26JM1fhYtyDHzGvUGuttZSyB3/hWdyAn3P4KXwYg+dywAfx9mTmf8JH+A5P45Ls'
147 b'Ox/XUkpJzM/kJofxJ17JjR7GTfgGX2EfHsHZ3PRM5OsynvvEtTCrGR+Ih3fmJvsTgmsSEtE5'
148 b'lb5N7g7qVgO0LIk/FM9r5N14uUPM3TiY/bVbK5hbDPdEaudq8/VQnBu4Lzo7XJiWJA8Y2reK'
149 b'5/F4jN6L9/EaHsQFsVewHZtWSfLUeTBqBbfitxi6HTfiisjn1pTPA2daNZ7PDengh8R9b9Cz'
150 b'L0g6G73bcGUguSv7/1UypkFYlnA9iZfwPR4K+V7EM/H2cIh2JGG7sCszFXW1oZDNeA57Ujre'
151 b'CkSfSClYpYRcH3Ie7EJU2ySPilnBCXwW4rwdKH4axlYcDwe+xnsbSvlO1vt6Lsa/TGH7OP0X'
152 b'+Dy6J/BtdD5pbK1BfjVg8aL4QlMIj2btscaBN5s9D3RkW4hWpwaztXmAapfwTdAdEbOF/BoP'
153 b'JlyamD/VvU6tV/OgrCxzu3BZq7NqFE/iXdzXkW5UOspAZznonaVUY6t9zeRdKMu4YeVUa507'
154 b'lg51lrXlPS+dh6MwTJibw6dBSdn4J1K6R39ofPDH8L9/c/4GBa36v+mJzSMAAAAASUVORK5C'
155 b'YII=')
156
157 mdpEVT = wx.NewEventType()
158 EVT_DATE_CHANGED = wx.PyEventBinder(mdpEVT, 1)
159
160
161 class mdpEvent(wx.PyCommandEvent):
162 def __init__(self, eventType, eventId=1, date=None, value=''):
163 """
164 Default class constructor.
165
166 :param `eventType`: the event type;
167 :param `eventId`: the event identifier.
168 """
169 wx.PyCommandEvent.__init__(self, eventType, eventId)
170 self._eventType = eventType
171 self.date = date
172 self.value = value
173
174 def GetDate(self):
175 """
176 Retrieve the date value of the control at the time
177 this event was generated, Returning a wx.DateTime object"""
178 return self.date
179
180 def GetValue(self):
181 """
182 Retrieve the formatted date value of the control at the time
183 this event was generated, Returning a string"""
184 return self.value.title()
185
186 def GetDateTime(self):
187 """
188 Retrieve the date value of the control at the time
189 this event was generated, Returning a python datetime object"""
190 return wx.wxdate2pydate(self.date)
191
192 def GetTimeStamp(self):
193 """
194 Retrieve the date value represented as seconds since Jan 1, 1970 UTC.
195 Returning a integer
196 """
197 return int(self.date.GetValue()/1000)
198
199 class MiniDatePicker(wx.Control):
200 def __init__(self, parent, id=wx.ID_ANY, pos=wx.DefaultPosition, size=wx.DefaultSize,
201 style=wx.BORDER_SIMPLE, name="MiniDatePicker", date=0, formatter=''):
202
203 wx.Control.__init__(self, parent, id, pos=pos, size=size, style=style, name=name)
204 self.parent = parent
205 self._date = date
206 if formatter:
207 format = formatter
208 else:
209 format = lambda dt: dt.FormatISODate()
210 font = wx.SystemSettings.GetFont(wx.SYS_SYSTEM_FONT)
211 self.SetWindowStyle(wx.BORDER_NONE)
212 self._style = style
213 self._calendar_style = wx.adv.CAL_MONDAY_FIRST
214 self._calendar_headercolours = None
215 self._calendar_highlightcolours = None
216 self._calendar_Bg = None
217 self._calendar_Fg = None
218 if size == wx.DefaultSize:
219 dc = wx.ScreenDC()
220 dc.SetFont(font)
221 trialdate = format(wx.DateTime(28,9,2022)) # a Wednesday in September = longest names in English
222 w, h = dc.GetTextExtent(trialdate)
223 size = (w+64, -1) # Add image width (24) plus a buffer
224 del dc
225 self._veto = False
226 #try:
227 # locale.setlocale(locale.LC_TIME, ".".join(locale.getlocale()))
228 #except Exception as e:
229 # pass
230 txtstyle = wx.TE_READONLY
231
232 if style & wx.TE_LEFT or style == wx.TE_LEFT:
233 txtstyle = txtstyle | wx.TE_LEFT
234 elif style & wx.TE_RIGHT:
235 txtstyle = txtstyle | wx.TE_RIGHT
236 else:
237 txtstyle = txtstyle | wx.TE_CENTRE
238 if style & wx.TE_READONLY:
239 txtstyle = txtstyle | wx.TE_READONLY
240 if style & wx.BORDER_NONE:
241 txtstyle = txtstyle | wx.BORDER_NONE
242
243 # MiniDatePicker
244
245 self.ctl = wx.TextCtrl(self, id, value=str(self._date),
246 pos=pos, size=size, style=txtstyle, name=name)
247 self.button = wx.BitmapButton(self, -1, bitmap=img.Bitmap)
248 self.MinSize = self.GetBestSize()
249 # End
250
251 # Bind the events
252 self._formatter = format
253 self.button.Bind(wx.EVT_BUTTON, self.OnCalendar)
254 self.ctl.Bind(wx.EVT_LEFT_DOWN, self.OnCalendar)
255 self.SetValue(date)
256
257 sizer = wx.BoxSizer(wx.HORIZONTAL)
258 sizer.Add(self.ctl, 1, wx.EXPAND, 0)
259 sizer.Add(self.button, 0, wx.ALIGN_CENTER_VERTICAL, 0)
260 self.SetSizerAndFit(sizer)
261 self.Show()
262
263 def OnCalendar(self, _event=None):
264 window = CalendarPopup(
265 self, self._date, self.OnDate, self.GetTopLevelParent(), wx.SIMPLE_BORDER)
266 pos = self.ClientToScreen((0, 0))
267 size = self.GetSize()
268 window.Position(pos, (0, size.height))
269
270 def SetFormatter(self, formatter):
271 '''formatter will be called with a wx.DateTime'''
272 self._formatter = formatter
273 self.OnDate(self._date)
274
275 def SetLocale(self, alias):
276 try:
277 locale.setlocale(locale.LC_TIME, locale=alias)
278 except Exception as e:
279 locale.setlocale(locale.LC_TIME, locale='')
280 self.SetValue(self._date)
281
282 def SetCalendarStyle(self, style=0):
283 self._calendar_style = style
284
285 def SetCalendarHeaders(self, colFg=wx.NullColour, colBg=wx.NullColour):
286 self._calendar_headercolours = colFg, colBg
287
288 def SetCalendarHighlights(self, colFg=wx.NullColour, colBg=wx.NullColour):
289 self._calendar_highlightcolours = colFg, colBg
290
291 def SetCalendarFg(self, colFg=wx.NullColour):
292 self._calendar_Fg = colFg
293
294 def SetCalendarBg(self, colBg=wx.NullColour):
295 self._calendar_Bg = colBg
296
297 def SetButton(self, button=True):
298 if button:
299 self.button.Show()
300 else:
301 self.button.Hide()
302 self.Layout()
303
304 def OnDate(self, date):
305 self._date = date
306 self.ctl.SetValue(self._formatter(date).title())
307 self.MinSize = self.GetBestSize()
308 if self._veto:
309 self._veto = False
310 return
311 event = mdpEvent(mdpEVT, self.GetId(), date=date, value=self._formatter(date))
312 event.SetEventObject(self)
313 self.GetEventHandler().ProcessEvent(event)
314
315 def GetValue(self):
316 return self.ctl.GetValue()
317
318 def GetDate(self):
319 return self._date
320
321 def GetDateTimeValue(self):
322 """
323 Return a python datetime object"""
324 return wx.wxdate2pydate(self._date)
325
326 def GetTimeStamp(self):
327 """
328 Retrieve the date value represented as seconds since Jan 1, 1970 UTC.
329 Returning a integer
330 """
331 return int(self._date.GetValue()/1000)
332
333 def GetLocale(self):
334 return locale.getlocale(category=locale.LC_TIME)
335
336 def SetValue(self, date):
337 if isinstance(date, wx.DateTime):
338 pass
339 elif isinstance(date, datetime.date):
340 date = wx.pydate2wxdate(date)
341 elif isinstance(date, int) and date > 0:
342 date = wx.DateTime.FromTimeT(date)
343 elif isinstance(date, float) and date > 0:
344 date = wx.DateTime.FromTimeT(int(date))
345 else: # Invalid date value default to today's date
346 date = wx.DateTime.Today()
347 self._date = date.ResetTime()
348 self._veto = True
349 self.SetFormatter(self._formatter)
350
351
352 class CalendarPopup(wx.PopupTransientWindow):
353 def __init__(self, parent, date, callback, *args, **kwargs):
354 '''date is the initial date; callback is called with the chosen date'''
355 super().__init__(*args, **kwargs)
356 self.callback = callback
357 self.calendar = wx.adv.GenericCalendarCtrl(self, pos=(5, 5), style=parent._calendar_style)
358 self.calendar.SetDate(date)
359 if parent._calendar_headercolours:
360 self.calendar.SetHeaderColours(parent._calendar_headercolours[0],parent._calendar_headercolours[1])
361 if parent._calendar_highlightcolours:
362 self.calendar.SetHighlightColours(parent._calendar_highlightcolours[0],parent._calendar_highlightcolours[1])
363 if parent._calendar_Bg:
364 self.calendar.SetBackgroundColour(parent._calendar_Bg)
365 self.SetBackgroundColour(parent._calendar_Bg)
366 if parent._calendar_Fg:
367 self.calendar.SetForegroundColour(parent._calendar_Fg)
368 sizer = wx.BoxSizer(wx.VERTICAL)
369 sizer.Add(self.calendar, 1, wx.ALL | wx.EXPAND)
370 self.SetSizerAndFit(sizer)
371 self.calendar.Bind(wx.adv.EVT_CALENDAR, self.OnChosen)
372 self.calendar.SetToolTip("Arrow keys and PageUp/pageDn\nAdjust the Calendar")
373 self.Popup()
374
375 def OnChosen(self, _event=None):
376 self.callback(self.calendar.GetDate())
377 self.Dismiss()
378
379
380 class DemoFrame(wx.Frame):
381 def __init__(self, parent):
382 wx.Frame.__init__(self, parent, -1, "MiniDatePicker Demo")
383
384 #format = (lambda dt:
385 # (f'{dt.GetWeekDayName(dt.GetWeekDay())} {str(dt.day).zfill(2)}/{str(dt.month+1).zfill(2)}/{dt.year}')
386 # )
387
388 #format = (lambda dt: (f'{dt.Format("%a %d-%m-%Y")}'))
389 format = (lambda dt: (f'{dt.Format("%A %d %B %Y")}'))
390
391 panel = wx.Panel(self)
392
393 self.mdp = MiniDatePicker(panel, -1, pos=(50, 50), style=wx.TE_CENTRE, date=0, formatter=format)
394 self.mdp.SetLocale('fr_FR.UTF-8')
395 #self.mdp.SetFormatter(format)
396 x=datetime.datetime.now()
397 self.mdp.SetValue(x.timestamp())
398 #self.mdp.SetValue(0)
399 #self.mdp.SetButton(False)
400 self.mdp.SetCalendarStyle(wx.adv.CAL_SHOW_WEEK_NUMBERS|wx.adv.CAL_MONDAY_FIRST)
401 self.mdp.button.SetBackgroundColour('white')
402 self.mdp.ctl.SetBackgroundColour('lightgreen')
403 self.mdp.SetCalendarHeaders(colFg='red', colBg='lightgreen')
404 self.mdp.SetCalendarHighlights(colFg='red', colBg='lightgreen')
405 self.mdp.SetCalendarBg(colBg='azure')
406 self.Bind(EVT_DATE_CHANGED, self.OnEvent)
407
408 def OnEvent(self, event):
409 print("\nevt", event.GetValue())
410 print("evt", event.GetDate())
411 print("evt", event.GetDateTime())
412 print("evt", event.GetTimeStamp())
413 print("func", self.mdp.GetValue())
414 print("func", self.mdp.GetDate())
415 print("func", self.mdp.GetDateTimeValue())
416 print("func", self.mdp.GetTimeStamp())
417 print("func", self.mdp.GetLocale())
418
419
420 if __name__ == '__main__':
421 app = wx.App()
422 frame = DemoFrame(None)
423 frame.Show()
424 app.MainLoop()
Download source
Additional Information
Link :
https://discuss.wxpython.org/t/a-wx-datepickerctrl-with-a-customisable-format/36295
- - - - -
https://wiki.wxpython.org/TitleIndex
Thanks to
J Healey (MiniDatePicker.py coding), the wxPython community...
About this page
Date(d/m/y) Person (bot) Comments :
16/01/23 - Ecco (Created page for wxPython Phoenix).
Comments
- blah, blah, blah...