Kivy Date Picker Widget

[SOLVED] The following is a description of the accepted answer and source code for working with kivy DatePicker widgets.

I study Kivy and decided to make widgets for choosing a date as a training exercise.

import kivy kivy.require('1.4.0') from kivy.uix.gridlayout import GridLayout from kivy.uix.boxlayout import BoxLayout from kivy.uix.label import Label from kivy.uix.button import Button from kivy.app import App from datetime import date, timedelta class DatePicker(BoxLayout): def __init__(self, **kwargs): super(DatePicker, self).__init__(**kwargs) self.date = date.today() self.orientation = "vertical" self.header = BoxLayout(orientation = 'horizontal', size_hint = (1, 0.2)) self.body = GridLayout(cols = 7) self.add_widget(self.header) self.add_widget(self.body) self.populate_body() self.populate_header() def populate_header(self): self.header.clear_widgets() self.previous_month = Button(text = "<") self.next_month = Button(text = ">") self.current_month = Label(text = repr(self.date), size_hint = (2, 1)) self.header.add_widget(self.previous_month) self.header.add_widget(self.current_month) self.header.add_widget(self.next_month) def populate_body(self): self.body.clear_widgets() date_cursor = date(self.date.year, self.date.month, 1) while date_cursor.month == self.date.month: self.date_label = Label(text = str(date_cursor.day)) self.body.add_widget(self.date_label) date_cursor += timedelta(days = 1) # Not yet implimented ### # def set_date(self, day): # self.date = date(self.date.year, self.date.month, day) # self.populate_body() # self.populate_header() # # def move_next_month(self): # if self.date.month == 12: # self.date = date(self.date.year + 1, 1, self.date.day) # else: # self.date = date(self.date.year, self.date.month + 1, self.date.day) # def move_previous_month(self): # if self.date.month == 1: # self.date = date(self.date.year - 1, 12, self.date.day) # else: # self.date = date(self.date.year, self.date.month -1, self.date.day) # self.populate_header() # self.populate_body() class MyApp(App): def build(self): return DatePicker() if __name__ == '__main__': MyApp().run() 

I hit the road block and cannot decide how to proceed. I want to add a method so that when I click on the date_labels, they set self.date for the date object with this part of the day.

I tried adding

 self.date_label.bind(on_touch_down = self.set_date(date_cursor.day)) 

but just got maximum recursion errors.

DECISION:

 import kivy kivy.require('1.4.0') from kivy.uix.gridlayout import GridLayout from kivy.uix.boxlayout import BoxLayout from kivy.uix.label import Label from kivy.uix.button import Button from kivy.app import App from datetime import date, timedelta from functools import partial class DatePicker(BoxLayout): def __init__(self, *args, **kwargs): super(DatePicker, self).__init__(**kwargs) self.date = date.today() self.orientation = "vertical" self.month_names = ('January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December') if kwargs.has_key("month_names"): self.month_names = kwargs['month_names'] self.header = BoxLayout(orientation = 'horizontal', size_hint = (1, 0.2)) self.body = GridLayout(cols = 7) self.add_widget(self.header) self.add_widget(self.body) self.populate_body() self.populate_header() def populate_header(self, *args, **kwargs): self.header.clear_widgets() previous_month = Button(text = "<") previous_month.bind(on_press=partial(self.move_previous_month)) next_month = Button(text = ">", on_press = self.move_next_month) next_month.bind(on_press=partial(self.move_next_month)) month_year_text = self.month_names[self.date.month -1] + ' ' + str(self.date.year) current_month = Label(text=month_year_text, size_hint = (2, 1)) self.header.add_widget(previous_month) self.header.add_widget(current_month) self.header.add_widget(next_month) def populate_body(self, *args, **kwargs): self.body.clear_widgets() date_cursor = date(self.date.year, self.date.month, 1) for filler in range(date_cursor.isoweekday()-1): self.body.add_widget(Label(text="")) while date_cursor.month == self.date.month: date_label = Button(text = str(date_cursor.day)) date_label.bind(on_press=partial(self.set_date, day=date_cursor.day)) if self.date.day == date_cursor.day: date_label.background_normal, date_label.background_down = date_label.background_down, date_label.background_normal self.body.add_widget(date_label) date_cursor += timedelta(days = 1) def set_date(self, *args, **kwargs): self.date = date(self.date.year, self.date.month, kwargs['day']) self.populate_body() self.populate_header() def move_next_month(self, *args, **kwargs): if self.date.month == 12: self.date = date(self.date.year + 1, 1, self.date.day) else: self.date = date(self.date.year, self.date.month + 1, self.date.day) self.populate_header() self.populate_body() def move_previous_month(self, *args, **kwargs): if self.date.month == 1: self.date = date(self.date.year - 1, 12, self.date.day) else: self.date = date(self.date.year, self.date.month -1, self.date.day) self.populate_header() self.populate_body() class MyApp(App): def build(self): return DatePicker() if __name__ == '__main__': MyApp().run() 
+6
source share
1 answer

you make a small mistake in your binding, you call the method instead of passing it (this way you pass the result self.set_date(date_cursor.day) , but you call self.set_date calls self.populate_body too, so you get infinite recursion, only stopped python recursion limit.

What you want to do is bind the method, but with date_cursor.day as the first parameter, the partial function from functools ideal for this.

self.date_label.bind(on_touch_down=partial(self.set_date, date_cursor.day))

Creates a new fonction, similar to self.set_date, but with the date_cursor.day parameter preloaded as the first argument.

edit: also, when your partial function is called by event binding, it will receive other arguments, so it’s nice to add **args at the end of your arguments in the functions / methods that you use ass callbacks (here set_date ).

+3
source

All Articles