それでは書いた複数のメモの表示と、ファイルへの入出力ができるようにしていきます。先ずは 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のコードに比べて格段に短くなっています。何か問題があるのかもしれませんが、今のところ順調に動いていますので、このまま次へ進みます。次は、ちょっとした改良に関してです。