Search code examples
pythontkinterpygamepython-multithreading

How can I make a tkinter inaccessible while it is minimized after iconify is run?


I am attempting to open a tkinter window simultaneously with a pygame window, and I have scoured everywhere and come up with this code which I have trimmed so that it is a minimum viable product with the smallest amount of code possible, as pasting all of the code here would be including many different encapsulated methods and classes, and this shows the main idea of my code. I want to have a tkinter window open when a pygame button from the main pygame screen is clicked on and I have successfully set up a class that allows me to create windows by clicking on said pygame button. This doesn't work sometimes, but when I do not move my mouse a lot, it does. This is my best explanation for what is happening, as I have no idea what else could be causing the issue. If the issue were that it never worked, I would have abandoned this approach, but it does work when the mouse movements are slow and robotic. I am open to all suggestions, because I am stumped.

import pygame as pg
import tkinter as tk
import os
from tkinter import ttk
# imports

# pg setup
pg.init()
screen = pg.display.set_mode((400,400))
screen.fill("black", pg.Rect((0,0), (400,400)))


# the encapsulated tkinter class
class FriendDialogue(tk.Tk):
    def __init__(self, title):
        self.root = super().__init__()
        self._title = title
        self.geometry("450x150")
        self.title(self._title)
        friend_dialogue.protocol("WM_DELETE_WINDOW", self.del_friend_window)  # set 
        # the function to override the [x] button on tkinter window so that 
        # the window doesn't open multiple times
        self.iconify()

    def del_friend_window():
        self.destroy()
        self.root = None

friend_dialogue = FriendDialogue("Phone a friend")  # create new tkinter window
# to make it so the button (screen) only works once
notRun = True

# main loop
while True:
    for event in pg.event.get():  # event loop
        if event.type == pg.QUIT:  # [x] button on pg window
            exit()
        if event.type == pg.MOUSEBUTTONDOWN and notRun:  # if click on screen
            notRun = False  # to run once
            friend_dialogue.deiconify()

    pg.display.update()  # update pygame window 
    if friend_dialogue.root:  # if not clicked yet, this won't run
        friend_dialogue.update()

EDIT: Currently with the above code, the window will always open but sometimes, it will immediately close with other times it staying open and being manipulable. When this happens the pygame window stops running and the tkinter window is manipulable, this is my desired behavior. The issue is that other times, with the same code, the tkinter window will open and then, after a few seconds of neither window responding, both windows will crash. I believe that sometimes the mainloop will overtake the main thread where as other times the mainloop fails to start, messing with the pygame while loop updating, crashing both windows. The reason I think this is since the print("init"*50) is never run, in the intended and in the buggy behavior except for when the behavior works as intended and then the tkinter window is closed with the [x] button.

So, where I am successful is in allowing for these two types of windows to be open at the same time and where I am unsuccessful is in allowing this behavior to happen consistently. I appreciate any help whatsoever, thanks for taking the time to read this question.

EDIT 2: I have narrowed down the problem to running the update() or mainloop() methods of the tkinter window, though I still have no idea as to why these methods fail to work for me. When these methods are run, the entire program crashes with no error code. Any code before these methods is run will work fine, but nothing after will run. The only reason I can think of why these methods wouldn't work is if something that is required to be defined for these methods isn't but:

  1. No error code indicating this is raised, and
  2. These methods only presumably require the window to be defined which it is.

Edit 3: Following trying the approach posted in the comments involving using iconify() and deiconify(), I have found that it works fine save for the fact that it can be opened at any time. If there is anyway to not allow for a tkinter window to be opened while it is an icon, or withdrawn or 'iconified', it may solve my problem.


Solution

  • Thanks to acw1668 for the solution: "I would suggest to create an instance of FriendDialogue as the root window and make it hidden initially. Then using .after() to create a loop for pygame. When mouse is clicked on the pygame window, show (deiconify) the root window. When the close button in the title bar of the tkinter root window is clicked, hide (withdraw) the root window."

    I did not need to implement a .after() so for anyone else in the future this is how I implemented this to have a pygame and tkinter window open simultaneously and then to be able to be closed and opened on command.

    import pygame as pg
    import tkinter as tk
    import os
    from tkinter import ttk
    
    pg.init()
    screen = pg.display.set_mode((400,400))
    screen.fill("black", pg.Rect((0,0), (400,400)))
    
    class FriendDialogue(tk.Tk):
        def __init__(self, title):
            self.root = super().__init__()
            self.geometry("450x150")
            self.title(title)
            self.withdraw()
    
    friend_dialogue = FriendDialogue("Phone a Friend")
    
    opened = False
    
    while True:
        for event in pg.event.get():
            if event.type == pg.QUIT:
                exit()
            if event.type == pg.MOUSEBUTTONDOWN and not opened:
                opened = True
                friend_dialogue.deiconify()
        
        if opened:
            friend_dialogue.update()
        pg.display.update()