簡體   English   中英

Python Kivy RecycleView 數據模型變更

[英]Python Kivy RecycleView data model change

我正在嘗試在 Kivy RecycleView 上實現一個“表”。 軟件啟動后,RecycleView 會填充一個字典列表,例如:

{ 
  'full_name': 'name surname', 
  'hours': [{}, {}, {}, ...],
  'datetime': datetime(),
  'upn': 'name.surname@domain.com'
}

使用這段代碼:

        tbl = []
        for user in self.users:
            filtered = time_tables.filter_by_user(user.user_principal_name)
            if len(filtered) == 0:
                continue
            tbl_row = {
                'full_name': user.full_name,
                'hours': [{'hours': timedelta(seconds=0), 'id': None} for i in range(days_in_month(timestamp))],
                'datetime': timestamp.timetuple(),
                'upn': user.user_principal_name
            }
            for item in filtered:
                tbl_row['hours'][(item.Date + timedelta(hours=5)).day - 1] = {
                    'hours': item.get_working_hours(),
                    'id': item.Id
                }
            tbl.append(tbl_row)
            self.ids.UserList.data.clear()
            self.ids.UserList.data = tbl

這在第一次運行時按預期工作,但是,當我更改時間戳值時,例如將days_in_month結果從 31 更改為 30 時,RecycleView 變得一團糟,有些行有 30 天,而其他行有 31 天......背景數據是正確的並且每個tbl_row對象都具有相同的長度。

有誰可以指出我錯過了什么? 我需要刷新data_model嗎? 如果是,如何?

提前感謝阿萊西奧

- - - - - - - - - 編輯 - - - - - - - - - -

我嘗試添加一個最小可重現的例子......

KV 文件:

<UserListViewItem@BoxLayout>
    full_name: ''
    hours: []
    upn: ''
    datetime: ()
    size_hint: 1, 1
    orientation: 'horizontal'
    Label:
        id: lblFullName
        text: root.full_name
        size_hint: .2, .5
        size_hint_max_x: dp(240)

<PyTimbratureUI>:
    BoxLayout:
        orientation: 'horizontal'
        size_hint: 1, .2
        size_hint_max_y: dp(60)
        Button:
            size_hint: .2, 1
            text: '<< Precedente'
            on_release: root.prev_month()
        Label:
            id: lblCurrentMonth
            size_hint: .6, 1
            text: 'Placeholder'
        Button:
            size_hint: .2, 1
            text: 'Successivo >>'
            on_release: root.next_month()

    RecycleView:
        orientation: 'vertical'
        viewclass: 'UserListViewItem'
        id: UserList
        size_hint: 1, 1
        RecycleBoxLayout:
            height: self.minimum_height
            default_size: None, 45
            default_size_hint: 1, None
            size_hint_y: None
            spacing: 5
            orientation: 'vertical'

Python腳本:


def days_in_month(dt: datetime):
    leap = 0
    if dt.year % 400 == 0:
        leap = 1
    elif dt.year % 100 == 0:
        leap = 0
    elif dt.year % 4 == 0:
        leap = 1
    if dt.month == 2:
        return 28 + leap
    list = [1, 3, 5, 7, 8, 10, 12]
    if dt.month in list:
        return 31
    return 30


def timedelta_to_string(data: timedelta or float):
    if isinstance(data, timedelta):
        hours = int(data.total_seconds() / 3600)
        minutes = int(int(data.total_seconds() / 60) - (hours * 60))
    else:
        hours = int(data)
        minutes = int(int(data * 60) - (hours * 60))

    return '%02d:%02d' % (hours, minutes)


class UserListViewItem(BoxLayout, RecycleDataViewBehavior):
    def __init__(self, **kwargs):
        super(UserListViewItem, self).__init__(**kwargs)
        Clock.schedule_once(self.finish_init)
        self.labels = []

    def get_overtime(self, dt):
        if isinstance(dt, timedelta) and dt > timedelta(hours=8):
            return dt - timedelta(hours=8)
        if isinstance(dt, int) or isinstance(dt, float):
            if dt > 8:
                return dt - 8
        return timedelta(hours=0)

    def finish_init(self, dt):
        current_datetime = datetime(self.datetime[0], self.datetime[1], 1)
        for i in range(days_in_month(current_datetime)):
            label = HourLabel(ore='0', straordinario='0')
            self.labels.append(label)
            self.add_widget(label)

        i = 0
        for hour_obj in self.hours:
            hour = hour_obj['hours']
            current_datetime = datetime(self.datetime[0], self.datetime[1], i+1)
            self.labels[i].ore = timedelta_to_string(hour)
            self.labels[i].user = self.upn
            self.labels[i].record_id = hour_obj['id'] if hour_obj['id'] is not None else ''
            if isinstance(hour, timedelta):
                self.labels[i].straordinario = timedelta_to_string(self.get_overtime(hour))
            else:
                self.labels[i].straordinario = timedelta_to_string(0)
            self.labels[i].color = self.get_label_color(current_datetime, hour)
            i += 1
        print(self.hours)



class PyTimbratureUI(BoxLayout):
    def __init__(self, **kwargs):
        super(PyTimbratureUI, self).__init__(**kwargs)
        Clock.schedule_once(self.on_start, 1)
        self.current_date = datetime.now()
        self.shp = None
        self.users = []
        self.start_data_update()

    def next_month(self):
        self.current_date += timedelta(days=days_in_month(self.current_date))
        self.start_data_update()

    def prev_month(self):
        self.current_date -= timedelta(days=days_in_month(self.current_date))
        self.start_data_update()

    def start_data_update(self):
        self.ids.UserList.data = []
        self.ids.lblCurrentMonth.text = self.current_date.strftime('%m/%Y')
        #self.downloadThread = Thread(target=self.load_data, args=[datetime(2021, 12, 1)])
        self.downloadThread = Thread(target=self.load_data, args=[self.current_date])
        self.downloadThread.start()

    @staticmethod
    def get_from_month_start(lst: SharepointList, timestmap: datetime = datetime.now()):
        first_day = timestmap.strftime('%Y-%m-01')
        last_day = timestmap.strftime('%Y-%m-') + str(days_in_month(timestmap))
        query = lst.q().on_list_field('Data').greater_equal(first_day)
        query = query.chain('and').less_equal(last_day)
        return lst.get_items(query=query, expand_fields=True)

    def load_data(self, timestamp: datetime = datetime.now()):
        self.shp = SharepointManger()
        self.shp.connect()
        self.users = self.shp.get_users()
        #timbrature = self.shp.get_list('Timbrature')
        #time_tables = Timetable.from_sharepoint_items(self.get_from_month_start(timbrature, timestamp))
                self.users = [{'full_name': 'Test1', 'user_principal_name': 'test1@example.com'},
                 {'full_name': 'Test2', 'user_principal_name': 'test2@example.com'},
                 {'full_name': 'Test3', 'user_principal_name': 'test3@example.com'},
                 {'full_name': 'Test4', 'user_principal_name': 'test4@example.com'},
                 ]
        tbl = []
        for user in self.users:
            tbl_row = {
                'full_name': user['full_name'],
                'hours': [{'hours': timedelta(seconds=0), 'id': None} for i in range(days_in_month(timestamp))],
                'datetime': timestamp.timetuple(),
                'upn': user['user_principal_name']
            }
        self.ids.UserList.data.clear()
        self.ids.UserList.data = tbl

        Clock.schedule_once(lambda x: self.create_headers(days_in_month(timestamp), x, tbl))


    def find_class_in_childrem(self, collection, classtype):
        for child in collection:
            if isinstance(child, classtype):
                return True
        return False

    def create_headers(self, count, dt, tbl):
        userlist = self.ids.UserList
        while self.find_class_in_childrem(self.ids.lytTableHeader.children, TableHdr):
            for widget in self.ids.lytTableHeader.children:
                if isinstance(widget, TableHdr):
                    self.ids.lytTableHeader.remove_widget(widget)

        for i in range(count):
            hdrLabel = TableHdr(text=str(i+1), size_hint=(0.03, 1))
            self.ids.lytTableHeader.add_widget(hdrLabel)

        userlist.data.clear()
        userlist.data = tbl
        userlist.refresh_from_data()


    def on_start(self, t):
        print(self.ids)


class PyTimbratureApp(App):
    def __init__(self, **kwargs):
        super(PyTimbratureApp, self).__init__(**kwargs)
        self.ui = None

    def build(self):
        self.ui = PyTimbratureUI()
        return self.ui

深入挖掘似乎問題與文檔中報告的緩存小部件有關https://kivy.org/doc/stable/api-kivy.uix.recycleview.html

似乎還沒有清除緩存的類實例的方法。

解決方法是在主窗口類中完全刪除 RecycleView 跟蹤它,並在每次我需要更新列數時創建一個新的 istance。

class PyTimbratureUI(BoxLayout):
    def __init__(self, **kwargs):
        ....
        self.tt = None # This keep track of the instantiated RecycleView
        ....

    def update_data(self, count, dt, tbl):
        .....
        # At the begin the class is accessed through its id, then using the 
        # object property tt
        if self.tt is not None: 
            self.remove_widget(self.tt)
        else:
            self.remove_widget(self.ids.UserList)

        self.tt = TimeTableRV()
        self.tt.data = tbl
        self.add_widget(self.tt)

希望這對其他人有幫助...

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM