I did some more work on this and now have a fairly useful functional
minimalpoe.pl example.
1) You need POE installed.
2) You need the POE::Loop::Wx module in your path somewhere (attached)
This seems to run on both linux and win32 -- haven't tried under OS X yet.
To save space, I stripped out the commenting on the regular wx minimal
stuff, but tried to add more comments explaining what I'm doing with POE.
Basically, each Wx frame runs as it's own POE session, created with the
POEFrameWrapper->spawn statement. At this point, I still need a dummy
POE session to keep POE alive, but I suspect having a "background"
session that goes and polls for new data etc is not a bad thing.
POE::Kernel->run(); is used to start things instead of the $app->MainLoop();
Most of the work is done in the POEFrameWrapper class -- it creates the
frame and puts it inside a new POE Session. I also was able to get a
basic event build_handler working (got the ideas from POCO:SubWrapper
component).
The cool thing about this event handler concept is that you can now let
either the background session or another frame post events to a target
frame. Ideas I plan to use this for: a) when submitting a change in one
frame, force another frame to refresh, b) have the background session
periodically poll a server for fresh data and then force a frame to
refresh with the new data, etc.
Perhaps there are better Wx ways of doing this, but I like the
flexibility that POE gives on this...
Mattia - would you be open to the POE::Loop::Wx module you did posted to
the poe.perl.org site?
Mike Schroeder wrote:
Nothing too fancy yet - but at least it works! ;^)
Rename the POE-Loop-Wx.pm file to Wx.pm and put in path for POE/Loop/Wx.pm.
My big question so far is if it is appropriate to continue coding as we have
been in Wx, and code in normal POE style for that side of things? That way
lets the two styles run in parallel under the same Loop. Or, should we
adapt our style to make the Wx calls be more POE "states"? For example,
should an EVENT just call a subref that does a POE yield? Or should we let
Wx continue to manage all of those things.
More questions than answers so far, but hopefully this is helpful to you.
Mike Schroeder
-----Original Message-----
From: wxperl-users-admin@xxxxxxxxxxxxxxxxxxxxx
[mailto:wxperl-users-admin@xxxxxxxxxxxxxxxxxxxxx]On Behalf Of Simon
Flack
On Wed, 18 Aug 2004 07:27:06 -0600, Mike Schroeder wrote
On a slightly different topic, I found a post from Mattia of an initial
version of POE::Loop::Wx for those wanting to combine POE with
wxPerl. You could then use POE for many other non-blocking
interactions (poe.perl.org) So far I have invested a bit of time and
have the minimal.pl working with POE::Loop::Wx. I still need to do
some more complex examples, as well as some thinking about how POE
affects programming style in wxPerl. If anyone else some ideas or
examples, that would be great. I'd be happy to share anything I
have so far.
Yes please. Funny, I was thinking about this a few weeks ago. I'd
like to see
some examples, even simple ones.
#!/usr/bin/perl
#############################################################################
## Name: minimalpoe.pl
## Purpose: Minimal wxPerl using POE::Loop::Wx sample
## Author: Mike Schroeder based on minimal.pl from Mattia Barbon
## Modified by:
## Created: 11/10/2004
## Licence: This program is free software; you can redistribute it and/or
## modify it under the same terms as Perl itself
#############################################################################
use Wx;
use POE;
#-----------------------------------------------------------------------------
package MyApp;
#-----------------------------------------------------------------------------
use strict;
use base qw( Wx::App );
sub OnInit {
my $self = shift;
# We need to make sure the frame is part of self so that is available
# later when we want to post events to the frame from other frames
$self->{frame} = MyFrame->new( "Minimal wxPerl and POE Example",
Wx::Point->new( 50, 50 ),
Wx::Size->new( 450, 350 )
);
$self->SetTopWindow( $self->{frame} );
$self->{frame}->Show( 1 );
return 1;
}
1;
#-----------------------------------------------------------------------------
package MyFrame;
#-----------------------------------------------------------------------------
use strict;
use vars qw(@ISA);
@ISA=qw(Wx::Frame);
use Wx::Event qw(EVT_MENU);
use Wx qw(wxBITMAP_TYPE_ICO wxMENU_TEAROFF);
sub new {
my( $class ) = shift;
my( $this ) = $class->SUPER::new( undef, -1, $_[0], $_[1], $_[2] );
$this->SetIcon( Wx::GetWxPerlIcon() );
my( $mfile ) = Wx::Menu->new( undef, wxMENU_TEAROFF );
my( $mhelp ) = Wx::Menu->new();
my( $ID_ABOUT, $ID_EXIT ) = ( 1, 2 );
$mhelp->Append( $ID_ABOUT, "&About...\tCtrl-A", "Show about dialog" );
$mfile->Append( $ID_EXIT, "E&xit\tAlt-X", "Quit this program" );
my( $mbar ) = Wx::MenuBar->new();
$mbar->Append( $mfile, "&File" );
$mbar->Append( $mhelp, "&Help" );
$this->SetMenuBar( $mbar );
EVT_MENU( $this, $ID_EXIT, \&OnQuit );
EVT_MENU( $this, $ID_ABOUT, \&OnAbout );
$this->CreateStatusBar( 1 );
$this->SetStatusText( "Welcome to wxPerl!", 1 );
$this;
}
sub OnCreateStatusBar {
my( $this ) = shift;
my( $status ) = Wx::StatusBar->new( $this, -1 );
$status->SetFieldsCount( 2 );
$status;
}
sub OnQuit {
my( $this, $event ) = @_;
$this->Close( 1 );
Wx::wxTheApp->ExitMainLoop();
}
use Wx qw(wxOK wxICON_INFORMATION wxVERSION_STRING);
sub OnAbout {
my( $this, $event ) = @_;
Wx::MessageBox( "This is the about dialog of minimal sample.\n" .
"Welcome to wxPerl " . $Wx::VERSION . "\n" .
wxVERSION_STRING,
"About minimal", wxOK | wxICON_INFORMATION,
$this );
}
sub ext_event {
my( $this, $event ) = @_;
print "ext_event this is $this\n";
# let one POE-managed Frame publish events to another POE-managed frame
# kindof like a Publish/Subscribe system between Wx frames
# this could be used for auto-refresh of data from a background POE
# session or letting one frame update the data on another frame, etc.
print "got an ext_event\n";
Wx::MessageBox( "This is an external event\n",
"External event", wxOK | wxICON_INFORMATION,
$this );
}
1;
#-----------------------------------------------------------------------------
package POEFrameWrapper;
#-----------------------------------------------------------------------------
use POE;
use Wx::Perl::Carp;
sub spawn {
print "POEFrameWrapper->spawn: Entering\n";
my $type = shift;
my $package = shift; # the module/frame to wrap
my $alias = shift; # the POE Session alias to use
my $evt_ref = shift; # the frame event routines to wrap as POE events
croak "Too many args" if scalar @_;
$alias = $package unless defined($alias) and length($alias);
my %states;
$states{'_start'} = sub {
my ($kernel, $heap, $alias ) = @_[KERNEL, HEAP, ARG0 ];
#$heap->{self} = eval("use $package; $package->new();");
$heap->{self} = eval("$package->new();");
if ( $@ ) {
print "eval err: $@\n";
}
print "self is $heap->{self}\n";
$kernel->alias_set($alias);
};
$states{'_stop'} = sub {
my ($kernel, $heap) = @_[KERNEL, HEAP];
};
foreach my $event ( @{ $evt_ref } ) {
print "building event handler for $event\n";
$states{$event} = build_handler( $package, $event );
}
POE::Session->create(
inline_states => \%states,
args => [ $alias ],
);
undef;
}
sub build_handler {
my ( $pacakge, $sub ) = @_;
my $ref = sub {
my ($kernel, $heap, $sender, $callback, @args )
= @_[KERNEL, HEAP, SENDER, ARG0, ARG1 .. $#_ ];
my $self = $heap->{self};
print "handler self = $self->{frame}\n";
my $result = scalar $self->{frame}->$sub( @args );
$kernel->post($sender, $callback, $result) if $callback;
return;
};
return $ref;
}
1;
#-----------------------------------------------------------------------------
package main;
#-----------------------------------------------------------------------------
use POE;
# spawn a frame that will run as a POE session
POEFrameWrapper->spawn( 'MyApp', 'MyApp', [ qw( ext_event ) ] );
# for now, we need to start a background Session that keeps POE alive
# this could be some background process that checks for new data, etc.
# TODO - figure out how to get an open Wx frame to be enough reason for
# POE to stay alive...
POE::Session->create(
inline_states => {
_start => \&session_start,
_stop => \&session_stop,
count => \&session_count,
}
);
POE::Kernel->run();
sub session_start {
print "Session ", $_[SESSION]->ID, " has started.\n";
$_[HEAP]->{count} = 0;
$_[KERNEL]->delay_set("count",3);
}
sub session_stop {
print "Session ", $_[SESSION]->ID, " has stopped.\n";
}
sub session_count {
my ( $kernel, $heap ) = @_[ KERNEL, HEAP ];
my $session_id = $_[SESSION]->ID;
my $count = ++$heap->{count};
print "Session $session_id has counted to $count.\n";
unless ( $count % 5 ) { # trigger an event every 5th cycle
print "attempting to post to MyApp ext_event\n";
$kernel->post('MyApp', 'ext_event', $count );
}
$kernel->delay_set("count", 3);
}
#############################################################################
## Name: Wx.pm
## Purpose: POE::Loop::Wx, wxPerl event loop for POE
## Author: Mattia Barbon,
## Created: 26/05/2003
## RCS-ID: $Id: $
## Copyright: (c) 2003 Mattia Barbon
## Note: Part of the code comes almost straight from
## POE::Loop::Gtk and POE::Loop::Select
## Licence: This program is free software; you can redistribute it and/or
## modify it under the same terms as Perl itself
#############################################################################
package POE::Loop::Wx;
use Wx;
use strict;
use vars qw($VERSION);
$VERSION = "0.01";
package POE::Kernel;
use strict;
# Declare which event loop bridge is being used, but first ensure that
# no other bridge has been loaded.
BEGIN {
die( "POE can't use Wx and " . &POE_LOOP_NAME . "\n" )
if defined &POE_LOOP;
};
sub POE_LOOP () { LOOP_WX }
my @loop_vectors = ("", "", "");
my %loop_filenos;
my $time_watcher_timer;
my $file_watcher_timer;
###############################################################################
# Administrative functions
###############################################################################
sub loop_initialize {
my $self = shift;
# Initialize the vectors as vectors.
@loop_vectors = ( "", "", "" );
vec($loop_vectors[MODE_RD], 0, 1) = 0;
vec($loop_vectors[MODE_WR], 0, 1) = 0;
vec($loop_vectors[MODE_EX], 0, 1) = 0;
# start file polling timer
$file_watcher_timer = POE::Loop::Wx::PollTimer->new;
$file_watcher_timer->Start( 50, 0 );
}
sub loop_finalize {
my $self = shift;
# This is "clever" in that it relies on each symbol on the left to
# be stringified by the => operator.
my %kernel_modes =
( MODE_RD => MODE_RD,
MODE_WR => MODE_WR,
MODE_EX => MODE_EX,
);
while (my ($mode_name, $mode_offset) = each(%kernel_modes)) {
my $bits = unpack("b*", $loop_vectors[$mode_offset]);
if (index($bits, "1") >= 0) {
warn "<rc> LOOP VECTOR LEAK: $mode_name = $bits\a\n";
}
}
$time_watcher_timer->Destroy;
$file_watcher_timer->Destroy;
undef $time_watcher_timer;
undef $file_watcher_timer;
}
sub loop_do_timeslice {
die "doing timeslices currently not supported in the Wx loop";
}
sub loop_run {
Wx::wxTheApp()->MainLoop;
}
sub loop_halt {
Wx::wxTheApp->ExitMainLoop;
}
###############################################################################
# Signal handling, straight from POE::Loop::Gtk
###############################################################################
#------------------------------------------------------------------------------
# Signal handlers/callbacks.
sub _loop_signal_handler_generic {
if (TRACE_SIGNALS) {
warn "<sg> Enqueuing generic SIG$_[0] event";
}
$poe_kernel->_data_ev_enqueue
( $poe_kernel, $poe_kernel, EN_SIGNAL, ET_SIGNAL, [ $_[0] ],
__FILE__, __LINE__, time(),
);
$SIG{$_[0]} = \&_loop_signal_handler_generic;
}
sub _loop_signal_handler_pipe {
if (TRACE_SIGNALS) {
warn "<sg> Enqueuing PIPE-like SIG$_[0] event";
}
$poe_kernel->_data_ev_enqueue
( $poe_kernel, $poe_kernel, EN_SIGNAL, ET_SIGNAL, [ $_[0] ],
__FILE__, __LINE__, time(),
);
$SIG{$_[0]} = \&_loop_signal_handler_pipe;
}
# Special handler. Stop watching for children; instead, start a loop
# that polls for them.
sub _loop_signal_handler_child {
if (TRACE_SIGNALS) {
warn "<sg> Enqueuing CHLD-like SIG$_[0] event";
}
$SIG{$_[0]} = "DEFAULT";
$poe_kernel->_data_ev_enqueue
( $poe_kernel, $poe_kernel, EN_SCPOLL, ET_SCPOLL, [ ],
__FILE__, __LINE__, time(),
);
}
#------------------------------------------------------------------------------
# Signal handler maintenance functions.
sub loop_watch_signal {
my ($self, $signal) = @_;
# Child process has stopped.
if ($signal eq "CHLD" or $signal eq "CLD") {
# For SIGCHLD triggered polling loop.
# $SIG{$signal} = \&_loop_signal_handler_child;
# Begin constant polling loop. Only start it on CHLD or on CLD if
# CHLD doesn"t exist.
$SIG{$signal} = "DEFAULT";
$self->_data_ev_enqueue
( $self, $self, EN_SCPOLL, ET_SCPOLL, [ ],
__FILE__, __LINE__, time() + 1,
) if $signal eq "CHLD" or not exists $SIG{CHLD};
return;
}
# Broken pipe.
if ($signal eq "PIPE") {
$SIG{$signal} = \&_loop_signal_handler_pipe;
return;
}
# Artur Bergman (sky) noticed that xterm resizing can generate a LOT
# of WINCH signals. That rapidly crashes perl, which, with the help
# of most libc"s, can"t handle signals well at all. We ignore
# WINCH, therefore.
return if $signal eq "WINCH";
# Everything else.
$SIG{$signal} = \&_loop_signal_handler_generic;
}
sub loop_ignore_signal {
my ($self, $signal) = @_;
$SIG{$signal} = "DEFAULT";
}
# End of stuff copied from POE::Loop::Gtk
sub loop_attach_uidestroy {
my( $self, $window ) = @_;
# Don"t bother posting the signal if there are no sessions left. I
# think this is a bit of a kludge: the situation where a window
# lasts longer than POE::Kernel should never occur.
Wx::Event::EVT_CLOSE( $window,
sub {
if( $self->_data_ses_count() ) {
$self->_dispatch_event
( $self, $self,
EN_SIGNAL, ET_SIGNAL, [ "UIDESTROY" ],
__FILE__, __LINE__, time(), -__LINE__
);
}
return undef;
}
);
}
###############################################################################
# Alarm or timer functions
###############################################################################
sub loop_reset_time_watcher {
my( $self, $next_time ) = @_;
if( $time_watcher_timer ) {
$time_watcher_timer->Destroy;
undef $time_watcher_timer;
}
$time_watcher_timer = POE::Loop::Wx::Timer->new;
$self->loop_resume_time_watcher( $next_time );
}
BEGIN {
if( $^O eq "MSWin32" ) {
eval "sub MIN_TIME() { 1 }";
} else {
eval "sub MIN_TIME() { 0 }";
}
}
sub loop_resume_time_watcher {
my( $self, $next_time ) = @_;
$time_watcher_timer = POE::Loop::Wx::Timer->new
unless $time_watcher_timer;
$next_time -= time();
$next_time *= 1000;
$next_time = MIN_TIME if $next_time <= 0;
$time_watcher_timer->Start( $next_time, 1 );
}
sub loop_pause_time_watcher {
$time_watcher_timer->Stop;
}
###############################################################################
# File activity functions; similar to POE::Loop::Select
###############################################################################
#------------------------------------------------------------------------------
# Maintain filehandle watchers.
sub loop_watch_filehandle {
my( $self, $handle, $mode ) = @_;
my $fileno = fileno( $handle );
vec( $loop_vectors[$mode], $fileno, 1 ) = 1;
$loop_filenos{$fileno} |= ( 1 << $mode );
}
sub loop_ignore_filehandle {
my( $self, $handle, $mode ) = @_;
my $fileno = fileno( $handle );
vec( $loop_vectors[$mode], $fileno, 1 ) = 0;
$loop_filenos{$fileno} &= ~ ( 1 << $mode );
}
sub loop_pause_filehandle {
my( $self, $handle, $mode ) = @_;
my $fileno = fileno( $handle );
vec( $loop_vectors[$mode], $fileno, 1 ) = 0;
$loop_filenos{$fileno} &= ~ ( 1 << $mode );
}
sub loop_resume_filehandle {
my( $self, $handle, $mode ) = @_;
my $fileno = fileno( $handle );
vec( $loop_vectors[$mode], $fileno, 1 ) = 1;
$loop_filenos{$fileno} |= ( 1 << $mode );
}
# End of stuff similar to POE::Loop::Select
package POE::Loop::Wx::Timer;
use strict;
use base "Wx::Timer";
sub Notify {
package POE::Kernel;
my $self = $poe_kernel;
$self->_data_ev_dispatch_due();
$self->_test_if_kernel_is_idle();
# Register the next timeout if there are events left.
if( $self->get_event_count() ) {
$self->loop_resume_time_watcher( $self->get_next_event_time() );
}
}
package POE::Loop::Wx::PollTimer;
use strict;
use base "Wx::Timer";
sub Notify {
package POE::Kernel;
my $self = $poe_kernel;
# Determine which files are being watched.
my @filenos = ();
while( my( $fd, $mask ) = each( %loop_filenos ) ) {
push( @filenos, $fd ) if $mask;
}
return unless @filenos;
# Check filehandles, or wait for a period of time to elapse.
my $hits = select( my $rout = $loop_vectors[MODE_RD],
my $wout = $loop_vectors[MODE_WR],
my $eout = $loop_vectors[MODE_EX],
0,
);
return unless $hits > 0;
# This is where they"re gathered. It"s a variant on a neat
# hack Silmaril came up with.
my( @rd_selects, @wr_selects, @ex_selects );
foreach ( @filenos ) {
push( @rd_selects, $_ ) if vec( $rout, $_, 1 );
push( @wr_selects, $_ ) if vec( $wout, $_, 1 );
push( @ex_selects, $_ ) if vec( $eout, $_, 1 );
}
@rd_selects and
$self->_data_handle_enqueue_ready( MODE_RD, @rd_selects );
@wr_selects and
$self->_data_handle_enqueue_ready( MODE_WR, @wr_selects );
@ex_selects and
$self->_data_handle_enqueue_ready( MODE_EX, @ex_selects );
}
1;
__END__
|