kivy

kivy戦記(16-9) Filechooserで現在選択されているパスを表示する(暫定版)

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

でも、ちょっと違和感が、、、、
さっきの選択画面に戻ろう。

あれ?
ここはどこなんだ?
現在選択中のパスがわからないと言う問題が、ここで発生したのだ。
ここで、上部の「読み込み中」の後ろに、現在選択パスを入れることにした。
filelist.py


import os
import sys
import json
from kivy.app import App
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.floatlayout import FloatLayout
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 setSelect(self):
        '''
        content = LoadDialog(load = self.setSelectLoad, cancel = self.setSelectCancel)
        self._popup = Popup( title="読み込み中", content=content, size_hint=(0.9,0.9))
        self._popup.open()
        '''
        self._popup = LstPOP(   load = self.setSelectLoad, \
                                cancel = self.setSelectCancel, \
                                title='読み込み中')
        self._popup.open()
    def setSelectLoad (self, path, filename):
        pprint(path)
        pprint(filename)
        #self.begin_path_dsp.text = filename[0]
        self.begin_path_dsp.text = 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):
        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'
    def selfCancel(self):
        sm.current = 'all'
#class LoadDialog(FloatLayout):
class LstPOP(Popup):
    current_dir = os.path.dirname(os.path.abspath(__file__))
    load = ObjectProperty(None)
    cancel = ObjectProperty(None)
    def is_dir(self, dirname, filename):
        # ディスレクトリならTrueを返す
        return os.path.isdir(os.path.join(dirname, filename))
    def lstChooser(self, path, selection):
        self.title = '読み込み中 ' + path
        print('クリックされました' + path)
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):
        path = ''
        thisos = os.name
        if thisos == 'posix':
            path = os.environ.get('HOME')
        elif thisos == 'nt':
            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 = path
        self.tv.add_node(TreeViewLabel(text ='シンカリオン E1 とき'))
        self.tv.add_node(TreeViewLabel(text ='シンカリオン 0 ひかり'))
        self.tv.add_node(TreeViewLabel(text ='シンカリオン キハ32 鉄道ホビートレイン'))
    def setupButtonClicked(self):
        sm.current = 'set'
    def show_load(self):
        # 仮処置 SetSCN側のロジックが確定次第、そのロジックに変更します
        '''
        content = LoadDialog(load = self.load, cancel = self.dismiss_popup)
        self._popup = Popup( title="読み込み中", content=content, size_hint=(0.9,0.9))
        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()
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()
<AllSCN>:
    name: 'all'
    tv: tv
    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.show_load()
                Button:
                    size_hint_x: 0.1
                    text: '実行'
        BoxLayout: #ResultSCN
            size_hint_y: 0.9
            TreeView:
                id: tv
<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: 'OK'
            on_release: root.ok_button()
<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()
#<LoadDialog>:
<LstPOP>:
    id:lst_pop
    size_hint: 0.9, 0.9
    BoxLayout:
        size: root.size
        pos: root.pos
        orientation: 'vertical'
        FileChooserListView:
            id: filechooser
            path: root.current_dir
            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:

ここまでで大きな変更になったのは、LoadDialogクラスをLstPOPクラスに変更したこと。
これは今まで、

としていたLoadDialogクラスを使った画面は、概念的には、

で、kvファイル側もこう書いてあったので、これをPopupを含めたLstPOPという新設classにして、titleの制御をしやすくする。

このように、LstPOPを新設し、これをPopupから継承する。
こういう形に変更しようと思う。
filelist.kvでは、旧LoadDialogを利用し、このLstPOPを作成する。


#<LoadDialog>:
<LstPOP>:
    id:lst_pop
    size_hint: 0.9, 0.9
    BoxLayout:
        size: root.size
        pos: root.pos
        orientation: 'vertical'
        FileChooserListView:
            id: filechooser
            path: root.current_dir
            filters: [root.is_dir]
            on_touch_down: lst_pop.lstChooser(filechooser.path, filechooser.selection)

FileChooserListView:on_touch_downは、リスト選択時のイベントで、ここでtitleを変更させる。(後述)
filelist.pyの


class SetSCN(Screen):
    begin_path_dsp = ObjectProperty(None)
    def __init__(self, **kwargs):
        super(SetSCN, self).__init__(**kwargs)
    def setSelect(self):
        '''
        content = LoadDialog(load = self.setSelectLoad, cancel = self.setSelectCancel)
        self._popup = Popup( title="読み込み中", content=content, size_hint=(0.9,0.9))
        self._popup.open()
        '''
        self._popup = LstPOP(   load = self.setSelectLoad, \
                                cancel = self.setSelectCancel, \
                                title='読み込み中')
        self._popup.open()

まず、ここself._popupは、元はLoadDialogをインスタンス化してから、Popupをインスタンス化したが、LstPOPのインスタンス化1本にまとめる。
そしてこの、LstPOPだが、


#class LoadDialog(FloatLayout):
class LstPOP(Popup):
    current_dir = os.path.dirname(os.path.abspath(__file__))
    load = ObjectProperty(None)
    cancel = ObjectProperty(None)
    def is_dir(self, dirname, filename):
        # ディスレクトリならTrueを返す
        return os.path.isdir(os.path.join(dirname, filename))
    def lstChooser(self, path, selection):
        self.title = '読み込み中 ' + path
        print('クリックされました' + path)

LoadDialogを利用して、LstPOPを作る。
def lstChooserは、kvファイル側の<LstPOP>:で指定したon_touch_downから呼び出される。
しかしこのときは、ここに罠があることには気がつかなかった。
これを実行させてみよう。起動後、「設定」→「・・・」。

これで、「../」を押下。

あれ?
では、「01_filelist_testdata」を押下しよう。

あれれ?
LstPOPclassのlstChooserメソッドで、


class LstPOP(Popup):
    (省略ですわよ)
    def lstChooser(self, path, selection):
        self.title = '読み込み中 ' + path
        print('クリックされました' + path)

pathを使っているのだけど、これが使えないのか!
そして、いろいろいじって幾星霜。
pathの代わりにselectionを使用するということで、回避いたしました。
ただし、単にselectionを使うだけだと、中身が入っていないので、FileChooserListViewのdirselectをTrueとして、実現しました。
これ、今回はディレクトリだけの選択画面だから、この手は使えるんだけど、ファイルの選択の時はどうするんだろうと思うと、昼も寝られません。
しかも、ファイル選択は、ダブルクリックでの選択になるので、なんだかこれでも苦し紛れのロジックぽいんですけどね、私の現在の実力ではこれがどうも限界です。
filechooser.pyも読んでみたんですけどね。
filelist.py


import os
import sys
import json
from kivy.app import App
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.floatlayout import FloatLayout
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 setSelect(self):
        '''
        content = LoadDialog(load = self.setSelectLoad, cancel = self.setSelectCancel)
        self._popup = Popup( title="読み込み中", content=content, size_hint=(0.9,0.9))
        self._popup.open()
        '''
        self._popup = LstPOP(   load = self.setSelectLoad, \
                                cancel = self.setSelectCancel)
        self._popup.open()
    def setSelectLoad (self, path, filename):
        #pprint(path)
        #pprint(filename)
        #self.begin_path_dsp.text = filename[0]
        self.begin_path_dsp.text = 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):
        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'
    def selfCancel(self):
        sm.current = 'all'
#class LoadDialog(FloatLayout):
class LstPOP(Popup):
    current_dir = os.path.dirname(os.path.abspath(__file__))
    load = ObjectProperty(None)
    cancel = ObjectProperty(None)
    def __init__(self, **kwargs):
        super(LstPOP, self).__init__(**kwargs)
        self.title ='読み込み中 ' + os.path.dirname(os.path.abspath(__file__))

    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]
            #print('クリックされました path:' + path)
            #print('クリックされました selection:' + 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):
        path = ''
        thisos = os.name
        if thisos == 'posix':
            path = os.environ.get('HOME')
        elif thisos == 'nt':
            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 = path
        self.tv.add_node(TreeViewLabel(text ='シンカリオン E1 とき'))
        self.tv.add_node(TreeViewLabel(text ='シンカリオン 0 ひかり'))
        self.tv.add_node(TreeViewLabel(text ='シンカリオン キハ32 鉄道ホビートレイン'))
    def setupButtonClicked(self):
        sm.current = 'set'
    def show_load(self):
        '''
        content = LoadDialog(load = self.load, cancel = self.dismiss_popup)
        self._popup = Popup( title="読み込み中", content=content, size_hint=(0.9,0.9))
        self._popup.open()
        '''
        self._popup = LstPOP(   load = self.load, \
                                cancel = self.dismiss_popup)
        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()
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()
<AllSCN>:
    name: 'all'
    tv: tv
    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.show_load()
                Button:
                    size_hint_x: 0.1
                    text: '実行'
        BoxLayout: #ResultSCN
            size_hint_y: 0.9
            TreeView:
                id: tv
<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: 'OK'
            on_release: root.ok_button()
<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()
#<LoadDialog>:
<LstPOP>:
    id:lst_pop
    size_hint: 0.9, 0.9
    BoxLayout:
        size: root.size
        pos: root.pos
        orientation: 'vertical'
        FileChooserListView:
            id: filechooser
            dirselect: True
            path: root.current_dir
            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:

これで起動、「設定」→「…」で、

初期のパス表示が出る。そして、「../」押下(ダブルクリック 以下同じ)で、
ここで「01_filelist_testdata」へ。

「旧」へ

「左巻きなると」へ

ここで「読み込み」

これで入力結果が反映。
なお、起動直後の「…」も、同じロジックにしているので、同様に確認する。



ここで「読み込み」押下で、

ロジックの説明ですけど、filelist.py上の


#class LoadDialog(FloatLayout):
class LstPOP(Popup):
(省略ですわよ)
    def lstChooser(self, path, selection):
        if 0 < len(selection):
            self.title = '読み込み中 ' + selection[0]
            #print('クリックされました path:' + path)
            #print('クリックされました selection:' + selection[0])

ここが、ダブルクリップとしなければならなくロジックで、一回目のクリックではselectionにはなにも入っておらず、二回目のクリックでselectionに値が入ってくるので、これを利用したのだけど、もっといいロジックないかな。

(2018-10-5現在)
すみません、現状私のレベルでは、解決できませんでした。また機会があったら改善しようと思います。
タイトルとURLをコピーしました