Kivy日期选择器小部件

8
[SOLVED] 请看下面接受的答案和可运行的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()

我是一名有用的助手,以下是您需要翻译的内容:

我遇到了难题,不知道该如何继续。我想添加一个方法,这样当点击日期标签时,它们会将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()

1
另外,作为一条注释,因为这不是你的问题,如果您之后不使用该值,则无需在实例中存储,因此在populate_body中,self.date_label可以替换为任何地方的date_label。 顺便提一下,请遵守pep8:P,函数调用中不要在“=”周围加空格,在每个类定义之间有两行且仅有两行。祝您拥有愉快的一天:) 编辑:哦,还要感谢您为kivy制作日期选择器,我相信它对其他人也很有用 :) - Tshirtman
是的,我经历了几次date_label和self.date_label的迭代,最终以这种形式发布。顺便说一下,我到处看到你的头像,每当我在查找东西时都会看到它。我开始觉得这是某种默认/模因头像! - Adam
嘿嘿,我尽力回答所有关于Kivy的问题^^ - Tshirtman
我希望你不介意我借用这个,@Horba。 - Noob Saibot
您是否将其实现为弹出窗口?如果是,您会制作一个弹出窗口并将其放入内容区域吗? - Erick Adam
你的代码在Python3上无法运行,因为has_key已被移除。我已经为Python3创建了这个示例。 https://gitlab.com/uak/kivy-examples/-/blob/main/Examples/date_picker_near_today.py - uak
1个回答

4
您在绑定中犯了一个小错误,您“调用”了该方法而不是将其“传递”(因此,您传递了self.set_date(date_cursor.day)的结果,但是调用self.set_date也会调用self.populate_body,因此您会陷入无限递归,只有Python递归限制才能停止。)
您需要做的是绑定该方法,但将date_cursor.day作为第一个参数,对此,来自functoolspartial函数非常完美。 self.date_label.bind(on_touch_down=partial(self.set_date, date_cursor.day)) 创建一个新函数,就像self.set_date一样,但预加载了date_cursor.day作为第一个参数。
编辑:另外,当事件绑定调用您的部分函数时,它将接收其他参数,因此在您用作回调的函数/方法中的参数末尾添加**args是一个好习惯(这里是set_date)。

谢谢,我知道会是这样的问题,但是我无法理解如何在不调用函数的情况下传递所需的参数。functools就是答案!今晚我会继续努力并报告结果。 - Adam

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接