tjwrona
tjwrona

Reputation: 9025

Error Handling Using Perl Tkx

I am working on a Perl Tkx application and I am having trouble getting useful/correct error messages. I am attempting to use croak so it will tell me exactly where it fails, but every time it croaks it just says the error is located "at C:/Perl64/lib/Tkx.pm line 347."

I have written a very simple script to demonstrate what is happening:


#!/usr/bin/perl

use strict;
use warnings;

use Carp;
use Tkx;

my $mw = Tkx::widget->new(".");
my $b = $mw->new_button(
    -text => "Hello, world",
    -command => sub { croak; },
);
$b->g_pack;

Tkx::MainLoop();

When I run this script and click the button I get the following popup box:

croak popup

How can I make Tkx handle errors similar to using croak in a regular Perl script? is there a way to force croak to print to STDOUT or STDERR?


EDIT:

Michael Carman brings up a good point that generally with a GUI the console will be suppressed and the user will never see an error sent to STDOUT or STDERR, but what about finding a way to modify the text in the "Application Error" dialogue box to say something useful? Is that possible?

The text doesn't necessarily have to be useful to the end user, but should at least be understandable by the programmer so they know where to look for the issue when it is reported by the end user.


EDIT2:

Okay it appears that using die or confess instead of croak prints a more useful error message to the "Application Error" window, but once I click "OK" the program just continues and doesn't actually die. How can I "kill it until it's dead" and make sure it stays dead?


POSSIBLE SOLUTION:

Here is a possible solution built off of Michael Carman's response. I modified it slightly because I think a messageBox looks a little bit nicer :)

#!/usr/bin/perl

use strict;
use warnings;

use Tkx;
use Carp;

my $mw = Tkx::widget->new(".");
my $b  = $mw->new_button(
    -text    => "Hello, world",
    -command => sub { die "uh-oh"; },
);
$b->g_pack;

Tkx::eval(<<'EOT');
proc bgerror {message} {
    tk_messageBox -title "Application Error" -message $message -icon error -type ok
    destroy .
}
EOT

Tkx::MainLoop();

So far this is the best solution but 5 days still remain in the bounty so keep those answers coming!

Upvotes: 2

Views: 460

Answers (2)

Michael Carman
Michael Carman

Reputation: 30851

croak reports errors from the perspective of the caller. Use die to report errors from the perspective of your code.

croak is typically used when writing modules so that you can report problems in the way your code is used. (e.g. for argument validation) GUI programming is sort of a mirror image of this. Instead of writing a library for someone else to use, you're injecting your code into a library that was written by someone else. When you pass a code reference to -command the caller becomes Tkx, so it's better to report any errors from the perspective of your code.

Tkx catches fatal errors in callbacks and reports them via the "Application Error" dialog you're seeing. It's not uncommon for GUI applications to be disconnected from the console, meaning that STDOUT and STDERR are closed and any messages written to them are lost. Without the dialog your application would simply vanish and the user would have no clue as to why.

e.g.

use Tkx;

my $mw = Tkx::widget->new(".");
my $b  = $mw->new_button(
    -text    => "Hello, world",
    -command => sub { die "uh-oh" },
);
$b->g_pack;

Tkx::MainLoop();

When I run this (and press the button) the dialog message is

uh-oh at c:\temp\foo.pl line 9.

If you need full control over how Tk handles errors you can override the default handler. The catch is that you have to do it in Tcl. Here's a minimal version to create a dialog with the error message and exit the application when it's closed.

Tkx::eval(<<'EOT');
proc bgerror {message} {
    tk_dialog .error "Error" $message [] 0 Close
    destroy .
}
EOT

Upvotes: 4

H&#229;kon H&#230;gland
H&#229;kon H&#230;gland

Reputation: 40778

The following scripts prints the error message to STDOUT:

use strict;
use warnings;

use Carp;
use Tkx;

my $mw = Tkx::widget->new(".");
my $b = $mw->new_button(
    -text => "Hello, world",
    -command => \&error_test,
);
$b->g_pack;

Tkx::MainLoop();

sub error_test {
    eval {
        confess;
    };
    print "$@\n";
}

Output:

 at ./p.pl line 20.
    eval {...} called at ./p.pl line 19
    main::error_test() called at /home/hakon/perl5/lib/perl5/Tkx.pm line 347
    eval {...} called at /home/hakon/perl5/lib/perl5/Tkx.pm line 347
    Tkx::i::DoOneEvent(0) called at /home/hakon/perl5/lib/perl5/Tkx.pm line 56
    Tkx::MainLoop() called at ./p.pl line 16

Edit

The following could be used to print the error message to a text field of the Tk GUI:

use strict;
use warnings;
use Carp;
use Tkx;

my $mw = Tkx::widget->new(".");
my $b = $mw->new_button(
    -text => "Hello, world",
    -command => \&error_test,
);
$b->g_grid(-column => 0, -row => 0);
my $text = $mw->new_tk__text(-width => 100, -height => 30);
$text->g_grid(-column => 0, -row => 1);

Tkx::MainLoop();

sub error_test {
    eval {
        confess;
    };
    print "$@\n";
    $text->insert("end", "$@\n");
}

Edit

To destroy the window after error message has been shown, you could use:

use strict;
use warnings;
use Carp;
use Tkx;

my $mw = Tkx::widget->new(".");
my $b = $mw->new_button(
    -text => "Hello, world",
    -command => \&error_test,
);
$b->g_pack;

Tkx::MainLoop();

sub error_test {
    eval {
        confess;
    };
    Tkx::tk___messageBox(-message => "$@");
    $mw->g_destroy;
}

Upvotes: 0

Related Questions