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: Win32 Programming Help:
Class or Package?

 



PapaGeek
User

Feb 14, 2014, 6:36 AM

Post #1 of 14 (13146 views)
Class or Package? Can't Post

I’m a relative Perl newbie, but I’ve been programming computers since 1967, hence the name PapaGeek!

My first Perl project is a personal investment management program to manage my 401K / IRA investments. The first step is to use Win32::GUI to create the menus for the front end. Please feel free to pick my plan apart.

I plan to publish the source when I am done, for your use, and also a step by step guide and example for using Win32::GUI.

Plan A is to create a Top Level Menu that merely has a few button that take you to the other parts of the process: managing what stock symbols to invest in, tracking what I’m invested in, tracking which accounts are invested in what stocks/funds. As one menu calls another the plan is to use code like:

Code
	$thisMenu->Show();   # Display the new menu 
$parentMenu->Hide(); # And hide the previous

When I am finished with the new menu:

Code
	$thisMenu->Hide();   # Hide the menu we are done with 
$parentMenu->Show(); # And go back to caller

For Pop Up error messages with only an OK button, I plan to use enable and disable so the parent menu will remain visible.

My first attempt was to place all the code for all the menus in a single file. Huge Mistake!

Plan B is to create separate Perl files for each menu. The directory structure would look something like a tree, but some of the branches would intersect. For example; many of the menus would have a dropdown to allow you to select which stock symbol you want to update or modify. Each dropdown would include an entry to allow you to enter a new symbol. Since this dropdown would exist on multiple menus, the Add Symbol menu could be called from multiple location in the menu tree.

The plan is to call the top level create menu process then show the top level menu and start the dialog. The top level menu Perl file would know what other menus it can link to and call the create menu process for those menus within its own create menu process. Each create menu process would return the handle toe the menu it just created and each Perl menu file would remember the handles to the menus it can call as it creates them.

Since the add symbol menu can be called from multiple other menus, it should remember the handle to the menu after the first call and merely return it the next time it is called. This would also avoid infinite recursive coding. If A calls B which calls C which can call B again. Perl file A’s create process would call the menu B create process which would call the menu C create process which in turn calls the Menu B create process, but this time the create process would know that B was already created so it would merely return to previously create menu handle and not follow the code that creates C again.

Each menu would call itself “$thisMenu” and the menu that called thisMenu would be call “$parentMenu” so the code for things like the cancel and OK buttons could merely be copied from one source file to the next.

Is it best to use classes to create each menu, or would a package be better.

Each menu will need a series of global variables for things like the handle to the menu itself, $thisMenu, and the menu that called it, $parentMenu, as well as the handles to various controls within the individual menu. Because the menu dialog process calls a number of event processing subroutines, these sub process have to know the handles to the various controls and menus needed by the individual menu. Can these variable me made global to the package / single perl file and also hidden from other programs even if they know the package name?

Also, I am assuming that if I use a class or package, that I can call the Cancel button on each menu “Cancel” and that when the dialog process call sub Cancel_Click that it knows to use MenuA::Cancel_Click or MenuB::Cancel_Click, etc. based on which cancel button was clicked.

Is that correct in both classes and packages?

Again, what is the best coding practice to use? Class or Package?


FishMonger
Veteran / Moderator

Feb 14, 2014, 7:19 AM

Post #2 of 14 (13143 views)
Re: [PapaGeek] Class or Package? [In reply to] Can't Post

In Perl a class is a package, but a package is not necessarily a class.

Packages could have either a functional or OO interface. The OO interface is a blessed class.

If you need a menu option/object to "remember" the menu handlers, then you'll want to store that in an OO object.


FishMonger
Veteran / Moderator

Feb 14, 2014, 7:25 AM

Post #3 of 14 (13141 views)
Re: [PapaGeek] Class or Package? [In reply to] Can't Post

If you haven't already done so, I'd recommend developing a detailed flowchart of your app. Doing so would help you to visualize what you're needing to do as well as help others to understand your goals and help catch design flaws.


PapaGeek
User

Feb 16, 2014, 2:07 AM

Post #4 of 14 (13023 views)
Re: [FishMonger] Class or Package? [In reply to] Can't Post

Thanks for the feedback.

I already have a fairly detailed flowchart of the entire app. The top level of the chart is the GUI, then other parts are designated as LOGIC, DATABASE, WEB ACCESS, INTRANET, etc based on the primary functionality.

You stated that: “If you need a menu option/object to "remember" the menu handlers, then you'll want to store that in an OO object.”

I broke it down into two separate packages, not OO, and it still requires that all event handlers reside in the Perl script that starts the dialog.

I tried starting a dialog in the package, but that caused everything to fail.

I’ll try the OO approach is you can give me a hint on how to set it up. My next step will be to create separate open and close functions in each menu and look for a way to pause the dialog in the parent menu so I can start a new dialog in the child menu.

Again, my biggest concern is that in a large GUI application with many menus, having every event handler for every menu in one location will make maintenance of the code a nightmare.

Here is the stripped down code for a single button with a single click event handler.



Code
TestGUI.pl 

use Modern::Perl '2013';
use Win32::GUI();
use TopMenu;

main();

sub main
{
my $topMenu; # Mainline is a skeleton program
$topMenu = TopMenu::Create(); # It only creates the top menu
$topMenu->Show(); # Displays it
Win32::GUI::Dialog(); # And starts the dialog
}

# This is the code I want to avoid
# I don't want to have every event from every menu in one place
# This will make maintenance of the GUI very difficult as the process grows

sub OpenUpdate_Click
{
print "Open Update from mainline\n";
TopMenu::OpenUpdate_Click();
}



Code
TopMenu.pm 

use Modern::Perl '2013';
use Win32::GUI();
package TopMenu; # Each menu file/package has a unique name
my $thisMenu; # always call this menu

sub Create
{
my $TopMenuWidth = 200;
my $TopMenuHeight = 100;
my $screen_width = Win32::GUI::GetSystemMetrics(0);
my $screen_height = Win32::GUI::GetSystemMetrics(1);

$thisMenu = new Win32::GUI::Window(
-name => "TheTopMenu",
-title => "Main Window",
-pos => [ ($screen_width - $TopMenuWidth)/2,
($screen_height - $TopMenuHeight)/2 ],
-size => [ $TopMenuWidth, $TopMenuHeight ],
);

$thisMenu->AddButton(
-name => "OpenUpdate",
-text => "Update Monthly Reports",
-pos => [ 10, 10 ],
);

return $thisMenu;
}

# This code is not call by the dialog process
# It calls the parallel code in the Perl file that started the dialog

sub OpenUpdate_Click {
print "Update Button Hit\n";
}
1;


FishMonger
Veteran / Moderator

Feb 16, 2014, 10:40 AM

Post #5 of 14 (12992 views)
Re: [PapaGeek] Class or Package? [In reply to] Can't Post

I won't be able to have access to a system that has Win32::GUI until Tue, so I can't do any testing until then.

I'm not 100% sure what you're want to achieve. Are you wanting a separate package (namespace) for each menu? If so, then that would be the wrong approach.

What you can do, and I'm not even sure it is the best approach, is to put all the subs for a given menu in its own .pl library file and load them in the main script with a do { } block. Anything directly related to the main toplevel window should be in the main script i.e., your Create() sub should be defined in the main script instead of an external package or lib file.

e.g.,
TestGUI.pl

Code
  
use Modern::Perl '2013';
use Win32::GUI();

do {
'menu1_libs.pl';
'menu2_libs.pl';
}

That will load the subs into the main:: namespace so you can call them without specifying a different package/namespace.

Those scripts should only contain subs. They should not execute code blocks outside of their defined subs.


PapaGeek
User

Feb 17, 2014, 2:57 PM

Post #6 of 14 (12898 views)
Re: [FishMonger] Class or Package? [In reply to] Can't Post

“I’m not 100% sure what you want to achieve.”

The process as described below will have a multiple menu User Interface, I’d like to be able to break the coding for each menu into a separate file, PL or PM doesn’t matter. If it is PL, I can assign a simple 2 letter mnemonic prefix to all subs associated with a given menu so the names don’t collide. I’d like the Win32::GUI::Dialog(); to call the event subs in each PL file directly. The PM approach I used called the event subs in the same file where the dialog started, not the individual menu files. I will try your do{…} approach with PL files and see how that works.


Overview of the Project

The project I’m working on is to convert my manual 401K analysis process into a Perl Program. My 401K, as with most, gives me a small list of available investments. There is a performance page that tells me how well each investment did over the past 3 months, 6 months, 1, 3, and 5 years. I sort the investments and assign a rank for each percentage return category and then create an overall rank for the investment. I then display the possible investments in rank order along with how much I have invested in each. By looking at the chart I can see if my investment are in the best performing funds and also look at the investment types to insure that everything is not invested in small-cap or large-cap or international etc. I want the investment in the best funds while spread out over various investment sectors.

This process works well but requires a fair amount of time each month and also gives me no way to look at what the system would have told me 6 months ago.

The program I’m working on will have a dumb top level menu that will take me to the various actions I want to perform.

Accounts: a list of investment account that I have. Each account has a nickname for display such as his 401k or her Roth IRA, as well as a full description of the account; broker, fees, etc.

Sectors: a list of the sectors that I place each investment in; mid-cap, small-cap, international, medical, high tech, etc.

Tickers: a list of all of the ticker symbols for the stocks and funds that I can or want to invest in. This also includes a list of the accounts that can invest in each ticker and the sector of each ticker.

Investments: how much is invested in each ticker in each account.

Update: the ability to run an update as of a given date.

The normal process is to create accounts, then create sectors, assign the tickers to a sector, and manage the investment in each account. The reality is that you might be entering a new investment in an account and realize that the ticker is not in the system yet. You have to enter the Ticker menu from the investment menu instead of the top level menu. While in the ticker menu you might also decide you need a new sector. Each menu should return to its parent when it is completed and as just stated, that parent might vary.

Also, each of these menus will probably have a few pop-up menus to tell you thing like you can’t enter a date in the future for running the reports or a status bar menu to show the progress of running the reports for each account. All of the reports will be created on disk as an intranet website. All of the account data and the historical ticker prices will be stored in a local database.

I would be happy to share the final code when the project is completed.


PapaGeek
User

Feb 18, 2014, 7:25 AM

Post #7 of 14 (12844 views)
Re: [FishMonger] Class or Package? [In reply to] Can't Post

FishMonger,

You have been a major help and I thank you for that. Based on what you have said so far, I have experimented with a possible coding scheme. As stated, I want each menu to be contained in its own file, per your advice, a PL file. You mentioned a do loop of sorts, couldn’t get that working, but individual do’s for each menu file works great.

Here are three files that seem to work and I’d appreciate your critique of what was done so far.

TestGUI.pl contains a commented list of the basic menu structure, only two of the menus are in the test so far and each menu has a prefix letter to insure that there is no code interference. I load each menu with a do statement and then create each menu. Each menu has a local variable that hold the handle to the menu. I use the main menu handle to show the main menu and then start the dialog.

The M or main menu contains a set of buttons to call the other menus. Only the first one does anything and the list is not correct or complete yet. The open accounts event at the end of the file uses the main handle to hide that menu and the account handle to show the next menu.

The A or accounts menu only has one button so far to close it. There are two events at the end of the code. The first is to terminate the menu when the bottom is clicked which links to the code to terminate the menu when the X is hit. In either case I merely hide the pop up menu and restore the main menu.

I realize the code is very primitive and is missing large chunks, but how do you feel about the basic approach?

Thanks again for all your help so far

PapaGeek



Code
use Modern::Perl '2013'; 
use Win32::GUI();

main();

# Menus used by GUI:
# M Main:
# A Accounts:
# S Sectors:
# E Exist: E_Set('Sector'); Show() => Sector already exists / . . .
# T Tickers:
# E Exist: E_Set('Ticker'); Show() => Ticker already exists / . . .
# S Sectors: Tickers can call Sectors if it doesn't exist yet!
# I Investments:
# T Tickers: Investments can call Tickers if it doesn't exist yet!
# U Update:
# C Crystal Ball: Can't request a date in the future
# P Progress:

sub main
{
do 'MenuA.pl';
do 'MenuM.pl';
A_Create(); # It only creates the menu
M_Create(); # It only creates the menu
M_Handle()->Show(); # Displays top menu
Win32::GUI::Dialog(); # And starts the dialog
}


Code
use Modern::Perl '2013'; 
use Win32::GUI();
my $M_Menu; # always call this menu

sub M_Create
{
my $TopMenuWidth = 335;
my $TopMenuHeight = 250;
my $screen_width = Win32::GUI::GetSystemMetrics(0);
my $screen_height = Win32::GUI::GetSystemMetrics(1);
my $TimesRoman20 = Win32::GUI::Font->new(
-name => "Times New Roman",
-size => 20,
-bold => 1,
);

$M_Menu = new Win32::GUI::Window(
-name => "TheTopMenu",
-title => "Main Window",
-pos => [ ($screen_width - $TopMenuWidth)/2,
($screen_height - $TopMenuHeight)/2 ],
-size => [ $TopMenuWidth, $TopMenuHeight ],
-background => [128,255,128],
);

# $M_Menu->AddButton(
# -name => "OpenMonthly",
# -text => "Run Monthly Reports",
# -pos => [ 10, 10 ],
# -align => "center",
# -valign => "center",
# -size => [ 300, 40],
# -font => $TimesRoman20,
# );

$M_Menu->AddButton(
-name => "M_OpenAccounts",
-text => "Personal Accounts",
-pos => [ 10, 10 ],
-align => "center",
-valign => "center",
-size => [ 300, 40],
-font => $TimesRoman20,
);
$M_Menu->AddButton(
-name => "M_OpenView",
-text => "View Monthly Reports",
-pos => [ 10, 60 ],
-align => "center",
-valign => "center",
-size => [ 300, 40],
-font => $TimesRoman20,
);
$M_Menu->AddButton(
-name => "M_OpenInvestments",
-text => "Manage Investments",
-pos => [ 10, 110 ],
-align => "center",
-valign => "center",
-size => [ 300, 40],
-font => $TimesRoman20,
);
$M_Menu->AddButton(
-name => "M_OpenFunds",
-text => "Manage Funds",
-pos => [ 10, 160 ],
-align => "center",
-valign => "center",
-size => [ 300, 40],
-font => $TimesRoman20,
);


return;
}
sub M_Handle {
return $M_Menu;
}

# This code is not call by the dialog process
# It calls the parallel code in the Perl file that started the dialog

sub M_OpenAccounts_Click {
print "Accounts Button Hit\n";
M_Handle()->Hide();
A_Handle()->Show();
}


Code
use Modern::Perl '2013'; 
use Win32::GUI();
my $A_Menu; # always call this menu

sub A_Create
{
my $TopMenuWidth = 200;
my $TopMenuHeight = 100;
my $screen_width = Win32::GUI::GetSystemMetrics(0);
my $screen_height = Win32::GUI::GetSystemMetrics(1);

$A_Menu = new Win32::GUI::Window(
-name => "A_AccountsMenu",
-title => "Accounts Window",
-pos => [ ($screen_width - $TopMenuWidth)/2,
($screen_height - $TopMenuHeight)/2 ],
-size => [ $TopMenuWidth, $TopMenuHeight ],
);

$A_Menu->AddButton(
-name => "A_CloseAccounts",
-text => "Close Menu",
-pos => [ 10, 10 ],
);

return;
}

sub A_Handle {
return $A_Menu;
}

# This code is not call by the dialog process
# It calls the parallel code in the Perl file that started the dialog


sub A_CloseAccounts_Click {
print ("Click Button\n");
return A_AccountsMenu_Terminate();
}

sub A_AccountsMenu_Terminate {
print ("Click Menu X\n");
A_Handle()->Hide();
M_Handle()->Show();
return 0; # Don't do standard terminate
}



FishMonger
Veteran / Moderator

Feb 18, 2014, 6:51 PM

Post #8 of 14 (12803 views)
Re: [PapaGeek] Class or Package? [In reply to] Can't Post

I was too busy at work to do any real evaluation and Win32::GUI fails to install on my home system, so I can't do any real testing, but I can give a little feedback.

First, I like your "C" menu, but I'd remove the "Can't". :-)

Your formatting/indentation is inconsistent. You should use Perl::Tidy as well as Perl::Critic to cleanup the formatting and evaluate the code for best practices.
http://search.cpan.org/~shancock/Perl-Tidy-20130922/lib/Perl/Tidy.pod
http://search.cpan.org/~thaljef/Perl-Critic-1.121/lib/Perl/Critic.pm

Vars should be declared in the smallest scope they require. Even though the way you're declaring and assigning $M_Menu and $A_Menu and then calling them via M_Handle()->Show(); will work, that is not the best practice. Instead those vars should be declared in their subs and returned by the subs.

Since 95% or more of the logic and functionality will be in external files, your main script should have a copious amount of POD documentation. Some of that documentation will need to be duplicated in the external scripts. That duplication is generally frowned on, but is sometimes acceptable.


PapaGeek
User

Feb 19, 2014, 5:23 AM

Post #9 of 14 (12735 views)
Re: [FishMonger] Class or Package? [In reply to] Can't Post

Thanks again for the reply, your comments are always welcome.

OMG! Is this really a chance for me to return a favor?

You said you had problems installing Win32::GUI on your system. Mine failed the first time also. I found the solution in the thread, https://rt.cpan.org/Public/Bug/Display.html?id=60865

Half way down the thread they recommend to use the “patched2” link, and that link did fail when I tried to install it (found that link on another thread). The last post recommends using “patched3”, and that one did install on my Windows7 64 bit system.


Try my patched version by running the following command from command prompt:

pip http://strawberryperl.com/package/kmx/perl-modules-patched/Win32-GUI-1.06_patched3.tar.gz


I hope that works for you if you are interested in the GUI.


FishMonger
Veteran / Moderator

Feb 19, 2014, 7:15 AM

Post #10 of 14 (12708 views)
Re: [PapaGeek] Class or Package? [In reply to] Can't Post

I'll give that a try when I get home.

I'm not familiar with pip, but isn't it a python command?


PapaGeek
User

Feb 19, 2014, 7:47 AM

Post #11 of 14 (12702 views)
Re: [FishMonger] Class or Package? [In reply to] Can't Post

Not sure what pip is, as I said, I’m a relative newbie to Perl.

I’m working on your suggestion. I’m going to eliminate the X_Create and the X_Handle subs and replace them with X_Menu subs. I found the “our” alternative to “my” which keeps the variable around between calls and tested how that works. Plan A, or is that up to X or Y now!, is to convert the beginning of each file from:

Code
use Modern::Perl '2013'; 
use Win32::GUI();
my $M_Menu; # always call this menu

sub M_Create
{
my $TopMenuWidth = 335;
my $TopMenuHeight = 300;
. . . create the menu
$M_Menu = new Win32::GUI::Window(
. . . create the menu
return;

To:

Code
use Modern::Perl '2013'; 
use Win32::GUI();

sub M_Create
{
our $thisMenu; # same name in all files, copying controls
# from menu to menu will be a lot easier
my $TopMenuWidth = 335;
my $TopMenuHeight = 300;
if (defined($this_menu))
{ return $thisMenu; }
. . . create the menu
$thisMenu = new Win32::GUI::Window(
. . . create the menu
Return $thisMenu;


Changing from menu to menu changes from;

Code
sub M_OpenAccounts_Click {  
M_Handle()->Hide();
A_Handle()->Show();
}



To:

Code
sub M_OpenAccounts_Click {  
M_Menu()->Hide();
A_Menu()->Show();
}



FishMonger
Veteran / Moderator

Feb 19, 2014, 8:00 AM

Post #12 of 14 (12698 views)
Re: [PapaGeek] Class or Package? [In reply to] Can't Post

The our keyword creates a global var, which is not needed or wanted. Use the my keyword to create a lexical var.

In the main script instead of:

Code
M_Create(); 
M_Handle()->Show();


Do:

Code
my $M_handle = M_Create(); 
$M_handle->Show;



PapaGeek
User

Feb 19, 2014, 8:44 AM

Post #13 of 14 (12693 views)
Re: [FishMonger] Class or Package? [In reply to] Can't Post

I’m trying to avoid the concept of creating a menu every time I want to show it, or reference it. That concept limits what I can do within the logic, things like changing the appearance or content of a menu on the fly based on the circumstances.

What I need is a variable that remains defined from one call to a sub to the next, but restricted to that sub or file alone, not visible outside the sub/file. The only way to do this is with a package, but that makes the dialog loop extremely complicated to implement.

“Perl got this one wrong from version 1 and onwards. The default variable scope in Perl 5 is the “package variable”, a kind of global variable. Define something inside a block; still see it outside.”

It looks like Perl missed the boat on that one. In order for a variable to remain defined it must be global and available to all subs at the same time.

I’ll just have to rely on a few global variables: $menu_A, $menu_B, etc


Laurent_R
Veteran / Moderator

Feb 23, 2014, 3:18 AM

Post #14 of 14 (12127 views)
Re: [PapaGeek] Class or Package? [In reply to] Can't Post


In Reply To
What I need is a variable that remains defined from one call to a sub to the next, but restricted to that sub or file alone, not visible outside the sub/file. The only way to do this is with a package, but that makes the dialog loop extremely complicated to implement.


This can be done without any problem (see below).


In Reply To
“Perl got this one wrong from version 1 and onwards. The default variable scope in Perl 5 is the “package variable”, a kind of global variable. Define something inside a block; still see it outside.”

Not true if you use strictures and lexical ("my") variables.


In Reply To
It looks like Perl missed the boat on that one. In order for a variable to remain defined it must be global and available to all subs at the same time.


Not so.

See http://perldoc.perl.org/perlfaq7.html#How-do-I-create-a-static-variable? : starting with Perl 5.10, "the state declaration creates the lexical variable that persists between calls to the subroutine."


Code
sub counter { state $count = 1; $count++ }

In this example, the $count variable is set to 1 the first time and is then icremented. The counter function "remembers" the value of $count from one call to the next.

Another way to obtain the same result (available with older versions of Perl) is to use closures:


Code
{ 
my $count = 0;
sub counter { return $count++;}
}
print counter(), "\n" for (1..10);


which prints:

Code
0 
1
2
3
4
5
6
7
8
9


For more information on closures: http://perldoc.perl.org/perlfaq7.html#What%27s-a-closure?


(This post was edited by Laurent_R on Feb 23, 2014, 3:19 AM)

 
 


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

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