kivy

kivy戦記(19-4) kv言語で子を作ったときの初期化方法

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

その後、さらに簡略化したソースでteratailで質問(kv言語で、Pythonコードからのデータの受け渡しができずに、悩んでいます。https://teratail.com/questions/145166?modal=r-comp)したのですが、回答をいただきました。

要は、kv言語でWidgetの子を作ったときの初期化のやり方が、そうでないときと異なり、子を作ったときはkv言語で適用されるのが遅れるのだそうで。

ありがとうございます。

 

さて、これを反映したときのkv19d.pyをkv19d2.pyとすると、


from kivy.app import App
from kivy.lang import Builder
from kivy.properties import ObjectProperty, StringProperty
from kivy.clock import Clock
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.screenmanager import ScreenManager, Screen
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()
Builder.load_string('''
<ResultParts>:
    rcAdd_p: rcAdd_v
    Label:
        id: rcAdd_v
        text: 'XXX'

<TestSCN>:
    BoxLayout:
        Label:
            text: 'AAAAA'
        ResultParts:
''')
class ResultParts(BoxLayout):
    rcAdd_p = ObjectProperty(None)
    def __init__(self, **kwargs):
        super(ResultParts, self).__init__(**kwargs)
        #self.rcAdd_p.text = 'ZZZ'
        Clock.schedule_once(self._once_after_init)
    def _once_after_init(self, dt):
        self.rcAdd_p.text = sm.start_path + 'CCCCC'
class TestSCN(Screen):
    def __init__(self, **kwargs):
        super(TestSCN, self).__init__(**kwargs)
        sm.start_path = 'BBBBBB'
class TestApp(App):
    def build(self):
        sm.add_widget(TestSCN(name='test'))
        return sm
if __name__ == '__main__':
    TestApp().run()

ResultPartsの初期化直後にClockで自分のclassの_once_after_initにすぐ飛んで処理をするというロジックである。

なお、


class ResultParts(BoxLayout):
    rcAdd_p = ObjectProperty(None)
    def __init__(self, **kwargs):
        super(ResultParts, self).__init__(**kwargs)
        #self.rcAdd_p.text = 'ZZZ'
        Clock.schedule_once(self._once_after_init)

この部分は、悪戦苦闘の跡(笑)ですが、このコメントアウトを外すと、

 


   File "/home/******/MyApp/01_filelist/kv19d2.py", line 39, in __init__
     self.rcAdd_p.text = 'ZZZ'
 AttributeError: 'NoneType' object has no attribute 'text'

というエラーメッセージが出ます。

なお、実行結果はこうなります。

 

さてこれを、filelist.pyとfilelist.kvに反映してみる。

 

filelist.py


import os
import sys
import json
from kivy.app import App
from kivy.lang import Builder
from kivy.utils import platform
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 PathRead:
    def __init__(self, **kwargs):
        self.path_json = None
        self.notfound = False
        self.err = False
        try:
            #raise BaseException('やっちまったな')
            with open(path_inf, 'r') as fd:
                self.path_json = json.load(fd)
        except FileNotFoundError:
            self.notfound = True
        except PermissionError as err:
            self.msg = '保存データを読み込もうとしましたが、読み込み許可がありません。\n'
            self.msg += '読みこもうとしたパス・ファイルは、' + path_inf + 'です。\n'
            self.msg += '保存データの読み込み許可を確認するか、パス・ファイルが適当かを確認してください。\n\n'
            self.msg += str(err)
            content = MsgboxOkWithPost ( \
                btn_ok=self.btn_ok, \
                btn_ok_post=kwargs['btn_ok_post'], \
                message_text=self.msg)
            self.popup = Popup(title='読み込み許可エラー', content=content)
            self.popup.open()
            self.err = True
        except IOError as err:
            self.msg = '保存データを読み込もうとしましたが、読み込めません。\n'
            self.msg += '読み込み先機器に問題があるようです。\n'
            self.msg += '読み込み先機器の状態を確認してください。\n\n'
            self.msg += str(err)
            content = MsgboxOkWithPost ( \
                btn_ok=self.btn_ok, \
                btn_ok_post=kwargs['btn_ok_post'], \
                message_text=self.msg)
            self.popup = Popup(title='I/Oエラー', content=content)
            self.popup.open()
            self.err = True
        except BaseException as err:
            self.msg = '保存データを読み込もうとしましたが、何らかのエラーが起きました。\n\n'
            self.msg += str(err)
            content = MsgboxOkWithPost ( \
                btn_ok=self.btn_ok, \
                btn_ok_post=kwargs['btn_ok_post'], \
                message_text=self.msg)
            self.popup = Popup(title='その他のエラー', content=content)
            self.popup.open()
            self.err = True
    def btn_ok(self):
        self.popup.dismiss()
class PathWrite:
    def __init__(self, **kwargs):
        self.err = True
        path_inf_dict = kwargs['path_inf_dict']
        self.pd = {}
        self.pd['begin_path'] = path_inf_dict['begin_path']
        try:
            #raise BaseException('やっちまったな')
            with open(path_inf, 'w') as fd:
                json.dump(self.pd, fd)
                self.err = False
        except PermissionError as err:
            self.msg = '保存データを書き込もうとしましたが、書き込み許可がありません。\n'
            self.msg += 'パスは、' + self.pd['begin_path'] + 'です。\n'
            self.msg += '保存先の書き込み許可を確認するか、パスが適当かを確認してください。\n\n'
            self.msg += str(err)
            content = MsgboxOkWithPost ( \
                btn_ok=self.btn_ok, \
                btn_ok_post=kwargs['btn_ok_post'], \
                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 = MsgboxOkWithPost ( \
                btn_ok=self.btn_ok, \
                btn_ok_post=kwargs['btn_ok_post'], \
                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 = MsgboxOkWithPost ( \
                btn_ok=self.btn_ok, \
                btn_ok_post=kwargs['btn_ok_post'], \
                message_text=self.msg)
            self.popup = Popup(title='その他のエラー', content=content)
            self.popup.open()
    def btn_ok(self):
        self.popup.dismiss()
class MsgboxOk(BoxLayout):
    message_text = StringProperty()
    btn_ok = ObjectProperty(None)
class MsgboxOkWithPost(BoxLayout):
    message_text = StringProperty()
    btn_ok = ObjectProperty(None)
    btn_ok_post = 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 (\
                 btn_ok=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):
        self.popup.dismiss()
    def msgTestCancel(self):
        self.popup.dismiss()
    def selfOk(self):
        begin_path_result = self.setBeginPathCheck()
        if begin_path_result == True:
            self.pd = {}
            self.pd['begin_path'] = self.begin_path_dsp.text
            pw = PathWrite( \
                path_inf_dict=self.pd, \
                btn_ok_post=self.pathwhite_ng)
            if pw.err == False:
                self.manager.get_screen('all').file_name.text = self.begin_path_dsp.text
                sm.current = 'all'
        else:
            self.msg = '入力した開始パスが存在しません\n\n'
            self.msg += self.begin_path_dsp.text
            content = MsgboxOk (\
                 btn_ok=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'
    def pathwhite_ng(self):
        #
        # PathWhite()で異常だった時の処理
        #
        sys.exit()
class LstPOPDriveBtn(BoxLayout):
    filechooser = ObjectProperty(None)
    def on_select_button(self, button):
        sm.current_screen.popup.start_path = button.text
        sm.current_screen.popup.title = '読み込み中 ' +  button.text
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
        if platform == 'win':
            import win32api
            dv = win32api.GetLogicalDriveStrings()
            self.drive_list.data = []
            btn_list = dv.split('\000')[:-1]   #ドライブ名をリストに、最後につく余分な空白は削除
            for btn_list_idv in btn_list:
                self.drive_list.data.append({'value': btn_list_idv})
        else:
            self.drive_list.width = 0
            self.drive_list.size_hint_x = 0
    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 ResultParts(BoxLayout):
    start_path = StringProperty(None)
    tv_p = ObjectProperty(None)
    def __init__(self, **kwargs):
        super(ResultParts, self).__init__(**kwargs)
        Clock.schedule_once(self._once_after_init)
    def _once_after_init(self, dt):
        if 'start_path' in dir(sm):
            self.start_path = sm.start_path
            self.tv_p.bind(minimum_height = self.tv_p.setter('height'))
            self.tree_height = self.idv_dir(self.start_path, None)
    def idv_dir(self, this_dir, parent):
        try:
            #raise BaseException('やっちまったな')
            print('this_dir:', this_dir)
            ls = os.listdir(path=this_dir)
            # 指定ディスレクトリ内のファイル・ディレクトリを一度フルパスにしてから、ディレクトリであればリストに入れる
            ls_dir = [ls_idv for ls_idv in ls if os.path.isdir(os.path.join(this_dir, ls_idv))]
            ls_dir.sort()
            # 指定ディスレクトリ内のファイル・ディレクトリを一度フルパスにしてから、ファイルであればリストに入れる
            ls_file = [ls_idv for ls_idv in ls if os.path.isfile(os.path.join(this_dir, ls_idv))]
            ls_file.sort()
            for ls_dir_idv in ls_dir:
                tree_node = self.tv_p.add_node(TreeViewLabel(text=ls_dir_idv, is_open=False), parent)
                self.idv_dir(os.path.join(this_dir, ls_dir_idv), tree_node)
            if ls_file:
                for ls_file_idv in ls_file:
                    self.tv_p.add_node(TreeViewLabel(text=ls_file_idv, is_open=False), parent)
            else:
                # ディレクトリの中にファイルが1つも入っていない時の処理
                self.tv_p.add_node(TreeViewLabel(text='(empty)', is_open=False), parent)
        except PermissionError as err:
            pass
        except IOError as err:
            self.msg = 'ファイルデータを読み込もうとしましたが、読み込めません。\n'
            self.msg += '読み込み先機器に問題があるようです。\n'
            self.msg += '読み込み先機器の状態を確認してください。\n\n'
            self.msg += str(err)
            content = MsgboxOkWithPost ( \
                btn_ok=self.btn_ok, \
                btn_ok_post=sm.result_parts_ng, \
                message_text=self.msg)
            self.popup = Popup(title='読み込み許可エラー', content=content)
            self.popup.open()
        except BaseException as err:
            self.msg = 'ファイルデータを読み込もうとしましたが、何らかのエラーが起きました。\n\n'
            self.msg += str(err)
            content = MsgboxOkWithPost ( \
                btn_ok=self.btn_ok, \
                btn_ok_post=sm.result_parts_ng, \
                message_text=self.msg)
            self.popup = Popup(title='I/Oエラー', content=content)
            self.popup.open()
class AllSCN(Screen):
    tv = ObjectProperty(None)
    file_name = ObjectProperty(None)
    loadfile = ObjectProperty(None)
    text_input = ObjectProperty(None)
    def msgOsErrorOk(self):
        self.popup.dismiss()
        sys.exit()
    def msgFileNameInputErrorOk(self):
        self.popup.dismiss()
    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 (\
                btn_ok=self.msgOsErrorOk, \
                message_text=msg)
            self.popup = Popup(title='OSサポートエラー', content=content)
            self.popup.open()
            return
        #
        # AllSCN用の初期設定 ファイルパス読み込み
        #
        pr = PathRead(btn_ok_post=self.fileread_ng)
        if pr.path_json != None:
            #
            # PathRead()で正常だった時の処理
            #
            path_inf_begin = pr.path_json['begin_path']
            self.manager.get_screen('set').begin_path_dsp.text = path_inf_begin
            self.file_name.text = path_inf_begin
            sm.file_name_previous = path_inf_begin
            sm.begin_path_previous = path_inf_begin
        else:
            if pr.notfound == True:
                #
                # PathRead()で見つからなからった時の処理
                #
                self.manager.get_screen('set').begin_path_dsp.text = self.init_path
                self.file_name.text = self.init_path
                sm.file_name_previous = self.init_path
                sm.begin_path_previous = self.init_path
        if pr.err == False:
            # AllSCNのtree処理
            sm.start_path = self.file_name.text
    def setupButtonClicked(self):
        #
        # SetSCNへの画面遷移時の内容
        #
        sm.current = 'set'
    def allSelect(self):
        self.popup = LstPOP(load=self.allSelectLoad, \
                             cancel=self.allSelectCancel, \
                             start_path=self.file_name.text)
        self.popup.open()
    def allSelectLoad(self, path, filename):
        self.file_name.text = path
        sm.file_name_previous = path
        self.popup.dismiss()
    def allSelectCancel(self):
        self.popup.dismiss()
    def allFileNameCheck(self):
        if os.path.isdir(self.file_name.text) == False:
            return False
        return True
    def allExec(self):
        file_name_result = self.allFileNameCheck()
        if file_name_result == True:
            sm.file_name_previous = self.file_name.text
        else:
            self.msg = '入力した開始パスが存在しません\n\n'
            self.msg += self.file_name.text
            content = MsgboxOk (\
                 btn_ok=self.msgFileNameInputErrorOk, \
                 message_text=self.msg)
            self.popup = Popup(title='入力エラー', content=content)
            self.popup.open()
            self.file_name.text = sm.file_name_previous
    def msgTestOk(self):
        self.popup.dismiss()
    def msgTestCancel(self):
        self.popup.dismiss()
    def fileread_ng(self):
        #
        # ファイル読み込み処理で異常だった時の処理
        #
        sys.exit()
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


<SetSCN>:
    name: 'set'
    begin_path_dsp: begin_path_dsp
    BoxLayout:
        orientation: 'vertical'
        BoxLayout:
            size_hint_y: 0.1
            orientation: 'horizontal'
            BoxLayout:
                Label:
                    size_hint_x: 0.1
                    text: '開始パス'
                TextInput:
                    id: begin_path_dsp
                    size_hint_x: 0.8
                Button:
                    size_hint_x: 0.1
                    text: '…'
                    on_release: root.setSelect()
        BoxLayout:
            size_hint_y: 0.8
        BoxLayout:
            size_hint_y: 0.1
            Button:
                size_hint_x: 0.5
                text: 'OK'
                on_release: root.selfOk()
            Button:
                size_hint_x: 0.5
                text: 'キャンセル'
                on_release: root.selfCancel()
<ResultParts>
    tv_p: tv_v
    ScrollView:
        #size: self.size
        do_scroll_x: False
        TreeView:
            id: tv_v
            root_options: {'text':'ホーム', 'font_size': 32}
            hide_root: False
            size_hint_y: None

<AllSCN>:
    name: 'all'
    file_name: file_name
    BoxLayout:
        orientation: 'vertical'
        BoxLayout: #OpnSCN
            size_hint_y: 0.1
            orientation: 'horizontal'
            BoxLayout:
                Button:
                    size_hint_x: 0.1
                    text: '設定'
                    on_release: root.setupButtonClicked()
                TextInput:
                    id: file_name
                    size_hint_x: 0.7
                    text: 'ファイルパス'
                Button:
                    size_hint_x: 0.1
                    text: '…'
                    on_release: root.allSelect()
                Button:
                    size_hint_x: 0.1
                    text: '実行'
                    on_release: root.allExec()
        BoxLayout: #ResultSCN
            size_hint_y: 0.9
            ResultParts:
<MsgboxOk>
    orientation: 'vertical'
    Label:
        size_hint_y: 0.9
        text: root.message_text
    BoxLayout:
        size_hint_y: 0.1
        orientation: 'horizontal'
        Button:
            size_hint_x: 10
            text: '了解'
            on_release:
                root.btn_ok()
<MsgboxOkWithPost>
    orientation: 'vertical'
    Label:
        size_hint_y: 0.9
        text: root.message_text
    BoxLayout:
        size_hint_y: 0.1
        orientation: 'horizontal'
        Button:
            size_hint_x: 10
            text: 'OK'
            on_release:
                root.btn_ok()
                root.btn_ok_post()
<MsgboxOkCancel>
    orientation: 'vertical'
    Label:
        size_hint_y: 0.9
        text: root.message_text
    BoxLayout:
        size_hint_y: 0.1
        orientation: 'horizontal'
        Button:
            size_hint_x: 10
            text: 'OK'
            on_release: root.ok_button()
        Button:
            size_hint_x: 10
            text: 'キャンセル'
            on_release: root.cancel_button()
<LstPOPDriveBtn>:
    canvas:
        Color:
            rgba: 1, 1, 1, 1
        Rectangle:
            size: self.size
            pos: self.pos
    value: ''
    Button:
        text: root.value
        background_normal: ''
        background_color: 0.5, 0.5, 0.75, 1
        color: 1, 1 ,1 ,1
        on_press: root.on_select_button(self)
<LstPOP>:
    id:lst_pop
    size_hint: 0.9, 0.9
    drive_list: drive_list
    auto_dismiss: False
    BoxLayout:
        size: root.size
        pos: root.pos
        orientation: 'vertical'
        BoxLayout:
            orientation: 'horizontal'
            RecycleView:
                id: drive_list
                size_hint_x: 0.3
                scroll_type: ['bars', 'content']
                scroll_wheel_distance: sp(30) #スクロール速度
                bar_width: sp(10)
                viewclass: 'LstPOPDriveBtn'
                RecycleBoxLayout:
                    default_size: None, sp(40)
                    default_size_hint: 1, None
                    size_hint_y: None
                    height: self.minimum_height
                    orientation: 'vertical'
                    spacing: sp(2)
            FileChooserListView:
                id: filechooser
                size_hint_x: 0.7
                dirselect: True
                path: root.start_path
                filters: [root.is_dir]
                on_touch_down: lst_pop.lstChooser(filechooser.path, filechooser.selection)
        BoxLayout:
            size_hint_y : None
            height : 30
            Button:
                text: 'キャンセル'
                on_release: root.cancel()
            Button:
                text: '読み込み'
                on_release: root.load(filechooser.path, filechooser.selection)
AllSCN:

が、次の結果に戦慄した。

Treeの内容が出ない。

まだ、何かあるのだ。

 

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