How to make an arbirtary number of windows using Python and Tkinter? - python

I'm writing an app that doesn't have a main window (it runs inside a Python interpreter in another app), and I thought I had a good solution for getting Tkinter to cooperate--I made the Tkinter.Tk class into a Borg.
class RootWindow(Tk):
""" Invisible window that serves as the default parent
of all others.
"""
groupDict = {}
def __init__(self):
self.__dict__ = self.groupDict
if self.__dict__ == {}: # first instance
Tk.__init__(self)
self.withdraw()
Then I have my Windows, which are subclassed from Tkinter.Toplevel, default to parent=RootWindow(). The result should be that I can create any number of Windows and they'll use the same root.
It works once fine for the first Window, but after that things get all messed up. :(
see pic
What am I doing wrong? Is this even a feasible solution?
Thanks
EDIT: I should add that even though there's other stuff running in the picture, the problem can be duplicated just by using RootWindow as the parent of a Tkinter.Toplevel.
EDIT: I overrode Window.mainloop so everything uses the RootWindow event loop.
def mainloop(self):
self.master.wait_window(self)
Then I create each visible window like this:
test = Window()
test.mainloop()
It seems to work because the windows do show up, but their contents are packed in an odd way that's hard to describe. It alternates between no contents at all and having everything squished horizontally and expanded vertically.

One problem appears to be that you are forgetting to start the event loop. Or, based on further edits of your question, you may be starting more than one main loop. You need exactly one main loop that is run from the root window.
Tkinter certainly allows an arbitrary number of top level windows, but your question doesn't appear to have enough details to show what is wrong unless my first guess is correct.

Related

PyQt5: Using DeleteOnClose when switching to new window

I am currently creating a GUI in Python 3.7, using PyQt5 and Qt Designer in the Spyder environment. The GUI has many different windows. Basically I am starting with the UI_Start window and then open the next window when a button is pressed. The GUI is working kind of fine, however after approximately 50 windows the program suddenly doesn't show the next window anymore but also doesn't stop the execution. The weird thing about this issue is that:
the exact same window class has been called a lot of times beforehand and there have never been any issues
the problem does not only occur for one window but it can also occur for another window class (but after the same amount of windows being shown)
I tried to figure out why the .show() command is suddenly not working anymore. I used print statements to see where the program "breaks down". I saw that even the print statements after the .show() command are working but then as the window isn't shown I can't press any button to trigger the next event. So basically the program is hanging.
I am relatively new to programming in Python and creating GUIs but I thought that maybe the problem occurs due to memory leak. This is why I am now trying to open memory space when closing a window by using self.setAttribute(QtCore.Qt.WA_DeleteOnClose, True). However, now I am facing the problem that the next window doesn't show up anymore. So how can I use DeleteOnClose if I want to show a new window afterwards?
Also if anyone has a suggestion for the original problem, please let me know. I am trying to figure out the problem since like a week but have not come any further.
Thank you already!
Some part of my code to work with:
class UI_Start(QtWidgets.QMainWindow):
def __init__(self):
super(UI_Start, self).__init__() # Call the inherited classes __init__ method
uic.loadUi('Screen_Start.ui', self) # Load the .ui file
self.setAttribute(QtCore.Qt.WA_DeleteOnClose, True) # added newly
self.Start_pushButton_Start.clicked.connect(self.openKommiScreen)
def openKommiScreen(self):
self.close()
self.KommiScreen = UI_Kommi(self)
class UI_Kommi(QtWidgets.QMainWindow):
def __init__(self, parent = None):
super(UI_Kommi, self).__init__(parent)
uic.loadUi('Screen_Kommi.ui', self)
self.setAttribute(QtCore.Qt.WA_DeleteOnClose, True)
global sheetNo
sheetNo = 1
self.WeiterButton = self.findChild(QtWidgets.QPushButton,'pushButton_Weiter')
self.WeiterButton.clicked.connect(self.openScanScreen)
self.show()
def openScanScreen(self):
self.close()
self.ScanScreen = UI_Scan(self)
if __name__ == '__main__':
app = QtWidgets.QApplication(sys.argv)
window = UI_Start()
window.show()
sys.exit(app.exec_())
At first I would guess it's a garbage collection problem. The only reference to your new window is stored in your previous one. Which is deleted, so there is no more reference to your window object and python may delete it automatically.
In these cases I often goes for a global variable to store the current windows references.

Python Tkinter - How do I change variables from outside the main window class?

Now i understand the concept of instance variables and classes, I've never had a problem with them before and I use them frequently. However when I make my MainWindow class, everything is peachy until i try accessing instance variables.
http://pastebin.com/tDs5EJhi is the full code, but at this point it's just placing labels and frames and whatnot, no actual logic is going on. The window looks fine and nothing bad happens.
My question comes to be when I try changing things inside of the window externally. I figured I could just make an instance of the class and change variables from there (namely instancevariable.ImageCanvas.itemconfig()) like i can normally, but Tkinter isn't being nice about it and I think it's a result of Tkinter's mainloop().
Here's the tidbit of my class MainWindow() that i'm having trouble with (ln 207)
...
self.C4 = Tk.PhotoImage(file="temp.png")
self.card4 = self.CardCanvas.create_image(120,46,image=self.C4, state=Tk.NORMAL)
#self.CardCanvas.itemconfig(4, state=Tk.HIDDEN) # < It works here
...
self.root.mainloop()
window = MainWindow()
window.CardCanvas.itemconfig(4, state=Tk.HIDDEN) # < It doesn't work here
That's how i learned how to edit instance variables. When the window pops up, the itemconfig command doesn't actually apply like it would were it inside the class (or maybe it did and the window just didn't update?) and after closing the window I get this error:
_tkinter.TclError: invalid command name
which I assume is just because it's trying to apply a method to variables that don't exist anymore, now that the window has closed.
So I guess here's my big question - I have a MainWindow class, and from what I can tell, nothing can be changed from outside of the class because the Tk.mainloop() is running and won't stop to let other code after it run, like the itemconfig. How do I go about changing those variables? Code after the instance variable declaration doesn't seem to run until the MainWindow() is closed.
You are correct that code after mainloop doesn't run. It does, but only after the GUI has been destroyed. Tkinter is designed for the call to mainloop be the last (or very nearly last) line of executable code. Once it is called, all other work must be done as reaction to events. That is the essence of GUI programming.
The answer to "how do I go about changing the variables" is simple: do it before you call mainloop, or do it in reaction to an event. For example, do it in a callback to a button, do it in a function bound to an event, or to a time-based event via after, and so on.

python tkinter Toplevel .destroy() vs .quit() not working as intended

I have a class Duplicates that checks for duplicates within 40 words.
I have a class Window that creates and runs the main window where i post the result.
I have a class popWindow that creates a Toplevel window when asking user for what to do with a possible double.
My problem is closing the popWindow once a choice is submited.
the version I have that actualy runs and posts an aswer (the text with marked duplicates) uses quit to terminate the window (meaning the popup is still there in the way) or to simply have multiple popups till you are done.
class Duplicates:
def markWord(self):
self.appendMarkedWord(self.word)
self.checked.append(self.word)
self.pop.topLevel_exit()
return ""
class popUpWindow:
temp = Button( self, font = 8,
text = "Allowed this run only",
command = app.newFile.markWord
)
temp.place( x = 178,
y = 55
)
if I instead use .destroy() the window shuts but the program stops running and that is worse.
How do i work around this so it shuts the window but still continues to run the program?
Ok, after many many hours it seemed the real problem was destroy() was not stopping my popUpWindow.mainloop() so I now have altered my exit code to first do quit() and then do destroy(). This is not what i have seen as examples at all and it seems to me that destroy() on toplevel mainloop is not terminating it (destroy() works fine on my root.mainloop).
def topLevel_exit(self):
self.pop.quit()
self.pop.destroy()
If you call destroy() on a toplevel window, it will not stop the application from running. If your application stops, there must be more to your code that what you're telling us. Without question, the right way to get rid of the popup is to call destroy on the instance of the Toplevel.
A way to hide the window and keep the program running would be to use .withdraw() on the window, and .reiconify() to get it back (if needed). Or you could use .destroy() on a Toplevel window. If you need examples just ask, hope this helps you.
The solution for me was:
def topLevel_exit(self):
self.top.quit()
self.top.destroy()
I do not know if this is common praxis but is what I had to do since destroy was not stoping my top.mainloop()
If you use a topLevel window, self.pop.destroy() should still work as you are using mainloop()
Otherwise use quit() or both but in my opinion of all of these, I prefer destroy()

Create a python tkinter window with no X (close) button

I'm writing a 'wizard' type Python Tkinter GUI that collects information from the user and then performs several actions based on the user's entries: file copying, DB updates, etc. The processing normally takes 30-60 seconds and during that time, I want to:
Provide the user with text updates on the activity and progress
Prevent the user from closing the app until it's finished what it's doing
I started on the route of having the text updates appear in a child window that's configured to be trainsient and using wait_window to pause the main loop until the activities are done. This worked fine for other custom dialog boxes I created which have OK/cancel buttons that call the window's destroy method. The basic approach is:
def myCustomDialog(parent,*args):
winCDLG = _cdlgWin(parent,*args)
winCDLG.showWin()
winCDLG.dlgWin.focus_set()
winCDLG.dlgWin.grab_set()
winCDLG.dlgWin.transient(parent)
winCDLG.dlgWin.wait_window(winCDLG.dlgWin)
return winCDLG.userResponse
class _cdlgWin():
def __init__(self,parent,*args):
self.parent = parent
self.dlgWin = tk.Toplevel()
self.userResponse = ''
def showWin(self):
#Tkinter widgets and geometry defined here
def _btnOKClick(self):
#self.userResponse assigned from user entry/entries on dialog
self.dlgWin.destroy()
def _btnCancelClick(self):
self.dlgWin.destroy()
However this approach isn't working for the new monitor-and-update dialog I want to create.
First, because there's no user-initiated action to trigger the copy/update activities and then the destroy, I have to put them either in showWin, or in another method. I've tried both ways but I'm stuck between a race condition (the code completes the copy/update stuff but then tries to destroy the window before it's there), and never executing the copy/update stuff in the first place because it hits the wait_window before I can activate the other method.
If I could figure out a way past that, then the secondary problem (preventing the user from closing the child window before the work's done) is covered by the answers below.
So... is there any kind of bandaid I could apply to make this approach work the way I want? Or do I need to just scrap this because it can't work? (And if it's the latter, is there any way I can accomplish the original goal?)
self.dlgWin.overrideredirect(1) will remove all of the buttons (make a borderless window). Is that what you're looking for?
As far as I know, window control buttons are implemented by the window manager, so I think it is not possible to just remove one of them with Tkinter (I am not 100% sure though). The common solution for this problem is to set a callback to the protocol WM_DELETE_WINDOW and use it to control the behaviour of the window:
class _cdlgWin():
def __init__(self,parent,*args):
self.parent = parent
self.dlgWin = tk.Toplevel()
self.dlgWin.protocol('WM_DELETE_WINDOW', self.close)
self.userResponse = ''
def close(self):
tkMessageBox.showwarning('Warning!',
'The pending action has not finished yet')
# ...

Enter-Notify-Event Signal not working on gtk.ToolButton

On a happy (if not irrevelent) note, this is the absolute last obstacle in this particular project. If I fix this, I have my first significant dot release (1.0), and the project will be going public. Thanks to everyone here on SO for helping me through this project, and my other two (the answers help across the board, as they should).
Now, to the actual question...
I have a toolbar in my application (Python 2.7, PyGTK) which has a number of gtk.ToolButton objects on it. These function just fine. I have working "clicked" events tied to them.
However, I need to also connect them to "enter-notify-event" and "leave-notify-event" signals, so I can display the button's functions in the statusbar.
This is the code I have. I am receiving no errors, and yet, the status bar messages are not appearing:
new_tb = gtk.ToolButton(gtk.STOCK_NEW)
toolbar.insert(new_tb, -1)
new_tb.show()
new_tb.connect("clicked", new_event)
new_tb.connect("enter-notify-event", status_push, "Create a new, empty project.")
new_tb.connect("leave-notify-event", status_pop)
I know the issue is not with the "status_push" and "status_pop" events, as I've connected all my gtk.MenuItem objects to them, and they work swimmingly.
I know that gtk.ToolButton objects are in the Widgets class, so "enter-notify-event" and "leave-notify-event" SHOULD technically work. My only guess is that this particular object does not emit any signals other than "clicked", and thus I'd have to put each in a gtk.EventBox.
What am I doing wrong here? How do I fix this?
Thanks in advance!
Your guess was correct, you should wrap your widget in a gtk.EventBox, here is an example that i hope will be hopeful:
import gtk
def callback(widget, event, data):
print event, data
class Win(gtk.Window):
def __init__(self):
super(Win, self).__init__()
self.connect("destroy", gtk.main_quit)
self.set_position(gtk.WIN_POS_CENTER)
self.set_default_size(250, 200)
tb = gtk.ToolButton(gtk.STOCK_NEW)
# Wrap ``gtk.ToolButton`` in an ``gtk.EventBox``.
ev_box = gtk.EventBox()
ev_box.connect("enter-notify-event", callback, "enter")
ev_box.connect("leave-notify-event", callback, "leave")
ev_box.add(tb)
self.add(ev_box)
if __name__ == '__main__':
Win()
gtk.main()
It appears, based on experimentation and evidence, this is impossible in PyGtk 2.24.

Categories