Alex Z
Alex Z

Reputation: 1549

Tkinter Toplevel - strange behavior as built windows exe

I'm using tk for a project's GUI. I have some very strange behavior with it, but only as a built executable on windows. Essentially I have a function launches a new process and needs to update some GUI elements after it completes. This works fine on OS X and Windows running interpreted. It works fine as a OS X binary. But as a Windows binary, the code causes a second main window to appear for unknown reason.

The app is launched via:

root = tk.Tk()
root.withdraw()
app = impy()
root.mainloop()

where

class impy(tk.Toplevel):

Then sometime later a user clicks a button which causes this to run:

dialog = Progress_Dialog()
dialog.set_text('Implosion generation...')
dialog.update_idletasks()

# Use helper function:
parent_conn, child_conn = Pipe()
p = Process(target=ImplosionRunner.run, args=(child_conn,))
self.processes.append(p)

# start the process and send implosion:
p.start()
try:
    parent_conn.send(self.imp)
except:
    raise Exception('Implosion object passed to ImplosionRunner is not pickleable!')

obj = None
# Loop while the process is active:
def callback():
    nonlocal dialog, p, parent_conn
    if dialog.cancelled:
        dialog.withdraw()
        p.terminate()
        return

    # Try to receive from the Pipe:
    if parent_conn.poll():
        # Update the progress, or end otherwise:
        obj = parent_conn.recv()
        if isinstance(obj, Exception):
            from tkinter.messagebox import showerror
            showerror('Error!', 'A problem occurred generating the implosion (class '+self.imp.name()+')\n'+obj.__str__())
            dialog.withdraw()
            p.terminate()
            return
        elif isinstance(obj, float):
            dialog.set(100*obj)
        elif isinstance(obj, Implosion):
            # Pass info back to the main app:
            self.imp = obj
            self.after(10, self.__postImplosion__)
            dialog.withdraw()
            p.terminate()
            return

    self.after(25, callback)

self.after(10, callback)

The callback loop eventually completes via the elif isinstance(obj, Implosion) clause. Then those functions are all called. Something that they do causes a second Toplevel window to appear which is essentially a clone of the main window. The UI operations are applied to the clone. The __postImplosion__ method is just:

for key in self.modControlChecks.keys():
        self.modControlChecks[key].configure(state=tk.NORMAL)

    # Run any modules that were already checked in refresh mode
    for key in self.modControlChecks.keys():
        self.modRedisplay[key] = (self.modControlVars[key].get() == 1)
    for mod in allModules():
        if self.modRedisplay[mod.name()]:
            self.__runModule__(mod.name())

it just has to loop over some check boxes and enable them. I'm pretty baffled since this is only a problem with Windows binaries. Any thoughts?

Update: Some more troubleshooting: The extra main window appears immediately after p.start() is called. So this seems to be some weird behavior. Why can't I launch a process without an extra Tk window appearing?

Upvotes: 0

Views: 149

Answers (1)

Alex Z
Alex Z

Reputation: 1549

OK, as usual, the solution is that I should have RTFM. According to the docs there is a magic function that must be called to fix weird problems with multiprocessing when frozen on Windows.

if __name__ == "__main__":
    freeze_support()
    root = tk.Tk()
    root.withdraw()
    app = impy()
    root.mainloop()

Upvotes: 1

Related Questions