それでは書いた複数のメモの表示と、ファイルへの入出力ができるようにしていきます。先ずは jsonファイルから読み込んだデータをメモ一覧画面に表示しましょう。memo.kvの <AllMemoView>:の RecycleView:に viewclass: 'MemoListItem'と記述します。その状態でmain.pyを起動すると、memo.kvの65行 text: root.memo_titleでエラーが起きます。そこで main.pyの MemoListItemクラスに
memo_content = StringProperty()
memo_title = StringProperty()
memo_index = NumericProperty()
の3行を記述し、StringPropertyと NumericPropertyを追加 importします。そして main.pyを起動すると、下の画面のようになり jsonファイルから読み込んだデータがメモ一覧画面に表示されます。
次に、追加ボタンを押したときの処理です。note.kvの <AllMemoView>:の Button:に on_release: app.add_memo()と記述し、main.pyの MemoAppクラスに下のように add_memo()メソッドを作成します。 add_memo()メソッドには edit_memo()メソッドが使われていますから、 edit_memo()メソッドも併せて記述しています。
def add_memo(self): self.memos.data.append({'title': '', 'content': ''}) memo_index = len(self.memos.data) - 1 self.edit_memo(memo_index) def edit_memo(self, memo_index): memo = self.memos.data[memo_index] name = 'memo{}'.format(memo_index) if self.root.has_screen(name): self.root.remove_widget(self.root.get_screen(name)) view = MemoView( name=name, memo_index=memo_index, memo_title=memo.get('title'), memo_content=memo.get('content')) self.root.add_widget(view) self.transition.direction = 'left' self.root.current = view.name
追加ボタンを押すとメモ入力画面に遷移するはずですが、今は ”TypeError: object.__init__() takes exactly one argument (the instance to initialize)”とのエラーが起こります。そこで main.pyの MemoViewクラスに
memo_index = NumericProperty()
memo_title = StringProperty()
memo_content = StringProperty()
の三行を記述して main.pyを起動し、追加ボタンを押すと下の画面に遷移します。うまくいきました。
次は内容ボタンを押したときの処理です。memo.kvの <MemoListItem>:の Button:に on_release: app.edit_memo(root.memo_index)と追加します。そして main.pyを起動し、表示されたメモリストから選んで内容ボタンを押すと、画面遷移してそのメモの内容が表示されます。うまくいきました。
しかしメモ表示画面では、リストボタンや削除ボタンを押しても何も起きません。そこで次はリストボタンの処理です。memo.kvの <MemoView>:の Buttonに on_release: app.go_memos()と追加し、main.pyの MemoAppクラスに下記の go_memos()メソッドを作成します。メモ表示画面でリストボタンを押すとメモ一覧画面に遷移しますね。追加ボタンとリストボタンを押すことで表示画面が交互に遷移することがわかります。
def go_memos(self): self.transition.direction = 'right' self.root.current = 'memos'
次は削除ボタンを押したときの処理です。memo.kvの <MemoView >:の Button:に on_release: app.del_memo(root.memo_index)と追加し、main.pyの MemoAppクラスに下記の del_memo()メソッドを作成します。 del_memo()メソッドには refresh_memos()メソッドと save_memos()メソッドが使われていますから、これらも併せて記述しています。さらに、memo.kvの <AllMemoView>:の Button:に on_release: app.add_memo()と追加します。これで削除ボタンが機能するようになります。
def del_memo(self, memo_index): del self.memos.data[memo_index] self.save_memos() self.refresh_memos() self.go_memos() def refresh_memos(self): data = self.memos.data self.memos.data = [] self.memos.data = data def save_memos(self): with open(self.FILENAME, 'w') as fd: json.dump(self.memos.data, fd)
しかし、まだメモの編集や新規追加ができません。そこで memo.kvの <MemoView>:に
on_memo_content: app.set_memo_content(self.memo_index, self.memo_content)
on_memo_title: app.set_memo_title(self.memo_index, self.memo_title)
の二行を記述します。そして main.pyの memoAppクラスに set_memo_content()と set_memo_title()の二つのメソッドを作成します。
def set_memo_title(self, memo_index, memo_title): self.memos.data[memo_index]['title'] = memo_title self.save_memos() self.refresh_memos() def set_memo_content(self, memo_index, memo_content): self.memos.data[memo_index]['content'] = memo_content data = self.memos.data self.memos.data = [] self.memos.data = data self.save_memos() self.refresh_memos()
しかしこれだけではまだ不十分で、メモの編集後のTextInputの変更をキャッチしなければなりません。そこで memo.kvの TextInputに on_text:イベントを記述します。
<memoView>:のタイトル用の TextInput には、 on_text: root.memo_title = self.text
<memoView>:のメモ内容用の TextInput には、 on_text: root.memo_content = self.text
と追加しましょう。
これで各Buttonと各TextInputが正常に機能するようになりました。いろいろと試してみてください。現時点でのmain.pyとmemo.kvを下に載せています。
import json from kivy.app import App from kivy.uix.screenmanager import ScreenManager, Screen, SlideTransition from kivy.properties import ListProperty, AliasProperty, StringProperty, NumericProperty from kivy.uix.boxlayout import BoxLayout import urllib.request from kivy.core.text import LabelBase, DEFAULT_FONT from kivy.resources import resource_add_path resource_add_path("font") LabelBase.register(DEFAULT_FONT, "ipaexg.ttf") class MemoView(Screen): memo_index = NumericProperty() memo_title = StringProperty() memo_content = StringProperty() class MemoListItem(BoxLayout): memo_content = StringProperty() memo_title = StringProperty() memo_index = NumericProperty() class AllMemoView(Screen): data = ListProperty() def get_data(self): return [{ 'memo_index': index, 'memo_content': item['content'], 'memo_title': item['title']} for index, item in enumerate(self.data)] dataAllMemos = AliasProperty(get_data, bind=['data']) class MemoApp(App): FILENAME="data/memos.json" def build(self): self.memos = AllMemoView(name='memos') self.load_memos() self.transition = SlideTransition(duration=.35) root = ScreenManager(transition=self.transition) root.add_widget(self.memos) return root def load_memos(self): fd = open(self.FILENAME, mode='r') data = json.load(fd) fd.close() self.memos.data = data def save_memos(self): with open(self.FILENAME, 'w') as fd: json.dump(self.memos.data, fd) def add_memo(self): self.memos.data.append({'title': '', 'content': ''}) memo_index = len(self.memos.data) - 1 self.edit_memo(memo_index) def edit_memo(self, memo_index): memo = self.memos.data[memo_index] name = 'memo{}'.format(memo_index) if self.root.has_screen(name): self.root.remove_widget(self.root.get_screen(name)) view = MemoView( name=name, memo_index=memo_index, memo_title=memo.get('title'), memo_content=memo.get('content')) self.root.add_widget(view) self.transition.direction = 'left' self.root.current = view.name def go_memos(self): self.transition.direction = 'right' self.root.current = 'memos' def del_memo(self, memo_index): del self.memos.data[memo_index] self.save_memos() self.refresh_memos() self.go_memos() def set_memo_title(self, memo_index, memo_title): self.memos.data[memo_index]['title'] = memo_title self.save_memos() self.refresh_memos() def set_memo_content(self, memo_index, memo_content): self.memos.data[memo_index]['content'] = memo_content data = self.memos.data self.memos.data = [] self.memos.data = data self.save_memos() self.refresh_memos() def refresh_memos(self): data = self.memos.data self.memos.data = [] self.memos.data = data if __name__ == '__main__': MemoApp().run()
<Screen>: canvas: Color: rgb: .2, .2, .2 Rectangle: size: self.size <MemoView>: on_memo_content: app.set_memo_content(self.memo_index, self.memo_content) on_memo_title: app.set_memo_title(self.memo_index, self.memo_title) BoxLayout: orientation: 'vertical' BoxLayout: orientation: 'horizontal' size_hint_y: None height: '48dp' padding: '2dp' canvas: Color: rgb: 0, 1, 0 Rectangle: pos: self.pos size: self.size Button: text: 'リスト' size_hint_x: None width: self.height on_release: app.go_memos() TextInput: id: text_box text: root.memo_title font_size: '16sp' multiline: False hint_text: 'タイトル' on_text: root.memo_title = self.text Button: text: '消去' size_hint_x: None width: self.height on_release: app.del_memo(root.memo_index) TextInput: id: text_box_2 font_size: '16sp' multiline: True hint_text: 'メモ内容' background_color: .8,.8,.9,1 text: root.memo_content on_text: root.memo_content = self.text <MemoListItem>: height: '48sp' size_hint_y: None canvas: Color: rgb: .3, .3, .3 Rectangle: pos: self.pos size: self.width, 1 BoxLayout: padding: '5dp' Label: text: root.memo_title Button: text: '内容' size_hint_x: None width: self.height on_release: app.edit_memo(root.memo_index) <AllMemoView>: BoxLayout: orientation: 'vertical' BoxLayout: orientation: 'horizontal' size_hint_y: None height: '48dp' padding: '5dp' canvas: Color: rgb: .3, .3, .3 # list表示画面上段の背景色 文字色は白 Rectangle: pos: self.pos size: self.size Image: source: 'data/icon.png' mipmap: True size_hint_x: None width: self.height Label: text: '全メモリスト' font_size: '16sp' Button: text: '追加' size_hint_x: None width: self.height on_release: app.add_memo() RecycleView: data: root.dataAllMemos viewclass: 'MemoListItem' RecycleBoxLayout: default_size: None, dp(56) default_size_hint: 1, None size_hint_y: None height: self.minimum_height orientation: 'vertical' spacing: dp(2)
上のコードは、日本語処理を追加したにも関わらず、Noteのコードに比べて格段に短くなっています。何か問題があるのかもしれませんが、今のところ順調に動いていますので、このまま次へ進みます。次は、ちょっとした改良に関してです。