Tkx scollable frame

Posted by critiquel on 2010-11-23 09:32

So I made a Tkx GUI that adds and removes text boxes as needed so users can put in as much information as needed each time they use it.

Only problem is the number of entries can get long (in the 20s, sometimes 30s). This of course means the GUI ends up taller than the screen can display.

I saw an example of a scrollable frame as the following

use Tkx;
Tkx::package_require("tile");
Tkx::package_require("BWidget");

my $mw = Tkx::widget->new(".");
my $sw = $mw->new_ScrolledWindow(-managed => 0);
my $sf = $sw->new_ScrollableFrame();

Tkx::grid(rowconfigure => $mw, 0, -weight => 1);
Tkx::grid(columnconfigure => $mw, 0, -weight => 1);

# This is the one tricky conversion.  We are getting a widget ref
# back from Tcl that has never been seen as a widget in Tkx, thus
# the Tkx::widget call makes the right kind of object.
my $subf = Tkx::widget->new($sf->getframe());

my $b;
for my $row (1..20)
{
    for my $col (1..20)
        {
        $b = $subf->new_ttk__button(-text => "$row,$col",
                        -command => sub { exit(); });
        $b->g_grid(-row => $row, -column => $col);
    }
}

$sw->setwidget($sf);
$sw->g_grid(-row => 0, -column => 0, -sticky => "nsew");

foreach (Tkx::SplitList($mw->g_winfo_children))
{
    Tkx::grid_configure($_, -padx => 2, -pady => 2);

}

Tkx::update('idletasks');
#Tkx::wm_geometry($mw,
#    Tkx::winfo_reqwidth($subf) . "x" . Tkx::winfo_reqheight($subf));
Tkx::wm_geometry($mw, (Tkx::winfo('reqwidth', $subf) + 10) . "x" .
    (Tkx::winfo('reqheight', $subf) + 10));

Tkx::MainLoop;

And this works just fine, I run it, I drag a corner so that it's too small, and bam- scroll bars that work.

However, when I try to implement this for my GUI which has all sorts of text boxes, labels, and buttons, it does display scrollbars, but they don't do anything. Do scrollable frames only work if the frame size is done changing before the GUI finishes loading? Here is the code that I am trying to make scrollable.

# XML file reading
use XML::Parser;
use XML::SimpleObject;
# xml writing
use XML::Writer;
use IO::File;
# spreadsheet input
use Spreadsheet::ParseExcel;
use Spreadsheet::XLSX;
# GUI
use Tkx;
Tkx::package_require("tile");
Tkx::package_require("BWidget");
# directories
use File::Path qw(make_path remove_tree);

## SETTINGS ##
$outputFolder = "generated_configdata";
$XML_interface_template_file = 'WidgetDataInterfaces_keywords.xml';     # XML file containing defintions for widget types
$EXCEL_file = 'BLANK.xls';                      # excel spreadsheet to be parsed
$XML_Config_Output = "BLANK.xml";       # XML file name to create
%ExcelIN_xmlOUT_hash = ();                      # mapping of excel files to parse and related XML file to create
$PRINT_DEBUG = 0;                       # 0: no printing | 1: print debugging info

## INTERFACE TEMPLATES ##
@widgetInterface_list;          # list of interface types
%widgetShortName_hash = ();     # mapping of widget shortnames to full names
%widgetFields_hash = ();        # map of interface types to fields

## DATA ##
@widgets_list = ();                     # list of widget names gathered from EXCEL
%widgetType_hash = ();  # widget types mapped to widget names from EXCEL
%widgetSSID_hash = ();  # SSIDs mapped to widget names from EXCEL
$WidgetInterfaceTemplates;      # widget templates parsed from XML

print "\n===============================================\n";
print " Automated widget XML config generation script";
print "\n===============================================\n";

######################################
###     Command Line Argument Handling ###
######################################

# command for passing in an xml file that contains a list of excel files and xml outputs
if( $ARGV[0] eq '-list' )
{
        my $EXCEL_list_file = $ARGV[1];
        # check if the excel file is .xls or .xlsx since we have to use different Perl modules
        my $ext = substr( $EXCEL_list_file, rindex($EXCEL_list_file, '.') + 1 );
       
        if( $ext eq 'xls' )
        {
                my $parser   = Spreadsheet::ParseExcel->new();
                my $ListBook = $parser->parse($EXCEL_list_file);

                if ( !defined $ListBook ) # check success
                {
                        die $parser->error(), ".\n";
                }

                my $listSheet = $ListBook->worksheet( 'EXCEL XML MAPPING' ); # only accepted a qualified sheet regardless of its location in the workbook

                my ( $row_min, $row_max ) = $listSheet->row_range();
                my ( $col_min, $col_max ) = $listSheet->col_range();

                my $excel_col = $col_min;
                for my $row ( $row_min .. $row_max )
                {
                        my $excelFile = $listSheet->get_cell( $row, $excel_col )->unformatted(); # get excel filename to parse
                        my $xmlFile = $listSheet->get_cell( $row, $excel_col+1 )->unformatted(); # get XML filename to output
                       
                        $ExcelIN_xmlOUT_hash{$excelFile} = $xmlFile; # store the mapping in the hash
                }
        }
        elsif( $ext eq 'xlsx' )
        {
                my $ListBook = Spreadsheet::XLSX -> new ($EXCEL_list_file);
                my $listSheet = $ListBook->worksheet( 'EXCEL XML MAPPING' );
               
                $listSheet -> {MaxRow} ||= $listSheet -> {MinRow};
                my $excel_col = $listSheet -> {MinCol};
                foreach my $row ($listSheet -> {MinRow} .. $listSheet -> {MaxRow})
                {
                        #first column is excel files, second column is XML files
                        my $excelFile = $listSheet -> {Cells} [$row] [$excel_col] -> {Val};
                        my $xmlFile = $listSheet -> {Cells} [$row] [$excel_col+1] -> {Val};
                       
                        $ExcelIN_xmlOUT_hash{$excelFile} = $xmlFile;
                }
        }
        else    # INVALID file input ERROR
        {
                print "[ $EXCEL_list_file ] is an invalid file - exiting...\n";
                exit(0);
        }
       
        if( $ARGV[2] eq '-d' ) # command for enabling debug printing
        {
                $PRINT_DEBUG = 1;
        }
}
# command for single excel file to read and xml file to create
elsif( $ARGV[0] eq '-x' )
{
        $ExcelIN_xmlOUT_hash{$ARGV[1]} = $ARGV[2];
       
        if( $ARGV[3] eq '-d' )  # command for enabling debug printing
        {
                $PRINT_DEBUG = 1;
        }
}
elsif( scalar($ARGV) < 1 )
{}
# any other command results in help print out
else
{
        print"\n\nArguments:\n\t-x [EXCEL_FILE] [XML_FILE]";
        print"\n\t\t process EXCEL_FILE and output XML_FILE";
        print"\n\t-list [EXCEL_FILE]";
        print"\n\t\t process file pairs listed in EXCEL_FILE";
        print"\n\t-d \n\t\tas the LAST argument turns on debug printing";
        print"\nMake sure your current directory contains the excel files.\n";
       
        exit(0);
}

#####################
### Start Working ###
#####################
print "\n";
Parse_XML_interface_template_file();

###########
### GUI ###
###########
my $mw = Tkx::widget->new(".");
$mw->g_wm_title("Command Directive Reflection Generator");

my $sw = $mw->new_ScrolledWindow(-managed => 0);
my $sf = $sw->new_ScrollableFrame();

my $frm = Tkx::widget->new($sf->getframe());#$mw->new_ttk__frame(-padding => "3 3 12 12");
$frm->g_grid(-column => 0, -row => 0, -sticky => "nwes");
$mw->g_grid_columnconfigure(0, -weight => 1);
#$mw->g_grid_columnconfigure(1, -weight => 3);
$mw->g_grid_rowconfigure(0, -weight => 1);

        # labels and text fields #
$frm->new_ttk__label(-text => "Root folder for artifact files:")->g_grid(-column => 0, -row => 0, -sticky => "w");
my $rootEntry = $frm->new_ttk__entry(-width => 60, -textvariable => \$rootFolder);
$rootEntry->g_grid(-column => 1, -row => 0, -columnspan => 3, -sticky => "we");

$frm->new_ttk__label(-text => "Excel Artifacts File")->g_grid(-column => 0, -row => 1, -sticky => "w");
$frm->new_ttk__label(-text => "Subject for XML File")->g_grid(-column => 2, -row => 1, -sticky => "w");

my $genButton = $frm->new_ttk__button(-text => "Find artifacts", -command => sub {findArtifactSheets();});
$genButton->g_grid(-column => 4, -row =>0, -sticky => "w");

my $genButton = $frm->new_ttk__button(-text => "Generate", -command => sub {generate();});
$genButton->g_grid(-column => 3, -row =>1, -sticky => "w");

my $addButton = $frm->new_ttk__button(-text => "Add Pair", -command => sub {addPair();});
$addButton->g_grid(-column => 4, -row => 1, -sticky => "w");

$sw->setwidget($sf);
$sw->g_grid(-row => 0, -column => 0, -sticky => "nsew");

foreach (Tkx::SplitList($frm->g_winfo_children))
{
    Tkx::grid_configure($_, -padx => 5, -pady => 5);
}

Tkx::update('idletasks');

@excelFiles = ();
@xmlSubjects = ();
$removeButton;
$totalPairs = 0;

sub addPair
{      
        my $row = $totalPairs + 2;
       
                # excel filename and xml subject entries
        my $exlEntry = $frm->new_ttk__entry(-width => 60);
        $exlEntry->g_grid(-column => 0, -row => $row+1, -columnspan => 2, -sticky => "we");
       
        my $xmlEntry = $frm->new_ttk__entry(-width => 60);
        $xmlEntry->g_grid(-column => 2, -row => $row+1, -columnspan => 2, -sticky => "we");
       
        if( $totalPairs < 1 )
        {
                $removeButton = $frm->new_ttk__button(-text => "Remove Last Pair", -command => sub {removePair();});
                $removeButton->g_grid(-column => 4, -row => $row+1, -sticky => "w");
        }
               
        push( @excelFiles, $exlEntry );
        push( @xmlSubjects, $xmlEntry );
       
        # foreach (Tkx::SplitList($frm->g_winfo_children))
        # {
                Tkx::grid_configure($exlEntry, -padx => 5, -pady => 5);
                Tkx::grid_configure($xmlEntry, -padx => 5, -pady => 5);
        # }
       
        $totalPairs++;
}

sub removePair
{      
        my $rowToRemove = $totalPairs + 2;
       
        #print( "\nr: $rowToRemove " . $commParamTypes[$rowToRemove]->current() . " e: " . $commParamPaths[$rowToRemove]->get() );
                #print( "\nREM r: $rowToRemove " );
               
        foreach( ( Tkx::SplitList($frm->g_grid_slaves( -row => $rowToRemove ))
                                , Tkx::SplitList($frm->g_grid_slaves( -row => $rowToRemove+1 )) ) )
        {
                #print " $_ ";
                Tkx::grid_remove( $_ );
                Tkx::destroy( $_ );
        }
       
        #print "t: $commParamTypes[$rowToRemove] p: $commParamPaths[$rowToRemove] l: $commParamLabels[$rowToRemove]";
       
        pop( @excelFiles );
        pop( @xmlSubjects );
       
        $totalPairs--;
       
        Tkx::update();
}

sub findArtifactSheets
{
        if(     $rootFolder !~ m/\\$/ )
        {
                $rootFolder = $rootFolder . "\\";
        }
       
        # find all items in directory
        opendir(dir, $rootFolder) || die("Could not open ".$rootFolder);

        my @dirContents = readdir(dir);
        shift(@dirContents); # ignore '.' and '..'
        shift(@dirContents);
        closedir(dir);

        if( $PRINT_DEBUG )
        {
                foreach( @dirContents )
                {
                        print "\n$_";
                }
                print "\n";
        }

        # check each item in directory
        foreach my $item ( @dirContents )
        {
                if( -f $rootFolder . $item && # is a file
                        $item =~ m/_artifact/i &&
                        $item =~ m/\.xls/ )
                {
                        addPair();
                       
                        $excelFiles[$totalPairs-1]->insert(0, $item);
                        my $subj = substr( $item, 0, index( lc($item), "_artifact") );
                        if( $subj =~ m/View_T$/ )
                        {
                                $subj = substr( $subj, 0, rindex($subj, "View_T") );
                        }
                        $subj =~ s/_//g;
                        $xmlSubjects[$totalPairs-1]->insert(0, $subj);
                }      
        }
       
        $sw->setwidget($sf);
        $sw->g_grid(-row => 0, -column => 0, -sticky => "nsew");
        Tkx::update('idletasks');
       
        Tkx::wm_geometry($mw, (Tkx::winfo('reqwidth', $frm) + 10) . "x" .
    (Tkx::winfo('reqheight', $frm) + 10));
}

## START ##
Tkx::MainLoop();

sub generate
{
        my $progbar = $frm->new_ttk__progressbar(-maximum => $totalPairs, -orient => 'horizontal', -length => 200, -mode => 'determinate');
        $progbar->g_grid(-column => 2, -row => $totalPairs+3, -sticky => "we");
        Tkx::grid_configure($progbar, -padx => 5, -pady => 5);

        if(     $rootFolder !~ m/\\$/ )
        {
                $rootFolder = $rootFolder . "\\";
        }

        for( $i = 0; $i < scalar(@excelFiles); $i++ )
        {
                $ExcelIN_xmlOUT_hash{ $rootFolder . $excelFiles[$i]->get() } = $xmlSubjects[$i]->get();
        }

        # debug output for what was accepted as the files to process
        if( $PRINT_DEBUG )
        {
                print "\n---FILES TO PROCESS---\n";
                foreach $k ( keys %ExcelIN_xmlOUT_hash )
                {
                        print $k . "\n\t" . $ExcelIN_xmlOUT_hash{$k} . "\n";
                }
                print "\n\n";
        }

        make_path( $outputFolder );
       
        # for all excel to XML mappings
        foreach my $excel ( keys %ExcelIN_xmlOUT_hash )
        {
                print "\n\nProcessing \n[ $excel, $ExcelIN_xmlOUT_hash{$excel} ]\n";
               
                $EXCEL_file = $excel;
                if( -e $EXCEL_file )
                {
                        $XML_Config_Output = $ExcelIN_xmlOUT_hash{$excel};
                       
                        # check if the excel file is .xls or .xlsx since we have to use different Perl modules
                        my $ext = substr( $EXCEL_file, rindex($EXCEL_file, '.') + 1 );
                       
                        if( $ext eq 'xls' )
                        {
                                Parse_EXCEL_widget_File();
                        }
                        elsif( $ext eq 'xlsx' )
                        {
                                Parse_XLSX_widget_File();
                        }
                        else    # invalid file input
                        {
                                print "[ $EXCEL_file ] is an invalid file - SKIPPING\n";
                        }
                }
                else
                {
                        print "[ $EXCEL_file ] does not exist - SKIPPING\n";
                }
               
                $progbar->step();
               
                @widgets_list = ();                     # list of widget names gathered from EXCEL
                %widgetType_hash = ();  # widget types mapped to widget names from EXCEL
                %widgetSSID_hash = ();  # SSIDs mapped to widget names from EXCEL
               
                Tkx::update();
        }

        Tkx::grid_remove( $progbar );
        Tkx::destroy( $progbar );
       
        %ExcelIN_xmlOUT_hash = ();     
}