Tanktalus has asked for the wisdom of the Perl Monks concerning the following question:

I'm looking at Wx just kind of as a way to teach myself a bit of something new ... and I'm stuck. Really early, too. *sigh* What I'm trying to do is create a frame with naught but a menu and a multi-line text box. The menu should include the standard "cut", "paste", etc. So, I started hooking stuff in to try, but found that as soon as I hook in the Paste menu item, for example, it stops working. That is, if I don't add that item to the menu, I can paste with Ctrl-V just fine. But when I try to hook up the menu item, that stops working. And I can't seem to set an EVT_MENU for it, either, at least not pointing directly to the Paste method of the Wx::TextCtrl object.

Ok, there's what the problem is - here's the code I'm using:

#!/usr/bin/perl use strict; use warnings; use Wx; use Smart::Comments '###'; package WxPMEdit; use base 'Wx::App'; use Wx qw( wxDEFAULT_FRAME_STYLE ); sub OnInit { my $self = shift; my($frame) = WxPMFrame->new( undef, # parent -1, # ID (-1 = don't care) "wxPMTest", # title [-1,-1], # position (don't care) [500,300], # size wxDEFAULT_FRAME_STYLE, # window name? not really sure what this + is ); $frame->Show(1); return 1; # success } package WxPMText; use base 'Wx::TextCtrl'; use Wx qw( wxTE_MULTILINE ); sub new { my $class = shift; ### assert: @_ >= 3 $_[3] ||= [-1,-1]; $_[4] ||= [-1,-1]; $_[5] |= wxTE_MULTILINE; my $self = $class->SUPER::new(@_); # this doesn't work... so have to hack the parameters (see above) # $self->SetStyle(Wx::TextCtrl::wxTE_MULTILINE); $self; } package WxPMFrame; use base 'Wx::Frame'; use Wx::Event; use Wx qw( wxID_EXIT wxID_CUT wxID_COPY wxID_PASTE wxID_UNDO wxID_REDO ); my @menu = ( [ '&File', [ -1, "&Open\tCtrl-O", 'Open' ], '-', [ wxID_EXIT, "E&xit\tCtrl-Q", 'Exit' ], ], [ '&Edit', [ wxID_UNDO, "&Undo\tAlt", 'Undo' ], #{ menuitem => [ wxID_UNDO, "&Undo\tAlt", 'Undo' ], object => ' +_text', method => 'Undo' }, [ wxID_REDO, "&Redo\tCtrl-Alt", 'Redo' ], '-', #[ wxID_CUT, "Cu&t\tCtrl-X", 'Cut' ], #[ wxID_COPY, "&Copy\tCtrl-C", 'Copy' ], { menuitem => [ wxID_PASTE, "&Paste\tCtrl-V", 'Paste' ], object + => '_text' }, #[ wxID_PASTE, "&Paste\tCtrl-V", 'Paste' ], [ -1, "Select &All\tCtrl-A", 'Select All' ] ], ); sub _SetupMenu { my $self = shift; my $menubar = Wx::MenuBar->new(); for my $top_menu (@menu) { my $menu = Wx::Menu->new(); $menubar->Append($menu, shift @$top_menu); for (@$top_menu) { if (ref $_) { # we may have an array ref, or a hash ref (with the ar +ray ref # inside) my $data = ref $_ eq 'ARRAY' ? { menuitem => $_ } : $_ +; my $item = $data->{menuitem}; my $id = #$self->{_menu}{$item->[2]} = $menu->Append(@$item)->GetId(); # look for the handler - with fallback my $func = $data->{method} || $item->[2]; # who will handle? my $object = $data->{object} || $self; $object = $self->{$object} if not ref $object; # how do we do this? my $code = $func; if (ref $code or $code = $object->can($func) or $code = $object->can(do { ($func = 'OnMenu' . $fun +c) =~ s/\s+//g; $func }) ) { $Data::Dumper::Deparse = 1; ### setup menu: $object ### id: $id ### code: $code ### func: $func Wx::Event::EVT_MENU($object, $id, $code); } else { warn "$item->[2] not implemented\n"; } } elsif ($_ eq '-') { $menu->AppendSeparator(); } else { die "bad data in \@menu"; } } } $self->SetMenuBar($menubar); } sub new { my $class = shift; my $self = $class->SUPER::new(@_); $self->{_text} = WxPMText->new($self, -1, ''); $self->{_text}->SetFocus; $self->_SetupMenu; $self } sub OnMenuOpen { my $self = shift; print "OnMenuOpen stub"; } sub OnMenuUndo { my $self = shift; $self->{_text}->Undo(); } sub OnMenuRedo { my $self = shift; $self->{_text}->Redo(); } sub OnMenuPaste { my $self = shift; ### pasting $self->{_text}->Paste(); ### paste code: $self->{_text}->can('Paste') } sub OnMenuExit { my $self = shift; $self->Close(1); } sub OnMenuSelectAll { my $self = shift; $self->{_text}->SetSelection(-1,-1); } package main; my $app = WxPMEdit->new(); $app->MainLoop(); 0;
Now, if I run this, Shift-Insert still pastes, but Ctrl-V doesn't. If I comment out the wxID_PASTE line, Ctrl-V starts working (but, of course, it's not in the menu). If I uncomment the following line, it works, but goes through the extra layer of the OnMenuPaste function in my Frame - which seems silly and excessive. I'd hate to have to have a sub for each menu item when all it's doing is forwarding, even if the subs are anonymous.

I must be missing something - so I'm hoping others can help here. If you don't have Smart::Comments installed, just comment out the line that uses it :-)

As you can see - I like data-driven programming - the menu is all in a list, and then I iterate over it to populate everything. I'm sure it'll get refactored again later if I start playing with events other than menu events.

Replies are listed 'Best First'.
Re: Wx: Passing events
by Anonymous Monk on Jun 28, 2008 at 06:03 UTC
    # this doesn't work... so have to hack the parameters (see above) # $self->SetStyle(Wx::TextCtrl::wxTE_MULTILINE);
    It doesn't work because SetStyle controls wxTextAttr (ie font, color ...), not "Window styles", you need
    $self->SetWindowStyle( wxTE_MULTILINE | $self->GetWindowStyleFlag ); # wxPerl bug, GetWindowStyle synonym unavailable, reported # http://sourceforge.net/tracker/index.php?func=detail&aid=2004831&gro +up_id=15655&atid=115655
      And that doesn't work on windows. The apperance is changed (scrollbar is added), but it doesn't accept multiline input (or respond to enter), apparently a wxwidgets win32 limitation.
Re: Wx: Passing events
by Anonymous Monk on Jun 28, 2008 at 07:04 UTC
    Your code is too confusing, so I modified samples\minimal to demonstrate what you need to do (find "WHAT YOU NEED TO DO"):
    #!/usr/bin/perl ###################################################################### +####### ## Name: samples/minimal/minimal.pl ## Purpose: Minimal wxPerl sample ## Author: Mattia Barbon ## Modified by: ## Created: 29/10/2000 ## RCS-ID: $Id: minimal.pl,v 1.6 2005/08/19 22:34:32 mbarbon Exp +$ ## Copyright: (c) 2000 Mattia Barbon ## Licence: This program is free software; you can redistribute it + and/or ## modify it under the same terms as Perl itself ###################################################################### +####### use Wx; # every program must have a Wx::App-derive class package MyApp; use strict; use vars qw(@ISA); @ISA=qw(Wx::App); # this is called automatically on object creation sub OnInit { my( $this ) = @_; # create new MyFrame my( $frame ) = MyFrame->new( "Minimal wxPerl app", Wx::Point->new( 50, 50 ), Wx::Size->new( 450, 350 ) ); # set it as top window (so the app will automatically close when # the last top window is closed) $this->SetTopWindow( $frame ); # WHAT YOU NEED TO DO use Wx qw( wxTE_MULTILINE wxID_PASTE ); $frame->{_text} = Wx::TextCtrl->new( $frame,-1,'', [-1,-1] , [-1,- +1],wxTE_MULTILINE ); # add paste to 1st (File) Menu in Menubar $frame->GetMenuBar()->GetMenu(0)->Append( wxID_PASTE , "&Paste\tCt +rl-V", 'Paste' ); Wx::Event::EVT_MENU( $frame, wxID_PASTE, sub { my ( $s, $e ) = @_; $s->{_text}->Paste; } ); # show the frame $frame->Show( 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); # Parameters: title, position, size sub new { my( $class ) = shift; my( $this ) = $class->SUPER::new( undef, -1, $_[0], $_[1], $_[2] ); # load an icon and set it as frame icon $this->SetIcon( Wx::GetWxPerlIcon() ); # create the menus 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 ); # declare that events coming from menu items with the given # id will be handled by these routines EVT_MENU( $this, $ID_EXIT, \&OnQuit ); EVT_MENU( $this, $ID_ABOUT, \&OnAbout ); # create a status bar (note that the status bar that gets created # has three panes, see the OnCreateStatusBar callback below $this->CreateStatusBar( 1 ); # and show a message $this->SetStatusText( "Welcome to wxPerl!", 1 ); $this; } # this is an addition to demonstrate virtual callbacks... # it ignores all parameters and creates a status bar with three fields sub OnCreateStatusBar { my( $this ) = shift; my( $status ) = Wx::StatusBar->new( $this, -1 ); $status->SetFieldsCount( 2 ); $status; } # called when the user selects the 'Exit' menu item sub OnQuit { my( $this, $event ) = @_; # closes the frame $this->Close( 1 ); } use Wx qw(wxOK wxICON_INFORMATION wxVERSION_STRING); # called when the user selects the 'About' menu item sub OnAbout { my( $this, $event ) = @_; # display a simple about box 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 ); } package main; # create an instance of the Wx::App-derived class my( $app ) = MyApp->new(); # start processing events $app->MainLoop(); # Local variables: # # mode: cperl # # End: #

      That part you said "WHAT YOU NEED TO DO" is *exactly* what I said worked - and thought there had to be a better/simpler way to do. That is, manually passing events (especially keyboard events) from the menu to the appropriate widget seems silly to me. What I'm not getting is why I can't just reroute the events to the widget who cares (and already has a built-in method to handle).

        That part you said "WHAT YOU NEED TO DO" is *exactly* what I said worked - and thought there had to be a better/simpler way to do.
        Sorry about that, I did say I found your code confusing.

        That is, manually passing events (especially keyboard events) from the menu to the appropriate widget seems silly to me. What I'm not getting is why I can't just reroute the events to the widget who cares (and already has a built-in method to handle).
        I'm surprised its not "weird", or a "bug" :) Read Event handling overview. When the textctrl has focus, it handles Ctrl-V by itself through the default handler. When the textctrl doesn't have focus, it doesn't receive events. I don't think your approach would work on a wxwidgets level.

        I say try the mailing list for more better help :)