ApplicationCommands for IronPython

久々に .NET ネタ。
なかなか面白いことをしている人を見つけたので。
C# + WPF + XAML コマンドのバインドとメニューバー – Symfoware
static の意味をイマイチ解っていないようだけど…
いやコレでも動くんだが。

とにかくおかげで解ったこと。
下記を利用すればあらかじめ用意されたメニューを勝手に入れてくれるようだ。
ApplicationCommands クラス (System.Windows.Input)

Windows にも全自動で国際化メニューにしてくれる便利なものがあったのね。
WPF のみみたいだけど。
GTK+ みたくリソースであるほうが理解しやすいんだけど。

で、リンク先は多々無駄があるのでもう少し単純なサンプルを書いてみる。

ただ IronPython で、コンパイル面倒クセェ!
一応 C# 屋が見てもなんとなく解るように。

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

"""
    WPF Simple TextEditor
    Read and Write encoding is UTF-8 Non BOM Text
"""

import clr

clr.AddReferenceByPartialName("PresentationCore")
clr.AddReferenceByPartialName("PresentationFramework")
clr.AddReferenceByPartialName("WindowsBase")

from System import *
from System.IO import *
from System.Windows import *
from System.Windows.Controls import *
from System.Windows.Input import *

from System.Windows.Markup import XamlReader
from Microsoft.Win32 import OpenFileDialog, SaveFileDialog

menu_str = """<Menu DockPanel.Dock="Top"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
    <MenuItem Header="_File">
        <MenuItem Command="New" />
        <Separator />
        <MenuItem Command="Open" />
        <MenuItem Command="Save" />
        <MenuItem Command="SaveAs" />
        <Separator />
        <MenuItem Command="Close" />
    </MenuItem>
    <MenuItem Header="_Edit">
        <MenuItem Command="Undo" />
        <MenuItem Command="Redo" />
        <Separator />
        <MenuItem Command="Cut" />
        <MenuItem Command="Copy" />
        <MenuItem Command="Paste" />
        <Separator />
        <MenuItem Command="SelectAll" />
    </MenuItem>
</Menu>"""

class TextEditor(Window):
    """
        Auto Internationalization menu Sample
        no mnemonic...
    """
    def __init__(self):
        # Menu
        menu = XamlReader.Parse(menu_str)
        # MenuItem Binding
        cb = CommandBinding(ApplicationCommands.New, self.on_new)
        self.CommandBindings.Add(cb)
        cb = CommandBinding(ApplicationCommands.Open, self.on_open)
        self.CommandBindings.Add(cb)
        cb = CommandBinding(ApplicationCommands.Save, self.on_save, self.on_can_execute)
        self.CommandBindings.Add(cb)
        cb = CommandBinding(ApplicationCommands.SaveAs, self.on_save_as, self.on_can_execute)
        self.CommandBindings.Add(cb)
        cb = CommandBinding(ApplicationCommands.Close, self.on_close)
        self.CommandBindings.Add(cb)
        # TextBox
        self.textbox = TextBox()
        self.textbox.TextWrapping = TextWrapping.NoWrap
        self.textbox.AcceptsReturn = True
        self.textbox.AcceptsTab = True
        self.textbox.VerticalScrollBarVisibility = ScrollBarVisibility.Auto
        self.textbox.HorizontalScrollBarVisibility = ScrollBarVisibility.Auto
        # Add
        docpanel = DockPanel()
        docpanel.Children.Add(menu)
        docpanel.Children.Add(self.textbox)
        self.Content = docpanel
        self.Width = 300
        self.Height = 300
        self.textbox.Focus()

    def on_new(self, sender, e):
        self.textbox.Text = ""
        self.Title = ""

    def on_open(self, sender, e):
        dlg = OpenFileDialog()
        if dlg.ShowDialog():
            sw = StreamReader(dlg.FileName) 
            try:
                self.textbox.Text = sw.ReadToEnd()
                self.Title = dlg.FileName
            except Exception, ex:
                MessageBox.Show(ex.Message)
            finally:
                sw.Close()

    def on_save(self, sender, e):
        if self.Title == "":
            self.on_save_as(sender, e)
        else:
            self.save_file(self.Title)

    def on_save_as(self, sender, e):
        dlg = SaveFileDialog()
        if dlg.ShowDialog():
            self.save_file(dlg.FileName)

    def on_close(self, sender, e):
        self.Close()

    def on_can_execute(self, sender, e):
        e.CanExecute = self.textbox.Text != ""

    def save_file(self, filename):
        sw = StreamWriter(filename) 
        try:
            sw.Write(self.textbox.Text)
            self.Title = filename
        except Exception, ex:
            MessageBox.Show(ex.Message)
        finally:
            sw.Close()

if __name__ == "__main__":
    w = TextEditor()
    Application().Run(w)

menu_win

何故メニューだけ XAML なんだ?と言わない。
Python with GTK+ ではコレが普通。
メニューとツールバー – L’Isola di Niente

とりあえずこのバインディングでやったことの解説を少し。

XAML は見ての通り ApplicationCommands を指定するだけ。
そしてコードで CommandBinding オブジェクトを作る。

CommandBinding クラス (System.Windows.Input)

第一引数に利用する ApplicationCommands
第二引数に結びつけたいイベントハンドラ名
第三引数は必要なら任意でメニューの有効無効を決めるハンドラ名

ハンドラってつまり既に有るオブジェクトですので別途で作成する必要は無い。
それを Window に CommandBindings.Add() すればいい。

ついでに TextBox のバッファが空だと保存できないように。
CommandBinding 第三引数でアッサリと実現できるんですね。

Ctrl+C でコピー等は TextBox が提供している機能なので別にメニューに入れなくても使えるんですけどね。

後今頃知ったけど StreamReader.ReadToEnd ってバイナリを読み込めるのね。
せっかく例外処理を入れたけど意味なかった。

var と new と中括弧とセミコロン追加で C# コードにもなるはずw

で、自動的に日本語メニューにはなったけどニーモニックが無いんですけど。
Alt+F, Alt+S みたいなことができないって少し困る。

以下駄文。

久々に IronPython を使ったけど面白い、DLR の初期化さえ早くなれば…
Microsoft は .NET がネイティブ、かつ local が UTF-8 の新規 OS を出してくれ。
ネイティブなら DLR 初期化なんて一瞬のはず、内部は UTF-16 ではなく UCS-4 なら更に嬉しい。
今更 Windows の local 変更なんて無理なのは解っているから新規で。