前の頁へ   次の頁へ

老い学の捧げ物 PartⅡ

7.イベント

それでは書いた複数のメモの表示と、ファイルへの入出力ができるようにしていきます。先ずは 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を下に載せています。

main.pyです。
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()
memo.kvです。
<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のコードに比べて格段に短くなっています。何か問題があるのかもしれませんが、今のところ順調に動いていますので、このまま次へ進みます。次は、ちょっとした改良に関してです。

前のページへ   次のページへ