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

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

新規登録して質問してみよう
ただいま回答率
85.50%
Kivy

Kivyは、Pythonを用いたNUI開発のためのオープンソースフレームワーク。マルチタッチなど多くの入力に対応したNUIアプリなどを開発することができます。多くの環境で動作するクロスプラットフォームです。

Python 3.x

Python 3はPythonプログラミング言語の最新バージョンであり、2008年12月3日にリリースされました。

Q&A

解決済

1回答

852閲覧

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

ulthar

総合スコア8

Kivy

Kivyは、Pythonを用いたNUI開発のためのオープンソースフレームワーク。マルチタッチなど多くの入力に対応したNUIアプリなどを開発することができます。多くの環境で動作するクロスプラットフォームです。

Python 3.x

Python 3はPythonプログラミング言語の最新バージョンであり、2008年12月3日にリリースされました。

0グッド

0クリップ

投稿2021/05/17 07:43

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

main.py

main

1from kivy.app import App 2from kivy.uix.boxlayout import BoxLayout 3from kivy.uix.recycleview import RecycleView 4from kivy.properties import ObjectProperty,BooleanProperty 5from kivy.uix.popup import Popup 6import copy 7class TestCheck(App): 8 def __init__(self, **kwargs): 9 super(TestCheck, self).__init__(**kwargs) 10 self.title = "Test" 11class Start(BoxLayout): 12 def start(self): 13 content=Check() 14 self.popup=Popup(title="start",content=content) 15 self.popup.open() 16class Check(BoxLayout): 17 tmplist=ObjectProperty() 18 def __init__(self,**kwargs): 19 super(Check, self).__init__(**kwargs) 20 self.tmplist.data=[{"name":"1","check":False},{"name":"2","check":False},{"name":"3","check":False},{"name":"4","check":False}] 21 Check.data = self.tmplist.data 22 def delete(self): 23 print(self.tmplist.data) 24 booklist = copy.copy(Check.data) 25 print(booklist) 26 27 for book in booklist: 28 id_num = Check.data.index(book) 29 print(book["name"],book["check"]) 30 if book["check"] == True: 31 Check.data.pop(id_num) 32 print("book{} delete!".format(book["name"])) 33 self.tmplist.data=Check.data 34 print(Check.data) 35 print(self.tmplist.data) 36 def restart(self): 37 Start.start(self) 38class FileButton(BoxLayout): 39 name = ObjectProperty() 40 is_checked = BooleanProperty(False) 41 def checkpush(self,checkbox): 42 print(checkbox.active) 43 for book in Check.data: 44 if book["name"] == self.name: 45 book["check"]=checkbox.active 46 47 48if __name__ == "__main__": 49 TestCheck().run()

testcheck.kv

testcheck

1Start 2<Start> 3 BoxLayout: 4 Button: 5 on_press: root.start() 6<Check> 7 tmplist: tmp_list 8 BoxLayout: 9 Label: 10 text: "test" 11 RecycleView: 12 id: tmp_list 13 viewclass: "FileButton" 14 RecycleBoxLayout: 15 orientation: "vertical" 16 default_size_hint: 1,None 17 default_size: None,dp(45) 18 size_hint_y: None 19 scaping: 5 20 height: self.minimum_height 21 padding: 10 22 Button: 23       text: "delete" 24 on_press: root.delete() 25 Button: 26 text: "start" 27 on_press: root.restart() 28<FileButton> 29 name: "" 30 CheckBox: 31 active: root.is_checked 32 on_press: root.checkpush(self) 33 Label: 34 text: root.name 35

簡略化してありますが、起動後ボタンを押すとポップアップを表示、ポップアップ内に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ボタンでリセットを行うとチェックボックスにチェックが入ったままになることがあります。こちらの解決方法も教えていただけると助かります。
よろしくお願いします。

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

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

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

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

guest

回答1

0

ベストアンサー

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

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との結び付きは無くなりますね。

python

1rv = RecycleView() 2data = [{'number': i} for i in range(4)] 3rv.data = data 4assert data is not rv.data 5print(type(data)) 6print(type(rv.data)) 7data.pop() 8data[0]['number'] = 100 9print(rv.data)

text

1<class 'list'> 2<class 'kivy.properties.ObservableList'> 3[{'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()の次の行なんですが

python

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

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

python

1class Check(BoxLayout): 2 def delete(self): 3 assert self.tmplist.data is Check.data 4 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/20 13:08

gottadiveintopy

総合スコア736

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

ulthar

2021/05/21 00:40

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

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

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

ただいまの回答率
85.50%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問