質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

ただいまの
回答率

87.61%

KivyのRecycleViewからCheckBoxを使って削除したい

解決済

回答 1

投稿

  • 評価
  • クリップ 0
  • VIEW 182

score 5

Python3.8.1においてkivyを用いて簡単なアプリを作っています。kivyのRecycleViewを用いる際に、viewclassにCheckBoxを持たせて要素を選択、選択した要素をRecycleViewのリストから削除しようとしています。

main.py

from kivy.app import App
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.recycleview import RecycleView
from kivy.properties import ObjectProperty,BooleanProperty
from kivy.uix.popup import Popup
import copy
class TestCheck(App):
    def __init__(self, **kwargs):
        super(TestCheck, self).__init__(**kwargs)
        self.title = "Test"
class Start(BoxLayout):
    def start(self):
        content=Check()
        self.popup=Popup(title="start",content=content)
        self.popup.open()
class Check(BoxLayout):
    tmplist=ObjectProperty()
    def __init__(self,**kwargs):
        super(Check, self).__init__(**kwargs)
        self.tmplist.data=[{"name":"1","check":False},{"name":"2","check":False},{"name":"3","check":False},{"name":"4","check":False}]
        Check.data = self.tmplist.data
    def delete(self):
        print(self.tmplist.data)
        booklist = copy.copy(Check.data)
        print(booklist)

        for book in booklist:
            id_num = Check.data.index(book)
            print(book["name"],book["check"])
            if book["check"] == True:
                Check.data.pop(id_num)
                print("book{} delete!".format(book["name"]))
        self.tmplist.data=Check.data
        print(Check.data)
        print(self.tmplist.data)
    def restart(self):
        Start.start(self)
class FileButton(BoxLayout):
    name = ObjectProperty()
    is_checked = BooleanProperty(False)
    def checkpush(self,checkbox):
        print(checkbox.active)
        for book in Check.data:
            if book["name"] == self.name:
                book["check"]=checkbox.active


if __name__ == "__main__":
    TestCheck().run()


testcheck.kv

Start
<Start>
    BoxLayout:
        Button:
            on_press: root.start()
<Check>
    tmplist: tmp_list
    BoxLayout:
        Label:
            text: "test"
        RecycleView:
            id: tmp_list
            viewclass: "FileButton"
            RecycleBoxLayout:
                orientation: "vertical"
                default_size_hint: 1,None
                default_size: None,dp(45)
                size_hint_y: None
                scaping: 5
                height: self.minimum_height
                padding: 10
        Button:
       text: "delete"
            on_press: root.delete()
        Button:
            text: "start"
            on_press: root.restart()
<FileButton>
    name: ""
    CheckBox:
        active: root.is_checked
        on_press: root.checkpush(self)
    Label:
        text: root.name


簡略化してありますが、起動後ボタンを押すとポップアップを表示、ポップアップ内にRecycleViewを表示しています。さらにRecycleViewの各要素にCheckBoxがついており、Checkしてある要素をdeleteから削除しています。delete時にもポップアップによって削除の確認をしており、削除時のClassとRecycleViewのClassが異なることで上手にRecycleViewのリストを更新できずに困っています。

起こったエラー

一度削除を行い、残ったリストを再度選択し、再度削除を行う

File "/Library/Frameworks/Python.framework/Versions/3.9/lib/python3.9/site-packages/kivy/uix/recycleview/__init__.py", line 225, in refresh_views
     lm.compute_sizes_from_data(data, f)
   File "/Library/Frameworks/Python.framework/Versions/3.9/lib/python3.9/site-packages/kivy/uix/recyclelayout.py", line 116, in compute_sizes_from_data
     del opts[v]
 IndexError: list assignment index out of range


一度削除を行い、何も選択せずに再度削除を行う

File "/Library/Frameworks/Python.framework/Versions/3.9/lib/python3.9/site-packages/kivy/uix/recycleview/__init__.py", line 225, in refresh_views
     lm.compute_sizes_from_data(data, f)
   File "/Library/Frameworks/Python.framework/Versions/3.9/lib/python3.9/site-packages/kivy/uix/recyclelayout.py", line 133, in compute_sizes_from_data
     assert len(data) == len(opts)
 AssertionError


RecycleViewの削除処理を複数回にわたって行っても安定する方法を教えていただけるとありがたいです。

また、削除後に残った要素や、一度削除をした後にstartボタンでリセットを行うとチェックボックスにチェックが入ったままになることがあります。こちらの解決方法も教えていただけると助かります。
よろしくお願いします。

  • 気になる質問をクリップする

    クリップした質問は、後からいつでもマイページで確認できます。

    またクリップした質問に回答があった際、通知やメールを受け取ることができます。

    クリップを取り消します

  • 良い質問の評価を上げる

    以下のような質問は評価を上げましょう

    • 質問内容が明確
    • 自分も答えを知りたい
    • 質問者以外のユーザにも役立つ

    評価が高い質問は、TOPページの「注目」タブのフィードに表示されやすくなります。

    質問の評価を上げたことを取り消します

  • 評価を下げられる数の上限に達しました

    評価を下げることができません

    • 1日5回まで評価を下げられます
    • 1日に1ユーザに対して2回まで評価を下げられます

    質問の評価を下げる

    teratailでは下記のような質問を「具体的に困っていることがない質問」、「サイトポリシーに違反する質問」と定義し、推奨していません。

    • プログラミングに関係のない質問
    • やってほしいことだけを記載した丸投げの質問
    • 問題・課題が含まれていない質問
    • 意図的に内容が抹消された質問
    • 過去に投稿した質問と同じ内容の質問
    • 広告と受け取られるような投稿

    評価が下がると、TOPページの「アクティブ」「注目」タブのフィードに表示されにくくなります。

    質問の評価を下げたことを取り消します

    この機能は開放されていません

    評価を下げる条件を満たしてません

    評価を下げる理由を選択してください

    詳細な説明はこちら

    上記に当てはまらず、質問内容が明確になっていない質問には「情報の追加・修正依頼」機能からコメントをしてください。

    質問の評価を下げる機能の利用条件

    この機能を利用するためには、以下の事項を行う必要があります。

質問への追記・修正、ベストアンサー選択の依頼

  • gottadiveintopy

    2021/05/18 13:38 編集

    手前味噌なんですが RecycleView のdataをviewclassのinstanceから書き換える動画があるので参考にどうぞ。
    https://youtu.be/1iB40vjQ-yw

    キャンセル

回答 1

checkベストアンサー

+1

いくつか気付いた点を挙げますね。

errorの原因

booklist = copy.copy(Check.data)が原因くさいです。RecycleView.dataはObservableListという特殊なlistになっているので多分そのまま複製しないほうが良いですね。とりあえずこれをbooklist = list(Check.data)とする事でerrorは起きなくなります。(それでもまだprogramは期待通りには動きません)

削除時のClassとRecycleViewのClassが異なることで上手にRecycleViewのリストを更新できずに困っています。 

RecycleView.dataに何かのiterableを代入するとそこから作られたObservableListが最終的に入るので元のiterableとの結び付きは無くなりますね。

rv = RecycleView()
data = [{'number': i} for i in range(4)]
rv.data = data
assert data is not rv.data
print(type(data))
print(type(rv.data))
data.pop()
data[0]['number'] = 100
print(rv.data)
<class 'list'>
<class 'kivy.properties.ObservableList'>
[{'number': 100}, {'number': 1}, {'number': 2}, {'number': 3}]

ただ実行結果を見て分かる通りlist内の要素である辞書は共有されています。RecycleView.dataへ何かを代入した時には以上のような事が起きるので気に留めておくと良いかも知れません。

widgetの初期化が終わった後に行いたい処理はon_kv_post()で

これは今回のerrorの原因とは関係ないですが__init__()内はwidgetの初期化が完了している保証がないのでCheck.__init__()は削除して、代わりにon_kv_post()を使った方が良いです。

class Check(BoxLayout):
    def on_kv_post(self, *args, **kwargs):
        super().on_kv_post(*args, **kwargs)
        self.tmplist.data=[{"name":"1","check":False},{"name":"2","check":False},{"name":"3","check":False},{"name":"4","check":False}]
        Check.data = self.tmplist.data

無意味な代入

Check.delete()の次の行なんですが

class Check(BoxLayout):
    def delete(self):
        self.tmplist.data = Check.data

assert文を仕込むと分かる通り

class Check(BoxLayout):
    def delete(self):
        assert self.tmplist.data is Check.data
        self.tmplist.data = Check.data

同じobjectを指しています。勿論descriptorが介入するので完全に無意味とは言い切れないですが、正直何の為なのか分からない行になっています。

Check.data

おそらくFileButtonからRecycleView.dataを触りたいが為にCheck.dataというclass属性(実質global変数)を用いたんだと思いますが、これがcodeを分かりにくくしている要因の一つなので以下のように取り除くのがお薦めです。

class FileButton(BoxLayout):
    name = ObjectProperty()
    is_checked = BooleanProperty(False)

    @property
    def rv(self):
        '''これがviewclassからRecycleViewを得る方法'''
        return self.parent.recycleview

    def checkpush(self, checkbox):
        print(checkbox.active)
        for book in self.rv.data:
            if book["name"] == self.name:
                book["check"] = checkbox.active

あとはCheckclassのcode内にあるCheck.dataself.tmplist.dataに置き換えればこのclass属性を無くせます。そもそもなんですがinstance毎に作られるobjectをself経由で触れる状況でわざわざclass経由で触る理由が無いです。

if book["check"] == True:

if book["check"]:の方が読みやすい上に安全ですね。if book["check"] == True:だと book["check"]1True以外の真値が入っている時に正しく動かないです。

RecycleView.dataとview widgetの同期

最後にprogramを正しく動かすために適切に色んな物を同期してあげないといけないです。これに関しては記事を書いたことがあるので参考にしてみてください。

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2021/05/21 09:40

    ありがとうございます。
    kivyの情報が少ない中、詳しく情報をまとめてくださっているブログなどにはいつも救われています。
    特にbooklistへのコピーの仕方、viewclassからrv.dataへの関わり方は非常に助かりました。
    テストコードでは問題なく機能することが確認できたため、こちらを参考に本コードに反映してみたいと思います。

    キャンセル

15分調べてもわからないことは、teratailで質問しよう!

  • ただいまの回答率 87.61%
  • 質問をまとめることで、思考を整理して素早く解決
  • テンプレート機能で、簡単に質問をまとめられる

関連した質問

同じタグがついた質問を見る