リンク

2017年6月27日火曜日

pythonでGUIツールを作る ~ ListCtrl (レポートビュー)~

今回は数あるウィジットの中でもよく使用されるListCtrlについて書いていきます。

環境
  • macOS Sierra 10.12.5
  • python 3.4
  • wxPython Phoenix 4.0.0

wxPythonのListCtrlには以下の4つのスタイルがあります。
  1. wx.LC_ICON
  2. wx.LC_SMALL_ICON
  3. wx.LC_LIST
  4. wx.LC_REPORT
今回はその中のwx.LC_REPORTをご紹介します。
wx.LC_ICON、wx.LC_SMALL_ICON、wx.LC_LISTの3つについてはこちらをご覧ください。

 wx.LC_REPORTスタイルは「レポートビュー」と呼ばれ、いわゆる「詳細表示」を行うスタイルです。
フォルダを詳細表示にした時の見た目と通じるものがありますね。


それではスクリプトを記述します。
# -*- coding: UTF-8 -*-

import wx

class App(wx.Frame):
    def __init__(self, parent, id, title):
        wx.Frame.__init__(self, parent, id, title, size=(400, 300), style=wx.DEFAULT_FRAME_STYLE)

        # ステータスバー
        self.CreateStatusBar()

        # パネル
        p = wx.Panel(self, wx.ID_ANY)

        # インスタンス化
        self.listctrl = wx.ListCtrl(p, wx.ID_ANY, style=wx.LC_REPORT)
        self.listctrl.Bind(wx.EVT_LIST_ITEM_SELECTED, self.item_select)

        # カラム
        self.listctrl.InsertColumn(0, "氏名", wx.LIST_FORMAT_LEFT, 150)
        self.listctrl.InsertColumn(1, "性別", wx.LIST_FORMAT_LEFT, 100)
        self.listctrl.InsertColumn(2, "年齢", wx.LIST_FORMAT_LEFT, 100)

        # ListCtrlにアイテムを追加
        for x in range(4):
            self.listctrl.InsertItem(x, '山田 %s郎' % x)
            self.listctrl.SetItem(x, 1, "男")
            self.listctrl.SetItem(x, 2, str((x + 1) * 5))

        layout = wx.BoxSizer(wx.VERTICAL)
        layout.Add(self.listctrl, flag=wx.EXPAND | wx.ALL, border=10, proportion=1)
        p.SetSizer(layout)

        self.Show()

    def item_select(self, event):
        """ アイテム選択時のイベントハンドラー """
        # 選択したアイテムのインデックスを取得する
        select_index = self.listctrl.GetFirstSelected()

        # インデックスのアイテムからテキストを取得する
        text0 = self.listctrl.GetItemText(select_index, 0)  # 氏名
        text1 = self.listctrl.GetItemText(select_index, 1)  # 性別
        text2 = self.listctrl.GetItemText(select_index, 2)  # 年齢

        item_label = text0 + "は" + text1 + "で" + text2 + "歳です"

        # ステータスバーの文字列を変更
        self.SetStatusText(item_label)

app = wx.App()
App(None, wx.ID_ANY, 'ListCtrl REPORT')
app.MainLoop()

<結果> ↓ ↓ ↓



インスタンス化
今回はレポートビューを使用するため、インスタンス化の際にスタイルをwx.LC_REPORTに指定します。
# インスタンス化
self.listctrl = wx.ListCtrl(p, wx.ID_ANY, style=wx.LC_REPORT)

カラムの追加
それぞれの列のタイトル部分(カラム)を作成します。
# カラム
self.listctrl.InsertColumn(0, "氏名", wx.LIST_FORMAT_LEFT, 150)
self.listctrl.InsertColumn(1, "性別", wx.LIST_FORMAT_LEFT, 100)
self.listctrl.InsertColumn(2, "年齢", wx.LIST_FORMAT_LEFT, 100)
ここでの引数は(インデックス、表示文字列、文字寄せ設定、幅)です。
文字寄せ設定は「wx.LIST_FORMAT_LEFT(左寄せ)」「 wx.LIST_FORMAT_RIGHT(右寄せ)」「wx.LIST_FORMAT_CENTRE(中央寄せ)」から選択できます。但し、インデックス0には適用されないことがあります。(なぜかはわからない)
幅は0にすることもでき、その場合は実質”非表示”となります。

アイテムの追加
各行に項目(アイテム)を追加します。
# ListCtrlにアイテムを追加
for x in range(4):
    self.listctrl.InsertItem(x, '山田 %s郎' % x)
    self.listctrl.SetItem(x, 1, "男")
    self.listctrl.SetItem(x, 2, str((x + 1) * 5))
アイテムを追加するにはInsertItem関数を用います。
引数は(インデックス、ラベル文字列、イメージリストのインデックス)です。
また、追加したアイテムのサブアイテムを追加する際はSetItem関数を用います。
引数は(インデックス、ヘッダーインデックス、表示文字列、イメージリストのインデックス)です。
アイテムを追加する方法は何パターンかあるようですが、これが一番わかりやすいのではないかと個人的に思っています。

イベントタイプ
ListCtrlのイベントタイプは様々なタイプがあります。
今回は最も頻度が高い「wx.EVT_LIST_ITEM_SELECTED」(アイテムが選択された時に発動する)を使っています。
self.listctrl.Bind(wx.EVT_LIST_ITEM_SELECTED, self.item_select)

その他のイベントタイプは次のようなものがあります。
  • wx.EVT_LIST_ITEM_DESELETED (アイテムの選択が解除された)
  • wx.EVT_LIST_ITEM_RIGHT_CLICK (アイテムを右クリックした)
  • wx.EVT_LIST_ITEM_MIDDLE_CLICK (アイテムを中クリックした)
  • wx.EVT_LIST_COL_CLICK (カラム部分をクリックした)
  • wx.EVT_LIST_COL_RIGHT_CLICK (カラム部分を右クリックした)
  • wx.EVT_LIST_INSERT_ITEM (アイテムが追加された)
  • wx.EVT_LIST_DELETE_ITEM (アイテムを削除した)
  • wx.EVT_LIST_DELETE_ALL_ITEMS (全てのアイテムを削除した)
  • wx.EVT_LIST_KEY_DOWN (何らかのキーが押された)
用途に合わせて使い分けてください。

主な関数のご紹介
●アイテムを追加する
 InsertItem関数
self.listctrl.InsertItem(x, '山田 %s郎' % x)

●カラムを追加する
 InsertColumn関数
self.listctrl.InsertColumn(0, "氏名", wx.LIST_FORMAT_LEFT, 150)

●任意の位置に文字列を追加する
 SetItem関数
self.listctrl.SetItem(x, 1, "男")

●アイテムを取得する(wx.ListItemが返る)
 GetItem関数
self.listctrl.GetItem(0, 1)

●任意の位置のアイテム文字列を取得する
 GetItemText関数
self.listctrl.GetItemText(0, 0)

●全てのアイテムを削除する(ヘッダー含む)
 ClearAll関数
self.listctrl.ClearAll()

●全てのアイテムを削除する(ヘッダー含まない)
 DeleteAllItems関数
self.listctrl.DeleteAllItems()

●指定のカラムを削除する
 DeleteColumn関数
self.listctrl.DeleteColumn(1)

●指定のアイテムを削除する
 DeleteItem関数
self.listctrl.DeleteItem(1)

今回はListCtrlの中のレポートビューをご紹介しました。
わざわざ他のスタイルとレポートビューを分けてご紹介したのはカラムやサブアイテムの追加など「扱いがかなり異なる」からです。

実際、ListCtrlの中で最もよく使うのはこのレポートビューだと思います。
1つのアイテムに対して複数の情報をまとめて表示させることができるため、とても便利な反面、慣れるまでは複雑だと思ってしまうかもしれません。

しかし、ListCtrlは非常に便利なウィジットの一つです。
様々なツールで必ずや貴方の役に立つことと思われますので、ぜひ覚えていってください。

12 件のコメント:

  1. 使い道が多く、とても助かります。ありがとうございます。

    返信削除
    返信
    1. コメントありがとうございます(^^)
      少しでもお役にたったようで、大変嬉しく思います!
      今後ともご愛顧のほどよろしくお願いいたします。

      削除
  2. 既に載せていたならごめんなさい。
    既にあるアイテムテキストを変更する方法を教えてください。

    返信削除
    返信
    1. すいません。既に説明してありました。お邪魔ごめんなさい。

      削除
  3. GetItemText GetItem とかで、特定の部分を取得できますが、ListCtrl内にあるすべての項目を取得したいです。
    列、行、全て取得です。
    ---戻り値の例---
    [("山田0郎","男","5"),("山田1郎","男","10"),("山田2郎","男","15"),("山田3郎","男","20")]

    返信削除
    返信
    1. コメントありがとうございます。
      ご質問の件、調査しましたが「GetAllItems」のような便利なメソッドはありませんでした。
      よって、地道に取得していくしかないようです。

      サンプルとして、私の取得方法を下記いたします。
      参考にしていただければ幸いです。
      ※コメント内では前方のスペースが前詰めになってしまいましたので、アンダーバーを代用しております。
      プログラムをコピーして使用する場合は、前方のアンダーバーを半角スペースに置き換えてください。

      # -*- coding: UTF-8 -*-

      import wx

      class App(wx.Frame):
      ____def __init__(self, parent, id, title):
      ________wx.Frame.__init__(self, parent, id, title, size=(400, 300), style=wx.DEFAULT_FRAME_STYLE)

      ________# ステータスバー
      ________self.CreateStatusBar()

      ________# パネル
      ________p = wx.Panel(self, wx.ID_ANY)

      ________# インスタンス化
      ________self.listctrl = wx.ListCtrl(p, wx.ID_ANY, style=wx.LC_REPORT)
      ________self.listctrl.Bind(wx.EVT_LIST_ITEM_SELECTED, self.item_select)

      ________# カラム
      ________self.listctrl.InsertColumn(0, "氏名", wx.LIST_FORMAT_LEFT, 150)
      ________self.listctrl.InsertColumn(1, "性別", wx.LIST_FORMAT_LEFT, 100)
      ________self.listctrl.InsertColumn(2, "年齢", wx.LIST_FORMAT_LEFT, 100)

      ________# ListCtrlにアイテムを追加
      ________for x in range(4):
      ____________self.listctrl.InsertItem(x, '山田 %s郎' % x)
      ____________self.listctrl.SetItem(x, 1, "男")
      ____________self.listctrl.SetItem(x, 2, str((x + 1) * 5))

      ________# ボタンを追加
      ________self.btn = wx.Button(p, wx.ID_ANY, 'ボタン')
      ________self.btn.Bind(wx.EVT_BUTTON, self.click)

      ________layout = wx.BoxSizer(wx.VERTICAL)
      ________layout.Add(self.listctrl, flag=wx.EXPAND | wx.ALL, border=10, proportion=1)
      ________layout.Add(self.btn)
      ________p.SetSizer(layout)

      ________self.Show()

      ________def item_select(self, event):
      ________""" アイテム選択時のイベントハンドラー """
      ________# 選択したアイテムのインデックスを取得する
      ________select_index = self.listctrl.GetFirstSelected()

      ________# インデックスのアイテムからテキストを取得する
      ________text0 = self.listctrl.GetItemText(select_index, 0) # 氏名
      ________text1 = self.listctrl.GetItemText(select_index, 1) # 性別
      ________text2 = self.listctrl.GetItemText(select_index, 2) # 年齢

      ________item_label = text0 + "は" + text1 + "で" + text2 + "歳です"

      ________# ステータスバーの文字列を変更
      ________self.SetStatusText(item_label)

      ____# ボタンクリックイベント
      ____def click(self, event):

      ________#全項目用の配列
      ________allitem = []

      ________count = self.listctrl.GetItemCount()
      ________cols = self.listctrl.GetColumnCount()
      ________for row in range(count):
      ____________# 1行ごとの配列
      ____________columnitem = []

      ____________for col in range(cols):
      ________________item = self.listctrl.GetItem(row, col)
      ________________columnitem.append(item.GetText())

      ____________#配列に格納する
      ____________allitem.append(columnitem)
      ________print(allitem)

      app = wx.App()
      App(None, wx.ID_ANY, 'ListCtrl REPORT')
      app.MainLoop()

      削除
    2. 難しい質問にこたえてくださりありがとうございます。
      問題なく実行できました。

      削除
  4. いつもいつも質問ばかりしていて本当にすみません。

    また質問なのですが、このListCtrlで境目をはっきりさせたくて、色を交互に表示させたいです。
    水色、白色、水色、白色のように、行ごとに色を交互に表示させたいのですが、可能でしょうか?

    返信削除
    返信
    1. コメントありがとうございます!
      ご質問の件ですが、ListCtrlの項目数を取得&ループし、偶数のとき・奇数のときでそれぞれ色をつければOKです!
      具体的には、本文サンプルの「p.SetSizer(layout)」と「self.Show()」の間に下記のプログラムを追加してみてください。
      (アンダーバーは半角スペースに置き換えてくださいね)

      ________# 色付け
      ________c = self.listctrl.GetItemCount()
      ________for y in range(c):
      ____________if y % 2 == 0:
      ________________self.listctrl.SetItemBackgroundColour(y,wx.Colour("RED"))
      ____________else:
      ________________self.listctrl.SetItemBackgroundColour(y,wx.Colour("BLUE"))

      これを追加すると、偶数行は赤、奇数行は青になります。
      あとは色の指定をお好みのものに変えてください!(^^)

      削除
    2. 返信ありがとうございます!!
      分かりやすい説明いつもありがとうございます!!

      削除
  5. かなり長い文字列データを格納しようとしたら入りきらなかったのですが、Listctrlのデータ長って分かりますか?

    返信削除
    返信
    1. コメントありがとうございます。管理人です。
      お問い合わせの件、調べましたがListCtrlの最大文字数について記載されているページを見つけきれませんでした。
      ただし、TextCtrlについて記載されているページがあり、そこには「65534文字で切り捨てられます。」とありました。
      あくまで推測ですが、ListCtrlも同じ65534文字ではないかと思われます。

      削除