I want to implement a history/recent-files functionality for my Perl/Tk program.
Here is a working code excerpt from my program to demonstrate my problem.
#!/usr/bin/perl
use strict;
use warnings;
use English qw( -no_match_vars );
use Tk;
my #history_entries = qw(Back To The History);
my $mw = MainWindow->new();
my $menubar = $mw->Menu();
$mw->configure( -menu => $menubar );
my $file = $menubar->cascade( -label => '~File' );
my $history = $file->cascade( -label => '~History', );
build_history();
MainLoop();
#=== FUNCTION ================================================================
# NAME: build_history
# PURPOSE: Polulate the history
#===============================================================================
sub build_history {
foreach (#history_entries) {
$history->command(
-label => $ARG,
-command => \&some_function,
-compound => 'left',
);
}
return;
} # ---------- end of subroutine build_history ----------
As you can see, the entries are created with $history->command but how can I delete them every time I call build_history?
The reason I want them deleted is that everytime a user opens a file, the last item (in this case 'History') should be deleted and a new item should be put on top of the list. So that I have a maximum of (in this example) four entries.
Any other ways of implementing this functionality with Tk are welcome.
Thanks for your time.
UPDATE:
I followed the advice of "mu" and tried to get the subwidgets with the children() function just after the function is called, like this:
my #child = $history->children();
foreach my $c ( #child ) {
if ($c->Exists()){
$c->destroy;
}
}
The program exits with the error:
Can't locate object method "children" via package "Tk::Menu::Cascade" at /home/alex/Desktop/stack_history.pl line 28.
you can create a submenu from the menubar first:
my $sub_menu = $menubar->Menu(-tearoff => 0);
Then you can pass this submenu to the cascade menuitem:
$file->cascade( -label => '~History', -menu => $sub_menu);
Then you can add/delete menuitems to the submenu:
foreach (#history_entries) {
$sub_menu->command(
-label => $ARG,
-compound => 'left',
);
}
...
$sub_menu->delete(0); # Remove first element
With this solution you can avoid rebulding the whole menu.
I ended up rebuilding the whole menu. That's how my code looks like atm. I am not proud of it but it works ... I am open to any form of advice.
#!/usr/bin/perl
use strict;
use warnings;
use English qw( -no_match_vars );
use Tk;
# History entries are stored in array
my #history_entries = qw(Back To The History);
my $mw = MainWindow->new();
$mw->geometry('200x200');
my $menubar = $mw->Menu();
#Build menus
$mw->configure( -menu => $menubar );
build_menu();
$mw->Button(
-text => 'Update History',
-command => \sub {
#when a user opens a file insert_history is called.
insert_history();
}
)->pack( -side => 'bottom', -anchor => 'sw' );
MainLoop();
#=== FUNCTION ================================================================
# NAME: build_menu
# PURPOSE: Update/Build the menu
#===============================================================================
sub build_menu {
#delete the whole menu
$menubar->delete(1);
#built it again
my $file = $menubar->cascade( -label => '~File' );
my $history = $file->cascade( -label => '~History', );
foreach (#history_entries) {
$history->command(
-label => $ARG,
-compound => 'left',
);
}
return;
} # ---------- end of subroutine build_menu ----------
#=== FUNCTION ================================================================
# NAME: insert_history
# PURPOSE: Do something with the array containing the history entries.
# Then rebuild the menu.
#===============================================================================
sub insert_history {
#make something with the array
my $last_element = pop #history_entries;
unshift #history_entries, $last_element;
#update menu
build_menu();
return;
} # ---------- end of subroutine insert_history ----------
Related
I am using the perl Tk interface where I want to have a button test_1 and upon clicking on this button I would like a variable $varchoice to be defined as test_1. If I press on the button test_2, the variable $varchoice should be defined as test_2.
Before is the snippet of code in which I attempted to accomplish this:
$budget_frame->Button(-text => 'test_1',-command => sub{$varchoice=budget_plot_1()})->pack(-side => "left");
$budget_frame->Button(-text => 'test_2',-command => sub{$varchoice=budget_plot_2()})->pack(-side => "left");
sub budget_plot_1()
{
print "plotting 1\n";
my $var=1;
return $var;
}
sub budget_plot_2()
{
print "plotting 2\n";
my $var=2;
return $var;
}
How do I tweak this code to get the desired results?
Your program seems to work fine. Here is an example of how I tested it:
use feature qw(say);
use strict;
use warnings;
use Tk;
my $budget_frame = MainWindow->new(-title=>"Button test");
my $varchoice;
$budget_frame->Button(
-text => 'test_1',
-command => sub{ $varchoice = budget_plot_1() }
)->pack(-side => "left");
$budget_frame->Button(
-text => 'test_2',
-command => sub{ $varchoice = budget_plot_2() }
)->pack(-side => "left");
MainLoop;
say "Value of \$varchoice = $varchoice";
sub budget_plot_1()
{
print "plotting 1\n";
return "test_1";
}
sub budget_plot_2()
{
print "plotting 2\n";
return "test_2";
}
Output:
Value of $varchoice = test_1
Given this Perl/Tkx code fragment:
#itemList = ({'attrib1' => 'name1', 'attrib2' => 'value1'},
{'attrib1' => 'name2', 'attrib2' => 'value2'});
$row = 0;
foreach $item (#itemList) {
push(#btn_list, new_ttk__button(-text => $item->{'attrib1'}, -command => sub {do_something($item->{'attrib2'});}));
$btn_list[-1]->g_grid(-column => 0, -row => $row);
$row++;
}
(In the real program #itemList is populated from a user editable config file.)
I do see two buttons labeled 'name1' and 'name2'. But when I click on either button it seems that the parameter that is passed to the callback is always $itemList[1]->{'attrib2'}; i.e. 'attrib2' of the last element of the #itemList array. What I would like is to have the first button call do_something($itemList[0]->{'attrib2'} and the second call do_something($itemList[1]->{'attrib2'}.
What am I doing wrong, please and thank you?
You have encountered a subtle feature of for loops in Perl. First the solution: use my in the for loop. Then $item will be able to create a proper closure in the anonymous sub you declare later in the loop.
for my $item (#itemlist) {
push(#btn_list, new_ttk__button(
-text => $item->{'attrib1'},
-command => sub {do_something($item->{'attrib2'});}));
$btn_list[-1]->g_grid(-column => 0, -row => $row);
$row++;
}
Further explanation: Perl implicitly localizes the subject variable of a for loop. If you don't use my in the for loop, the loop will be using a localized version of a package variable. That makes your code equivalent to:
package main;
$main::item = undef;
#itemList = ({'attrib1' => 'name1', 'attrib2' => 'value1'},
{'attrib1' => 'name2', 'attrib2' => 'value2'});
$row = 0;
foreach (#itemList) {
local $main::item = $_;
push(#btn_list, new_ttk__button(
-text => $main::item->{'attrib1'},
-command => sub {do_something($main::item->{'attrib2'});}));
$btn_list[-1]->g_grid(-column => 0, -row => $row);
$row++;
}
# at the end of the loop, value of $main::item restored to undef
Your anonymous subs still refer to the $main::item package variable, whatever value that variable holds at the time that those subroutines are invoked, which is probably undef.
Shorter solution: use strict
Additional proof-of-concept. Try to guess what the following program outputs:
#foo = ( { foo => 'abc', bar => 123 },
{ foo => 'def', bar => 456 } );
my #fn;
foreach $foo (#foo) {
push #fn, sub { "42" . $foo->{bar} . "\n" };
}
foreach my $foo (#foo) {
push #fn, sub { "19" . $foo->{foo} . "\n" };
}
print $_->() for #fn;
Here's the answer:
42
42
19abc
19def
I have an issue with a Perl CGI form which have two submit buttons. Let me explain with a sample code.
print $q->start_form(
-name => 'main',
-method => 'POST',
);
print $q->popup_menu( #drop down list
-name => 'popup1',
-values => #test,
-default => 'value2'
print $q->submit(
-name => 'do one thing',
);
print $q->submit(
-name => 'do two things',
);
if ($q->param("do one thing")) {
do certain functions;
}
if ($q->param("do two things")) {
##########output a checkbox#############
print $q->checkbox_group(
-name => 'checkemail',
-values => #test2,
-columns => 2,
-rows => 10,
);
###############submit the checkbox###########
print $q->submit(
-name => 'Submit',
);
if ($q->param("Submit")) {
do certain functions;
}
The code is simple. When we click "do one thing" button it should do certain function. When we click "do two things" its should display a checkbox. The form works fine until this. After the checkbox there is a submit button to submit this checkbox and do certain function. Unfortunately if I click submit button to submit checkbox it doesn't do the task after submit button. Also it even don't display the checkbox after we click the submit button of checkbox. Any help is appreciated.
Your indentation is chaotic. It might be a part of the problem: you misplaced the code that handles the Submit button. It is only run if the "do two things" button was pressed, but it is not possible to press both the buttons. Fixed (and runnable) code:
#!/usr/bin/perl
use strict;
use warnings;
use CGI;
my $q = 'CGI'->new;
my #test = qw(a b);
my #test2 = qw(A B);
print $q->start_form( -name => 'main',
-method => 'POST',
);
print $q->popup_menu( #drop down list
-name => 'popup1',
-values => \#test,
-default => 'value2');
print $q->submit(-name => 'do one thing');
print $q->submit(-name => 'do two things');
if ($q->param('do one thing')) {
print "One thing: ", $q->param('popup1'), "\n";
}
if ($q->param('do two things')) {
print $q->checkbox_group( -name => 'checkemail',
-values => \#test2,
-columns => 2,
-rows => 10,
);
print $q->submit(-name => 'Submit');
}
if ($q->param('Submit')) {
print 'Sencond thing', $q->param('checkemail'), "\n";
}
Notice the code uses array references in -values.
in perl Tk I want to ask how to get the calling widget when using the -command option.
I have a dialog widget that is dynamically created by a previously unknown number of entries. There are Entry-widget and next to each a button where one can browse possible (useful) strings for the Entry.
Therefore, if the button next to the 2nd Entry is pressed, the result should be stored in the 2nd Entry and so on.
The code looks as follows.
my $count = 0;
my #name = ();
my #val = ();
my #edit = ();
my #button = ();
my $fr = $wind->Frame->pack;
foreach ( #outputs ) {
$name[$count] = helper::trim($_);
$val[$count] = '';
$fr->Label(-text => $name[$count])->grid(-row => $count, -column => 0, -sticky => 'w');
$edit[$count] = $fr->Entry(-textvariable => \$val[$count], -width => 30)->grid(-row => $count, -column => 1);
$button[$count] = $fr->Button(-width => 20, -text => 'Select',
-command => sub { &select(\$val[$count], \$edit[$count]); }
)->grid(-row => $count, -column => 2);
$count++;
}
However, since &select is called after the complete dialog box was built $count is always on the last value and I don't get the info, which button I have pressed. Lines as
-command => sub { &select(\#val, \#edit, $count); }
also didn't work.
Is there some way to get the widget that called the sub-routine (so I can store necessary infos somehow in the respective button) or use the actual value (of $count here) in such a call?
Best regards.
inside the callback:
my $caller = $Tk::widget;
I want to add a new widget to my script that will open a new window with text and will close automatically after 1sec.
how can I do it ?
I think what you want is Tk::after.
#!/usr/bin/perl
use strict;
use warnings;
use Tk;
my $mw = MainWindow->new;
my $spawn = $mw->Button(
-text => 'spawn',
-command => sub {
my $subwindow = MainWindow->new;
my $label = $subwindow->Label(-text => "spawned");
$label->pack;
$subwindow->after(1_000, sub { $subwindow->destroy; });
}
);
$spawn->pack;
my $exit = $mw->Button(
-text => 'exit',
-command => sub { print "exiting...\n"; exit }
);
$exit->pack;
MainLoop;