CGI/Perl Guide | Learning Center | Forums | Advertise | Login
Site Search: in

  Main Index MAIN
INDEX
Search Posts SEARCH
POSTS
Who's Online WHO'S
ONLINE
Log in LOG
IN

Home: Perl Programming Help: Intermediate:
Multi Menu, multi-function Tk Ap

 



PapaGeek
User

Mar 11, 2014, 11:46 PM

Post #1 of 15 (2670 views)
Multi Menu, multi-function Tk Ap Can't Post

 
On Edit: My second post shows the same code broken into individual Perl menu files.

Well, I’ve switches to Tk, and of course every action now has a new name!

Is this the correct Tk way to create a main menu which calls various sub menus which control their own actions? I only want one menu visible at a time. If you are doing A you can’t start or see B.

Any suggestions on making this more Perlish or better Tk widgets to use is appreciated.

If this is correct, my next step will be to break up the code into separate Perl files for each menu and action.



Code
use Modern::Perl '2013';  
use Tk;

my $mw = new MainWindow;
$mw->geometry("200x100");
my $lblMsg = $mw -> Label(-text=>"Work with sub windows")-> grid();
my $openMenu1 = $mw -> Button(-text=>"See Option 1",
-command =>\&showOption1)-> grid();
my $openMenu2 = $mw -> Button(-text=>"See Option 2",
-command =>\&showOption2)-> grid();
my $endProgram = $mw -> Button(-text=>"Exit Program",
-command =>\&endProgram)-> grid();

my $option1 = new MainWindow;
$option1->geometry("200x200");
my $lblMsg1 = $option1 -> Label(-text=>"Program Option 1")-> grid();
my $doTask1 = $option1 -> Button(-text=>"Perform Task 1",
-command =>\&Task1)-> grid();
my $btnPostpone1 = $option1 -> Button(-text=>"Return to Main",
-command =>\&showMainFrom1)-> grid();
$option1-> withdraw();

my $option2 = new MainWindow;
$option2->geometry("250x150");
my $lblMsg2 = $option2 -> Label(-text=>"Program Option 2")-> grid();
my $doTask2 = $option2 -> Button(-text=>"Perform Task 2",
-command =>\&Task2)-> grid();
my $btnPostpone2 = $option2 -> Button(-text=>"Return to Main",
-command =>\&showMainFrom2)-> grid();
$option2-> withdraw();

MainLoop;

sub showOption1{
$mw-> withdraw();
$option1->deiconify();
$option1->raise();
}
sub showOption2{
$mw-> withdraw();
$option2->deiconify();
$option2->raise();
}
sub showMainFrom1{
$option1-> withdraw();
$mw->deiconify();
$mw->raise();
}
sub showMainFrom2{
$option2-> withdraw();
$mw->deiconify();
$mw->raise();
}
sub Task1{
say "doing task 1";
}sub Task2{
say "doing task 2";
}

sub endProgram{
exit;
}



(This post was edited by PapaGeek on Mar 12, 2014, 7:34 AM)


FishMonger
Veteran / Moderator

Mar 12, 2014, 7:43 AM

Post #2 of 15 (2664 views)
Re: [PapaGeek] Multi Menu, multi-function Tk Ap [In reply to] Can't Post

Rather than creating multiple independent MainWindow objects, it's preferable to use Tk::Toplevel to create the child windows.
http://search.cpan.org/~srezic/Tk-804.032/pod/Toplevel.pod

Here's an example pulled from a similar question on perlmonks.

Code
#!/usr/bin/perl 
use warnings;
use strict;
use Tk;

my $mw = tkinit;

my $top = $mw->Toplevel();
$top->configure(-title=>'Fruit Menu');

my $ls_show_fruit = $top->Scrolled('Listbox',
-relief=>'groove',
-width=>'20',
-height=>5,
-scrollbars=>'se' ,
-selectmode =>'single'
)->pack(-side=>'left',-anchor=>'sw');

$ls_show_fruit->insert('end',"apples");
$ls_show_fruit->insert('end',"oranges");
$ls_show_fruit->insert('end',"peaches");

$top->Button(-text => 'Ok',
-command => sub{
$top->withdraw;
$mw->deiconify;
$mw->raise;

})->pack();

$top->withdraw;


$mw->Button(-text=> 'select_fruit',
-command => sub{
$top->deiconify;
$top->raise;
$mw->withdraw;

})->pack;

MainLoop;


While it is common to use a button widget to create a child window, IMO it's better to use Tk::Menu to create a standard menubar with links that create the child windows and then use button widgets to preform specific tasks within the window.
http://search.cpan.org/~srezic/Tk-804.032/pod/Menu.pod


(This post was edited by FishMonger on Mar 12, 2014, 7:46 AM)


PapaGeek
User

Mar 12, 2014, 7:44 AM

Post #3 of 15 (2663 views)
Re: [PapaGeek] Multi Menu, multi-function Tk Ap [In reply to] Can't Post

The menus in this example are extremely simple. In real life, each menu will probably be far more complex. This makes it essential that each menu is contained in its own file. Switching between menus requires that each menu file knows the reference to other menus so it can hide are show menus within the system. I did this by making each menu reference “our” so the each menu can see the others.

I’d appreciate any advice on how to make this system more Perlish.

The code is now broken up into 4 separate Perl files:

TkMenus.pl: The mainline that calls the menu creation subs and runs the main loop.

Code
use Modern::Perl '2013'; 
use Tk;

our $mainMenu;
our $menu1;
our $menu2;

main();

sub main
{
do 'MainMenu.pl';
do 'Menu1.pl';
do 'Menu2.pl';

CreateMainMenu(); # This only creates the top menu
CreateMenu1(); # This creates and hides a sub menu
CreateMenu2(); # This creates and hides a sub menu

MainLoop;
}

MainMenu.pl: The top level menu that calls the other two menus

Code
use Modern::Perl '2013'; 
use Tk;

our $mainMenu;
our $menu1;
our $menu2;

sub CreateMainMenu
{
if (defined($mainMenu))
{ return $mainMenu; }
print "Create main menu\n";

$mainMenu = new MainWindow;
$mainMenu->geometry("200x100");
my $lblMsg = $mainMenu -> Label(-text=>"Work with sub windows")-> grid();
my $openMenu1 = $mainMenu -> Button(-text=>"See Option 1",
-command =>\&showMenu1)-> grid();
my $openMenu2 = $mainMenu -> Button(-text=>"See Option 2",
-command =>\&showMenu2)-> grid();
my $endProgram = $mainMenu -> Button(-text=>"Exit Program",
-command =>\&endProgram)-> grid();

return $mainMenu;
}

sub showMenu1{
$mainMenu-> withdraw();
$menu1->deiconify();
$menu1->raise();
}
sub showMenu2{
$mainMenu-> withdraw();
$menu2->deiconify();
$menu2->raise();
}

sub endProgram{
exit;
}

Menu1.pl: The first sub menu that can run non-menu tasks and return to the main menu.

Code
use Modern::Perl '2013'; 
use Tk;

our $mainMenu;
our $menu1;
our $menu2;

sub CreateMenu1
{
if (defined($menu1))
{ return $menu1; }
print "Create menu 1\n";
$menu1 = new MainWindow;
$menu1->geometry("200x200");
my $lblMsg1 = $menu1 -> Label(-text=>"Program Option 1")-> grid();
my $doTask1 = $menu1 -> Button(-text=>"Perform Task 1",
-command =>\&Task1)-> grid();
my $btnPostpone1 = $menu1 -> Button(-text=>"Return to Main",
-command =>\&showMainFrom1)-> grid();
$menu1-> withdraw();
return $menu1;
}

sub showMainFrom1{
$menu1-> withdraw();
$mainMenu->deiconify();
$mainMenu->raise();
}

sub Task1{
say "doing task 1";
}

Menu2.pl: The second sub menu that can run non-menu tasks and return to the main menu.

Code
use Modern::Perl '2013'; 
use Tk;

our $mainMenu;
our $menu1;
our $menu2;

sub CreateMenu2
{
if (defined($menu2))
{ return $menu2; }
print "Create menu 2\n";
$menu2 = new MainWindow;
$menu2->geometry("200x200");
my $lblMsg2 = $menu2 -> Label(-text=>"Program Option 2")-> grid();
my $doTask2 = $menu2 -> Button(-text=>"Perform Task 2",
-command =>\&Task2)-> grid();
my $btnPostpone2 = $menu2 -> Button(-text=>"Return to Main",
-command =>\&showMainFrom2)-> grid();
$menu2-> withdraw();
return $menu2;
}

sub showMainFrom2{
$menu2-> withdraw();
$mainMenu->deiconify();
$mainMenu->raise();
}

sub Task2{
say "doing task 2";
}



PapaGeek
User

Mar 12, 2014, 7:56 AM

Post #4 of 15 (2660 views)
Re: [FishMonger] Multi Menu, multi-function Tk Ap [In reply to] Can't Post

Thanks for the reply, looks like you posted yours one minute before I posted mine!

A few questions.

Within the schema of the menu system I see for my ap, many of the menus will be generated when a user request encounters a problem. When that happens a new menu will “pop up” to ask for more information. I need to make sure that only one menu is active at a time. This is what led to the withdraw and raise code in my example.

I am somewhat familiar with Tk::Toplevel. Each top level menu is the child of a parent menu. Can a second top level menu become the grandchild of another child menu?

$mainMenu = new MainWindow;
$child = $mainMenu->Toplevel(?options?)
$grandchild = $child->Toplevel(?options?)

And so forth as required.

Also, what is the correct way to disable, and / or hide, the parent menu when the child is displayed, and then re-enable the parent when the child is done?


FishMonger
Veteran / Moderator

Mar 12, 2014, 8:27 AM

Post #5 of 15 (2656 views)
Re: [PapaGeek] Multi Menu, multi-function Tk Ap [In reply to] Can't Post

My Tk coding is rusty, so I'll need to dig up my Mastering Perl/Tk book to refresh my memory before coding up a possible alternate approach.


Quote
Any suggestions on making this more Perlish or better Tk widgets to use is appreciated.

If this is correct, my next step will be to break up the code into separate Perl files for each menu and action.


I'm not sure what you mean by "If this is correct". Correct in what way?

If it compiles and does what you want without errors or warnings, then the syntax is "correct".

However, that doesn't mean that it's using good perl coding practices.


(This post was edited by FishMonger on Mar 12, 2014, 8:28 AM)


FishMonger
Veteran / Moderator

Mar 12, 2014, 8:30 AM

Post #6 of 15 (2654 views)
Re: [PapaGeek] Multi Menu, multi-function Tk Ap [In reply to] Can't Post

We seem to be cross posting each other which makes portions of our comments mute.


Quote
Also, what is the correct way to disable, and / or hide, the parent menu when the child is displayed, and then re-enable the parent when the child is done?


The code sample I posted demonstrates how to do that.


(This post was edited by FishMonger on Mar 12, 2014, 8:30 AM)


PapaGeek
User

Mar 12, 2014, 9:01 AM

Post #7 of 15 (2650 views)
Re: [FishMonger] Multi Menu, multi-function Tk Ap [In reply to] Can't Post

I'll work on changing things to you coding example and rely on the main menu child grandchild schema I asked about. Will post again when I get that working.


PapaGeek
User

Mar 12, 2014, 2:09 PM

Post #8 of 15 (2643 views)
Re: [FishMonger] Multi Menu, multi-function Tk Ap [In reply to] Can't Post

OK, I made the changes you suggested and it works the same way. Thanks, as I said I wanted to be more Perlish. The new code is basically the same as multi-file post (the third post of this thread) with the following minor changes:

$mainMenu = new MainWindow; was replaced with $mainMenu = tkinit;

And

$menu1 = new MainWindow; was replaced with $menu1 = $mainMenu->Toplevel();
Same for menu2!


But I do have one question on the code you supplied. I didn’t make this change, but why did you change the order of the menu hide and show logic?

Changing to the child window you have:
-command => sub{
$top->deiconify;
$top->raise;
$mw->withdraw;
})->pack;

And changing back to the main window you have:
-command => sub{
$top->withdraw;
$mw->deiconify;
$mw->raise;
})->pack();

Does the order make a difference? Or did you just want to perform top before main in both cases?


FishMonger
Veteran / Moderator

Mar 12, 2014, 2:15 PM

Post #9 of 15 (2641 views)
Re: [PapaGeek] Multi Menu, multi-function Tk Ap [In reply to] Can't Post

I didn't write the code so I can't say for sure why it was ordered like that without doing some testing.

Here's where it came from.
http://www.perlmonks.org/?node_id=918705

A good portion of the regulars on perlmonks are cpan module authors.


FishMonger
Veteran / Moderator

Mar 12, 2014, 2:22 PM

Post #10 of 15 (2640 views)
Re: [PapaGeek] Multi Menu, multi-function Tk Ap [In reply to] Can't Post


Quote
Thanks, as I said I wanted to be more Perlish.

I hope that you also mean that you want to develop your scripts using good/best practices. In which case, you should get rid of the global vars and instead use lexical vars and pass parameters to and from your subs.


PapaGeek
User

Mar 13, 2014, 6:02 AM

Post #11 of 15 (2573 views)
Re: [FishMonger] Multi Menu, multi-function Tk Ap [In reply to] Can't Post

I definitely want to learn “best practices” from the start, but understanding the “scope” of things in Perl is a bit confusing.

I’m working on a process to track investment in your personal 401Ks. If you move some of your money to stock symbol ABCDE:

Scenario one: (The flexible way) You enter the symbol and it is not in your database so it asks you to add it. While adding it you want to classify it as a “foreign” investment, but foreign is not yet a predefined class, so it asks you to add the class and define its meaning.

Scenario two: (The strict way) Before you can add each investment you have to check to see if the symbol exists. You have to revert to the main menu to check and possibly add the symbol, but before you can do that you have to start at the main menu to check to see if the classification of the symbol exists and add the new classification if needed.

My system will have multiple menus that might be called from various other menus, but only one menu will be visible at a time. The only thing I want to avoid is any possible loops in the logic. I plan to have a “switch menu” process that keeps a stack of the menus in the order they were called, so the go back function knows where to return to.

If the menuX.pl file keeps a global variable “my MenuX;” so it can be defined and have a sub function to return the menu’s reference, then menuK.pl and menuQ.pl which can both call menuX will have to include the line “do ‘menuX.pl’” so they can call MenuXref() to obtain the menu’s reference.

If the menuX.pl file declares a universal variable “our MenuX;”, then any other menu that wants to switch to it can merely include the “our MenuX;” line prior to any subs.

By using the list of universal “our menu?:” lines, each menu can be self-contained. They only have to know the “name” of the menu they wish to call, they do not have to “include/do” the code of those menus.

Another option would be to have a menu hash Perl file which maintains the references to all menus defined by their hash names.

I do not know how many menus my system will eventually have. I want to make it a simple process to expand the system as new ideas arise. I know my initial idea of a set of universal variables starting with the prefix “menu” is far from the right way to go. What do you recommend as the "best practice" to do this?


FishMonger
Veteran / Moderator

Mar 13, 2014, 7:42 AM

Post #12 of 15 (2531 views)
Re: [PapaGeek] Multi Menu, multi-function Tk Ap [In reply to] Can't Post


Quote
My system will have multiple menus windows that might be called from various other menus windows, but only one menus window will be visible at a time.

Proper terminology is important. Without it, confusion arises.


Quote
The only thing I want to avoid is any possible loops in the logic.

A lot of what you describe sounds to me like you'll end up with logic loops and/or spaghetti.


Quote
I plan to have a “switch menu” process that keeps a stack of the menus in the order they were called, so the go back function knows where to return to.

If you use $window->Toplevel to create your child window(s), then you can use the $child_window->parent method to find out which window you need to go back to.


Quote
Scenario one: (The flexible way) You enter the symbol and it is not in your database so it asks you to add it. While adding it you want to classify it as a “foreign” investment, but foreign is not yet a predefined class, so it asks you to add the class and define its meaning.

In this scenario I'd raise a child window to handle the defining of the foreign class without withdrawing the parent window. And construct it so that the parent window can't gain focus while the child window is shown.

Good strong arguments can be made for each of your scenarios. The first is probably more user friendly, but also more complex to setup.

Personally, I'd probably keep the number of windows to a minimum. The main window would have a navigation menu (just like any standard app) and the menu options would raise and withdraw the given frame that contains the widgets needed for that operation. If needed a child window could probably be opened within the frame.

I'm getting behind in my projects at work so I can't work up any example scripts until I get caught up.


PapaGeek
User

Mar 13, 2014, 9:51 AM

Post #13 of 15 (2490 views)
Re: [FishMonger] Multi Menu, multi-function Tk Ap [In reply to] Can't Post

OK, another try at you training an old programmer new tricks, making me Perlish!

Here is another set of Perl files. They are far more concise than the previous set.

I changed all of my menu references to window.

I changed the logic so main can call window 1 & 2 and 1 can also call 2. 2 will return properly to whoever called it.

The loop and spaghetti logic will be on me, I want the process to be flexible.

I’m not sure that $child_window-> parent will always work when the child can be called by multiple parents. I created a new Perl file, WindowHash.pl, that controls a hash of references to all windows and also a call stack.

I changed the code to use $window = getWindow('MainWindow')->Toplevel; instead of $window = new MainWindow; but I’m not sure what the difference will be if I do not intend to use the window’s parent logic due to multiple call from options.

I can now open the next window directly using “-command =>[\&ShowWindow, 'Window2']” and return to whoever called this window with “command =>\&PrevWindow”.

This new Perl file does contain two variables that are global within the file, but they are not universal to all other files. Is there a better way to create and handle the hash and array in this file?

Mainline.pl: simple file to load the other files, create the windows and start the loop.

Code
use Modern::Perl '2013'; 
use Tk;

main();

sub main
{
do 'WindowHash.pl';
do 'MainWindow.pl';
do 'Window1.pl';
do 'Window2.pl';

CreateMainWindow();
CreateWindow1();
CreateWindow2();

MainLoop;
}

WindowHash.pl: New logic to manage a hash of window references by name and a call stack of the windows
On Edit: I've change the name of this module to WindowManager.pl since that better describes what it does!

Code
use Modern::Perl '2013'; 
use Tk;

my @WindowStack;
my %WindowHashList;

sub defineWindow
{
my ($windowName, $windowRef) = @_;
$WindowHashList{$windowName} = $windowRef;
}

sub getWindow
{
my ($windowName) = @_;
return $WindowHashList{ $windowName };
}

sub InitWindowStack
{
my ($topWindow) = @_;
push @WindowStack, $topWindow;
}

sub ShowWindow
{
my ($nextWindow) = @_;
my $prevWindow = $WindowStack[-1];
push @WindowStack, $nextWindow;
getWindow($prevWindow) -> withdraw();
getWindow($nextWindow) -> deiconify();
getWindow($nextWindow) -> raise();
}

sub PrevWindow
{
my $thisWindow = pop @WindowStack;
my $prevWindow = $WindowStack[-1];
getWindow($thisWindow) -> withdraw();
getWindow($prevWindow) -> deiconify();
getWindow($prevWindow) -> raise();
}

MainWindow.pl: all windows are now called $window internally. This makes copying controls from one window to another easier. Due to the window hash logic, changing from one window to another can now be done directly by the Button –command =>[\&ShowWindow, 'Window1'].

Code
use Modern::Perl '2013'; 
use Tk;

sub CreateMainWindow
{
my $window;
$window = new MainWindow;

$window->geometry("200x100");
my $lblMsg = $window -> Label(-text=>"Work with sub windows")-> grid();
my $openWindow1 = $window -> Button(-text=>"See Option 1",
-command =>[\&ShowWindow, 'Window1'])-> grid();
my $openWindow2 = $window -> Button(-text=>"See Option 2",
-command =>[\&ShowWindow, 'Window2'])-> grid();
my $endProgram = $window -> Button(-text=>"Exit Program",
-command =>\&endProgram)-> grid();
defineWindow('MainWindow',$window);
InitWindowStack('MainWindow');
}

sub endProgram{
exit;
}

Window1.pl: can perform a task, return to the main menu, and now can also call window 2

Code
use Modern::Perl '2013'; 
use Tk;

sub CreateWindow1
{
my $window;
#$window = new MainWindow;
$window = getWindow('MainWindow')->Toplevel;
$window->geometry("200x200");
my $lblMsg1 = $window -> Label(-text=>"Program Option 1")-> grid();
my $doTask1 = $window -> Button(-text=>"Perform Task 1",
-command =>\&Task1)-> grid();
my $openWindow2 = $window -> Button(-text=>"See Option 2",
-command =>[\&ShowWindow, 'Window2'])-> grid();
my $btnPostpone1 = $window -> Button(-text=>"Return to Main",
-command =>\&PrevWindow)-> grid();
defineWindow('Window1',$window);
$window-> withdraw();
}

sub Task1{
say "doing task 1";
}

Window2.pl: called from both window 1 and the main window. -command =>\&PrevWindow will return to the proper caller.

Code
use Modern::Perl '2013'; 
use Tk;

sub CreateWindow2
{
my $window;
#$window = new MainWindow;
$window = getWindow('MainWindow')->Toplevel;
$window->geometry("200x200");
my $lblMsg2 = $window -> Label(-text=>"Program Option 2")-> grid();
my $doTask2 = $window -> Button(-text=>"Perform Task 2",
-command =>\&Task2)-> grid();
my $btnPostpone2 = $window -> Button(-text=>"Return to Caller",
-command =>\&PrevWindow)-> grid();
defineWindow('Window2',$window);
$window-> withdraw();
}


sub Task2{
say "doing task 2";
}



(This post was edited by PapaGeek on Mar 13, 2014, 2:29 PM)


FishMonger
Veteran / Moderator

Mar 13, 2014, 9:50 PM

Post #14 of 15 (2467 views)
Re: [PapaGeek] Multi Menu, multi-function Tk Ap [In reply to] Can't Post

Ok, even though I haven't tested it, I can say that it is much better than your previous sample.

However, I still see an issue with code duplication. The subs CreateWindow1 and sub CreateWindow2 are nearly identical. The only difference is the text label and sub used in the command. This is an indication that you should refactor the subs and use a single sub which accepts the required parameters for the differences. I'd probably pass a hash ref.

I realize that your actual subs will be more involved than these samples, but whenever you have code duplication like this, you should refactor the code.


PapaGeek
User

Mar 13, 2014, 11:13 PM

Post #15 of 15 (2462 views)
Re: [FishMonger] Multi Menu, multi-function Tk Ap [In reply to] Can't Post

Thanks FishMonger. I value your input. I totally agree with the refactoring in general, but in this example I wanted to show how different menus could be calling each other. In real life these routines would have been refactored. At least I’m getting more Perlish. At least I didn’t call it Pearlish!

I’m already using the new code schema for my project, again, thanks for all your assistance.

 
 


Search for (options) Powered by Gossamer Forum v.1.2.0

Web Applications & Managed Hosting Powered by Gossamer Threads
Visit our Mailing List Archives