Pythonでコマンドラインランチャを作ろう!第二回−イベント編

前置き

不定期連載第二回目!今日はBindの仕方とリストボックスの選択カーソルを上下するとこまでいくぞーo(^-^)o

やりたいこと概観

前回やったのは、テキストコントロールとリストボックスとを表示すること。テキストコントロールが入力ボックスにあたるわけですね。
今日やるのは、リストボックス内の処理。ここには、いろいろな項目が並ぶことになる。

fenrir


craftlaunch

みたいに。ファイルパスかコマンドかという違いはあるにしても、どちらにしてもキーボードで上下して、目的のを選ぶという形です。
でも、直接リストボックスをアクティブにして移動するわけにはいかない。craftlaunchやfenrirをみてもわかるように、テキストコントロールにフォーカスが当たってないとだめ。そうでないと、英数字のキーを入力したらそれに併せて下のリストボックスも変わる、というのができないのよ。
だから、やることは、テキストコントロール上でカーソルの上下が入力されたら、リストボックスの選択カーソルを移動し、それ以外のキーが入力されたときにはテキストコントロールに反映させるということです。これには、Bindを用います。

サンプルその2


import wx

List = ["tukasa","konata","kagami","miwiki","yutaka","minami"]

class MyExcalibur(wx.PySimpleApp):
    
    def OnInit(self):
        Frm = wx.Frame(None, -1, "wxPython", size=(400,48),pos=(400,400))
        self.TxtCtr = wx.TextCtrl(Frm, -1)
        self.TxtCtr.Bind(wx.EVT_KEY_DOWN, self.OnKeyDown)
        self.lbFrame = wx.Frame(None, 0, "wxPython", size=(420,200),pos=(400,448),style=wx.DOUBLE_BORDER)
        self.LBox = wx.ListBox(self.lbFrame, -1, choices = List, size=(415,200))
        Frm.Show()
        self.lbFrame.Show()
        return 1

    def OnKeyDown(self,event):
        key = event.GetKeyCode()
        if key == wx.WXK_UP:
            next = self.LBox.GetSelection() - 1
            self.LBox.SetSelection(next)
        elif key == wx.WXK_DOWN:
            next = self.LBox.GetSelection() + 1
            self.LBox.SetSelection(next)
        else: event.Skip()

app = MyExcalibur()
app.MainLoop()

前からの変更点

前につくったのに、次の行を付け加えてます。

self.TxtCtr.Bind(wx.EVT_KEY_DOWN, self.OnKeyDown)

あと、説明のためにあらかじめリストボックス内の項目を登録しています。choices = List の部分ね。

イベントのBind

Bindでwx.EVT_KEY_DOWNとself.OnKeyDownとを結びつけています。
イベントを発生させるときには、それぞれのコントロールに割り振られたイベントと関数を結びつけるということを行う。この関数は自分でつくり、そこで、そのイベントが起こったときに何をさせるかを決めます。
それぞれのコントロールに割り振られたイベントというのは、wx.EVTで始まるもの。それぞれのコントロールには、そこで起こるイベントがいくつか用意されている。テキストコントロールだったら、文字が入力されたらとか、キーが打たれたらとか、キーが押し下げられたら、とかいう具合に。リストボックスだったら、項目が選択されたらとか、ダブルクリックされたらとか。そして、それをBindで関数と結びつけていたら、今いったような動作がそのコントロールで起こったときに、自動でその関数が起動するわけですね。そして、その関数では、そのコントロールに入力された文字だとか、打たれたキーだとかを取得し利用することができる。

オブジェクト.Bind(起きるイベント, 実行される関数)

ということです。
wxPython Indexのコピペですが^^;

カーソルの上下の判別には、wx.EVT_KEY_DOWNを用います。みたらわかるとおり、キーが押し下げられたら、というイベントですね。

OnKeyDown

    def OnKeyDown(self,event):
        key = event.GetKeyCode()
        if key == wx.WXK_UP:
            next = self.LBox.GetSelection() - 1
            self.LBox.SetSelection(next)
        elif key == wx.WXK_DOWN:
            next = self.LBox.GetSelection() + 1
            self.LBox.SetSelection(next)
        else: event.Skip()

とりあえず、定義するときの()に、self,eventの二つを入れるということを覚えておき、event.hogehogeという形のものがイベントに関連したデータだということを知っていればいいと思う。
ここでは、event.GetKeyCode()で打ち込まれたキーを取得している訳です。それを、keyという変数に代入している、と。そして、そのキーによって分岐させ、それぞれでそれぞれの動作をさせる、という具合。

分岐させたいのは、カーソル下とカーソル上なのだから、それで分岐させればいいわけですね。
if key == wx.WXK_UP:
elif key == wx.WXK_DOWN:
というのがそれ。キーは、wx.WXK_ という形で表されます。他のキーが何に対応しているかは、たぶん適当に思いつきで入れてもわかる気がするけれど^^;
リファレンスのKeycodesを参考にすればいいと思います。
で、やりたいのは、打ち込まれたキーが下だったらリストボックスの選択カーソルを一つ下に、上だったら一つ上にすること。
self.LBox.GetSelection()で、いま選択しているカーソルが上から何番目かを数字で取得します。それから1を引けば、それは今選択しているカーソルの一つ上を、1を足せば一つ下を表すわけですな。それを、nextに代入。
そして、それをself.LBox.SetSelection(next)でセット。SetSelection()というのは、括弧内の数字に対応した場所に選択カーソルをあわせるというものです。

おまけ−ループ

でも、実は上の関数ではエラーがでることがある。選択カーソルが一番上にある時に上カーソルを、あるいは一番下にある時に下カーソルを押せばリストボックスに存在しない項目を選択することになる。
そこで、self.LBox.GetCount()で項目数を数え、それを超えたらそれをself.LBox.GetSelection()に入れないようにする。ついでに、上下ループを組み込むことにする。

    def OnKeyDown(self,event):
        key = event.GetKeyCode()
        if key == wx.WXK_UP:
            count = self.LBox.GetCount()
            next = self.LBox.GetSelection() - 1
            if next >=  0:
                self.LBox.SetSelection(next)
            else: self.LBox.SetSelection(count - 1)
        elif key == wx.WXK_DOWN:
            count = self.LBox.GetCount()
            next = self.LBox.GetSelection() + 1
            if next < count:
                self.LBox.SetSelection(next)
            else: self.LBox.SetSelection(0)

みたいな感じですね。
疲れたから説明はパスで>ω<;

まとめ

というわけで、今日はBindと、リストボックス内での選択カーソルの移動ができるようになったわけですね。
見た目だけで言うとほぼ完成に近づいており、カーソル上下でリストボックス内をひたすらループさせながら、「ほら俺すげーだろwwwwwwwwwwwwランチャ作ったんだぜwwwwwほらパスの上下ループwwwwwwww」と友達に自慢することができるわけです。「ちょwwwwwwwwwwwwwおまえマジぱねぇwwwwwwwwwwwwスーパーハカーじゃんwwwwwwwwww」と反応されることまちがいないですね、うふふふ。
Bindができるようになったから、そろそろ次は、リストボックスの絞り込みとかのもっと機能的な面について書くんじゃ・・・ないかな?かな?
一応、今日まででできたやつのソースを最後にのせときます。微妙につけたしてるコードとかあるけど、たぶんググったらわかるとおもいます。

サンプルその3

import wx

List = ["tukasa","konata","kagami","miwiki","yutaka","minami"]

class MyExcalibur(wx.PySimpleApp):
    
    def OnInit(self):
        Frm = wx.Frame(None, -1, "wxPython", size=(400,48),pos=(400,400))
        self.TxtCtr = wx.TextCtrl(Frm, -1)
        self.TxtCtr.Bind(wx.EVT_KEY_DOWN, self.OnKeyDown)
        self.lbFrame = wx.Frame(None, 0, "wxPython", size=(420,200),pos=(400,448),style=wx.DOUBLE_BORDER)
        self.LBox = wx.ListBox(self.lbFrame, -1, choices = List, size=(415,200))
        Frm.Show()
        self.lbFrame.Show()
        self.TxtCtr.SetFocus()
        return 1

    def OnKeyDown(self,event):
        key = event.GetKeyCode()
        if key ==  wx.WXK_ESCAPE:
            wx.Exit()
        elif key == wx.WXK_UP:
            count = self.LBox.GetCount()
            next = self.LBox.GetSelection() - 1
            if next >=  0:
                self.LBox.SetSelection(next)
            else: self.LBox.SetSelection(count - 1)
        elif key == wx.WXK_DOWN:
            count = self.LBox.GetCount()
            next = self.LBox.GetSelection() + 1
            if next < count:
                self.LBox.SetSelection(next)
            else: self.LBox.SetSelection(0)
        else: event.Skip()

app = MyExcalibur()
app.MainLoop()