IronPython で WPF
WPF を IronPython から使う方法。
2011-05-03 IronPython 2.7 (.NET 4.0 専用なので注意) コードに書き換え
2011-05-03 IronPython 2.7 (.NET 4.0 専用なので注意) コードに書き換え
注意
定数はすべて大文字、型名はアッパーキャメルケース、それ以外は全部小文字でスネークケース
いまさら聞けない「変数の命名規則」 - プログラマ 福重 伸太朗 ~基本へ帰ろう~
そしてインデントは半角スペース 4 つ
という Python の掟で書いています、名の知れた Python コードは大半がそうなっているはずです。
なので VC++ 使いや C# 屋の御仁には少し見にくいかもしれません。
いまさら聞けない「変数の命名規則」 - プログラマ 福重 伸太朗 ~基本へ帰ろう~
そしてインデントは半角スペース 4 つ
という Python の掟で書いています、名の知れた Python コードは大半がそうなっているはずです。
なので VC++ 使いや C# 屋の御仁には少し見にくいかもしれません。
コードで作成
PresentationCore.dll, PresentationFramework.dll, WindowsBase.dll
へのリファレンス参照が WPF を使う場合には必要、System.dll も必要だがコレは暗黙で行われる。
リファレンス参照とは VC# 等を持っている人なら解るだろうけどソリューションエクスプローラにある参照設定以下のこと。
VC# なら右クリックして「参照の追加」をする、コンパイルを行わない IronPython はコードで行う。
2.7 からは以下のように import wpf と書けばリファレンス参照が自動で行われるようです。
2.6 には wpf モジュールが存在しないのでエラーになります。
では WPF ウインドウを表示するだけな最小限コード
コレだけで ipyw.exe なら 32bit、ipyw64.exe なら 64bit で動くウインドウ完成です。
DLR は初期化が恐ろしく遅いという宿命があるので表示されるまで気長にお待ちください。
以後は from 文を使います、C# の using と少し意味合いが違うので注意して使いましょう。
Window や Application というクラスは System.Windows 名前空間にあります。
知っていると思うけど Python の名前空間はモジュール名そのままである。
上記を拡張しても後々で困るのでさっさとクラスに変更します。
ソースコード保存時の文字コードは先頭の coding 指定に必ず合わせてください。
C# にてコードのみのウインドウを作った人なら解るとおり C# とほとんど同じに書けます。
Python のクラスなのでメソッドの引数に必ず self が必要です。
VC# で作るのと同じように Window を継承したクラスを __main__ で作る。
最後に Application インスタンスの Run() にぶち込めばクラス化は完了である。
これで C# コードからの変換が簡単になりました。
ついでに終了時にダイアログを出すようにハンドラを追加してみた。
「いいえ」を選択すると終了しないのが確認できます。
次にコントロールを追加してみます。
レイアウタ(ここでは StackPanel)を利用してコントロールを配置していきます。
Button 等のクラスは System.Windows.Controls にあるので import する。
Window や Button に何かを乗せるには Content プロパティに代入する。
レイアウタにコントロールを追加するには Children.Add() メソッドを利用する。
という簡単な例である、最後に Run() の中で Window を作る小技をば。
へのリファレンス参照が WPF を使う場合には必要、System.dll も必要だがコレは暗黙で行われる。
リファレンス参照とは VC# 等を持っている人なら解るだろうけどソリューションエクスプローラにある参照設定以下のこと。
VC# なら右クリックして「参照の追加」をする、コンパイルを行わない IronPython はコードで行う。
2.7 からは以下のように import wpf と書けばリファレンス参照が自動で行われるようです。
2.6 には wpf モジュールが存在しないのでエラーになります。
では WPF ウインドウを表示するだけな最小限コード
# -*- coding: UTF-8 -*- import clr import wpf """ # IronPython 2.6 の場合はこう書く clr.AddReferenceByPartialName("PresentationCore") clr.AddReferenceByPartialName("PresentationFramework") clr.AddReferenceByPartialName("WindowsBase") """ import System w = System.Windows.Window() w.Title = "実験" a = System.Windows.Application() a.Run(w)
コレだけで ipyw.exe なら 32bit、ipyw64.exe なら 64bit で動くウインドウ完成です。
DLR は初期化が恐ろしく遅いという宿命があるので表示されるまで気長にお待ちください。
以後は from 文を使います、C# の using と少し意味合いが違うので注意して使いましょう。
Window や Application というクラスは System.Windows 名前空間にあります。
知っていると思うけど Python の名前空間はモジュール名そのままである。
上記を拡張しても後々で困るのでさっさとクラスに変更します。
ソースコード保存時の文字コードは先頭の coding 指定に必ず合わせてください。
# -*- coding: UTF-8 -*- import clr import wpf from System import * from System.Windows import * TITLE = "class の実験" class TestWin(Window): def __init__(self): self.Title = TITLE self.Closing += self.on_closing def on_closing(self, sender, e): r = MessageBox.Show( "終了する?", TITLE, MessageBoxButton.YesNo, MessageBoxImage.Warning ) if r == MessageBoxResult.No: e.Cancel = True if __name__ == "__main__": w = TestWin() a = Application() a.Run(w)
C# にてコードのみのウインドウを作った人なら解るとおり C# とほとんど同じに書けます。
Python のクラスなのでメソッドの引数に必ず self が必要です。
VC# で作るのと同じように Window を継承したクラスを __main__ で作る。
最後に Application インスタンスの Run() にぶち込めばクラス化は完了である。
これで C# コードからの変換が簡単になりました。
ついでに終了時にダイアログを出すようにハンドラを追加してみた。
「いいえ」を選択すると終了しないのが確認できます。
次にコントロールを追加してみます。
レイアウタ(ここでは StackPanel)を利用してコントロールを配置していきます。
# -*- coding: UTF-8 -*- import clr import wpf from System import * from System.Windows import * from System.Windows.Controls import * class TestWin(Window): def __init__(self): self.Title = "Controls" # ボタン作成及びクリックハンドラ登録 button = Button() button.Content = "ボタンだよ" button.Click += self.on_click # 一行エディットの作成 # 内容を他で使うのでアトリビュートにしておく self.textbox = TextBox() self.textbox.Text = "なんか書け" # TextBlock の作成 tb = TextBlock() tb.Text = "何か書き込んでボタンを押してね" # レイアウタを作成してこれらをセット sp = StackPanel() sp.Orientation = Orientation.Vertical sp.Children.Add(button) sp.Children.Add(self.textbox) sp.Children.Add(tb) # レイアウタを Window にセット self.Content = sp # この指定でコンテンツの大きさ固定なウインドウになる self.SizeToContent = SizeToContent.WidthAndHeight self.ResizeMode = ResizeMode.NoResize def on_click(self, sender, e): MessageBox.Show(self.textbox.Text) if __name__ == "__main__": Application().Run(TestWin())
Button 等のクラスは System.Windows.Controls にあるので import する。
Window や Button に何かを乗せるには Content プロパティに代入する。
レイアウタにコントロールを追加するには Children.Add() メソッドを利用する。
という簡単な例である、最後に Run() の中で Window を作る小技をば。
XAML の読み込み
とりあえずオーソドックスな XAML ファイルの読み込み方法。
以下の xml コードを xamltest.xaml という名前で保存します、必ず UTF-8 で保存してください。
BOM の有無は吸収してくれます(だから良くないんだが...)
xmlns の指定は必須です、この指定で PresentationFramework 等が参照できるようになる。
コードで書く clr.AddReferenceByPartialName と同じだと思えばいい。
XAML 名前空間は .NET 3.5 での指定でしたが .NET 4.0 でも変わっていませんでした。
ここでは xml:lang を指定していますが通常は不要のはずです。
コンパイルを行う C# では問題無いですが IronPython ではマレに文字化けが起こる場合があります。
文字化けが起こった場合はコレを追記すればイケるようなのでここでは一応書いておきます。
ということでコレを読み込んで表示する最小限のコードを書いてみます。
複雑な XAML ファイルを利用するならこんな感じで読み込みを行います。
注意しなければいけないことは IronPython からはイベントハンドラは XAML 中で指定できません。
後で説明するようにコードでハンドラのコネクトを行う必要があります。
しかし単純なアプリに場合はいちいち別ファイルを読み込むのも何か無駄を感じます。
そういう場合は Python ならではの docstring を利用しましょう。
XamlReader の Parse メソッドで簡単に XAML が読み込みできるしファイルも一つで終わりです。
ついでに XAML でデータバインディングもやっている、問題なく動作するのが確認できる。
これでコードもスッキリ。
以下の xml コードを xamltest.xaml という名前で保存します、必ず UTF-8 で保存してください。
BOM の有無は吸収してくれます(だから良くないんだが...)
<Window xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Title="タイトル" Height="240" Width="320" xml:lang="ja-JP"> <StackPanel> <TextBlock>はろぉわーるど</TextBlock> </StackPanel> </Window>
xmlns の指定は必須です、この指定で PresentationFramework 等が参照できるようになる。
コードで書く clr.AddReferenceByPartialName と同じだと思えばいい。
XAML 名前空間は .NET 3.5 での指定でしたが .NET 4.0 でも変わっていませんでした。
ここでは xml:lang を指定していますが通常は不要のはずです。
コンパイルを行う C# では問題無いですが IronPython ではマレに文字化けが起こる場合があります。
文字化けが起こった場合はコレを追記すればイケるようなのでここでは一応書いておきます。
ということでコレを読み込んで表示する最小限のコードを書いてみます。
# -*- coding: UTF-8 -*- import clr clr.AddReferenceByPartialName("PresentationFramework") from System import * from System.Windows import * from System.Windows.Markup import XamlReader from System.IO import * xaml_path = Path.Combine(Path.GetDirectoryName(__file__),"xamltest.xaml") fs = FileStream(xaml_path, FileMode.Open, FileAccess.Read) w = XamlReader.Load(fs) fs.Close() Application().Run(w)
複雑な XAML ファイルを利用するならこんな感じで読み込みを行います。
注意しなければいけないことは IronPython からはイベントハンドラは XAML 中で指定できません。
後で説明するようにコードでハンドラのコネクトを行う必要があります。
しかし単純なアプリに場合はいちいち別ファイルを読み込むのも何か無駄を感じます。
そういう場合は Python ならではの docstring を利用しましょう。
# -*- coding: UTF-8 -*- import clr import wpf from System.Windows import Application from System.Windows.Markup import XamlReader xaml = """<Window xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" SizeToContent="WidthAndHeight" Title="コピーテキスト"> <StackPanel> <TextBlock Text="何か書き込むと下にそのまんまコピーされる" /> <TextBox Name="_tname" /> <TextBlock Text="{Binding ElementName=_tname, Path=Text}" /> </StackPanel> </Window>""" w = XamlReader.Parse(xaml) Application().Run(w)
XamlReader の Parse メソッドで簡単に XAML が読み込みできるしファイルも一つで終わりです。
ついでに XAML でデータバインディングもやっている、問題なく動作するのが確認できる。
これでコードもスッキリ。
部品の一部だけを XAML にする
XAML で Window を作るというと全部の部品を XAML にしなければいけない錯覚が起こる。
GTK+ での GtkUIManager のようにメニューやツールバーのみ XML にすれば綺麗に作れるのに。
なんて思う人もいるでしょう、実際一部部品のみを XAML にすることは可能です。
ただし上記のように XAML 毎に xmlns 指定が必要です。
イベントハンドラのコネクトは以下のように行います。
Menu は ItemControl 派生クラスなので FindName メソッドが利用できるようだ。
メニュー全部に Name プロパティを指定する必要があるけど。
ということで単純なテキストエディタを作ってみる。
エンコード指定を行わないストリームでの読み書きはすべて BOM 無し UTF-8 となります。
.NET 4.0 の恩恵で Microsoft 名前空間ダイアログが Windows 7 標準になります。
OpenFileDialog クラス (Microsoft.Win32)
こういうふうに作れると気持ちがイイと思うのは私だけなのだろうか。
Visual Studio にて C# で作っていると多分こんなことをやろうなんて思いつかないでしょう。
GTK+ での GtkUIManager のようにメニューやツールバーのみ XML にすれば綺麗に作れるのに。
なんて思う人もいるでしょう、実際一部部品のみを XAML にすることは可能です。
ただし上記のように XAML 毎に xmlns 指定が必要です。
イベントハンドラのコネクトは以下のように行います。
Menu は ItemControl 派生クラスなので FindName メソッドが利用できるようだ。
メニュー全部に Name プロパティを指定する必要があるけど。
ということで単純なテキストエディタを作ってみる。
エンコード指定を行わないストリームでの読み書きはすべて BOM 無し UTF-8 となります。
# -*- coding: UTF-8 -*- """ This Code is Suitable for Japanese Read and Write encoding is UTF-8 Non BOM Text """ import clr import wpf from System import * from System.IO import * from System.Windows import * from System.Windows.Controls import * from System.Windows.Input import * from System.Windows.Controls.Primitives import * from System.Windows.Markup import XamlReader import Microsoft menu_str = """<Menu xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"> <MenuItem Header="_File"> <MenuItem Header="_Open" InputGestureText="Ctrl+O" Name="menu_open" /> <MenuItem Header="_Save" InputGestureText="Ctrl+S" Name="menu_save" /> <MenuItem Header="Save _As" Name="menu_save_as" /> <Separator/> <MenuItem Header="_Quit" InputGestureText="Ctrl+Q" Name="menu_close" /> </MenuItem> </Menu>""" class WPF_TextEditor(Window): """ Simple TextEditor """ def __init__(self): """ Initialization """ self.openfile = "" # Menu menu = XamlReader.Parse(menu_str) menu.FindName("menu_open").Click += self.on_open menu.FindName("menu_save").Click += self.on_save menu.FindName("menu_save_as").Click += self.on_save_as menu.FindName("menu_close").Click += self.on_close DockPanel.SetDock(menu, Dock.Top) # TextBox self.textbox = TextBox() self.textbox.TextWrapping = TextWrapping.NoWrap self.textbox.AcceptsReturn = True self.textbox.VerticalScrollBarVisibility = ScrollBarVisibility.Auto self.textbox.HorizontalScrollBarVisibility = ScrollBarVisibility.Auto # StatusBar statusbar = StatusBar() DockPanel.SetDock(statusbar, Dock.Bottom) self.statusbar_item = StatusBarItem() self.statusbar_item.Content = "no open" statusbar.Items.Add(self.statusbar_item) # DockPanel dpanel = DockPanel() dpanel.Children.Add(menu) dpanel.Children.Add(statusbar) dpanel.Children.Add(self.textbox) # self self.Content = dpanel self.Title = "WPF_TextEditor" # event self.KeyDown += self.on_keydown # Drag and drop self.AllowDrop = True self.PreviewDragOver += self.on_predrop self.Drop += self.on_drop def on_open(self, sender, e): """ namespace Microsoft Dialog """ dlg = Microsoft.Win32.OpenFileDialog() if dlg.ShowDialog(self): self.read_file(dlg.FileName) def on_save(self, sender, e): """ is Open ? """ if self.openfile == "": self.on_save_as(sender, e) else: self.save_file(self.openfile) def on_save_as(self, sender, e): """ save """ dlg = Microsoft.Win32.SaveFileDialog() dlg.Title = "SaveFile" if dlg.ShowDialog(): self.save_file(dlg.FileName) def on_close(self, sender, e): """ Bye """ self.Close() def on_keydown(self, sender, e): """ InputGestureText is Show only e is System.Windows.Input.KeyEventArgs """ if Keyboard.Modifiers == ModifierKeys.Control: if e.Key == Key.O: self.on_open(self, e) elif e.Key == Key.S: self.on_save(self, e) elif e.Key == Key.Q: self.on_close(self, e) def on_predrop(self, sender, e): """ PreviewDragOver Event """ e.Handled = e.Data.GetData(DataFormats.FileDrop) != None def on_drop(self, sender, e): """ File Dropped """ filenames = e.Data.GetData(DataFormats.FileDrop) filename = filenames[0].ToString() if filename != None and filename.Length != 0: self.read_file(filename) def read_file(self, filename): """ f = open(filename) try: s = f.read() self.textbox.Text = unicode(s, "utf8") except Exception, ex: MessageBox.Show(ex.Message) finally: f.close() """ sw = StreamReader(filename) try: self.textbox.Text = sw.ReadToEnd() self.statusbar_item.Content = Path.GetFileName(filename) self.openfile = filename except Exception, ex: MessageBox.Show(ex.Message) finally: sw.Close() def save_file(self, filename): """ f = open(filename, "w") try: f.write(self.textbox.Text.encode("utf8")) except Exception, ex: MessageBox.Show(ex.Message) finally: f.close() """ sw = StreamWriter(filename) try: buf = self.textbox.Text sw.Write(buf) self.statusbar_item.Content = Path.GetFileName(filename) self.openfile = filename except Exception, ex: MessageBox.Show(ex.Message) finally: sw.Close() if __name__ == "__main__": Application().Run(WPF_TextEditor())
.NET 4.0 の恩恵で Microsoft 名前空間ダイアログが Windows 7 標準になります。
OpenFileDialog クラス (Microsoft.Win32)
こういうふうに作れると気持ちがイイと思うのは私だけなのだろうか。
Visual Studio にて C# で作っていると多分こんなことをやろうなんて思いつかないでしょう。
Copyright(C) sasakima-nao All rights reserved 2002 --- 2024.