wxPerl:Perl的另一个GUI

wxPerl是什么?

如果你不仅仅使用Perl创建CGI脚本,你迟早需要为你的应用程序创建某种前端。你可能使用Curses库,但如果你需要一个漂亮的GUI,你可能会使用Tk。Tk绝对是Perl可用的最稳定、文档最完善和最广泛使用的GUI。然而,越来越多的人正在使用其他GUI,如Gtk和Win32::GUI。这主要是因为Tk没有最流畅的界面,与人们使用的环境完全匹配。Tk有一个类似Motif的界面,而Gnome用户会希望使用Gtk的外观,Windows用户会希望使用Windows的外观。当然,当你在Win32机器上使用Tk时,它看起来更像Windows,当你在Gnome下运行时,它看起来更像Gtk,但它仍然是一个不同的界面。

最近,我发现了一个新的Perl GUI。不,不是未维护的Qt和FWTK模块,而是wxPerl,它由Mattia Barbon开发。wxPerl是wxWindows(http://www.wxwindows.org)的Perl绑定,wxWindows是一个针对C++的跨平台GUI库。当我提到跨平台时,我确实是指跨平台:有Windows、Gtk、Motif和Macintosh的wxWindows。wxWindows自1992年以来一直在开发,版本2(当前版本)自1997年以来一直在开发。这不是从某个平台移植过来的GUI,它的根源在于某个平台:wx代表Windows和X——它被设计成跨平台。而且,它已经存在了一段时间,因此有机会成为一个稳定的产物。

wxPerl的方法

wxPerl拥有一套非常丰富的标准小部件(在Wx术语中称为“控件”),从简单的按钮到复杂的HTML窗口和字体对话框。这使得它成为创建功能齐全的应用程序的优秀GUI。所有需要的控件都“现成可用”,如果你仍然想要创建一个复杂的小部件,那么你可以轻松地做到这一点。这与Tk等工具有很大的不同,例如,在Tk中定义新小部件非常痛苦。

编写wxPerl的方式不同。它不比Tk或Gtk接口更好或更差,但它是一种完全不同的方法。主要原因是库的来源,它是一个C++库。它不太像Perl,但它更面向对象。

当你想创建一个新的wxPerl应用程序时,你开始创建一个新的从Wx::App继承的类。这个子类必须至少有一个名为OnInit的方法,它定义了应用程序使用的窗口(在Wx术语中称为“Frames”)。如果你想有一个默认的窗口,你可以使用默认的类。如果你想向窗口添加控件,你可以从默认的窗口类派生,并添加控件。

这是一个比Tk和Gtk使用的方法更面向对象的方法。但不幸的是,它缺少Tk使用的命名参数方法,这使得Tk看起来更像Perl。

目前,wxPerl有一个很大的缺点:它的文档非常差。也就是说:wxWindows有很多文档。如果你努力足够,你可以使用这些文档来为wxPerl编写文档。但这需要相当多的努力。但阅读这个wxPerl教程的第一部分后,你可能会感兴趣,并找到自己的wxPerl方式。

你好,世界!

像每个教程一样,这个教程也有自己的“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;

就像每个优秀的Perl应用程序一样,这个程序也是从-wuse strict开始的。然后我们使用use Wx,这是主要的wxPerl模块。在第9行,我们开始创建名为HelloWorld的包,它(第11行)继承自Wx::App,就像所有的wxPerl应用程序一样。这个新应用程序现在需要有一个OnInit方法来定义框架(第18行)并定义哪个框架是顶层窗口(第24行)。最后,我们调用Show方法(第25行),这使得创建的$frame可见。

框架是通过几个参数创建的。第一个是父窗口。如果我们创建两个框架,那么第二个框架可以通过使用$frame作为第二个框架构造函数的第一个参数,被指定为第一个框架的子窗口。但在我们的例子中,我们只有一个窗口,所以父窗口是undef

在这个时候,我们不关心第二个参数(窗口ID,-1表示默认值),但第三个和第四个参数更有趣。它们分别定义了屏幕上的位置和大小。这些参数作为数组引用传递,也可以分别是预定义的wxDefaultPositionwxDefaultSize

在定义了HelloWorld包之后,我们必须通过定义主包(第32行)来创建主程序。这个包从我们定义的HelloWorld包创建一个新的Wx对象(第34行),然后调用其上的MainLoop方法(第35行)。

主循环是唯一类似于Tk和Gtk GUIs的东西。定义新的Wx::App子类的方法完全不同。

当你运行这个第一个示例时,它看起来像这样

Hello World

填充空窗口。

所以这是一个简单的例子,创建了一个标题为“Hello World!”的空窗口。不是很令人兴奋,对吧?现在我们想在窗口中看到更多的控件。让我们看看如何添加一个无用的按钮和屏幕上的文本。

在我们能够在窗口内创建一些东西之前,需要了解一些关于wxPerl应用程序(和wxWindows应用程序)一般工作原理的背景信息。正如你在前面的例子中所看到的,要创建一个应用程序,我们需要从Wx::App派生。要创建我们自己的内容,即一个框架中,我们首先需要从Wx::Frame派生,并在新创建的Wx::App子类的OnInit方法中创建该派生框架的实例。

要将控件放入我们派生的框架中,你首先需要在其中创建一个Panel,因为控件只能放置在Wx::Panel的实例上。为了能够访问和修改Panel以及你希望在框架中放置的其他项目的属性,你需要将这些项目作为框架的对象。

这些都是很多(可能)令人困惑的信息。让我们看看这个例子

   =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::Panel->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;

你可以看到,我们再次定义了一个名为ButtonApp的Wx::App的子类(第39行)。但这次创建的框架不是Wx::Frame实例,而是一个MyFrame实例。这个MyFrame是我们定义在第9行的新的Wx::Frame子类。

基本上,我们只需要重写Wx::Framenew构造函数。我们想要扩展Wx::Frame类,所以我们的构造函数首先调用其SUPER类构造函数,然后在之后定义其扩展。我们的扩展包括一个新的Panel(第19行),它上面有一个StaticText(第22行)和一个Button(第27行)。就像原始的Wx::Frame类一样,我们的构造函数也返回$self(第32行),这完成了MyFrame的定义。

正如您所看到的,我们将ButtonStaticText对象定义为MyFrame的属性。现在这并不是必需的,但如果我们想给这个脚本添加一些交互,这在下一个例子中将会做,我们需要访问这些对象。由于它们现在被存储为MyFrame的属性,因此无论我们在哪里有MyFrame对象的访问权限,都可以访问ButtonStaticText。所以这里只是存储方式的问题,因为我们实际上在这个例子中没有用它做任何事情。

当你运行这个例子时,它会看起来像这样

Button example

添加交互

但它做了什么?嗯……它还没有做任何事情。但是,没有交互的GUI应用程序是毫无用处的。所以我们将实现一些交互。我在上一个例子中已经解释过:如果你想改变定义对象的属性,那么你必须将它们定义为Frame对象的属性。这样你就可以始终访问任何对象的任何属性,无论是StaticText、Button还是Menu。

考虑以下代码

   =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::Panel->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;

这个例子基本上与上一个例子相同,但主要区别是添加了一些交互。在上一个例子中,当你尝试点击按钮时,什么也没有发生。这次点击按钮将改变StaticText对象的文本。让我们看看代码中有什么变化。

首先,我们使用Wx::Event并导入EVT_BUTTONEVT_BUTTON是按钮事件的处理器子程序。有许多其他事件处理器可用,但我们现在只需要这个。

在第31行,我引入了一个变量来保存按钮ID,名为$BTNID。我仍然可以使用在上一个例子中使用的硬编码的1,但使用这个变量可以使我引用的地方更清晰。例如,它在第40行的EVT_BUTTON调用中是必要的。这是我们定义当按钮被点击时要做什么的地方。它接受$self对象、$BTNID和一个子程序引用作为参数。在第48行我们定义了那个子程序。

wxPerl中的事件回调始终接受两个参数:第一个是它所属的对象(导致事件发生),第二个是事件本身。在我们的例子中,我们不需要第二个参数,但我们确实需要第一个,因为我们想改变StaticText对象的文本。这是我们看到将StaticText对象定义为MyFrame对象属性的用途的地方。我们现在可以简单地调用该属性的SetLabel方法(第52行)。

在我们按下按钮之前,窗口将看起来与上一个例子中的窗口一样。在我们按下按钮之后,应用程序窗口将看起来像这样

Button interaction

结论

我展示了一些wxPerl的工作方式。更确切地说,我展示了如何使用wxPerl。很明显,这与其他GUI的方式不同。我承认,一开始我自己也认为这是一种不自然的Perl编程方式,更不用说Perl GUI编程了。但经过一些练习,我有一种感觉,这实际上比Tk或Gtk使用的方式更自然。当然,这完全取决于个人喜好。而且,个人喜好是无法衡量的。

在下一个wxPerl教程中,我将向您展示如何创建菜单,展示更多的事件处理,我甚至还会添加一些更高级的控件。但目标将是相同的:向您展示wxPerl隐藏的美丽之处!

标签

反馈

这篇文章有什么问题吗?请在GitHub上打开一个问题或拉取请求来帮助我们。