Beefy Boxes and Bandwidth Generously Provided by pair Networks
XP is just a number
 
PerlMonks  

Creating GUI's that work across several versions of Tk

by leriksen (Curate)
on May 28, 2006 at 11:52 UTC ( #552162=perlmeditation: print w/ replies, xml ) Need Help??

I have recently been working on a Perl/Tk app that I plan to host on Sourceforge for future development. As part of the preparation for this step, I asked a few people, with knowledge in the field this apps deals with, to review my approach and the usefulness of the app. And thats where my problems began ...

Firstly, not everyone I gave the app to had the most recent version of Tk installed, and they were not in a position to easily update the installed version (legacy apps bound to their old version of Tk, not their server etc.) And this was a problem for me, because I had developed against the most recent version (804.027), and not every widget I used in my app was supported in their older Tk install. For example, the Tk::Spinbox is not available in older Tk's. Also, some widget are available in all Tk releases, but some of the options those widgets support vary from release to release. For example, in the Tk::Listbox widget, the 'listvariable' option is not available in earlier Tk releases, requiring the user to manually populate the widget.

So, how can we support multiple versions of Tk (or any module with a long history and a varying interface) ? My solution involves

  1. creating a subroutine that does the right thing for each version of Tk e.g. one that creates Tk::Listbox's with the 'listvariable' option, and one that creates a Tk::Listbox without the 'listvariable' option
  2. making the 'right' subroutine available by a general name, once we know the version of Tk the user has installed.
  3. calling the function with the general name as required in the application.

In order to demonstrate these 3 steps with some code, lets imagine we have an application that needs to present some options for a user to select. For some options we want the user to select or enter a number, for others we want the user to select a value from a list. We have written this option display and processing code to all exist in one package/namespace we call Application::Option

package Application::Option; use strict; use warnings; use Tk; use version; ... # firstly, create subroutines that do the right thing for various Tk v +ersions # poulate Listboxes with modern Tk's sub renderListAutoPopulate { my (undef, $parentWidget, $listVariable) = @_; return $parentWidget->Listbox( -listvariable => $listVariable, # auto +populate from this list # other options as required )->pack( # pack options ); } # populate old Tk Listboxes sub renderListManualPopulate { my (undef, $parentWidget, $listVariable) = @_; my $thisListbox = $parentWidget->Listbox( # options as required )->pack( # pack options ); $thisListbox->insert('end', @$listVariable); return $thisListbox; } # present numeric options with modern Tk Spinbox sub renderNumericOptionAsSpinbox { my (undef, $parent, $value) = @_; $parent->Spinbox( # spinbox options as required )->pack( # pack options ); } # present numeric option as a Tk::Entry widget, for older Tk's sub renderNumericOptionAsEntry { my (undef, $parent, $value) = @_; $parent->Entry( # Entry options as required )->pack( # pack options ); } # secondly, populate typeglobs with the required function references # version.pm overloads relational and stringification operators conven +iently INIT { if (qv(Tk->VERSION()) >= "804.027") { # modern Tk *numericWidget = \&renderNumericOptionAsSpinbox; *listboxWidget = \&renderListAutoPopulate; } else { # older-style Tk *numericWidget = \&renderNumericOptionAsEntry; *listboxWidget = \&renderListManualList; } } # thirdly, call the code slot in the typeglob as required in the appli +cation ... } elsif (numericOption($option)) { $self->numericWidget($parent, $option); } elsif (listboxOption($option)) { $self->listboxWidget($parent, $option); } elsif (# other option types ...

It strikes me that this technique would also work well with other long-lived modules, like DBI, members of XML:: or Net::family etc. The key is to generalise what you want, then write specific subs to do that general thing for each version of the module you intend to support. I think that in Pattern language this is an example of an Adapter pattern.

...reality must take precedence over public relations, for nature cannot be fooled. - R P Feynmann

Comment on Creating GUI's that work across several versions of Tk
Download Code
Re: Creating GUI's that work across several versions of Tk
by Errto (Vicar) on May 29, 2006 at 04:19 UTC

    I have not yet faced this situation myself because I've always worked in more controlled environments (or on my own), but my general sense is that most people do one of the following things:

    • make an executive decision about the oldest version of the module you'll support, put a run-time check for this in your code and fail gracefully if the minimum isn't met, and then write all your code so it'll work with that version. The version you pick depends on circumstances; it should not be the most recent, nor should it be so old as to really hamper your work.
    • Bundle a separate version of the library with your application in such a way as not to conflict with the installed version. In the Perl world you can use PAR to help with this.
    • Make certain functionality in your application optional. Such features would simply not work (for example, their menu options could be greyed out) if the module versions they depend on aren't there.

    Your approach should work, but the risk I see is that you're giving yourself a lot more code to maintain if you need to make changes to the functionality.

Re: Creating GUI's that work across several versions of Tk
by strat (Canon) on May 29, 2006 at 07:56 UTC

    I always try to keep the Tk code within one or two modules e.g. MyApp::Widgets which contains little wrapper subroutines for the widgets I use, e.g

    package MyApp::Widgets; ... sub Button { my ($parent, $text, $callback, %options) = @_; if (ref $text) { $options{-textvariable} = $text } else { $options{-text} = $text } if (ref($callback) ....) { $options{-command} = $callback; } else { $options{-background} = '#ff0000'; } # else return $parent->Button( -font => $StdButtonFont, -background => '#ffdd00', %options, ); } # Button

    or the like (code not tested)

    and perhaps MyApp::Callbacks) which contains the whole callbacks to Tk. It's not too beautiful to call such a sub (e.g. my $button = &MyApp::Widgets::Button($parent, 'bla', sub { print "Exit }, -font => $BoldFont);), but this way I can give every widget default properties very easily; in addition to that, the whole Tk-Code is bundled within one or two modules which can easily be exchanged based on the Tk version by an installer or the like)

    Best regards,
    perl -e "s>>*F>e=>y)\*martinF)stronat)=>print,print v8.8.8.32.11.32"

Re: Creating GUI's that work across several versions of Tk
by zentara (Archbishop) on May 29, 2006 at 12:23 UTC
    <2cents> Another strategy for making things work relatively bug free across many platforms, is to limit your widget set to the basics. I would make everything on a canvas, and go from there. </2cents>

    I'm not really a human, but I play one on earth. flash japh

Log In?
Username:
Password:

What's my password?
Create A New User
Node Status?
node history
Node Type: perlmeditation [id://552162]
Approved by Zaxo
help
Chatterbox?
and the web crawler heard nothing...

How do I use this? | Other CB clients
Other Users?
Others pondering the Monastery: (9)
As of 2014-07-28 13:20 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    My favorite superfluous repetitious redundant duplicative phrase is:









    Results (198 votes), past polls