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


in reply to Tk performance and "UpdateWrapper: Failed to create container"

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

Replies are listed 'Best First'.
Re^2: Tk performance and "UpdateWrapper: Failed to create container"
by olgo (Acolyte) on Jan 17, 2022 at 11:56 UTC
    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.

        I have been struggling for the last couple of hours to get the column width working.
        I implemented a soft autosizing function that works sufficiently.
        What does not work, however, is when I have finished all my grids and just want to adapt the main window to cover all the TableMatrices (I have several, but the problem goes already for a single one). My legacy solution to this is to call reqwidth and then update the main window's geometry accordingly, to a certain limit. Geometry handlers should handle the rest.
        But it seems that the reqwidth reported is not the actual widths after calling ->colWidth. I guess it is some pixel/character issue.

        Appended some not working code for autosizing at the end of the above example:
        Strangely enough, the reqwidth is updated (637 without->827 with colWidth), but not to the value required to show the whole TableMatrix.
        use strict; # https://perlmonks.org/?node_id=11140544 use warnings; use Tk; use Tk::TableMatrix; my ($nrows, $ncolumns) = (10, 10); my $mw = MainWindow->new(); $mw->configure(-title=> "TableMatrix Demo"); $mw->geometry("600x800+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=>'10', -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 => 10, -rows =>10, #fixed number of rows!!! need to grow this dynamically +! #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 => 1, -relief => 'solid', -scrollbars=>'se', -exportselection =>1, )->pack(-expand =>1, -fill=>'both'); 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)+4); } } $mw->update; $table->update; $table_frame->update; #Needed my $rw = $table_frame->reqwidth; print "RW:".$rw."\n"; $mw->geometry($rw."x800+0+0"); MainLoop;

      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";'
        Hi cavac, and thanks for your effort.
        I actually combined your idea with Marshall's and ended up with a blazingly fast simple Tk::TableMatrix based spreadsheet in which I replace the cell over which the mouse pointer currently hovers with an actual editable widget. I had to rewrite quite a few GUI mechanisms (binds, binds, binds...) but some 10 hours later I am pretty much back to where I started, only with a responsive GUI.
        Only things I miss are autowidth of columns (my data differs quite a bit in size within the columns) and balloons. I guess you just can't have it all. Maybe I could attach a balloon to the cell I am currently hovering over but that would require me to first replace the TableMatrix cell with a (Tk::Label) widget, like I do for editable cells.
        Thanks!