今日また一つ賢くなった記念に。orz
XmlReaderオブジェクトはIDisposableインターフェイスを実装していますが、直接Dispose()メソッドを呼び出そうとするとコンパイルエラーになってしまいます。(普段はusingブロックで暗黙的にDisposeしているのでコンパイルエラーは発生しない)
XmlReader reader = new XmlTextReader( uri );
:
// reader.Dispose(); ← コンパイルエラーになってしまう
(reader as IDisposable).Dispose();
で、オライリーのプログラミングC#第4版を開いてインターフェイスの章をしばらく読んでいたら「明示的なインターフェイス...」という言葉を見つけました。これによると、同じメンバを持つ2つ以上のインターフェイスを実装するときに明示的にインターフェイスのメンバを実装することができるとのことです。
↓はその例:
using System;
namespace Sample6 {
interface IFoo {
void Print( string s );
}
interface IBar {
void Print( string s );
}
public class Sample6 : IFoo, IBar {
public static void Main() {
Sample6 o = new Sample6();
(o as IFoo).Print( "Hello" );
(o as IBar).Print( "Bye" );
}
void IFoo.Print( string s ) {
Console.WriteLine( "[IFoo] " + s );
}
void IBar.Print( string s ) {
Console.WriteLine( "[IBar] " + s );
}
}
}
C#2.0でもクロージャが使えるんですね。
知らなかったので、ここにメモ。
using System;
namespace Sample5 {
public class Sample5 {
delegate int X( int n );
static X GetFunc( int m ) {
return delegate( int n ) {
return m * n;
};
}
public static void Main() {
X x100 = GetFunc( 100 );
X x200 = GetFunc( 200 );
Console.WriteLine( "123 x 100 = " + x100( 123 ) );
Console.WriteLine( "123 x 200 = " + x200( 123 ) );
}
}
}
実行結果は以下のとおり。100倍する関数x100と200倍する関数x200が出来ました。
123 x 100 = 12300
123 x 200 = 24600
Javaのように無名クラスっていうのは無いのでしょうか。
匿名メソッドで済む場合が多いから、C#には要らないのかもしれませんけど。
XAMLプログラミングの解説を読んでいるとXAMLでのルーティングイベント(Routed Events)のイベントハンドラ記述方法は分かるのですが、C#などのコードで記述する方法が記載されていないのでどうやってルーティングイベントのイベントハンドラを作るのかよく分かりませんでした。
Windows SDKの「Routed Events Overview」を読んでいても親要素が子要素のイベントをどうやってとらえるのかイマイチ分かりませんでしたが、結局のところ、AddHandlerメソッドでイベントハンドラを追加すればよいみたいです。
>>> from wpf import *
>>> win = Window( Width=400, Height=300 )
>>> mainpanel = StackPanel( Orientation=Orientation.Vertical )
>>> subpanel = StackPanel( Orientation=Orientation.Horizontal )
>>> subpanel.Children.Add( Button( Content='ボタン s1', Name='s1' ) )
>>> subpanel.Children.Add( Button( Content='ボタン s2', Name='s2' ) )
>>> mainpanel.Children.Add( Button( Content='ボタン m1', Name='m1' ) )
>>> mainpanel.Children.Add( Button( Content='ボタン m2', Name='m2' ) )
>>> mainpanel.Children.Add( subpanel )
>>> win.Content = mainpanel
>>>
>>> def subpanel_handler( sender, e ):
... print '[sub] %s button clicked' % e.Source.Name
...
>>> def mainpanel_handler( sender, e ):
... print '[main] %s button clicked' % e.Source.Name
...
>>> subpanel.AddHandler( Button.ClickEvent, RoutedEventHandler(subpanel_handler) )
>>> mainpanel.AddHandler( Button.ClickEvent, RoutedEventHandler(mainpanel_handler) )
>>>
>>> win.Show()
>>>
>>> #ボタンs1をクリック
>>> [sub] s1 button clicked
[main] s1 button clicked
>>> #ボタンs2をクリック
>>> [sub] s2 button clicked
[main] s2 button clicked
>>> #ボタンm1をクリック
>>> [main] m1 button clicked
>>> #ボタンm2をクリック
>>> [main] m2 button clicked
Button.ClickEventはバブルなので、下階層のsubpanelからハンドラが呼び出されて、次に上階層のmainpanelのハンドラが呼び出されます。
RoutedEventArgsのHandledプロパティにTrueを指定すると、その要素より先の階層の要素では、イベントハンドラが呼び出されなくなります。
>>> from wpf import *
>>> win = Window( Width=400, Height=300 )
>>> mainpanel = StackPanel( Orientation=Orientation.Vertical )
>>> subpanel = StackPanel( Orientation=Orientation.Horizontal )
>>> subpanel.Children.Add( Button( Content='ボタン s1', Name='s1' ) )
>>> subpanel.Children.Add( Button( Content='ボタン s2', Name='s2' ) )
>>> mainpanel.Children.Add( Button( Content='ボタン m1', Name='m1' ) )
>>> mainpanel.Children.Add( Button( Content='ボタン m2', Name='m2' ) )
>>> mainpanel.Children.Add( subpanel )
>>> win.Content = mainpanel
>>>
>>> def subpanel_handler( sender, e ):
... print '[sub] %s button clicked' % e.Source.Name
... e.Handled = True
...
>>> def mainpanel_handler( sender, e ):
... print '[main] %s button clicked' % e.Source.Name
... e.Handled = True
...
>>> subpanel.AddHandler( Button.ClickEvent, RoutedEventHandler(subpanel_handler) )
>>> mainpanel.AddHandler( Button.ClickEvent, RoutedEventHandler(mainpanel_handler) )
>>>
>>> win.Show()
>>>
>>> #ボタンs1をクリック
>>>
>>>
>>> #ボタンs1をクリック
>>> [sub] s1 button clicked
>>> #ボタンs2をクリック
>>> [sub] s2 button clicked
なるほど。ルーティングイベントの実装方法がよく分かりました。
IronPythonを使えばこういった実機による検証も楽で助かります。
いつの間にかIronPython 2.0A2が公開されていたんですね。
娘の誕生(^^)/や健康診断orzに重なったので気づきませんでした。
さて、avalon.pyはコンソールから使うと不具合が発生する場合があります。例えば
>>> from avalon import *
>>> win = Window()
Traceback (most recent call last):
File , line 0, in System.Void(Delegate), using PythonBinder##37
File D:\temp\IronPython\IronPython-2.0A2\Tutorial\avalon.py, line 68,
in DispatchConsoleCommand NameError: name dispatcher is not defined
この現象は、クリップボードにコピーしてあった「from ...」行と「win = ...」行を同時にコマンドプロンプトに貼り付けると発生します。
avalon.pyでは、background threadを作ってApplicationクラスのRunを実行しますが、その直前でAutoResetEventオブジェクトareをSetしてシグナル状態にします。
するとforeground threadのare.WaitOne()が待ち状態から脱出するのですが、このときbackground thread側でon_startupイベントハンドラがApplicationクラスのインスタンスからまだ呼び出されていないとdispatcherオブジェクトが取得できていませんが、それにも関わらずforeground threadは処理を継続し続けるので、次の対話コマンド「win = Window()」を実行するときに「dispatcherが無いよ!!」とエラーになるわけです。
ということで、先月の記事にあるwpf.pyも同じ問題がありましたので、新しいwpf.pyを作り直してみました。
# wpf.py v2
# BOM付きUFT-8で保存
import clr
# 拡張子「.dll」を付けるとエラーになる
clr.AddReferenceByPartialName( 'PresentationFramework' )
clr.AddReferenceByPartialName( 'PresentationCore' )
clr.AddReferenceByPartialName( 'windowsbase' )
clr.AddReferenceByPartialName("IronPython")
clr.AddReferenceByPartialName("Microsoft.Scripting")
from System import *
from System.Windows import *
from System.Windows.Controls import *
from System.Windows.Media import *
from System.Windows.Media.Animation import *
from System.Windows.Input import *
from System.Windows.Shapes import *
from System.Threading import *
from System.Windows.Threading import *
from Microsoft.Scripting import ScriptDomainManager
# 以下、コンソールからインタラクティブにできるようにするためのスクリプト
class _WPFInteractiveThreadImpl:
def __init__( self ):
self.are = AutoResetEvent(False)
self.th = Thread( ThreadStart(self.thread),
IsBackground=True, ApartmentState=ApartmentState.STA )
self.th.Start()
self.are.WaitOne()
ScriptDomainManager.CurrentManager.SetCommandDispatcher(self.dispatchConsoleCommand)
def thread( self ):
try:
self.app = Application()
self.app.Startup += self.on_startup
self.app.Run()
finally:
ScriptDomainManager.CurrentManager.SetCommandDispatcher(None)
def on_startup( self, *args ):
self.dispatcher = Dispatcher.FromThread( self.th )
self.are.Set()
def dispatchConsoleCommand( self, consoleCommand ):
if consoleCommand:
self.dispatcher.Invoke(DispatcherPriority.Normal, consoleCommand)
import sys
if len(sys.argv) == 0:
_WPFInteractiveThread = _WPFInteractiveThreadImpl()
新しいwpf.pyではon_startupイベントハンドラでare.Set()するようにしたので、コマンドプロンプトに複数のコマンドをクリップボードから貼り付けてもエラーになることはありません。
また、ipy.exe起動時の引数の数が0の場合はWPFをbackground threadで動かすようにしました。こうすると、コマンドプロンプトの入力状態とWPFの画面が並列動作できるようになります。
ipy.exeにスクリプトファイルを指定したらWPFをforeground threadで動かします。ですから、スクリプトの最後には↓が必要になります。
app = Application()
app.Run()
IronPythonで.NET Frameworkのクラスをインスタンス化するときに、
引数に「プロパティ名=値」という書き方で初期化することができます。
from System.Windows.Forms import *
f = Form( Text='TEST', Width=320, Height=240 )
これを見て、Pythonという言語はクラスのインスタンスを作るときに「インスタンスの属性=値, インスタンスの属性=値, ...」と引数に書いて初期化できるのかと勘違いしてしまいました。
Pythonは引数名をキーワードにして「キーワード=値, キーワード=値, ...」を関数の引数に書くことができるんですね。
>>> class X:
... def __init__(self,a,b,c):
... self.a, self.b, self.c = a, b, c
...
>>> x1 = X(1,2,3)
>>> x1.__dict__
{'a': 1, 'b': 2, 'c': 3}
>>> x2 = X(b=1,c=2,a=3)
>>> x2.__dict__
{'a': 3, 'b': 1, 'c': 2}
と、いうことは、.NET Frameworkのクラスのコンストラクタは、IronPythonから見れば↓のようになっているということでしょうか。
class Form(ContainerControl):
def __init__(self, **keywords):
以下略
そうだとすれば、.NET Frameworkのクラスを基底に持つ派生クラスを定義するとき、
コンストラクタに**keywordsを付けてやると、派生クラスからも「プロパティ=値」で初期化することが可能なはずです。
>>> class MyForm(Form):
... def __init__(self, title, **keywords):
... Form.__init__(self, **keywords)
... self.Text = title
...
>>> f2 = MyForm( 'TEST2', Width=320, Height=480 )
>>> f2.Show()
予想通り、上記のMyFormは見事にWidthプロパティとHeightプロパティの値を初期化することができました。
なるほどねー。色々勉強になりました。
Win32ならWM_NCHITTESTのメッセージ処理でHTCAPTIONを返すことによりウィンドウをマウスでドラッグして表示位置を移動する処理が簡単に実装できたのですが、
下のサンプルスクリプトでは、ウィンドウをマウスでドラッグできるようにOnMouseDownやOnMouseMoveなどをオーバーライドして自前でウィンドウの表示位置を計算・移動していました。
が、その後、Windows SDKを眺めていたらHTCAPTION並みに簡単にウィンドウの移動処理を実装する方法が説明されていたので、ここに記しておきます。
class GradientWindow(Window):
def __init__(self, w, h, text):
Window.__init__( self )
#中略
self.Width = w
self.Height = h
def OnMouseLeftButtonDown( self, e ):
self.DragMove()
つまりマウスの左ボタンが押下されたときにWindow.DragMove()メソッドを呼び出すだけでウィンドウの移動処理が実装できます。超簡単ですね。
Window.DragMove()メソッドはマウスの左ボタンが押下されている場合にのみ有効です。それ以外のマウスのボタンを押下しているときに呼び出すと例外が発生します。
C#3.0で拡張メソッドというものができるそうですが、
Pythonではnewモジュールを使って後からメソッドを追加できます。
IronPython 2.0A1にはnewモジュールが無いようなので、
C Pythonのモジュールのソースを調べてやり方を勉強しました。
class _c:
def _m(self):
pass
new_instancemethod = type(_c._m)
newモジュールのinstancemethodはだいたい↑のような感じでしょうか。
このnew_instancemethodを使って後からメソッドを追加してみると:
>>> class Hoge:
... def __init__(self, n):
... self.n = n
...
>>> hoge = Hoge(12345)
>>>
>>> def p(self):
... print self.n
...
>>> hoge.p = new_instancemethod( p, hoge, type(Hoge) )
>>> hoge.p()
12345
type(_c._m)は<type 'instancemethod'>であることから、
type(_c._m)(関数名, インスタンス, クラス)はメソッドオブジェクトを作る(PyMethod_New)ということを意味するみたいですね。
上記はインスタンスごとにメソッドを追加する方法で分かりにくいですが、
クラスごとにメソッドを後付けするときは簡単で、
クラスの属性にそのまま関数を渡すだけです。
>>> class Hoge:
... def __init__(self, n):
... self.n = n
...
>>> hoge = Hoge(12345)
>>>
>>> def p(self):
... print self.n
...
>>> Hoge.p = p
>>> hoge.p()
12345
これまでの記事では「import wpf」とやっていましたが、これだと呼び出し元のほうで再度「from System.Windows import *」などを実行する必要があり、あまり便利じゃないなあと思っていました。
「from wpf import *」と記述することで、wpf.pyモジュールのスコープ内でimportした名前(System.Windows内のクラス名など)を「from wpf import *」を実行したスコープのローカル名前空間に定義できるんですね。知らなかった、というよりは、importの動作をちゃんと理解していませんでしたorz。
さて、そんなPython未熟者であり.NET Framework未熟者の私ですが、
懲りずにIronPythonによるWPFの使い方を書きつづっていきます。
XAMLプログラミングを読むと、WPFを使って簡単にフレーム無しの非矩形・透過ウィンドウを作ることができるようなので試してみました。
from wpf import *
from System.Windows.Input import *
clr.AddReference( 'System.Windows.Forms' )
from System.Windows.Forms import Cursor
class GradientWindow(Window):
def __init__(self, w, h, text):
Window.__init__( self )
brush = LinearGradientBrush( StartPoint=Point(0, 0.5), EndPoint=Point(1, 0.5) )
brush.GradientStops.Add( GradientStop( Color.FromArgb( 0, 0, 0, 0 ), 0.0 ) )
brush.GradientStops.Add( GradientStop( Color.FromArgb( 128, 0, 0, 0 ), 0.5 ) )
brush.GradientStops.Add( GradientStop( Color.FromArgb( 0, 0, 0, 0 ), 1.0 ) )
self.Background = brush
self.Clip = EllipseGeometry( Rect( Size( w, h ) ) )
self.WindowStyle = WindowStyle.None
self.AllowsTransparency = True
self.Content = Label( Content=text, FontSize=32,
Foreground = SolidColorBrush( Colors.White ),
HorizontalAlignment = HorizontalAlignment.Center,
VerticalAlignment = VerticalAlignment.Center )
self.Width = w
self.Height = h
self.pressed = False
def OnMouseDown( self, e ):
self.CaptureMouse()
self.pressed = True
self.DragLeft = self.Left
self.DragTop = self.Top
self.DragPosition = Cursor.Position
def OnMouseUp( self, e ):
self.ReleaseMouseCapture()
self.pressed = False
def OnMouseMove( self, e ):
if self.pressed:
self.Left = self.DragLeft + (Cursor.Position.X - self.DragPosition.X)
self.Top = self.DragTop + (Cursor.Position.Y - self.DragPosition.Y)
GradientWindow( 500, 100, '今日はいい天気ですね。' ).Show()
GradientWindow( 300, 200, 'そうですか?\n曇ってきましたよ。' ).Show()
Application().Run()
BOM付きのUTF-8形式でファイルに保存してください。wpf.pyは先月の記事の中にあります。
このサンプルスクリプトでは、WPFのWindowクラスから派生したGradientWindowクラスを作っています。
__init__関数の中では、LinearGradientBrushクラスで透明→半透明→透明のグラデーションを作り、WindowのBackgroundプロパティに設定しています。
さらに、WindowのClipプロパティにはEllipseGeometryクラスでウィンドウを楕円形にします。
透過ウィンドウを作るためには、WindowにWindowStyle.Noneを設定してさらにAllowsTransparencyプロパティをTrueにします。この状態でShow()メソッドを呼び出すと、BackgroundプロパティとClipプロパティによって楕円形の透過ウィンドウが出来上がります。
とても簡単にできますね。(^^)
Win32 APIだと、アルファチャンネル付きのHBITMAPをCreateDIBSectionで作って1ピクセルずつ色を計算してUpdateLayeredWindowしていましたから、これから作ろうと思ってもやる気がおきませんね。orz
なお、ウィンドウをマウスでドラッグできるように、OnMouseDownやOnMouseMoveなどをオーバーライドしています。
Win32のWindowProcだったらWM_NCHITTESTのメッセージ処理でHTCAPTIONを返してやるだけで済むのですが...
WPFの場合はどうやってWM_NCHITTESTを処理すればいいんでしょう?
|
|