Kivy Date Picker Widget

[РЕШЕНО] См. ниже приложение принятого ответа и исходный код для работы виджета kivy DatePicker.

Я изучал Kivy и решил сделать виджеты выбора даты в качестве учебного упражнения.

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

Я наткнулся на блокпост и не могу понять, как продолжить. Я хочу добавить такой метод, чтобы при нажатии на date_labels они устанавливали self.date в объект даты с этой частью дня.

я пытался добавить

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

но получил только максимальные ошибки рекурсии.

РЕШЕНИЕ:

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

person Adam    schedule 04.12.2012    source источник
comment
Кроме того, в качестве комментария, потому что это не ваш вопрос, но вам не нужно сохранять в своем экземпляре, если вы не будете использовать значение после этого, поэтому в populate_body self.date_label можно заменить только date_label везде. В качестве примечания к примечанию, пожалуйста, уважайте pep8: P, никаких пробелов вокруг = в вызове функции, две и только две строки между каждым определением класса. хорошего дня :) edit: а также спасибо за создание средства выбора даты для kivy, я уверен, что это может быть полезно другим :)   -  person Tshirtman    schedule 05.12.2012
comment
Да, я прошел через несколько итераций date_label и self.date_label и только что опубликовал сообщение в этой форме. Между прочим, я вижу твою аватарку везде, когда ищу материал. Я уже начал думать, что это какой-то аватар по умолчанию/мем!   -  person Adam    schedule 05.12.2012
comment
Хе-хе, ну, я стараюсь отвечать на все вопросы киви ^^   -  person Tshirtman    schedule 06.12.2012
comment
Надеюсь, ты не возражаешь, что я позаимствовал это, @Horba.   -  person Noob Saibot    schedule 15.12.2013


Ответы (1)


вы делаете небольшую ошибку в своей привязке, вы вызываете метод вместо того, чтобы передавать его (таким образом, вы передаете результат self.set_date(date_cursor.day), но вызов self.set_date также вызывает self.populate_body, так что вы получить бесконечную рекурсию, остановленную только пределом рекурсии python.

То, что вы хотите сделать, это привязать метод, но с date_cursor.day в качестве первого параметра, для этого идеально подходит функция partial из functools.

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

Создает новую функцию, аналогичную self.set_date, но с предварительно загруженным date_cursor.day в качестве первого аргумента.

редактировать: также, когда ваша частичная функция вызывается привязкой события, она получит другие аргументы, поэтому хорошей привычкой является добавление **args в конце ваших аргументов в функциях/методах, которые вы используете обратными вызовами (здесь set_date).

person Tshirtman    schedule 05.12.2012
comment
Спасибо, я знал, что это будет что-то вроде этого, но не мог понять, как передать необходимые аргументы без вызова функции. Функтулс это! Я собираюсь поработать над этим снова сегодня вечером и отчитаюсь. - person Adam; 05.12.2012