- 论坛徽章:
- 0
|
原作:Jouke Visser http://www.perl.com/pub/au/Visser_Jouke .
翻译改写:chiesa 个人主页:http://chiesa.ithelper.cn
最后更新:2005/9/22
缘起
wxPerl缘起于wxWindows项目。wxWindows 是一款 C++ 语言的跨平台图形界面工具箱。后来,受微软压力 wxWindows 改名为 wxWidgets。
wxWindows是个成功的项目,于是出现了多种语言的绑定。这些绑定来自于其他的开发者。wxPerl就是其中之一。在国内,wxPython的使用者似乎更多一点。
wx 的特色在哪里呢?
wx特色就在于本地化绑定,所以做出来的界面风格和本地gui相同。
比如,在windows,就像mfc,在linux,就像gtk。
wx的设计模仿了mfc,所以mfc程序员转到这个gui平台特别容易。
wx支持Windows, Gtk, Motif 和 Macintosh平台。
wx不仅仅是GUI库,也是跨平台的类库。提供了很多实用类,比如:wxNet,wxHTML,wxODBC,wxMedia。基本上,你可以用wx开发一个完美的跨平台程序。
wxPerl的特点呢?
It is less Perlish – but it’s more OO.
概述
一个wxPerl程序,开始的时候需要创建一个类继承Wx::App。它最少需要实现一个方法OnInit,用来创建程序所使用的窗口。如果你使用标准窗口,那么你使用标准窗口类就ok了。如果你想给窗口添加一些控件,那么你需要一个类来继承标准窗口。
所以,wxPerl比Tk,Gtk更加面向对象化,但是Perlish方面要差。
wxPerl的文档很少,所以,如果你需要帮助,那么请更多的参考wxWindows的文档,当然,我认为,更多的看demo代码是更好的途径。wxPerl,wxPython,wxWindows都提供了丰富的demo。
Hello World
一切从Hello World开始。
=1= #!/usr/bin/perl -w
=2= use strict;
=3= use Wx;
=4=
=5= ###########################################################
=6= #
=7= # Define our HelloWorld class that extends Wx::App
=8= #
=9= package HelloWorld;
=10=
=11= use base qw(Wx::App); # Inherit from Wx::App
=12=
=13= sub OnInit
=14= # Every application has its own OnInit method that will
=15= # be called when the constructor is called.
=16= {
=17= my $self = shift;
=18= my $frame = Wx::Frame->;new( undef, # Parent window
=19= -1, # Window id
=20= 'Hello World', # Title
=21= [1,1], # position X, Y
=22= [200, 150] # size X, Y
=23= );
=24= $self->;SetTopWindow($frame); # Define the toplevel window
=25= $frame->;Show(1); # Show the frame
=26= }
=27=
=28= ###########################################################
=29= #
=30= # The main program
=31= #
=32= package main;
=33=
=34= my $wxobj = HelloWorld->;new(); # New HelloWorld application
=35= $wxobj->;MainLoop;
从第9行开始讲解。
第9行,我们命名了一个package HelloWorld。第11行,我们申明这个包继承于wx::App,并且定义方法OnInit。所有的wxPerl开发都要像这样开始。
在 OnInit方法中,我们至少要让一个窗口跑起来。
所以在第18行,我们 new 了一个wx::Frame,这是wxPerl的一个标准窗口类型,并且定义该frame是TopWindow(第24行),也就是,把这个frame作为这个程序的最顶层的窗口。最后,我们调用show这个方法(第25行),让窗口显示出来。
创建frame的时候我们使用了一些参数。
第一个参数是父窗口。你可以把它理解为父窗口的句柄。如果我们创建2个frame,那么第二个frame就可以(但是不是必要的)设置为第一个frame的子窗口,方法是,把第一个frame,比如$frameFirst,作为参数传递给第二个frame的构造函数。在我们这个例子里面,只有一个frame,所以父窗口是undef,表示无指定。
第二个参数表示window id,-1是默认值,具体含义我们暂且不去管。通常你就使用默认值好了。
第三个参数表示窗口标题。
第四、五个指定了窗口的起始位置和窗口大小。这两个参数是作为引用传递的。当然你也可以使用wxDefaultPosition和wxDefaultSize 这两个预定义的值作为第四、五个参数,表示显示在系统默认的位置,采用默认的大小。 到此,我们的HelloWorld package就结束了。
下面我们要创建main程序段来调用 HelloWorld。
首先,定义 main package(第32行)。然后我们就new HelloWorld。执行完这句,wx的机制就已经启动了。OnInit被自动调用,窗口被显示出来。
最后是调用MainLoop方法(第35行)。这是为了让主线程不立即结束,而是等待窗口事件的处理。否则,窗口刚被显示出来,整个程序就结束了。
现在,执行我们这第一个例子程序,it will look like this:
![]()
Fill the empty window
呵呵,我们创建了一个简单的例子。显示了一个空窗口,标题是HelloWorld。不怎么兴奋?hoho。
那么下面让我们在窗口里面搞点花样。
下面我们的目标是,在窗口里面添加一段文字和一个button,不过这个button什么都不处理。
在概述里面我们说过,要在窗口里面搞花样,你必须实现一个继承标准窗口的类。这个窗口类和标准窗口的区别就是,你可以往里面自己加点花样,比如,加一个button控件。
为了把控件放入窗口,必须先要创建一个从属于窗口的Panel,因为控件只能被放在wx: annel的实例上。
我想你可以这样理解:窗口是一个综合体,它有标题栏,最大最小化框,还有窗口区域等,实际能放button的地方只有窗口区域,所以我们要先得到这个可以放置控件的区域。这个区域呢,就被抽象为一个概念:Pannel。我们要先得到这个区域,就要用窗口作为参数,创建一个Panel的实例。
下面让我们看看实现代码,也就是例子2:
=1= #!/usr/bin/perl -w
=2= use strict;
=3= use Wx;
=4=
=5= ###########################################################
=6= #
=7= # Extend the Frame class to our needs
=8= #
=9= package MyFrame;
=10=
=11= use base qw(Wx::Frame); # Inherit from Wx::Frame
=12=
=13= sub new
=14= {
=15= my $class = shift;
=16= my $self = $class->;SUPER::new(@_); # call the superclass' constructor
=17=
=18= # Then define a Panel to put the button on
=19= my $panel = Wx: anel->;new( $self, # parent
=20= -1 # id
=21= );
=22= $self->;{txt} = Wx::StaticText->;new( $panel, # parent
=23= 1, # id
=24= "A buttonexample.", # label
=25= [50, 15] # position
=26= );
=27= $self->;{btn} = Wx::Button->;new( $panel, # parent
=28= 1, # id
=29= ">;>;>; Press me <<<", # label
=30= [50,50] # position
=31= );
=32= return $self;
=33= }
=34=
=35= ###########################################################
=36= #
=37= # Define our ButtonApp class that extends Wx::App
=38= #
=39= package ButtonApp;
=40=
=41= use base qw(Wx::App); # Inherit from Wx::App
=42=
=43= sub OnInit
=44= {
=45= my $self = shift;
=46= my $frame = MyFrame->;new( undef, # Parent window
=47= -1, # Window id
=48= 'Button example', # Title
=49= [1,1], # position X, Y
=50= [200, 150] # size X, Y
=51= );
=52= $self->;SetTopWindow($frame); # Define the toplevel window
=53= $frame->;Show(1); # Show the frame
=54= }
=55=
=56= ###########################################################
=57= #
=58= # The main program
=59= #
=60= package main;
=61=
=62= my $wxobj = ButtonApp->;new(); # New ButtonApp application
=63= $wxobj->;MainLoop;
第39行,还是惯例,我们实现了Wx::App的继承类,这次的名字叫ButtonApp。不过呢,这次我们在app里面创建的窗口不是标准窗口Wx::Frame,而是MyFrame。MyFrame是Wx::Frame的继承类(第9行)。
继承标准窗口类Wx::Frame,我们只必须override(重载)它的构造函数,就可以达到基本的目的。因为我们是实现Wx::Frame的扩展,所以呢,在重载的这个构造函数里面,我们要调用SUPERclass(超类/父类)的构造(第16行)。这样呢,标准窗口的一些基本机制就可以启动起来了。
大家思考一下。
Wx::App的继承不需要调用supperclass(父类)的构造,为什么Wx::Frame的继承却需要呢?
其实,你也可以在Wx::App的继承类的构造过程中调用父类构造,但是由于Wx::App的构造没做什么时候我们关注的事情,所以它的构造是不必要得。而Wx::Frame的构造中,可以想见,是做了很多有用的事情的,最基本的,就是创建了窗口,如果不调用父类构造,那么继承类的根基:窗口就没有创建。hehe。
然后呢,我们就可以实现我们自己针对Wx::Frame所作的扩展了,包括,第19行,我们创建了一个Panel,往Panel上添加一个StaticText(静态文本控件)(第19行)和一个Button(按钮控件)(第27行)。最后呢,我们需要返回$self(第32行),以便调用MyFrame->;new的地方可以得到我们这个窗口的实例。
正如你所见,我们把button和StaticText控件定义为了MyFrame的属性,我们用了$self->;{btn}这样的语句。但是这并不是必须的,不过如果我们需要添加interaction(互动)(事件处理)的时候,我们就必须这样做了,因为我们要在事件处理过程访问这些对象,所以不能使用局部变量定义这些对象,而用$self->;{btn}这样的方式把他们定义为MyFrame的属性,以便在MyFrame的其他地方可以访问这些对象。
现在,执行我们这第二个例子程序,it will look like this:
![]()
添加事件处理
不过这个例子可以做什么么?oh。nothing。hehe。
所以,我们下一步的目标是让程序可以实现和用户的互动,添加事件的处理。刚才我们说过了,为了在事件处理中访问对象,我们必须把button和StaticText或者其他窗口内对象的实例定义为MyFrame的属性。
看看我们的实现代码:
=1= #!/usr/bin/perl -w
=2= use strict;
=3= use Wx;
=4=
=5= ###########################################################
=6= #
=7= # Extend the Frame class to our needs
=8= #
=9= package MyFrame;
=10=
=11= use Wx::Event qw( EVT_BUTTON );
=12=
=13= use base qw/Wx::Frame/; # Inherit from Wx::Frame
=14=
=15= sub new
=16= {
=17= my $class = shift;
=18=
=19= my $self = $class->;SUPER::new(@_); # call the superclass' constructor
=20=
=21= # Then define a Panel to put the button on
=22= my $panel = Wx: anel->;new( $self, # parent
=23= -1 # id
=24= );
=25=
=26= $self->;{txt} = Wx::StaticText->;new( $panel, # parent
=27= 1, # id
=28= "A buttonexample.", # label
=29= [50, 15] # position
=30= );
=31=
=32= my $BTNID = 1; # store the id of the button in $BTNID
=33=
=34= $self->;{btn} = Wx::Button->;new( $panel, # parent
=35= $BTNID, # ButtonID
=36= ">;>;>; Press me <<<", # label
=37= [50,50] # position
=38= );
=39=
=40= EVT_BUTTON( $self, # Object to bind to
=41= $BTNID, # ButtonID
=42= \&ButtonClicked # Subroutine to execute
=43= );
=44=
=45= return $self;
=46= }
=47=
=48= sub ButtonClicked
=49= {
=50= my( $self, $event ) = @_;
=51= # Change the contents of $self->;{txt}
=52= $self->;{txt}->;SetLabel("The button was clicked!" ;
=53= }
=54=
=55= ###########################################################
=56= #
=57= # Define our ButtonApp2 class that extends Wx::App
=58= #
=59= package ButtonApp2;
=60=
=61= use base qw(Wx::App); # Inherit from Wx::App
=62=
=63= sub OnInit
=64= {
=65= my $self = shift;
=66= my $frame = MyFrame->;new( undef, # Parent window
=67= -1, # Window id
=68= 'Button interaction example', # Title
=69= [1,1], # position X, Y
=70= [200, 150] # size X, Y
=71= );
=72= $self->;SetTopWindow($frame); # Define the toplevel window
=73= $frame->;Show(1); # Show the frame
=74= }
=75=
=76= ###########################################################
=77= #
=78= # The main program
=79= #
=80= package main;
=81=
=82= my $wxobj = ButtonApp2->;new(); # New ButtonApp application
=83= $wxobj->;MainLoop;
这第三个例子和前面第二个例子的不同在于添加了事件处理部分。此例中,当你点击 button ,程序会将 StaticText 的文字改掉。让我们看看代码中的变化:
首先,我们use Wx::Event,并引入了EVT_BUTTON(第11行)。EVT_BUTTON是所有button事件的处理函数。还有其他很多类似的处理函数,但是此例中我们仅使用EVT_BUTTON。
第32行,我们定义了一个变量 $BTNID,用来保持 button 的 id。然后在40行,我们告诉 EVT_BUTTON 函数,在遇到窗口( $self,也就是 MyFrame )中 id = $BTNID 的控件时,调用一个名称为 ButtonClicked 的 callback (回调)函数。
在第48行,我们定义了 ButtonClicked 回调函数。
wxPerl中,所有的事件回调函数都有2个参数:第一个是事件发生的窗口对象,第二个是事件本身(事件也是一个对象)。本例中,我们不需要第二个参数,但是我们需要第一个。我们通过这个窗口对象,得到它的一个属性,$self->;{txt} ,这个属性就是我们在第26行创建的 StaticText 实例。我们调用 StaticText 的方法 SetLabel ,来改变它的显示文字(第52行)。
于是,我们的目的就达到了。当我们点击 Button 的时候,事件处理函数执行 ButtonClicked 回调, StaticText 控件的文字被改变。如图:
![]()
思考
本文的3个例子解释了wxPerl的基本运行机制。
下面我们来总结wxPerl程序的一个基本流程。
首先:
实现标准窗口的继承类(你可以实现n个不同的窗口),在继承类的构造函数中,添加你想要添加的控件,比如,button,menu,或者其他控件。然后通过wx::Event提供的一些方法,比如:EVT_BUTTON,定义特定事件的处理回调。然后,实现这个回调函数,实现事件的处理。
接下来,实现wx::App的继承类,在这个类中,我们生成窗口实例,并且把它显示出来。
最后,我们需要生成main程序段,调用wx::App->;new,wx::App->;mainloop,让wx程序run起来。
下面谈谈wx和MVC模式的问题。
在wx中,App负责程序的启动,结束,事件调度,分派,也就是Control。窗口类负责窗口和窗口内控件的实现,也就是View,同时定义事件处理。在实际程序中,你还要处理大量的业务逻辑,比如:数据库处理,可以将这块分离出来,定义数据处理package,负责Model。这样呢,你可以发现,wx基本上是一个MVC的机制。
当然,以上是我个人理解。关于其他人的观点,请参考:http://www.yesky.com/20010202/157459.shtml,他是针对mfc来说的。
如果你熟悉mfc,那么你可以发现,基本上2者的机制很多地方是类似的。EVT_*相当于事件映射的宏。wx::App对应为mfc的app,窗口类对应于mfc中的各种窗口。
结论
wxPerl. Start to use it,or forget it. |
|