python-3.xtkintergnome-3

Change WM_CLASS of Dialog (tkinter)


Is there a way to change WM_CLASS of showinfo (and other dialogs)? className, class_ parameters do the job for Tk, Toplevel.

import tkinter as tk
from tkinter.messagebox import showinfo

if __name__ == '__main__':
    root = tk.Tk(className='ymail')
    mail_client = tk.Toplevel(root, class_='ymail')
    new_message = tk.Toplevel(root, class_='ymail')
    showinfo(title="Cancel sending", parent=new_message, message="""
Send is cancelled due to empty message""")
    root.mainloop()

For showinfo dialog

$ xprop WM_CLASS

gives

WM_CLASS(STRING) = "__tk__messagebox", "Dialog"

I think it is convenient to cycle tkinter windows with Alt-~ (Tilde), for which their WM_CLASS shall be the same.

I did the search ("tkinter change WM_CLASS showinfo"). Some of the hits are not applicable, some don't work (xdotool), and some I'd rather use as a last resort (converting C program to python).

Using    
Debian 10    
python 3.7.3    
GNOME 3.30.1

EDIT
Added workaround (using xdotool)

import threading
import subprocess
import time
import tkinter as tk
from tkinter.messagebox import showinfo


def change_dialog_class(from_="Dialog", to_="ymail"):
    cmd = f"xdotool search --class {from_} set_window --class {to_}"
    time.sleep(1)
    subprocess.run(cmd.split())


if __name__ == '__main__':
    root = tk.Tk(className='ymail')
    mail_client = tk.Toplevel(root, class_='ymail')
    new_message = tk.Toplevel(root, class_='ymail')
    tk.Frame.class_ = 'ymail'
    threading.Thread(target=change_dialog_class, args=("Dialog", "ymail"),
                     daemon=True).start()
    showinfo(title="Cancel sending", parent=new_message,
             message="""Send is cancelled due to empty message""")
    root.mainloop()

along with ymail.desktop it works

$ cat ~/.local/share/applications/ymail.desktop
[Desktop Entry]
Type=Application
Terminal=false
Name=ymail
Icon=python
StartupWMClass=ymail

yet, the python solution would be better


Solution

  • Since I'm not a XSystem user it took me some time to follow up. It seems like that you are looking for wm_group and unfortunately it isnt possible without subclassing it, which results in pretty much the same as writing your own class with tk.Toplevel. Anyway I hope toplevel.wm_group(root) ease things out and works for you.

    After I noticed that the SimpleDialog may has some functionality that you want to keep and can be hard to code for yourself, I decided to write an answer that you may want to use. It also provides the class_ option in case wm_group dosent work for you.

    Here is the code:

    import tkinter as tk
    import tkinter.simpledialog as simpledialog
    
    class MessageBox(simpledialog.SimpleDialog):
        def __init__(self, master,**kwargs):
            simpledialog.SimpleDialog.__init__(self,master,**kwargs)
            #root.tk.call('wm', 'group', self.root._w, master)
        def done(self,num):
            print(num)
            self.root.destroy()
            
    
    root = tk.Tk()
    MessageBox(root,title='Cancel',text='Im telling you!',class_='ymail',
               buttons=['Got it!','Nah'], default=None, cancel=None)
    root.mainloop()
    

    and here is the source: https://github.com/python/cpython/blob/main/Lib/tkinter/simpledialog.py#L31