第3章 Command パターン 4/4, IronPython

前の記事記事一覧次の記事

IronPython で学ぶ WPF プログラミングの世界《IronPython2.6》

Command パターン

《著》森こねこ・後藤いるか・小粒ちゃん《監修》小泉ひよ子とタマゴ倶楽部
第1版♪1995/07/02 ● 第2版♪2003/01/29 ● 第3版♪2008/04/28

■ 概要

WPF の特徴を活かして、デザインパターン Command を導入します。

イベントに呼応するアクションを規定したいときには〈GoF〉Command パターンを導入します。すると、密接に関係する「イベント/アクション」対を、再利用可能な「部品」として扱えます。

《Note》IronPython1.x 用に作成したセミナー課題を、IronPython2.6 で再構成しました。

■ 関連記事

ソースとターゲットとの関係は

ルーティングイベントハンドラー Execute では、2つのオブジェクトを参照できます。

Execute note
引数 sender 実際にイベントハンドラーが起動されたオブジェクト 
プロパティー .Source  最初にイベントが発生したオブジェクト

ここで、イベントが発生したオブジェクトと、それを処理するオブジェクトが同じときには、sender/.Source は同じものを参照します。

たとえば、ボタン〔Red〕をクリックすると、次のように出力されるので、

>ipy.exe CommandToolBar.py
sender: System.Windows.Controls.Button: Red
Source: System.Windows.Controls.Button: Red
...

ソース/ターゲットはともに、ボタン Button になるのが分かります。

# routedCommandToolBar2.py
class ExWindow(Window):
    def init(self):
        ...
        for content, command, toolBar in [
            ...
            self.CommandBindings.Add(binding)    # (@.@) here goes...

そこで、ターゲットをボタン button から、ウィンドウ self に変更すると、

class PaintColor(RoutedCommand):
    def Execute(self, sender, e):
        print "sender:", sender
        print "Source:", e.Source
        e = e.Source.Content        # (@.@) here goes...
        self.panel.Background = getattr(Brushes, e)

イベントハンドラー Execute では、それを呼び出したオブジェクト sender に代えて、イベントが発生したオブジェクト e.Source から、必要な情報を獲得できます。すると、


>ipy.exe CommandToolBar2.py CommandToolBar.xaml
sender: IronPython.NewTypes.System.Windows.Window_2
Source: System.Windows.Controls.Button: Red
...
Execute note control
引数 sender 実際にイベントハンドラーが起動されたオブジェクト  Window 
プロパティー .Source  最初にイベントが発生したオブジェクト Button

ソースはボタンに、ターゲットはウィンドウになるのが分かります。つまり、階層構造の末端にあるボタンから、その頂点にあるウィンドウまで遡って、ルーティングイベントハンドラーが起動されたことになります。

まとめ

イベントとアクションを一元管理するのに、Command パターンを適用するとともに、それを実現するための手段として、動的な問題解決を支援するコマンドバインディングを導入する方法を紹介しました。コマンドバインディングを導入すると、クラス継承が抱える問題点を回避して、動的な問題解決が可能になり、仕様変更にも柔軟に対処できるようになります。


Last updated♪09/06/20

《付録》routedCommandToolBar.py

# -*- coding: utf-8 -*-
#===============================================================================
#    Copyright (C) 2000-2008, 小泉ひよ子とタマゴ倶楽部
#
# History: WPF examples
#    2008/01/25, IronPython 1.1.1 (download)
#    2008/08/22, IronPython 1.1.2 (download)
#    2008/03/16, ver.2.0, WPF
#    2008/00/00, ver.2.1, IronPython 1.1.2 
#===============================================================================
from _ant import *
from System import *
from System.Windows import *
from System.Windows.Controls import *
from System.Windows.Input import *
from System.Windows.Media import *
from System.Windows.Media.Imaging import *
from System.Windows.Shapes import *

clr.AddReference("System.Windows.Forms")
from              System.Windows.Forms import OpenFileDialog

## --------------------             # Command::ConcreteCommand
class FileOpen(RoutedCommand):
    def __init__(self, panel):
        self.panel = panel
    def Execute(self, sender, e):
        dialog = OpenFileDialog()
        dialog.ShowDialog()
        e = dialog.FileName.split("\\")[-1]
        brush = ImageBrush(
            ImageSource=BitmapImage(Uri("Flags/%s"%e, UriKind.Relative))
            )
        self.panel.Children.Add(Rectangle(
            Width=50,
            Height=50,
            Fill=brush,
            ))
    def CanExecute(self, sender, e):
        e.CanExecute = True

## --------------------             # Command::ConcreteCommand
class FileClose(RoutedCommand):
    def __init__(self, window):
        self.window = window
    def Execute(self, sender, e):
        self.window.Close()
    def CanExecute(self, sender, e):
        e.CanExecute = True

## --------------------             # Command::ConcreteCommand
class PaintColor(RoutedCommand):
    def __init__(self, panel):
        self.panel = panel
    def Execute(self, sender, e):
        e = sender.Content
        self.panel.Background = getattr(Brushes, e)
    def CanExecute(self, sender, e):
        e.CanExecute = True

## --------------------             # Command::Client
class ExWindow(Window):
    def __init__(self, Content=None, **args):
        self.InitializeComponent(Content)
        self.init()
    def InitializeComponent(self, Content):
        self.Content = LoadXaml(Content)
    def init(self):
        target = "toolBarTray", "panel",
        self._Controls(target)
        target = "fileToolBar", "colorToolBar",
        for e in self.toolBarTray.ToolBars:
            if e.Name in target:
                setattr(self, e.Name, e)
        for content, command, toolBar in [
            ("Open...", FileOpen  (panel=self.panel), self.fileToolBar ),
            ("Close"  , FileClose (window=self)     , self.fileToolBar ),
            ("Red"    , PaintColor(panel=self.panel), self.colorToolBar),
            ("Green"  , PaintColor(panel=self.panel), self.colorToolBar),
            ("Blue"   , PaintColor(panel=self.panel), self.colorToolBar),
            ]:
            button = Button(
                Content=content,
                Command=command,
                )
            binding = CommandBinding(
                command,
                command.Execute,
                command.CanExecute)
            button.CommandBindings.Add(binding)
            toolBar.Items.Add(button)
    def _Controls(self, target):
        controls = xaml_controls(self)
        for e in target:
            setattr(self, e, controls[e])

## --------------------               
if __name__ == "__main__":
    xaml = __file__.replace(".py",".xaml")
    win = ExWindow(
        Title=__file__,
        Width=260, Height=120,
        Content=xaml,
        )
    Application().Run(win)

## --------------------