http://qs1969.pair.com?node_id=11140524

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

Greetings!
Once again, I need my fellow monks' advice regarding Tk.
After exchanging ActiveState for Strawberry and Win32::GUI for Tk, I have been struggling to refactor my old GUI into a Tk equivalent.
I am more or less back to where I started out, the GUI looks reasonably the same, but I am still struggling with a number of problems:

1. The GUI is significantly slower than the (native, so expected) Win32::GUI in terms of defining the GUI. I should mention that the GUI consists of a number of grids, which I have (with the help of you monks) used the grid() packer for. The size of the grid is quite large (up to thousands of cells), so it takes seconds to draw it.
2. When the grids get big and I flip between different tabs in a top notebook widget, I quite often get a "UpdateWrapper: Failed to create container" exit error message. Haven't been able to pinpoint exactly why/where this happens, but it appears when I stress the GUI.

Sometimes when stopping execution I can see that I am deeeply nested into Tk callbacks and whatnots, making me suspect that much of what is taking time is stuff that isn't providing any usable functionality for me.

Thus, some questions:
1. Is it possible to disable the GUI Event mechanisms, like the clicking on a button (temporarily while dealing with another event)? I am thinking that this might prove less stressful to the GUI.
2. Is it possible to disable the binding of variables to widgets, like -textvariable for Entry widgets? Just guessing this might speed things up a bit.
3. I have already replaced some really slow superwidgets for leaner alternative (e.g. JBrowseEntry for BrowseEntry) but maybe there are leaner widgets still?
4. What else?

As always, any help is deeply appreciated.

Replies are listed 'Best First'.
Re: Tk performance and "UpdateWrapper: Failed to create container"
by kcott (Archbishop) on Jan 17, 2022 at 11:23 UTC

    G'day olgo,

    Providing a prosaic description of your code, rather than actual code, is an exceptionally poor choice. This greatly hampers our ability to provide improved code. Please don't do this. See "How do I post a question effectively?" and SSCCE.

    "Is it possible to disable the GUI Event mechanisms, ..."

    Check the documentation on a widget-by-widget basis. Interactive widgets often have a -state option, with possible values such as disabled and readonly, which may be sufficient for your needs. For greater control of events, try Tk::bind (and do follow the additional links at the end of that page).

    "Is it possible to disable the binding of variables to widgets, like -textvariable for Entry widgets?"

    I expect you mean something else. The obvious answer to your question is don't use a -textvariable option. If you meant "delay" rather than "disable", see Tk::options.

    "I have already replaced some really slow superwidgets for leaner alternative (e.g. JBrowseEntry for BrowseEntry) but maybe there are leaner widgets still?"

    Maybe there are. Without any code, how could anyone tell which widgets you consider slow?

    "What else?"

    Again, no code, no answer possible.

    — Ken

      Here is a code example, not from my code base but from monk tybalt89's response to one of my earlier questions. The problem is generic enough.
      Noticable in this example is that the code up till Mainloop is actually quite fast, but then it takes a couple of seconds until the whole GUI is drawn.
      #!/usr/bin/perl use strict; # https://perlmonks.org/?node_id=11136705 use warnings; use Tk; use Tk::Pane; my ($rows, $columns) = (70, 20); my $sbsize = 22; my $mw = MainWindow->new(); # example of ->pack in a different Frame my $buttonbar = $mw->Frame(-bg => 'blue', )->pack(-fill => 'x'); $buttonbar->Button(-text => 'Exit', -command => sub{$mw->destroy}, )->pack(-side => 'right', -fill => 'x', -expand => 1); $buttonbar->Button(-text => 'Set Size', -command => \&setsize, )->pack(-side => 'right', -fill => 'x', -expand => 1); $buttonbar->Button(-text => 'Remove Column', -command => sub{$columns--; changegrid(); setsize(); }, )->pack(-side => 'right', -fill => 'x', -expand => 1); $buttonbar->Button(-text => 'Add Column', -command => sub{$columns++; changegrid(); setsize(); }, )->pack(-side => 'right', -fill => 'x', -expand => 1); $buttonbar->Button(-text => 'Remove Row', -command => sub{$rows--; changegrid(); setsize(); }, )->pack(-side => 'right', -fill => 'x', -expand => 1); $buttonbar->Button(-text => 'Add Row', -command => sub{$rows++; changegrid(); setsize(); }, )->pack(-side => 'right', -fill => 'x', -expand => 1); my ($screenw, $screenh) = ($mw->screenwidth, $mw->screenheight); print "w $screenw h $screenh\n"; $mw->maxsize($screenw, $screenh); #$mw->maxsize(1000,1200); #$mw->minsize(400,300); my $scroll = $mw->Scrolled('Pane', -scrollbars => 'osoe', -sticky => 'news', )->pack(-expand => 1, -fill => 'both'); my $f = $scroll->Frame->pack(-expand => 1, -fill => 'both'); changegrid(); $mw->update; setsize(); MainLoop; -M $0 < 0 and exec $0; # FIXME for testing sub setsize { $mw->update; my( $mwh, $sh, $fh) = map $_->height, $mw, $scroll, $f; my( $mww, $sw, $fw) = map $_->width, $mw, $scroll, $f; my( $mwrh, $srh, $frh) = map $_->reqheight, $mw, $scroll, $f; my( $mwrw, $srw, $frw) = map $_->reqwidth, $mw, $scroll, $f; my $deltaheight = $sh - $frh; my $deltawidth = $sw - $frw; print "height $mwh $mwrh $sh $srh $fh $frh\n"; print " width $mww $mwrw $sw $srw $fw $frw\n"; print "delta h $deltaheight w $deltawidth\n"; my $geo = $mw->geometry; my ($w, $h) = split /\D/, $geo; my $newgeo = ($w - $deltawidth + $sbsize + 10) . 'x' . ($h - $deltaheight + $sbsize); print "old $geo new $newgeo\n"; $mw->geometry( $newgeo ); } sub changegrid { $_->destroy for $f->children; foreach my $row (0..$rows - 1) { foreach my $col (0..$columns - 1) { $f->Entry( -text => "rc $row $col", -width => 0, )->grid(-row => $row, -column => $col, -sticky => 'news'); $row or $f->gridColumnconfigure($col, -weight => 1); } $f->gridRowconfigure($row, -weight => 1); } }
        If this is a good example what you want to do (basically a spreadsheet), then consider using a different widget.

        I built one application with the TableMatrix widget in a scrolled up/down, left/right frame. 100,000 cells was no problem -> eye-blink length of time. For operations on particular cells (like delete column, or even create new view into the database requiring yet another spreadsheet), I put in mouse a right click handler.

        Back in the day in which I wrote that code, the target platform was Win XP laptops with limited memory and essentially single core P5 processor. My code ran great on that Win XP platform - much, much faster than this code.

        I do remember that I did one ugly thing, I broke OO encapsulation and manipulated the TableMatrix object's storage directly. The "TableMatrix" is basically a hash table $hashptr->{"row","col"}=some string; update: $hashptr->{"row,$col"}=some value; That surprised me because I had expected an array of array as the fundamental data structure. I could write that hash table very quickly - the display of 100,000 cells on an ancient laptop was much, much faster than this code is on my more modern 64 bit machine. Probably breaking OO encapsulation is no longer necessary due to the vast increase in raw machine speeds.

        My code went EOL (End of Life) years ago. But, if you are interested, I can find an archived copy from 2008 and build a demo for you.

        The main idea is to look at TableMatrix which is a single widget instead of fiddling around with many thousands of widgets. (emphasis added in an update)

        UPDATE: DEMO code added:

        The code below has something like 100x (or even more) than the performance of the code using grid to pack in entry objects. When Perl starts, poof... the matrix is there!

        This is a hack of some code from more than a decade ago. But it does demo the speed of TableMatrix.
        This demo is for 1,000 rows and 20 columns: 20,000 cells. It displays almost instantly on my machine while the other code takes seconds for 70 rows.

        Many details like how to dynamically re-size the number of rows displayed in the matrix screen have answers, but at the moment, my brain is unable to remember the details (geez, its been >10 years!) and the Perl I wrote a decade+ ago is not as easy to understand as the code that I write now. (Duh!).

        The code below does break OO encapsulation for direct manipulation of the TableMatrix main data table internal structure - but not for any other aspects of the object (column widths, etc). This does circumvent any unwanted display updates when a massive re-organization the matrix is desired - perhaps sort a column? From memory, for a column sort, I transformed the entire hash table Matrix representation to an array of array, used an ST to sort that, then transformed back to the hash table representation. Then called my "set_col_width()" routine which I aliased to "refresh display()".

        Anyway, this idea is something to consider...
        To implement something like "delete column", I would use a standard Windows paradigm, like left click on a cell for selection, right click show menu with option to delete the column - not some button at the top of the GUI.

        TableMatrix is a complicated critter. Plan on spending quite a few hours experimenting and fine tuning the code to get exactly what you want. Not every behavior is documented in detail (otherwise the man page would be 300 pages!).

        Enjoy...

        #!/usr/bin/perl use strict; # https://perlmonks.org/?node_id=11140544 use warnings; use Tk; use Tk::TableMatrix; my ($nrows, $ncolumns) = (1000, 20); my $mw = MainWindow->new(); $mw->configure(-title=> "TableMatrix Demo"); $mw->geometry("1000x400+0+0"); my $buttonbar = $mw->Frame(-bg => 'blue', )->pack(-fill => 'x'); $buttonbar->Button(-text => 'Exit', -command => sub{$mw->destroy}, )->pack(-side => 'right', -fill => 'x', -expand => 1); $buttonbar->Button(-text => 'Set Size', -command => sub{}, )->pack(-side => 'right', -fill => 'x', -expand => 1); $buttonbar->Button(-text => 'Remove Column', -command => sub{}, )->pack(-side => 'right', -fill => 'x', -expand => 1); my %MainHash; my $tMain=\%MainHash; #main data structure for the TableMatrix my $table_frame = $mw->Frame(-height=>'10',-width=>'30', -relief=>'groove',-borderwidth=>'3' )->pack(-expand=>1, -fill=>'both',-pady=>'0'); my @col_heads = map{"Col Head $_"}0..$ncolumns; ### ### Main TableMatrix object ### my $table = $table_frame->Scrolled('TableMatrix', -cols => scalar(@col_heads), -rows =>1000, #fixed number of rows!!! need to grow this dynamical +ly! #I forget how I did this, but it is possible # -width => 5, #minimum width in columns to be shown # -height => 10, #minimum number of rows to be shown - seems to limit! +! not Min!?? -titlerows => 1, -variable => $tMain, -selectmode => 'single', -state => 'disabled', # no direct editing of cells -resizeborders => 'col', -bg => 'white', -rowheight => 1, #make row display more compact.... -bd => [0,1,0,1], -justify => 'left', -drawmode => 'compatible', -wrap => 0, -relief => 'solid', -scrollbars=>'se', -exportselection =>0, )->pack(-expand =>1, -fill=>'both'); $table->rowHeight(0,2); #varies height of title row (0) $table->tagRow('title',0); $table->tagConfigure('title', -bd=>2, -relief=>'raised'); foreach my $row (0..$nrows-1) { foreach my $col (0..$ncolumns-1) { $tMain->{"$row,$col"} = "row $row col $col"; } } set_col_width($table,@col_heads); #THIS WILL CAUSE A SCREEN REFRESH! sub set_col_width{ (my $table, my @col_head) = @_; my $i=0; foreach my $col (@col_head){ $table->colWidth($i++, length($col)); } } MainLoop;
        Update to Update:
        I do note some differences in the Tk display. The "slider" bar now doesn't get "smaller" based upon the number of rows to scroll. That control used to compress to such a small height (based upon total number of rows) that it was hard to "get ahold of with the mouse". That is no longer true although I notice that scrolling appears to be not as smooth with say 2,000 rows.

        One thing to be wary of, is that it can be hard to be absolutely sure that you have scrolled to the "end of all rows". In my application, I always added 5 blank rows to the end any data display. If you don't see at least one blank row, then you are not at the "end of data rows". These are the details that make a difference.

        There are two related ways that i know to speed this up dramatically. Both have to do with the fact that the main spreadsheet area doesn't actually have to use "real" widgets. In both cases, you just draw the data and any visual sugar as graphics.

        The first way is the classic spreadsheet way: You have one long editable field near the top of the screen. If you select a cell (calculate from the mouse click on the graphics which cells this would be), put its value in the edit field. Every time that value changes, redraw the appropriate part of the graphics, so the user thinks they are simultaneously editing the actual value in the table.

        The second way is one i have used before for HTML/JavaScript based tables (i think DataTables does it that way as well): again, use a generated image as the spreadsheet proper. If the user selects a cell, just spawn in an appropriate widget for editing on top of the image. When the user is done, remove the widget.

        The main advantage of doing the spreadsheet as simple graphics is speed and memory usage: All you do is print some text and paint some lines. Doesn't need to bless a gazillion instances and register a gazillion callbacks. You basically only have two callbacks: Mouseclicks (selecting cells) and scrolling.

        For moving between cells with the cursor/tab keys, you would just get those from the one and only editable widget.

        perl -e 'use Crypt::Digest::SHA256 qw[sha256_hex]; print substr(sha256_hex("the Answer To Life, The Universe And Everything"), 6, 2), "\n";'
Re: Tk performance and "UpdateWrapper: Failed to create container"
by tybalt89 (Monsignor) on Jan 17, 2022 at 23:45 UTC

    It just seems like you have too many widgets (windows) for the underlying system.

    See Tk::Pane widget size limit? which sound like a similar problem, at least speed-wise.

    4.What Else - Use fewer widgets and change their contents based on a scroll bar or something similar as shown in the above referenced post. I.E. instead of building huge Panes where most of it cannot be seen anyway, make a small display and change contents of displayed widgets.

Re: Tk performance and "UpdateWrapper: Failed to create container"
by Discipulus (Canon) on Jan 17, 2022 at 12:36 UTC
    Hello olgo,

    > 1. Is it possible to disable the GUI Event mechanisms, like the clicking on a button (temporarily while dealing with another event)? I am thinking that this might prove less stressful to the GUI.

    It is possible to disable widgets, one by one and by hand, but I doubt this will speed up the general execution time

    > 2..

    Even more work and no speed boost expected, imho.

    > The size of the grid is quite large (up to thousands of cells)..

    this is quite a lot of stuff but I'd expect a slow show up, to draw everything, but no broken functionalities: in my Tartaglia-s-triangle if I ask for a triangle with 127 rows it take several seconds to draw it, but then it works as expected.

    > I quite often get a "UpdateWrapper: Failed to create container"

    this error only appears at tkWinWm.c#L1940 and the function being called is UpdateWrapper which seems to me something that modifies an existing window: are you (over?)modifying the whole window while the program runs?

    L*

    There are no rules, there are no thumbs..
    Reinvent the wheel, then learn The Wheel; may be one day you reinvent one of THE WHEELS.
      Hi and thanks for your answer.
      Do you know if there is way to set a breakpoint in the execution before ending up in the UpdateWrapper? It is not my intent to modify the main window while running so I need to debug it to understand what is happening.
      I suppose it is down in som XS code somewhere, which is a bit outside my comfortability zone.
        I think trying to debug Tk is very unlikely to result in success for your application.

        If you exceed some Tk limit and that causes some error, then try another way.
        Finding and fixing some Tk bug is a very last resort.

        Please explain why my code at Marshall's TableMatrix code is not a sufficient example which can be adapted to your needs?

        That explanation would help all of us Monks understand what you are trying to do. At this point, I don't think any of us are sure.

        GUI code can be very, very tricky to write. And a lot of experimentation is usually required.

Re: Tk performance and "UpdateWrapper: Failed to create container"
by Anonymous Monk on Jan 17, 2022 at 11:41 UTC