2012-12-04 12 views
6

[SOLVED] Poniżej znajduje się przykład zastosowania akceptowanej odpowiedzi i kodu źródłowego do obsługi widgetu kivy DatePicker.Kivy Data Picker Widget

Uczę się Kivy i postanowiłem zrobić widżety wyboru daty jako ćwiczenie edukacyjne.

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

Uderzyłem w blokadę i nie mogę się zdecydować, jak kontynuować. Chcę dodać metodę taką, że po kliknięciu znaczników date_labels ustawiają self.date na obiekt daty z tą częścią dnia.

Próbowałem dodanie

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

ale dostał tylko max błędy rekursji.

rozwiązanie:

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

Ponadto, jako komentarz, bo to nie twoja sprawa, ale nie trzeba przechowywać w przypadku, jeśli nie będzie po użyć wartości, więc w populate_body, 'self.date_label' może być zastąpiony tylko przez' date_label' everwhere. Na marginesie uwagi, proszę szanować pep8: P, bez spacji wokół '=' w wywołaniu fonction, dwóch i tylko dwóch liniach pomiędzy definicjami klas. miłego dnia :) edytuj: och, dzięki za zrobienie selektora dat dla kivy, jestem pewien, że może być użyteczny dla innych :) – Tshirtman

+0

Tak, przeszedłem kilka iteracji date_label i self.date_label i właśnie zdarzyło mi się opublikować w tej formie. Widzę twój awatar wszędzie przy okazji, kiedy tylko szukam rzeczy. Zaczynałem myśleć, że to jakiś domyślny awatar/mema! – Horba

+0

Hehe, cóż, staram się odpowiedzieć na wszystkie pytania kivy ^^ – Tshirtman

Odpowiedz

3

zrobić mały błąd w swojej wiążące, ty wezwanie metoda zamiast przechodząc go (czyli przekazać wynik self.set_date(date_cursor.day), ale nazywając self.set_date rozmowy self.populate_body zbyt , więc dostajesz się do nieskończonej rekursji, tylko zatrzymany przez ograniczenie rekursji python.

Co chcesz zrobić, to powiązać metodę, ale z date_cursor.day jako pierwszym parametrem, dla tegoFunkcja, od functools jest idealna.

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

Tworzy nowy FONCTION, że jest tak jak self.set_date, ale z date_cursor.day fabrycznie jako pierwszy argument.

edit: też, gdy częściowy funkcja jest wywoływana przez wiązanie wydarzenie, będzie otrzymywać inne argumenty, więc jest to dobry zwyczaj, aby dodać **args na końcu swoich argumentów funkcji/metod użyć wywołania zwrotne ass (tutaj set_date).

+0

Dziękuję, wiedziałem, że to będzie coś takiego, ale nie mogłem pochylić głowy, przekazując wymagane argumenty bez wywoływania funkcji. functools to jest! Dziś wieczorem będę nad tym pracować i złożyć raport. – Horba