tkinter: sync geometry() and GNOME 3

159 Views Asked by At

For some tkinter app wanted to save/restore windows configurations. Was using geometry() method. Getting the results I have failed to comprehend, I composed an example below to illustrate the problem.

$ python
Python 3.9.2 (default, Feb 28 2021, 17:03:44) 
[GCC 10.2.1 20210110] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> from tkinter import *
>>> t1 = Toplevel()
>>> t1.title('T1')
''
>>> m = Menu(t1)
>>> t1.config(menu=m)
>>> m.add_command(label='Save')
>>> t2 = Toplevel()
>>> t2.config(menu=m)
>>> t2.title('T2')
''

Move T1 by mouse to an arbitrary position (better not in the upper-left corner) on the screen.

>>> t2.geometry(t1.geometry())
''
>>> 

Although the geometry() reports identical size/position, the windows are placed differently on the screen (10 pixels difference x, y). Moving the T1 window "by hand" is crucial, if it's moved by geometry() , no displacement observed.

T1 & T2

Using Debian GNU/Linux 11 (bullseye), GNOME Shell 3.38.6

UPDATE 9/20

When making T1, T2 have the same size/position by mouse before the following command:

>>> t2.geometry(t1.geometry())

the command works as expected, the windows [continue to] have the same size/position and .geometry() reports the same result.

PS Found an exception when the example from the OP works without any displacement. When windows are set up, it is enough to issue the command:

>>> t1.geometry('200x200+-10+19')

PPS It would be of great help if tried on some other platform.

UPDATE 9/21

Found a way how to illustrate the problem form another side.

The following code leaves the T1, T2 windows sized/positioned identically on the screen:

import tkinter as tk
r = tk.Tk()
r.withdraw()
t1, t2 = tk.Toplevel(r), tk.Toplevel(r)
t1.title('T1')
t2.title('T2')


def reset2(w):
    '''Kind of sync window manager and geometry function'''

    def pos(w):
        g = w.geometry()
        return g[g.find('+'):]

    r.update_idletasks()
    p = pos(w)
    r.update_idletasks()
    w.geometry('+-10+19')
    r.update_idletasks()
    w.geometry(p)


reset2(t1)
reset2(t2)
t2.geometry(t1.geometry())
tk.mainloop()

UPDATE 9/24

The problem in short: after moving the window by mouse (vs moving by calling the .geometry() method) the .geometry() method reports the wrong position (10 pix off x, y)

UPDATE 9/25

>>> from tkinter import Tk,Toplevel
>>> r = Tk()
>>> r.withdraw()
''
>>> t1 = Toplevel(r)
>>> t1.title('T1')
''
>>> t2 = Toplevel(r)
>>> t2.title('T2')
''
>>> g1 = t1.winfo_geometry()
>>> x, y = map(int, g1.split('+')[1:])
>>> y -= 37
>>> t2.geometry(f'+{x}+{y}')
''

Now T1, T2 windows visually indestinguishable on the screen

>>> t1.winfo_geometry(), t2.winfo_geometry()
('200x200+88+152', '200x200+88+152')
>>> 
>>> t1.geometry(), t2.geometry()
('200x200+78+107', '200x200+88+115')

I guess I need better undertanding how .geometry() works

1

There are 1 best solutions below

0
Vladimir Zolotykh On

It is a workaround.

class T1(tk.Toplevel):
    def __init__(self):
        super().__init__()
        menu = tk.Menu(self, tearoff=0)
        self.config(menu=menu)
        menu.add_command(label='Sway', command=self.sway)

    def getxy(self, g=None):
        if g is None:
            g = self.geometry()
        return map(int, g.split('+')[1:])

    def sway_needed(self):
        xg, _ = self.getxy()
        xw, _ = self.getxy(self.winfo_geometry())
        return xw != xg

    def sway(self):
        if not self.sway_needed():
            return
        x0, y0 = self.getxy()
        self.geometry(f'+{x0 + 1}+{y0}')
        self.update_idletasks()
        self.geometry(f'+{x0 - 1}+{y0}')
        self.geometry(f'+{x0}+{y0}')
        self.update_idletasks()

After moving T1 window by mouse (to an arbitrary position):

>>> t1 = T1()
>>> t1.geometry()
'200x200+776+535'

The reported position is +776+535, but actual possition of t1 on the screen is +786+545. After invoking 'Sway' menu item, the window visibly shifts and actual position ( +776+535) and reported one becomes identical (verified by screenruler)

sway_needed() method is used to detect that the reported and actual positions doesn't match. sway() method -- to hopefully "fix" that

My original objective was to save the Toplevel windows configuration. Since it's done on demand I can call sway(). .sway() doesn't affect the window geometry. I tested sway() being scheduled by .after()