wxPythonヘルパーモジュールの作成

手間を軽減するための簡単なモジュールを

tags:python, gui, tips
created:2006-03-25T00:11:17

手間を軽減するための簡単なモジュールを作成しました。 手書きは大変ですが少しでも手間を軽減するモジュールを作成しました。

ソースコード

wx_utils.py

#!/usr/bin/local/python
# -*- coding: utf-8 -*-

# Module: wx_utils.py
# Author: Noboru Irieda(Handle Name NoboNobo)

import sys
import os
import imp
from wx.xrc import XRCID, XRCCTRL

if hasattr(sys,"setdefaultencoding"):
    sys.setdefaultencoding("utf-8")

def is_frozen():
    return (hasattr(sys, "frozen") or # new py2exe
            hasattr(sys, "importers") # old py2exe
            or imp.is_frozen("__main__")) # tools/freeze

def executable():
    if is_frozen():
        return os.path.abspath(sys.executable)
    return os.path.abspath(sys.argv[0])


def manifest(app_name):
  return """<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
  <assembly xmlns="urn:schemas-microsoft-com:asm.v1"
  manifestVersion="1.0">
  <assemblyIdentity
      version="0.64.1.0"
      processorArchitecture="x86"
      name="Controls"
      type="win32"
  />
  <description>%s</description>
  <dependency>
      <dependentAssembly>
          <assemblyIdentity
              type="win32"
              name="Microsoft.Windows.Common-Controls"
              version="6.0.0.0"
              processorArchitecture="X86"
              publicKeyToken="6595b64144ccf1df"
              language="*"
          />
      </dependentAssembly>
  </dependency>
  </assembly>
  """ % app_name

class ImmutableDict(dict):
    '''A hashable dict.'''

    def __init__(self,*args,**kwds):
        dict.__init__(self,*args,**kwds)
    def __setitem__(self,key,value):
        raise NotImplementedError, "dict is immutable"
    def __delitem__(self,key):
        raise NotImplementedError, "dict is immutable"
    def clear(self):
        raise NotImplementedError, "dict is immutable"
    def setdefault(self,k,default=None):
        raise NotImplementedError, "dict is immutable"
    def popitem(self):
        raise NotImplementedError, "dict is immutable"
    def update(self,other):
        raise NotImplementedError, "dict is immutable"
    def __hash__(self):
        return hash(tuple(self.iteritems()))

class bind_manager:
  binds = {}
  def __init__(self):
    self.binds[hash(self)] = set()

  def __call__(self, *args, **keys):
    def deco(func):
      self.binds[hash(self)].add((func.func_name, args, ImmutableDict(keys)))
      return func
    return deco

  def bindall(self, obj):
    for func_name, args, keys in self.binds[hash(self)]:
      keys = dict(keys)
      if keys.has_key('event'):
        event = keys['event']
        del keys['event']
      else:
        event = args[0]
        args = list(args[1:])
      control = obj
      if keys.has_key('id'):
        if isinstance(keys['id'], str):
          keys['id'] = XRCID(keys['id'])
      if keys.has_key('control'):
        control = keys['control']
        if isinstance(control, str):
          control = XRCCTRL(obj, control)
        del keys['control']
      control.Bind(event, getattr(obj, func_name), *args, **keys)

いろんなノウハウを集めただけなのですが、 ミソといえば「bind_manager」クラスでしょうか・・・。

「wx_utils.py」ソースコードダウンロード

利用方法

雛形は以下の4つのファイルを提供する形で行おうと思っています。

  • resource.xrc
  • wx_utils.py
  • sample.py
  • setup.py

「resource.xrc」はXRCedで編集作成したリソースファイルです。 XRCedでの編集の仕方はまた後日解説します。

「wx_utils.py」はいつでも上記のソース内容です。

「sample.py」がメインコードです。これを実行することでアプリケーションを実行します。

「setup.py」はpy2exeでexe化するための設定を記述しています。

メインコードのソース

sample.py

#!/usr/bin/local/python
# -*- coding: utf-8 -*-

import sys
import os
import wx
import wx_utils
from wx.xrc import XRCID, XRCCTRL

class MainFrame(resource_xrc.xrcMainFrame):
  u"""MainFrame class deffinition.
  """
  binder = wx_utils.bind_manager()

  def __init__(self, parent=None):
    resource_xrc.xrcMainFrame.__init__(self, parent)
    self.SetIcon(main_icon.getIcon())
    self.binder.bindall(self)

  @binder(wx.EVT_ACTIVATE)
  def OnActivate(self, event):
    print 'Activate!'

# startup application.
if __name__=='__main__':
  app = wx.App(False)
  frame = MainFrame()
  app.SetTopWindow(frame)
  frame.Show()
  app.MainLoop()

上記のコードの90%がここで紹介する雛形の定例句です。 OnActivateメソッドの追加がイベントマップのサンプルです。

イベントマップのギミック

従来ではメソッドを定義した後、 「 別の場所 」 にあるマッピングテーブルにメソッド参照を記述する必要がありました。

別の場所 」に記述した場合、メソッド名を定義と参照の2度書くことになり、 マッピングミスが発生しやすいですね。

筆者もソースコードの上と下を行ったりきたり。 エディタのコード補完や、2分割を活用すればナントカなるとは思いますが・・・。 筆者はその作業に疲れてしまったのです。

binder = wx_utils.bind_manager()

これが、クラスごとのおまじないです。

メニューやツールボタンに対応したイベントを実装するには、

@binder(wx.EVT_MENU, id='ID_MENU1')
def 任意の名前のメソッド(self, event):

特定のコントロールに送られるイベントを実装するには、

@binder(wx.EVT_CHANGED, control='IDC_TREECTRL')
def 任意の名前のメソッド(self, event):

ウインドウやフレームに関するイベントを実装するには、

@binder(wx.EVT_*)
def 任意の名前のメソッド(self, event):

という風に実装できます。 このようにイベントマップとメソッドが一度に記述できるため楽に管理できます。

また、以下のように複数のイベントをひとつのメソッドに割り当てることもできます。

@binder(wx.EVT_MENU, id='ID_MENU1')
@binder(wx.EVT_MENU, id='ID_MENU2')
def 任意の名前のメソッド(self, event):

このように書くと、ID_MENU1でもID_MENU2でも同じこのメソッドが呼び出されます。 これまでに紹介した記述を混在させることもできます。

以上の記述で、メソッドとイベントの対応付けの準備が完了しました。 インスタンスを作った時に実際に対応付けを実行するには、

self.binder.bindall(self)

これが、インスタンス毎のおまじないです。 そのインスタンスのイベントハンドリングをすべてバインドします。

@binderはメソッド名とイベントバインド用のパラメータを記録するデコレータです。 インスタンス毎のおまじないを実行すると記録済みのメソッドを一括してバインドします。

リソースの取り扱い

pywxrc -p resource.xrc

というコマンドを実行すると「resource_xrc.py」というコードがジェネレートされます。 詳しくは pywxrcがリニューアル を参照してください。

resource_xrc.pyにはXRCedで編集したリソースがすべてコード化されて含まれるようになります。 (イメージなども!)

これをしておくとexe化したとき、リソースがexe中に埋め込まれるようになります。

リソースインスタンスへの最初のアクセス時にリソースのローディングと初期化が実行されます。

通常メインフレームのコンストラクタが最初になるので (resource_xrc.xrcMainFrame__init__)実行の際に、 「resource_xrc.py」の中に記述されたりソースを初期化しています。

この初期化処理が行われたら、「resource_xrc.get_resources()」で リソースインスタンスにアクセスできます。 このインスタンスでできることはwxWidgetsの「wxXmlResource」ヘルプを参照。

exeの作成

配布可能なアプリケーションには「exe化」が重要ですね。

「py2exe」を利用して「exe化」することができます。 この詳細は py2exeモジュールについて を参照。

「setup.py」ソース:
import sys
from distutils.core import setup
import py2exe
from wx_utils import manifest

if not 'py2exe' in sys.argv:
  sys.argv.insert(1, 'py2exe')

py2exe_options = {
  'compressed': 1,
  'optimize': 0,
  'includes': [],
  'excludes': [],
  'dll_excludes': [],
  'packages': [],
  'bundle_files': 1}

setup(
  options = {'py2exe': py2exe_options},
  windows = [{
    'script' : 'split.py',
    'icon_resources': [(1, 'main_icon.ico')],
    'other_resources': [(24,1,manifest('sample'))],
  }],
  data_files =[],
  zipfile = None)

このコードを実行すると、distフォルダが作成され、そこに

  • 「sample.exe」
  • 「Python24.dll」
  • 「MSVCR71.dll」
  • 「w9xpopen.exe」

4つのファイルが作成されます。

この4つのファイルを他のPCにインストールしても動作します。

「w9xpopen.exe」はWin9x系OSでの動作を補助するもので、なくてもおおむね問題ありません。 (「os.popen」などを利用していない限り)