前の頁へ   次の頁へ

老い学の捧げ物 PartⅡ

1.notes (メモ帳)の説明

PartⅠでは PyGame を取り上げました。今回は、スマホのアプリ作成に使える Kivy を取り上げます。kivyは pythonの中でも特殊な形態をもつフレームワークで、Gは使いこなすのに四苦八苦しています。先ずは、example-tutorialsにある notes (メモ帳)のコードを学びましょう。notes (メモ帳)の最終コードは "https://github.com/kivy/kivy/tree/master/examples/tutorials/notes/final" にあります。
以下、この notes (メモ帳)を Noteと呼びます。Noteの初期画面は以下のようになります。この画面をメモ一覧画面とよびます。

[+]ボタンを押すと次の画面に切り替わります。この画面をメモ入力画面とよびます。

メモ入力画面でNew Noteと表示されている箇所にメモのタイトルを記入し、下の空欄にメモの内容を記入します。[x]ボタンを押せば記入した内容が消去されます。[<]ボタンを押せば、下にメモのタイトルが表示されたメモ一覧画面に切り替わります。[+]ボタンを押せばメモを追加できます。
なお、以下に Noteの main.pyと note.kvを掲載しておきます。

main.pyです。
'''
Notes
=====
Simple application for reading/writing notes.
'''
__version__ = '1.0'

import json
from os.path import join, exists
from kivy.app import App
from kivy.uix.screenmanager import ScreenManager, Screen, SlideTransition
from kivy.properties import ListProperty, StringProperty, \
        NumericProperty, BooleanProperty, AliasProperty
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.floatlayout import FloatLayout
from kivy.clock import Clock


class MutableTextInput(FloatLayout):

    text = StringProperty()
    multiline = BooleanProperty(True)

    def __init__(self, **kwargs):
        super(MutableTextInput, self).__init__(**kwargs)
        Clock.schedule_once(self.prepare, 0)

    def prepare(self, *args):
        self.w_textinput = self.ids.w_textinput.__self__
        self.w_label = self.ids.w_label.__self__
        self.view()

    def on_touch_down(self, touch):
        if self.collide_point(*touch.pos) and touch.is_double_tap:
            self.edit()
        return super(MutableTextInput, self).on_touch_down(touch)

    def edit(self):
        self.clear_widgets()
        self.add_widget(self.w_textinput)
        self.w_textinput.focus = True

    def view(self):
        self.clear_widgets()
        if not self.text:
            self.w_label.text = "Double tap/click to edit"
        self.add_widget(self.w_label)

    def check_focus_and_view(self, textinput):
        if not textinput.focus:
            self.text = textinput.text
            self.view()


class NoteView(Screen):

    note_index = NumericProperty()
    note_title = StringProperty()
    note_content = StringProperty()


class NoteListItem(BoxLayout):
    note_content = StringProperty()
    note_title = StringProperty()
    note_index = NumericProperty()


class Notes(Screen):

    data = ListProperty()

    def _get_data_for_widgets(self):
        return [{
            'note_index': index,
            'note_content': item['content'],
            'note_title': item['title']}
            for index, item in enumerate(self.data)]

    data_for_widgets = AliasProperty(_get_data_for_widgets, bind=['data'])


class NoteApp(App):

    def build(self):
        self.notes = Notes(name='notes')
        self.load_notes()

        self.transition = SlideTransition(duration=.35)
        root = ScreenManager(transition=self.transition)
        root.add_widget(self.notes)
        return root

    def load_notes(self):
        if not exists(self.notes_fn):
            return
        with open(self.notes_fn) as fd:
            data = json.load(fd)
            # fd = 'C:\\Users\\user\\AppData\\Roaming\\note\\notes.json'

        self.notes.data = data
#        print(self.notes.data)
        '''
[{'title': 'a', 'content': '0001'}, {'title': 'b', 'content': '0002'}, {'title': 'c', 'content': '0003'}, {'title': 'd', 'content': '0004'}]
        '''
    def save_notes(self):
        with open(self.notes_fn, 'w') as fd:
            json.dump(self.notes.data, fd)

    def del_note(self, note_index):
        del self.notes.data[note_index]
        self.save_notes()
        self.refresh_notes()
        self.go_notes()

    def edit_note(self, note_index):
        note = self.notes.data[note_index]
        name = 'note{}'.format(note_index)

        if self.root.has_screen(name):
            self.root.remove_widget(self.root.get_screen(name))

        view = NoteView(
            name=name,
            note_index=note_index,
            note_title=note.get('title'),
            note_content=note.get('content'))

        self.root.add_widget(view)
        self.transition.direction = 'left'
        self.root.current = view.name

    def add_note(self):
        self.notes.data.append({'title': 'New note', 'content': ''})
        note_index = len(self.notes.data) - 1
        self.edit_note(note_index)

    def set_note_content(self, note_index, note_content):
        self.notes.data[note_index]['content'] = note_content
        data = self.notes.data
        self.notes.data = []
        self.notes.data = data
        self.save_notes()
        self.refresh_notes()

    def set_note_title(self, note_index, note_title):
        self.notes.data[note_index]['title'] = note_title
        self.save_notes()
        self.refresh_notes()

    def refresh_notes(self):
        data = self.notes.data
        self.notes.data = []
        self.notes.data = data

    def go_notes(self):
        self.transition.direction = 'right'
        self.root.current = 'notes'

    @property
    def notes_fn(self):
        return join(self.user_data_dir, 'notes.json')


if __name__ == '__main__':
    NoteApp().run()
note.kvです。
#:kivy 1.7.1
#:import Factory kivy.factory.Factory

 <Screen >:
    canvas:
        Color:
            rgb: .2, .2, .2
        Rectangle:
            size: self.size

 <MutableLabelTextInput@MutableTextInput >:
    Label:
        id: w_label
        pos: root.pos
        text: root.text

    TextInput:
        id: w_textinput
        pos: root.pos
        text: root.text
        multiline: root.multiline
        on_focus: root.check_focus_and_view(self)

 <MutableRstDocumentTextInput@MutableTextInput >:
    RstDocument:
        id: w_label
        pos: root.pos
        text: root.text

    TextInput:
        id: w_textinput
        pos: root.pos
        text: root.text
        multiline: root.multiline
        on_focus: root.check_focus_and_view(self)


 <NoteView >:

    on_note_content: app.set_note_content(self.note_index, self.note_content)
    on_note_title: app.set_note_title(self.note_index, self.note_title)

    BoxLayout:

        orientation: 'vertical'

        BoxLayout:

            orientation: 'horizontal'
            size_hint_y: None
            height: '48dp'
            padding: '5dp'

            canvas:
                Color:
                    rgb: .3, .3, .3
                Rectangle:
                    pos: self.pos
                    size: self.size

            Button:
                text: '<'
                size_hint_x: None
                width: self.height
                on_release: app.go_notes()

            TextInput:
                text: root.note_title
                font_size: '16sp'
                multiline: False
                on_text: root.note_title = self.text

            Button:
                text: 'X'
                size_hint_x: None
                width: self.height
                on_release: app.del_note(root.note_index)

        TextInput:
            text: root.note_content
            on_text: root.note_content = self.text

 <NoteListItem >:

    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.note_title

        Button:
            text: '>'
            size_hint_x: None
            width: self.height
            on_release: app.edit_note(root.note_index)

 <Notes >:

    BoxLayout:

        orientation: 'vertical'

        BoxLayout:

            orientation: 'horizontal'
            size_hint_y: None
            height: '48dp'
            padding: '5dp'

            canvas:
                Color:
                    rgb: .3, .3, .3
                Rectangle:
                    pos: self.pos
                    size: self.size

            Image:
                source: 'data/icon.png'
                mipmap: True
                size_hint_x: None
                width: self.height

            Label:
                text: 'Notes'
                font_size: '16sp'

            Button:
                text: '+'
                size_hint_x: None
                width: self.height
                on_release: app.add_note()

        RecycleView:
            data: root.data_for_widgets
            viewclass: 'NoteListItem'
            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のコードを参考にしながら Noteに準じたアプリを作成していきます。Kivyの文法に慣れるために、先ずはメモ内容を記入するメモ記入画面を作成しましょう。

2.Kivyの使い方

pythonは既にインストール済なので、kivy(2.0.0)をインストールします。pipで簡単にインストールできるはずですが、もしできなかったら
"https://creepfablic.site/2021/05/02/python-kivy-windows-mac-install/"をご覧ください。

無事にインストールが完了したら、テスト画面を表示させましょう。
kivyでは、pythonコード(main.py)と kvコード(*.kv)の二つを用います。pythonコードだけで作成することもできますが、Gは二つのファイルに分けて作成することにします。

pythonコード(main.py)です。

from kivy.app import App

class MemoApp(App):	
    pass

if __name__ == '__main__':
    MemoApp().run()

kivy.appから Appをインポートしています。この文は、kivyを使う上で必須となります。
Appを継承してクラス名(MemoApp)を定義します。今は何もしないので passとだけ記載します。
if文以下は呪文です。

kvコード(memo.kv)です。2行だけです。

Label:
    text: "Hello World"
 

"Hello World"と表示するラベルを配置させるコードです。
kvファイルには、表示するウィジェット(ここでは Labal)と、そのプロパティ(ここでは text)が書かれます。またファイル名は、pythonファイルのクラス名(MemoApp)から Appを除いた名称を小文字で記します。任意のファイル名を使う場合は、pyファイルに Builder.load_file('ファイル名.kv')と指定すれば OKです。
この二つのファイルを同じフォルダに配置し、コマンドプロンプトで python main.pyと打てば、中央に "Hello World"と書かれたラベルをもつ画像が表示されます。

前の頁へ   次の頁へ