rvosa has asked for the wisdom of the Perl Monks concerning the following question:

Oh divine ones, I'm back. Here's what I'm trying to do. Check out this code:
# a frame in my Tk main window my $frame = $mw->Frame; # a text widget with attached scrollbars in my frame my $OutputText = $frame->Scrolled('Text', -height => '50', -width => '150', -scrollbars => 'osoe' ); # pack frame and text widget $frame->pack(qw/-side left -fill y/); $OutputText->pack(qw/-side bottom -fill both -expand 1/); # here I try to tie STDOUT to the text widget my $widget = $OutputText->Subwidget("text"); tie *STDOUT, ref $widget, $widget;
So the idea in the code above is the following: anything printed to STDOUT later on in the program should appear in the text widget. Lo and behold, that works, except for what I assume is a buffering issue.

If I just look at STDOUT, the messages that the program spits out appear on STDOUT as they are printed. Fine. But if I look at my text widget, messages appear only much later (e.g., after we've iterated through a loop). For example, if I do:
for ( 1..10 ) { sleep 10; print "foo\n"; }
I get ten lines of "foo" after 100 seconds on my text widget, while I get a "foo" every 10 seconds on STDOUT.

I assume this is something to do with buffering but I don't quite understand how that works (esp. with respect to tie-ing). I don't think STDOUT is buffered at all? Or line-buffered? Setting STDOUT->autoflush(1) has no effect. What to do?

Thanks for any and all replies!

Replies are listed 'Best First'.
Re: Tk::Text and buffering, I think
by zentara (Cardinal) on May 12, 2005 at 10:49 UTC
    First of all, in Tk (or any gui program) using "sleep" will block the gui, so it's a "no-no". But to show you how to make it work ,in terms of your question, you need to set $|=1 and update your text widget after each print. Like:
    #!/usr/bin/perl use warnings; use strict; use Tk; $|=1; my $mw = tkinit; # a frame in my Tk main window my $frame = $mw->Frame; # a text widget with attached scrollbars in my frame my $OutputText = $frame->Scrolled('Text', -height => '10', -width => '50', -scrollbars => 'osoe' ); # pack frame and text widget $frame->pack(qw/-side left -fill y/); $OutputText->pack(qw/-side bottom -fill both -expand 1/); # here I try to tie STDOUT to the text widget my $widget = $OutputText->Subwidget("text"); tie *STDOUT, ref $widget, $widget; $mw->Button(-text=>'start', -command => [\&start])->pack;; MainLoop; sub start{ for ( 1..10 ) { sleep 1; print "foo\n"; $OutputText->update; } } __END__
    But here is probably a better way, without using sleep. The reason is that a repeat loop, lets the Tk event loop proceed, and respond to the gui, while sleep will block everything until it is done.
    #!/usr/bin/perl use warnings; use strict; use Tk; my $mw = MainWindow->new(); my $tx = $mw->Text()->pack(); tie *STDOUT, 'Tk::Text', $tx; $mw->repeat(1000, \&tick); MainLoop; my $count; sub tick { ++$count; print "$count\n"; }

    However, you don't say what you are ultimately trying to do, and tie'ing STDOUT in Tk is not done very often, because there are usually better ways to accomplish it, like printing directly to the text box. What kind of program are you trying to make?


    I'm not really a human, but I play one on earth. flash japh
Re: Tk::Text and buffering, I think
by polettix (Vicar) on May 12, 2005 at 10:49 UTC
    I don't think it has to do with buffering. AFAIK, Perl/Tk has a processing model in which you transfer the control to Tk via the MainLoop function, which in turn calls the callbacks provided by you when some events happen.

    Now, when you're in the for loop (which is probably buried inside such a callback), Tk has no way to update the text box until you return from the callback and give control back to it. I'm no expert in Perl/Tk, but you can look for some "interim-update" functions that give control back to Tk temporarily, just to perform this kind of screen updates.

    Does it make sense to you?

    Update: perusing zentara's answer I noticed that I overlooked the fact that you're using sleeps, which you generally shouldn't for the reasons excellently explained above. OTOH, I hope you were using those sleeps just to explain your point clearly; if those sleeps actually mean "some heavy computation" try anyway to see if there's some interim-update function that you can put inside the computation inself, just to give some feedback ASAP.

    Flavio (perl -e 'print(scalar(reverse("\nti.xittelop\@oivalf")))')

    Don't fool yourself.
      Thanks for the replies, y'all. I should point out that the sleep loop should be taken as "something that takes long"... so not the sleep function per se.