kivy

kivy戦記(16-11) 入力チェックと、ScreenManager管理の画面間データの受け渡し

kivy
この記事は約24分で読めます。

例えば、この画面

このフィールドのパスの「左巻きなると」(実在する)を、「why」(実在しない)に書き直す。

すると、、、

・・・ここはどこだ?

とりあえず遡ろう。

 

 

落ちなかったけど、変な方向に行くので、やはりチェックをかけよう。

今回入力値を日本語にしようとしたが、漢字の入力が出来なかった。(Ubuntu環境)

これがkivyのバグとして有名な「日本語の入力が出来ない」というものだと思う。

実際に直面して正直愕然とした。
これでは実用性に欠けると。
おそらく、中国語や韓国語など、マルチバイト入力は全滅ではないかと思う。

ただし、どうやらkivyの次のバージョン(1.11?)で対策できるようである。
(Kivyで日本語入力を行う方法について(実行編) https://qiita.com/dario_okazaki/items/815cdebe91211f2b3826)

もともとこのkivy戦記シリーズは「できるだけ日本語を使おう」と言うことでやってきた。
ちなみに、今回「why」と入力したが、入れたかった文字は「美しい俺」である。

では、修正の方針はこうしておきましょう。
A:「・・・ボタン」(setSelect())
ボタンが押された段階で、開始パスフィールド入力値が存在するかをチェックを行い、存在しなかったらこのように処理をする。
  • エラーメッセージを出す。
  • フィールド入力値は、前回入力値に戻す
B:「OKボタン」(selfOK())
ボタンが押された段階で、SetSCNのすべてのフィールド入力値(現状では1つだけ)が正しいか?(現状ではフィールド入力値が存在するか)のチェックを行い、正しくなかったらこのように処理をする。
  • すべての問題フィールドについて、エラーメッセージを出す。
  • 問題のあったフィールドは、前回入力値に戻す。
  • 画面の遷移を行わない。

こうした場合、必要なのは、

  • 開始パスチェックロジック(A,Bとも)
  • 開始パスフィールドの前回値

である。

前回値は、このA,BでチェックしたときにOKしたときに、更新することにしておこう。

 

・・・で、なにげなく「前回値」と書いたんだけど、この前回値自体どこに書けばいいんだろう?

これが、screenmanagerを使っているとわかりにくい。

ちなみに画面にダミーのフィールド(高さ・幅が0)を設けた場合、

右上の所に、隠しているはずの前回値が!

どうしよう。

 

画面経由でないと、値が渡せないのか?

そうだ。コロンブスの卵的に。ScreenManagerのオブジェクトそのものに、前回値を入れてしまえ。

試してみたが、これでOKだった。

大量にデータがある場合は、工夫が必要だとおもうが、これでなんとかなりそう。

 

filelist.py


import os
import sys
import json
from kivy.app import App
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.popup import Popup
from kivy.uix.treeview import TreeViewLabel
from kivy.uix.screenmanager import ScreenManager, Screen
from kivy.properties import ObjectProperty, StringProperty
from kivy.clock import Clock
from pprint import pprint
from kivy.core.text import LabelBase, DEFAULT_FONT
from kivy.resources import resource_add_path
# 日本語フォント設定
resource_add_path('./fonts')
LabelBase.register(DEFAULT_FONT, 'ipaexg.ttf')
sm = ScreenManager()
path_inf = 'path_inf.json'
class MsgboxOk(BoxLayout):
    message_text = StringProperty()
    ok_button = ObjectProperty(None)
class MsgboxOkCancel(BoxLayout):
    message_text = StringProperty()
    ok_button = ObjectProperty(None)
    cancel_button = ObjectProperty(None)
class SetSCN(Screen):
    begin_path_dsp = ObjectProperty(None)
    def __init__(self, **kwargs):
        super(SetSCN, self).__init__(**kwargs)
    def setBeginPathCheck(self):
        if os.path.isdir(self.begin_path_dsp.text) == False:
            return False
        return True
    def setSelect(self):
        begin_path_result = self.setBeginPathCheck()
        if begin_path_result == True:
            self._popup = LstPOP(load=self.setSelectLoad, \
                                 cancel=self.setSelectCancel, \
                                 start_path=self.begin_path_dsp.text)
            self._popup.open()
        else:
            self.msg = '入力した開始パスが存在しません\n\n'
            self.msg += self.begin_path_dsp.text
            content = MsgboxOk \
                (ok_button=self.msgTestOk, \
                 message_text=self.msg)
            self.popup = Popup(title='入力エラー', content=content)
            self.popup.open()
            self.begin_path_dsp.text = sm.begin_path_previous
    def setSelectLoad (self, path, filename):
        self.begin_path_dsp.text = path
sm.begin_path_previous = path self._popup.dismiss() def setSelectCancel(self): self._popup.dismiss() def msgTestOk(self): pprint ('OKが押されました') self.popup.dismiss() def msgTestCancel(self): pprint ('CANCELが押されました') self.popup.dismiss() def selfOk(self): begin_path_result = self.setBeginPathCheck() if begin_path_result == True: self.bl = {} self.bl['begin_path'] = self.begin_path_dsp.text pprint (self.bl) try: #raise OverflowError('やっちまったな') with open(path_inf, 'w') as fd: json.dump(self.bl, fd) except PermissionError as err: self.msg = '保存データを書き込もうとしましたが、書き込み許可がありません。\n' self.msg += 'パスは、' + self.bl['begin_path'] + 'です。\n' self.msg += '保存先の書き込み許可を確認するか、パスが適当かを確認してください。\n\n' self.msg += str(err) content = MsgboxOk \ (ok_button= self.msgTestOk, \ message_text= self.msg) self.popup = Popup(title='書き込み許可エラー', content=content) self.popup.open() except IOError as err: self.msg = '保存データを書き込もうとしましたが、書き込めません。\n' self.msg += '書き込み先機器に問題があるようです。\n' self.msg += '書き込み先機器の状態を確認してください。\n\n' self.msg += str(err) content = MsgboxOk \ (ok_button= self.msgTestOk, \ message_text= self.msg) self.popup = Popup(title='I/Oエラー', content=content) self.popup.open() except BaseException as err: self.msg = '保存データを書き込もうとしましたが、何らかのエラーが起きました。\n\n' self.msg += str(err) content = MsgboxOk \ (ok_button= self.msgTestOk, \ message_text= self.msg) self.popup = Popup(title='その他のエラー', content=content) self.popup.open() finally: sm.current = 'all' else: self.msg = '入力した開始パスが存在しません\n\n' self.msg += self.begin_path_dsp.text content = MsgboxOk \ (ok_button=self.msgTestOk, \ message_text=self.msg) self.popup = Popup(title='入力エラー', content=content) self.popup.open() self.begin_path_dsp.text = sm.begin_path_previous def selfCancel(self): sm.current = 'all' class LstPOP(Popup): #current_dir = os.path.dirname(os.path.abspath(__file__)) load = ObjectProperty(None) cancel = ObjectProperty(None) start_path = ObjectProperty(None) def __init__(self, **kwargs): super(LstPOP, self).__init__(**kwargs) self.title ='読み込み中 ' + self.start_path def is_dir(self, dirname, filename): # ディスレクトリならTrueを返す return os.path.isdir(os.path.join(dirname, filename)) def lstChooser(self, path, selection): if 0 < len(selection): self.title = '読み込み中 ' + selection[0] class AllSCN(Screen): tv = ObjectProperty(None) file_name = ObjectProperty(None) loadfile = ObjectProperty(None) text_input = ObjectProperty(None) def msgOsErrorOk(self): print('OKが押されました') self.popup.dismiss() sys.exit() def begin_display(self, dt): # # SetSCNの最初期内容 # self.init_path = '' thisos = os.name if thisos == 'posix': self.init_path = os.environ.get('HOME') elif thisos == 'nt': self.init_path = os.environ.get('HOMEDRIVE') + os.environ.get('HOMEPATH') else: msg = '内部OS判定(os.name)が、"' + thisos + '”でした。\n' msg += 'サポートしているOSは、UNIX系統と、Windowsです。\n' msg += 'したがってこのOSはサポート外です。すみません。' content = MsgboxOk \ (ok_button= self.msgOsErrorOk, \ message_text= msg) self.popup = Popup(title='OSサポートエラー', content=content) self.popup.open() return #self.manager.get_screen('set').begin_path_dsp.text = self.init_path # # AllSCNのtree処理 # self.tv.add_node(TreeViewLabel(text ='シンカリオン E1 とき')) self.tv.add_node(TreeViewLabel(text ='シンカリオン 0 ひかり')) self.tv.add_node(TreeViewLabel(text ='シンカリオン キハ32 鉄道ホビートレイン')) def setupButtonClicked(self): # # SetSCNへの画面遷移時の内容 # try: #raise OverflowError('やっちまったな') with open(path_inf, 'r') as fd: path_json = json.load(fd) self.manager.get_screen('set').begin_path_dsp.text = path_json['begin_path'] sm.begin_path_previous = path_json['begin_path'] sm.current = 'set' except FileNotFoundError as err: self.manager.get_screen('set').begin_path_dsp.text = self.init_path sm.begin_path_previous = self.init_path sm.current = 'set' except PermissionError as err: self.msg = '保存データを読み込もうとしましたが、読み込み許可がありません。\n' self.msg += 'パスは、' + self.bl['begin_path'] + 'です。\n' self.msg += '保存先の読み込み許可を確認するか、パスが適当かを確認してください。\n\n' self.msg += str(err) content = MsgboxOk \ (ok_button= self.msgTestOk, \ message_text= self.msg) self.popup = Popup(title='読み込み許可エラー', content=content) self.popup.open() except IOError as err: self.msg = '保存データを読み込もうとしましたが、読み込めません。\n' self.msg += '読み込み先機器に問題があるようです。\n' self.msg += '読み込み先機器の状態を確認してください。\n\n' self.msg += str(err) content = MsgboxOk \ (ok_button= self.msgTestOk, \ message_text= self.msg) self.popup = Popup(title='I/Oエラー', content=content) self.popup.open() except BaseException as err: self.msg = '保存データを読み込もうとしましたが、何らかのエラーが起きました。\n\n' self.msg += str(err) content = MsgboxOk \ (ok_button= self.msgTestOk, \ message_text= self.msg) self.popup = Popup(title='その他のエラー', content=content) self.popup.open() else: sm.current = 'set' def show_load(self): # 暫定ロジックです self._popup = LstPOP( load = self.load, \ cancel = self.dismiss_popup, \ start_path='あああ') self._popup.open() def load (self, path, filename): pprint(filename) #self.file_name.text = filename[0] self.file_name.text = path #with open (os.path.join(path, filename[0])) as stream: # self.text_input.text = stream.read() self.dismiss_popup() def dismiss_popup(self): self._popup.dismiss() def msgTestOk(self): pprint ('OKが押されました') self.popup.dismiss() def msgTestCancel(self): pprint ('CANCELが押されました') self.popup.dismiss() class FilelistApp(App): def build(self): allscn = AllSCN() setscn = SetSCN() Clock.schedule_once(allscn.begin_display, 0) sm.add_widget(allscn) sm.add_widget(setscn) return sm if __name__ == '__main__': FilelistApp().run()

 

filelist.kvは、実質上変更なし。

これで、起動。それで「設定」ボタン押下。

「開始パス」に存在しないパスを入力。

そして「・・・」ボタンを押下。

エラーメッセージが出ることを確認したら、「OK」を押す。

開始パスの値が前回値(左巻きなると)になっていることを確認して、再度「・・・」を押下。

「太公望」ディレクトリに移動する。

「読み込み」押下。

「開始パス」が「太公望」になっていることを確認した後、存在しないディレクトリを入力して「OK」を押下。

エラーメッセージを確認したら「OK」

「開始パス」が「太公望」になっていることを確認して、「OK」押下。

開始画面に戻ったら、再度「設定」を押下。

「開始パス」が、「太公望」になっていることを確認。

 

これで、SetSCNの完成である。

長くかかったなー。

 

ロジックの説明としては、まず画面チェックロジックの整理。

入力されたファイルがあるかの、共通のエラーチェックロジックを作成する。

 


class SetSCN(Screen):
    begin_path_dsp = ObjectProperty(None)
    def __init__(self, **kwargs):
        super(SetSCN, self).__init__(**kwargs)
    def setBeginPathCheck(self):
        if os.path.isdir(self.begin_path_dsp.text) == False:
            return False
        return True

 

ただし非常に短いので、本当に共通化すべきか悩んだところ。

その後、これを使ってエラーチェックロジックを書き換える。


class SetSCN(Screen):
    (省略ですわよ)
    def setSelect(self):
        begin_path_result = self.setBeginPathCheck()
        if begin_path_result == True:
            self._popup = LstPOP(load=self.setSelectLoad, \
                                 cancel=self.setSelectCancel, \
                                 start_path=self.begin_path_dsp.text)
            self._popup.open()
        else:
            self.msg = '入力した開始パスが存在しません\n\n'
            self.msg += self.begin_path_dsp.text
            content = MsgboxOk \
                (ok_button=self.msgTestOk, \
                 message_text=self.msg)
            self.popup = Popup(title='入力エラー', content=content)
            self.popup.open()
            self.begin_path_dsp.text = sm.begin_path_previous

 

 


class SetSCN(Screen):
    (省略しましたわよ)
    def selfOk(self):
        begin_path_result = self.setBeginPathCheck()
        if begin_path_result == True:
            self.bl = {}
            self.bl['begin_path'] = self.begin_path_dsp.text
            pprint (self.bl)
            try:
                #raise OverflowError('やっちまったな')
      (省略ですわ)
        else:
            self.msg = '入力した開始パスが存在しません\n\n'
            self.msg += self.begin_path_dsp.text
            content = MsgboxOk \
                (ok_button=self.msgTestOk, \
                 message_text=self.msg)
            self.popup = Popup(title='入力エラー', content=content)
            self.popup.open()
            self.begin_path_dsp.text = sm.begin_path_previous

エラーメッセージは、将来開始パス以外の項目が追加されるのに備えて、あえて共通化せず。

で、一番最後でしらっと出ているsm.begin_path_previousに前回値を入れているので、これを使って更新。

ちなみにこのsm.begin_path_previousを設定しているのは、json読み込み部


class AllSCN(Screen):
(省略だわ)
    def setupButtonClicked(self):
        #
        # SetSCNへの画面遷移時の内容
        #
        try:
            #raise OverflowError('やっちまったな')
            with open(path_inf, 'r') as fd:
                path_json = json.load(fd)
                self.manager.get_screen('set').begin_path_dsp.text = path_json['begin_path']
                sm.begin_path_previous = path_json['begin_path']
                sm.current = 'set'
        except FileNotFoundError as err:
            self.manager.get_screen('set').begin_path_dsp.text = self.init_path
            sm.begin_path_previous = self.init_path
            sm.current = 'set'

と、入力更新部である


class SetSCN(Screen):
(省略ね)
    def setSelect(self):
        begin_path_result = self.setBeginPathCheck()
        if begin_path_result == True:
            self._popup = LstPOP(load=self.setSelectLoad, \
                                 cancel=self.setSelectCancel, \
                                 start_path=self.begin_path_dsp.text)
            self._popup.open()
            (省略よ)
    def setSelectLoad (self, path, filename):
        self.begin_path_dsp.text = path
        sm.begin_path_previous = path
        self._popup.dismiss()

 

である。

今回ScreenManager管理の2つの画面のデータの受け渡しをどうしようか悩んだのですが、どうやらこれでいけるようだ。

 

タイトルとURLをコピーしました