Shimmering in Tk

Tk has its own shimmering. It is when you have widgets with dark backrounds and try to switch between the widgets' shows, at that the backround flashes. Another road to the shimmering hell is paved with intentions to have a toplevel window of a certain geometry which can be easily obtained - but with a shimmering ghost window alongside.

For example, in TKE editor when you set a dark theme and try to switch between tabs of the tab bar, you see a short flash of light background before showing a text, as a rule (but not as a strict rule) seen at the first presentation of the text.

But even when you have ttk::frame styled so that it has a dark background, you can see (though mild, yet seen) shimmering. Example of this is presented by alited editor's versions before 1.3.5 (when black themed too): in its "Preferences" when you switched between tabs, you could see shimmering of two dark backgrounds. It's related to the fact that alited uses two main backgrounds: one for the base, one for the fields like entry, text etc. And you can style ttk::frame to have only one of them.

In complex layouts, overcoming this annoyance can become a tricky task, because the problem can reveal itself in most unusual places.

Perhaps, it would be nice for Tk to have a couple of commands like freeze widget / unfreeze widget, so that you might to do all you need with a "frozen" widget and its children and then unfreeze it to show the result only, without the intermediate changes being seen.

Still, I'd like to share my tiny experience about this "fight against shimmering".


Immediately after loading Tk, hide its generic window, i.e. the following pair should be a must for your GUI apps:

    package require Tk
    wm withdraw .


For the same reason, never rely on wish as a starter of your applications. wish is good as a Tcl/Tk shell only. In particular, never use Unix's shebangs like this:

    #! /usr/bin/env wish

Otherwise, at start of your applications, you will enjoy a little window flashing in the screen center.


When a toplevel window has to be shown, hide it immediately after creation and show after GUI done (setting its geometry before deiconifying):

    toplevel $wtop {*}$options
    wm withdraw $wtop
    # ... then populating $wtop
    wm geometry $wtop $geom
    wm deiconify $wtop


When a toplevel window has to be centered over a parent window, use ::tk::PlaceWindow this way

    ::tk::PlaceWindow $win widget $parent


You might smile but label and ttk::label both can be used as containers instead of frame and ttk::frame, though not replacing them in other aspects. At that, in my opinion, label is a more flexible type as it allows to configure a specific widget without touching others. The use of labels can solve problems with shimmering in some specific cases. For example, in alited, only a label container removed shimmering of embedded calendar (no other means helped).


Also, frame can be used instead of ttk::frame for the same reason: it allows to configure a specific widget without touching others.


A toplevel window (notebook tabs etc.) can have a base frame to include all other widgets, so that the base frame will define the background. Thus it will remove the shimmering backgrounds:

    toplevel $wtop ...
    set base [frame $wtop.baseframe ...]
    set lab1 [ttk::label $base.lab1 ...]
    set ent1 [ttk::entry $base.ent1 ...]


One of the most useful Tcl commands applied to Tk is after. It allows to complete Tk tasks that are hard to be completed otherwise. With after you can create widgets so that their creation isn't visible and, consequently, doesn't produce the shimmering effect for a user.

E.g. after can be used to fill menus (which is anyhow good to be separated into a proc to run by after) or to populate notebook tabs that aren't visible at opening notebook.

Another use of after is when you need to configure some widgets with the configuration depending on some GUI events, e.g. on widgets reaching their full sizes. Tk is great to layout widgets, still some container widgets need to reach their full sizes first - and to be populated afterward only.


At using after, you cannot always rely on timeouts, as the real timeouts depend on OS's other tasks. Instead, you might rely on the number of event loop. If your code needs to be executed after some events, it would be sure executed after N loops of vwait rather than after N milliseconds of waiting (which means "after 1 loop only" and sometimes not after a waited event).

Thus, instead of

   after 100 ::InitActions

you might use the following (more quick and sure) way:

   after 10 {after 10 {after 10 {after 10 {after 10 ::NS::InitActions}}}}

At that, "after idle" is probably a best way of using after when the appropriate use cases allow this - i.e. when you may rely on running commands immediately after the first event loop.


Also, coroutine can be useful at fulfilling the widgets of multiple items. You can fill only initial (visible at start) items and then run coroutine (or even a combination of coroutine and "after idle") to fill the remaining ones. This will ensure the quick response at displaying the appropriate widgets and as such it will eliminate the shimmering effect.


Strangely enough, it's rarely used - parenting the toplevel windows. And we have a "great shimmering", nay disappearing windows at all!

It is when you open a toplevel window from its parent toplevel window and (by chance) click at the parent and - bang! your child window disappears (being overlapped by the parent) and you need to search it somewhere/somehow in the app bar or Alt-Tab app switcher.

This "silly" behavior is due to the fact your toplevels are independent, so that a window manager opens them independently. However, using a real child of the parent toplevel window, you might avoid this artifact.

Thus, instead of

    set wparent [toplevel .win1 ...]
    set wchild [toplevel .win2 ...]  ;# to be open from .win1 as independent toplevel

you might use the following way:

    set wparent [toplevel .win1 ...]
    set wchild [toplevel .win1.win2 ...]  ;# to be open from .win1 as "real child"


Alas, in KDE, even this way doesn't save you from disappearing children. For this desktop you might consider using "-topmost 1" option of wm. Or provide redrawing the hidden windows with some toolbar icons.

KDE may be specific in other aspects as well, so that everyone who wants to make his/her application "Linux adequate" should test it in KDE at least. There is a hope this testing will ensure the app's proper behavior in MacOS as well.

KDE may be checked this way:

   proc ::isKDE {} {
     expr {[info exists ::env(XDG_CURRENT_DESKTOP)] && $::env(XDG_CURRENT_DESKTOP) eq {KDE}}

To redraw a hidden window, the following procedure might be used:

   proc ::redrawWindow {win} {
     wm withdraw $win
     wm deiconify $win