1

Using Perl Tkx, I want to get some input from the user, close the window, and maybe do it again later. For user input, I'm just displaying some buttons, and the user gets to click on one of them. Here's what I have now:

sub prompt_user {
  my $answer;
  my $mw = Tkx::widget->new(".");   ## the main window is unavailable the second time
  $mw->g_wm_title("Window Title");  ## line 40
  $mw->g_wm_minsize(300, 200);

  my $label = $mw->new_label( -text => "Question to the user?");
  $label->g_pack( -padx => 10, -pady => 10);

  my $button1 = $mw->new_button(
          -text => "Option One",
          -command => sub { $answer = 0; $mw->g_destroy; },
         );
  $button1->g_pack( -padx => 10, -pady => 10);
  my $button2 = $mw->new_button(
          -text => "Option Two",
          -command => sub { $answer = 1; $mw->g_destroy; },
         );
  $button2->g_pack( -padx => 10, -pady => 10);
  Tkx::MainLoop();     ## This blocks until the main window is killed

  return $answer;
}

So the user clicks on one of the buttons, the window closes, prompt_user() returns 0 or 1 (depending on which button the user clicked), and execution continues. Until I try to prompt the user again. Then I get an error:

can't invoke "wm" command:  application has been destroyed at MyFile.pm line 40

I just want a way to put up a bunch of buttons, let the user click one, wait to see which one is clicked, and maybe do it again later. Is there a way I can wait for a response to the button click without destroying the main window? Maybe create a subwindow?

I'm new to using Tkx, and googling shows lots of simple examples like the above code (using MainLoop/g_destroy), but I couldn't find any examples of recreating windows. I did see stuff about a Dialog Box or Message Box, but those won't suit my needs. I want to put text on the buttons, and use an arbitrary number of buttons (so I don't want to be limited to yes/no/cancel, and only have 3 options).

Update Here's what I was able to use

# hide the main window, since I'm not using it
my $mw = Tkx::widget->new(".");
$mw->g_wm_withdraw();

# function to prompt the user to answer a question
# displays an arbitrary number of answers, each on its own button
sub prompt {
  my $prompt = shift;
  my $list_of_answers = shift;

  # Note:  the window name doesn't matter, as long as it's './something'
  my $answer = Tkx::tk___dialog( "./mywindowpath", # window name
        "Prompt",            # window title
        $prompt,             # text to display
        undef,               # tk bmp library icon
        undef,               # default button
        @$list_of_answers);  # list of strings to use as buttons

  return $answer;
}

# use the button to ask a question
my $index = prompt("Who was the best captain?",
                   [ "Kirk", "Picard", "Cisco", "Janeway", "Archer" ] );
Donal Fellows
  • 133,037
  • 18
  • 149
  • 215
Tim
  • 8,912
  • 3
  • 39
  • 57

1 Answers1

2

I'm not really familiar with Tkx but Tk doesn't really work well that way. In general Tk applications are asynchronous. You should re-write your application in term of callbacks (kind of like javascript).

Basically, this kind of logic:

sub do_something {

    perform_some_action();

    my $result = prompt_user();

    perform_some_other_action($result);
}

should be re-written to something like:

sub do_something {

    perform_some_action();

    prompt_user(perform_some_other_action);
}

Your program should basically not have a main loop. Instead the call to Tkx::MainLoop at the end of your program becomes the main loop and you should do all processing by handling events.

Having said that, there are some mechanisms available that emulates blocking. Read the documantation for vwait. Though, I think even that requires a running Tkx::MainLoop so it does not necessarily avoid refactoring your whole program.

On the question of how to create and destroy windows there are two solutions:

  1. Use the main window (.) but don't destroy it at the end. Instead hide it and destroy all its children. You can then later reuse . by unhiding it.

  2. Hide . and don't use it. Instead create other windows (toplevels) and use them. Since toplevels are children of . they are safe to destroy without screwing up Tk.

slebetman
  • 109,858
  • 19
  • 140
  • 171
  • I like option 2, but how to I create other windows and hide '.'? I haven't been able to find the actual function calls necessary to do this. – Tim Dec 07 '10 at 04:49
  • 1
    Looked at the Tkx docs and found them to be very, very short. All the information is there but it assumes you have some prior knowledge of Tcl and/or Tk. From the docs it looks like you can create window by doing `my $window = $mw->new_toplevel`. To know what widgets you can create you need to read the Tk manual: http://www.tcl.tk/man/tcl8.5/TkCmd/contents.htm – slebetman Dec 07 '10 at 05:25
  • 1
    OK, having read the docs further, hiding the main window is: `$mw->g_wm_withdraw()`. Tkx doesn't look too bad for a thin wrapper around tcl/tk. Most of the stuff are handled by AUTOLOAD and forwarded directly to the tcl interpreter which is a nice general way to support everything Tk can do (even future versions of Tk). The only thing is I believe the documentation should be improved so that those not intimately familiar with tcl can use it. But Tk itself is well documented so it is not too bad. – slebetman Dec 07 '10 at 05:33
  • @slebetman: For better or worse the dearth of Tkx documentation is deliberate. It basically just defines how the wrapper works (i.e. how to translate from Tk syntax to Perl syntax), deferring everything else to the Tk docs. I agree that it's awkward -- at least initially -- but ultimately it's the right decision. Tkx doesn't even know which version of Tk it's using! (You can install your own.) http://www.tkdocs.com has some nice tutorials. The examples cover native Tk as well as various bindings (Ruby, Perl, and Python). – Michael Carman Dec 07 '10 at 16:33
  • @Michael: Then in the tradition of other Perl modules it should have a cookbook. The more comprehensive, the more examples, the better. The examples in the docs is, to me, not enough. – slebetman Dec 07 '10 at 17:28
  • Aha, using a combination of `$mw->g_wm_withdraw()` and `Tkx::tk___dialog`, I'm able to accomplish what I need. Thanks for your help! – Tim Dec 07 '10 at 21:14