Python Forum
[Tkinter] Spawn sub-window with button press - Printable Version

+- Python Forum (https://python-forum.io)
+-- Forum: Python Coding (https://python-forum.io/forum-7.html)
+--- Forum: GUI (https://python-forum.io/forum-10.html)
+--- Thread: [Tkinter] Spawn sub-window with button press (/thread-13702.html)



Spawn sub-window with button press - malonn - Oct-27-2018

Hey, I'm trying to open a sub-window on top of my main window for a specific time, then close it. I'm going the tkinter.Toplevel route for this and plan on destroying it with subwindow.destroy(). But I keep getting an error that I have no idea what to do with. Code:
class MainWindow(ttk.Frame):
    def __init__(self, parent):
        ttk.Frame.__init__(self)
        ttk.Frame.grid(self, column=0, row=0, sticky=('N', 'E', 'S', 'W'))
        ttk.Frame.columnconfigure(self, 0, weight=1)
        ttk.Frame.rowconfigure(self, 0, weight=1)
        self.mem_exists = tkinter.StringVar()
        self.reg_scan = tkinter.StringVar()
        self.widgets()

    def widgets(self):
        self.mem_labl1 = ttk.Label(self, text='Memory Dumps:')
        self.mem_labl1.grid(column=0, row=0)
        self.mem_labl2 = ttk.Label(self, textvariable=self.mem_exists)
        self.mem_labl2.grid(column=0, row=1)
        self.mem_buton = ttk.Button(self, text='Delete', command=self.delete_dmps)
        self.mem_buton.grid(column=0, row=2)
        self.dmp_separ = ttk.Separator(self, orient='horizontal')
        self.dmp_separ.grid(column=0, row=3, rowspan=1, sticky=('EW'))
        self.scn_labl1 = ttk.Label(self, text='Scan for Dumps')
        self.scn_labl1.grid(column=0, row=4)
        self.scn_buton = ttk.Button(self, text='Scan', command=self.scan_pressed)
        self.scn_buton.grid(column=0, row=5)
        self.vrt_separ = ttk.Separator(self, orient='vertical')
        self.vrt_separ.grid(column=1, row=0, rowspan=6, sticky=('NS'))
        self.reg_labl1 = ttk.Label(self, text='Unneeded Keys')
        self.reg_labl1.grid(column=2, row=0)
        self.reg_labl2 = ttk.Label(self, textvariable=self.reg_scan)
        self.reg_labl2.grid(column=2, row=1)
        self.reg_buton = ttk.Button(self, text='Delete', command=self.delete_keys)
        self.reg_buton.grid(column=2, row=2)
        ......
    def scan_pressed(self):
        self.sub_win = tkinter.Toplevel(self, root) <----- ERROR HERE
        self.write_dumps()
        size = self.get_size()
        self.mem_exists.set(f'{size} KB of memory dumps found!')
When I press the button (scn_buton) I want it to spawn a little window with a label that says "scanning..." (or something of that nature), but it gives me this error:

Exception in Tkinter callback
Traceback (most recent call last):
File "C:\Python37\lib\tkinter\__init__.py", line 1705, in __call__
return self.func(*args)
File "c:\users\mark\downloads\practice_gui_2.py", line 83, in scan_pressed
self.sub_win = tkinter.Toplevel(self, root)
File "C:\Python37\lib\tkinter\__init__.py", line 2334, in __init__
if wmkey in cnf:
File "C:\Python37\lib\tkinter\__init__.py", line 1489, in cget
return self.tk.call(self._w, 'cget', '-' + key)
TypeError: can only concatenate str (not "int") to str

I have no clue where to even begin on that one. Google results turn up things not related to tkinter.

malonn

Well, I figured out why the window wouldn't spawn. The fix was to remove the master from the call to Toplevel() like so
self.sub_win = tkinter.Toplevel(self)
but the window doesn't spawn until after the rest of the code for that method ("scan_pressed") is run. Why is that? The code calls os.walk which takes a while to complete and I want the second window to block it while it happens.


RE: Spawn sub-window with button press - woooee - Oct-27-2018

See if this makes more sense. It also runs without errors.
import sys
if 3 == sys.version_info[0]: ## 3.X is default if dual system
    import tkinter as tk     ## Python 3.x
    from tkinter import ttk
else:
    import Tkinter as tk     ## Python 2.x
    import ttk

class MainWindow():
    def __init__(self, parent):
        self.parent=parent
        self.fr=ttk.Frame(parent)
        self.fr.grid(column=0, row=0, sticky='nsew')
        self.fr.columnconfigure(0, weight=1)
        self.fr.rowconfigure(0, weight=1)
        self.mem_exists = tk.StringVar()
        self.reg_scan = tk.StringVar()
        self.sub_win=None
        self.widgets()
 

    def delete_dmps(self):
        if self.sub_win:
            self.sub_win.destroy()

    def widgets(self):
        self.mem_labl1 = ttk.Label(self.fr, text='Memory Dumps:')
        self.mem_labl1.grid(column=0, row=0)
        self.mem_labl2 = ttk.Label(self.fr, textvariable=self.mem_exists)
        self.mem_labl2.grid(column=0, row=1)
        self.mem_buton = ttk.Button(self.fr, text='Delete', command=self.delete_dmps)
        self.mem_buton.grid(column=0, row=2)
        self.dmp_separ = ttk.Separator(self.fr, orient='horizontal')
        self.dmp_separ.grid(column=0, row=3, rowspan=1, sticky=('EW'))
        self.scn_labl1 = ttk.Label(self.fr, text='Scan for Dumps')
        self.scn_labl1.grid(column=0, row=4)
        self.scn_buton = ttk.Button(self.fr, text='Scan', command=self.scan_pressed)
        self.scn_buton.grid(column=0, row=5)
        self.vrt_separ = ttk.Separator(self.fr, orient='vertical')
        self.vrt_separ.grid(column=1, row=0, rowspan=6, sticky=('NS'))
        self.reg_labl1 = ttk.Label(self.fr, text='Unneeded Keys')
        self.reg_labl1.grid(column=2, row=0)
        self.reg_labl2 = ttk.Label(self.fr, textvariable=self.reg_scan)
        self.reg_labl2.grid(column=2, row=1)
##        self.reg_buton = ttk.Button(self.fr, text='Delete', command=self.delete_keys)
##        self.reg_buton.grid(column=2, row=2)
 
    def scan_pressed(self):
        self.sub_win = tk.Toplevel(self.parent) ##<----- ERROR HERE
##        self.write_dumps()
##        size = self.get_size()
##        self.mem_exists.set(f'{size} KB of memory dumps found!')

root=tk.Tk()
MW=MainWindow(root)
root.mainloop()



RE: Spawn sub-window with button press - malonn - Oct-28-2018

I'm sorry, I didn't post the full program, just an excerpt of the problem I was having. Here's the full (working) program:

import os
import winreg
import tkinter
from tkinter import ttk
import ctypes
import sys


class MainWindow(ttk.Frame):
    def __init__(self, parent):
        ttk.Frame.__init__(self)
        ttk.Frame.grid(self, column=0, row=0, sticky=('N', 'E', 'S', 'W'))
        ttk.Frame.columnconfigure(self, 0, weight=1)
        ttk.Frame.rowconfigure(self, 0, weight=1)
        self.mem_exists = tkinter.StringVar()
        self.reg_scan = tkinter.StringVar()
        self.widgets()

    def widgets(self):
        self.mem_labl1 = ttk.Label(self, text='Memory Dumps:')
        self.mem_labl1.grid(column=0, row=0)
        self.mem_labl2 = ttk.Label(self, textvariable=self.mem_exists)
        self.mem_labl2.grid(column=0, row=1)
        self.mem_buton = ttk.Button(self, text='Delete', command=self.delete_dmps)
        self.mem_buton.grid(column=0, row=2)
        self.dmp_separ = ttk.Separator(self, orient='horizontal')
        self.dmp_separ.grid(column=0, row=3, rowspan=1, sticky=('EW'))
        self.scn_labl1 = ttk.Label(self, text='Scan for Dumps')
        self.scn_labl1.grid(column=0, row=4)
        self.scn_buton = ttk.Button(self, text='Scan', command=self.scan_pressed)
        self.scn_buton.grid(column=0, row=5)
        self.vrt_separ = ttk.Separator(self, orient='vertical')
        self.vrt_separ.grid(column=1, row=0, rowspan=6, sticky=('NS'))
        self.reg_labl1 = ttk.Label(self, text='Unneeded Keys')
        self.reg_labl1.grid(column=2, row=0)
        self.reg_labl2 = ttk.Label(self, textvariable=self.reg_scan)
        self.reg_labl2.grid(column=2, row=1)
        self.reg_buton = ttk.Button(self, text='Delete', command=self.delete_keys)
        self.reg_buton.grid(column=2, row=2)

    def scan_dumps(self):
        '''Walks the OS drive and returns a dict with the path to and
        size of each .DMP file found on the drive.'''
        dumps = {}
        k = 0
        for dpath, dname, fname in os.walk('C:\\'):
            for f_n in fname:
                if f_n.endswith('.dmp'):
                    dumps[k] = [os.path.join(dpath, f_n), os.stat(os.path.join(dpath, f_n)).st_size]
                    k += 1
        return dumps

    def write_dumps(self):
        dumps = self.scan_dumps()
        with open(os.path.join(sys.path[0], 'practice_gui_2.ini'), 'w') as f:
            for key, value in dumps.items():
                f.write(f'{value}\n')

    def read_dumps(self):
        dumps = {}
        k = 0
        with open(os.path.join(sys.path[0], 'practice_gui_2.ini'), 'r') as f:
            for line in f:
                lst = line.split(',')
                pth = lst[0][2:-1]
                sze = lst[1][1:-2]
                dumps[k] = [os.path.normpath(pth), sze]
                k += 1
        return dumps

    def get_size(self):
        '''Reads from ini created by "write_dumps()" and gets total size
        of all .DMP files to display in the GUI.'''
        dumps = self.read_dumps()
        tsize = 0
        for key, value in dumps.items():
            tsize += int(value[1])
        tsize /= 1024
        tsize = round(tsize)
        return format(tsize, ',')

    def scan_pressed(self):
        self.sub_win = tkinter.Toplevel(self)
        self.write_dumps()
        size = self.get_size()
        self.mem_exists.set(f'{size} KB of memory dumps found!')

    def get_profile_keys(self):
        '''Opens a specific registry key and creates a list of key +
        sub-key.'''
        reg_key = 'SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\NetworkList\\Profiles'
        with winreg.OpenKey(winreg.HKEY_LOCAL_MACHINE, reg_key, 0, winreg.KEY_ALL_ACCESS | winreg.KEY_WOW64_64KEY) as k:
            ns = winreg.QueryInfoKey(k)[0]
            subs = []
            for i in range(ns):
                subs.append(reg_key + '\\' + winreg.EnumKey(k, i))
        return subs

    def get_newest_keys(self):
        '''Takes "get_profile_keys" and decodes the "DateCreated" registry
        value.  Next finds the newest values per "DateCreated"and returns
        a list of the full registry key for the newest key(s).'''
        keys = self.get_profile_keys()
        stamp = []
        stamps = {}
        for i in range(len(keys)):
            reg_key = keys[i]
            with winreg.OpenKey(winreg.HKEY_LOCAL_MACHINE, reg_key, 0, winreg.KEY_ALL_ACCESS) as k:
                val = winreg.QueryValueEx(k, 'DateCreated')
                stamp.append(int.from_bytes(val[0][:2], 'little'))
                stamp.append(int.from_bytes(val[0][2:4], 'little'))
                stamp.append(int.from_bytes(val[0][6:8], 'little'))
                stamp.append(int.from_bytes(val[0][8:10], 'little'))
                stamp.append(int.from_bytes(val[0][10:12], 'little'))
                stamp.append(int.from_bytes(val[0][12:14], 'little'))
                stamps[i] = stamp
                stamp = []
        if len(stamps) == 1:
            return None
        elif len(stamps) == 2:
            z = 0
            for x in stamps[0]:
                if x > stamps[1][z]:
                    return keys[0]
                elif x < stamps[1][z]:
                    return keys[1]
                z += 1
        # add condition for more than two keys

    def delete_dmps(self):
        '''Walks the dict created by "scan_dumps()"and removes each file
        stored.'''
        dumps = self.read_dumps()
        for key, value in dumps.items():
            path = value[0]
            os.remove(path)
        self.write_dumps()
        size = self.get_size()
        self.mem_exists.set(f'{size} KB of memory dumps found!')

    def delete_keys(self):
        '''Takes the list returned by "get_newest_keys" and deletes each
        key.'''
        delete = self.get_newest_keys()
        if delete is None:
            return None
        elif isinstance(delete, str):
            winreg.DeleteKey(winreg.HKEY_LOCAL_MACHINE, delete)
        else:
            for x in delete:
                winreg.DeleteKey(winreg.HKEY_LOCAL_MACHINE, x)

    def set_mem_label(self):
        size = self.get_size()
        self.mem_exists.set(f'{size} KB of memory dumps found!')

    def set_reg_label(self):
        keys = self.get_profile_keys()
        if len(keys) > 1:
            num = len(keys) - 1
            self.reg_scan.set(f'{num} unwanted key(s) found!')
        else:
            self.reg_scan.set('No unwanted keys found!')


if ctypes.windll.shell32.IsUserAnAdmin():
    root = tkinter.Tk()
    gui = MainWindow(root)
    gui.set_mem_label()
    gui.set_reg_label()
    gui.mainloop()
else:
    ctypes.windll.shell32.ShellExecuteW(None, 'runas', sys.executable, 'practice_gui_2.py', None, 1)
It all works, I managed to get the sub-window to show. The problem is it doesn't show until after the rest of the method code runs.

def scan_pressed(self):
        self.sub_win = tkinter.Toplevel(self)
        self.write_dumps()
        size = self.get_size()
        self.mem_exists.set(f'{size} KB of memory dumps found!')
The first line in that code is to show the sub-window. Why doesn't the sub-window show first, then the rest of the code runs. I just want to window to pop up to distract the user while the methods are called (which could take time).
Thanks for the help though,@woooee


RE: Spawn sub-window with button press - malonn - Oct-28-2018

The problem has been solved through the use of wait_visibility(). Merely add this:
def scan_pressed(self):
        self.sub_win = tkinter.Toplevel(self)
        self.sub_win.wait_visibility()
and the window now opens before the rest of the code runs.
malonn