Kivy 日期选择器小部件

2022-01-15 00:00:00 python kivy

问题描述

[已解决] 请参阅下面的应用程序接受的答案和运行 kivy DatePicker 小部件的源代码.

[SOLVED] Please see below for application of accepted answer and source code for functioning kivy DatePicker widget.

我一直在学习 Kivy,并决定制作一个日期选择器小部件作为学习练习.

I've been learning Kivy and decided to make a date picker widgets as a learning 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()

我遇到了障碍,不知道如何继续.我想添加一种方法,以便在单击 date_labels 时,它们将 self.date 设置为具有该日期部分的日期对象.

I've hit a road block and can't work out how to continue. I want to add a method such that when the date_labels are clicked, they set self.date to a date object with that day part.

我试过添加

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


解决方案

你在绑定中犯了一个小错误,你调用方法而不是传递它(因此,你传递了 self.set_date(date_cursor.day) 的结果,但是调用 self.set_date 也会调用 self.populate_body,所以你得到在无限递归中,只被python递归限制停止.

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

你想要做的是绑定方法,但使用 date_cursor.day 作为第一个参数,对于这个 partial 函数,来自 functools 完美.

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

<代码>self.date_label.bind(on_touch_down=partial(self.set_date, date_cursor.day))

创建一个新函数,与 self.set_date 类似,但将 date_cursor.day 作为第一个参数预加载.

Creates a new fonction, that is just like self.set_date, but with date_cursor.day preloaded as a first argument.

另外,当你的部分函数被事件绑定调用时,它会接收其他参数,所以在函数的参数末尾添加 **args 是一个好习惯/使用 ass 回调的方法(此处为 set_date).

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

相关文章