Shimmering in Tk

Difference between version 8 and 10 - Previous - Next
[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 [https://github.com/phase1geo/tke/%|%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, a lite editor%|%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, a lite editor%|%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".

****1. ****
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 .

======
****2. ****
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.
****3. ****
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

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

======

    ::tk::PlaceWindow $win widget $parent

======
****5. ****
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, a lite editor%|%alited%|%], only a [label] container removed shimmering of embedded calendar (no other means helped).
****6. ****
Also, [frame] can be used instead of [ttk::frame] for the same reason: it allows to configure a specific widget without touching others.
****7. ****
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 ...]
    ...

======
****8. ****
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 [after]ward only.
****9. ****
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.
****10. ****
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.
****11. ****
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"

======
****12.****
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 toplevel childrden 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 {} {     ifexpr {[info exists ::env(XDG_CURRENT_DESKTOP)] && $::env(XDG_CURRENT_DESKTOP) eq {KDE}} {
       return true
     }
     return false
   }

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

======

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

<<categories>> GUI | Tk