I inherited a class from tkSimpleDialog.Dialog. From a tkInter application, I open it, it has some Entry fields, an OK and a Cancel button.
When this dialog is open and I press the Enter button the dialog and also the main application freezes completely and won't do anything further.
I tried to bind <"Return"> event to it, to catch, but nothing else happened, just the freeze.
Sadly I can't attach code, because it is my work in a company.
Does anyone have an idea how could I resolve the problem and maybe set the Enter button to activate th OK?
I use Python 2.7 and cannot use other versions.
from Tkinter import *
import ttk
from tkFileDialog import askopenfilename, asksaveasfilename
import tkSimpleDialog
from mem_data import *
from collections import namedtuple
class OpenFileDialog(tkSimpleDialog.Dialog):
def body(self, master):
self.master = master
def apply(self):
self.result = "OK"enter code here
And I call it like this:
d = OpenFileDialog(self.master)
mcu = d.result
The problem is that you are setting self.master. Unfortunately, Tkinter uses self.master internally. To fix this you need to choose a different variable name.
class OpenFileDialog(tkSimpleDialog.Dialog):
def body(self, master):
self._master = master
...
Related
This is a very simple version of my application. Basically I have a Tkinter application I want to start from another python application, but I do not have the option to have tkinter as my main window.
So my question is, if it's possible to pass some information from my main class to my tkinter application. Not once, but repeatedly.
I tried something like this, but this does not work, probably because of the .mainloop() method.
Right now I just try to print the variable "labelContent" whenever the button is pressed.
Is there some way I can pass information to my tkinter application after calling .mainloop() (besides using external files like .txt because this would obviously work)?
gui.py
import tkinter
class BasicWindow:
def __init__(self):
tk = tkinter.Tk()
label = tkinter.Label(tk, text="Hello World!")
label.pack()
self.labelContent = ""
button = tkinter.Button(tk,text="OK",command=self.buttonMethod)
button.pack(side="bottom")
tk.mainloop()
def buttonMethod(self):
print(self.labelContent)
main.py
from gui import BasicWindow
import time
class Main:
def __init__(self):
self.gui = BasicWindow()
self.gui.labelContent = "This is the new label content"
mainApp = Main()
while True:
mainApp.gui.labelContent = time.time()
Thanks in advance! :)
I'm using Tkinter for the GUI of a little tool I wrote with Python. Basically I just want a callback-method to be executed as soon as the contents of an entry widget have changed. This can be done with Tkinter's own variable classes (StringVar, BooleanVar, etc. - see documentation for details: http://effbot.org/tkinterbook/variable.htm).
So I couldn't get the mechanism to work and I found a snippet online, where it works perfectly fine. Now I'm trying to figure out why my version does not work.
As you can see in the two code examples the only difference is, that I'm using the event-listening functionality inside a class, whereas the snippet I found online only demonstrates it in a straight top-to-bottom manner.
Here's what I've already tried:
I instantiated the Tk instance directly in the constructor of my GUI class - same behaviour.
I inherited directly from the Tk class (instead of Frame) - same behaviour.
I placed the callback outside of the class - same behaviour.
The only idea I have is that the problem might be scope related, which I tried to verify.
Working code snippet:
from tkinter import *
import tkinter as tk
def text_changed(*args):
print("Text changed.")
top = tk.Tk()
string_listener = StringVar()
string_listener.set("Init Text")
string_listener.trace("w", text_changed)
entry_widget = tk.Entry(top, textvariable = string_listener)
entry_widget.pack()
top.mainloop()
Not working code snippet
from tkinter import *
import tkinter as tk
root = tk.Tk()
class GUI(tk.Frame):
def __init__(self, master=root):
super(GUI, self).__init__(master)
string_listener = StringVar()
string_listener.set("Init Text")
string_listener.trace("w", self.text_changed_callback)
entry_widget = tk.Entry(master, textvariable=string_listener)
entry_widget.pack()
def text_changed_callback(self, *args):
print("Text changed.")
gui = GUI()
gui.mainloop()
Like in the working example, my code ought to print Text changed., everytime a character is either deleted from or appended to the string in the extry-widget.
The problem is that string_listener is a local variable, and python is destroying the variable when __init__ finishes running. This doesn't happen in your original code since the variable is created in the global scope.
A simple solution is to save a reference as an attribute of the class:
import tkinter as tk
root = tk.Tk()
class GUI(tk.Frame):
def __init__(self, master=root):
super(GUI, self).__init__(master)
self.string_listener = tk.StringVar()
self.string_listener.set("Init Text")
self.string_listener.trace("w", self.text_changed_callback)
entry_widget = tk.Entry(master, textvariable=self.string_listener)
entry_widget.pack()
def text_changed_callback(self, *args):
print("Text changed.")
gui = GUI()
gui.mainloop()
note: I also changed StringVar to tk.StringVar so that I could remove the redundant wildcard import of tkinter.
I have the following problem. I created a gui with Tkinter and when I run it in my IDE (Spyder) everything works perfectly fine, but when I save the file as and want to start it by just executing the .py, everytime a window is created or a dialog opens, a second Tkinter window is poping up. Same problem appears when I save the code as .pyw .
I posted a short example that lasts in the same Problem.
import tkinter as tk
from tkinter import messagebox
class test_GUI(tk.Frame):
def __init__(self,master=None):
super().__init__(master)
self._initializeWindow()
self._window.protocol("WM_DELETE_WINDOW", self.__on_closing)
self._window.mainloop()
def _initializeWindow(self):
self._window=tk.Tk()
self._window.title("The window I initzialized")
def __on_closing(self):
if(messagebox.askokcancel("Quit", "Quit program?")):
self._window.destroy()
self._window.quit()
app=test_GUI()
You define your class as
class test_GUI(tk.Frame):
so your class inherits from tk.Frame, which means that your class basically is a Frame with extra features.
When you do
super().__init__(master)
You initialize the class from which you are inheriting, which is tk.Frame. At this time, there is no tk.Tk object (and master=None). Because a Frame (or any other tkinter widget) cannot exist without an instance of tk.Tk, tkinter silently makes one for you. This is your first window.
After that you call
self._window = tk.Tk()
to make a tk.Tk instance yourself. This is your second window. Besides that you don't want two windows, you should never have more than one instance of tk.Tk (or more accurately the associated Tcl interpreter) running at the same time because this leads to unexpected behavior.
So how can you fix this?
You basically have two options: remove inheritance or initiate tk.Tk before initiating your class.
Without inheritance your app can be structured like
import tkinter as tk
class test_GUI():
def __init__(self):
self._window=tk.Tk()
self._window.title("The window I initzialized")
self.button = tk.Button(self._window, text='Test button')
self.button.pack()
...
self._window.mainloop()
With inheritance you can do it like this
import tkinter as tk
class test_GUI(tk.Frame):
def __init__(self, master=None):
super().__init__(master)
self.master = master
self.master.title("The window I initzialized")
self.button = tk.Button(self, text='Test button')
self.button.pack()
root = tk.Tk()
app=test_GUI(root)
app.pack(fill='both', expand=True)
root.mainloop()
Both ways work fine. I personally like the version with inheritance. Also check out Bryan Oakley's post on structuring a tkinter application here.
def _initializeWindow(self):
self._window=tk.Tk()
self._window.title("The window I initzialized")
self._window.withdraw()
self._window.withdraw() will remove the second window.
super().__init__(master) is actually responsible for the first window. Comment it out. In that case you won't need to withdraw the window you created in _initializeWindow.
import tkinter as tk
from tkinter import messagebox
class test_GUI(tk.Frame):
def __init__(self,master=None):
#super().__init__(master)
self._initializeWindow()
self._window.protocol("WM_DELETE_WINDOW", self.__on_closing)
self._window.mainloop()
def _initializeWindow(self):
self._window=tk.Tk()
self._window.title("The window I initzialized")
#self._window.withdraw()
def __on_closing(self):
if(messagebox.askokcancel("Quit", "Quit program?")):
self._window.destroy()
self._window.quit()
app=test_GUI()
For those attempting to unit test your GUI and trying to insert the root tk dependency via dataclasses, you can fix the multi window problem by putting tk initialization in the __post_init__ method:
from dataclasses import dataclass
import tkinter as tk
#dataclass
class App():
tk_root: tk.Tk = None
def __post_init__(self):
if self.tk_root is None:
self.tk_root = tk.Tk()
# ...
Then, if you utilize tkinter classes (like StringVars) that need a root Tk to be initialized, you'll need to patch tk in your pytest fixture:
import pytest
from unittest.mock import patch, MagicMock
from GUI import App
#pytest.fixture
def app():
with patch('GUI.tk'):
return GUI(tk_root=MagicMock())
I wrote a basic script in Python3 to create a basic function that would display then close a window. I would like to have a function to close the window, so I could import the script into a different file I have without cluttering it up too much. I tried this, however the compiler returns with name tkdel() is not defined.
def tkdisp(t):
import tkinter as tk
class disp(tk.Frame):
def __init__(self, master=None):
super().__init__(master)
self.pack()
self.distk = tk.Label(self)
self.distk["text"] = t
self.distk.pack(side="top")
root = tk.Tk()
disp(master=root).mainloop()
def tkdel():
global tkdel
root.destroy
tkdisp('nice')
tkdel()
well I had to change the code quite a bit but this works (Hope it helps):
import tkinter as tk
root = tk
class disp(tk.Frame):
def __init__(self, master=None):
super().__init__(master)
self.pack()
self.distk = tk.Label(self)
self.distk["text"] = t
self.distk.pack(side="top")
root = tk.Tk()
disp(master=root).mainloop()
def tkdel():
#global tkdel
app = None
exit()
disp.tkdel()
I try to not use globals too much as it can get complicated in a big program. :) it's better to send data between places directly. :)
I just reread the question. If you want to use another FILE - like file.py you can import the file on startup just by saying: import file
If you want to close the window from anywhere just use disp.tkdel() as this is a direct path. Really hope this helps. :)
I'm using Python's TkInter module for a GUI. Below is a simple checkbox code.
def getCheckVal():
print cbVar.get()
windowTime=Tk.Tk()
cbVar = Tk.IntVar()
btnC = Tk.Checkbutton(windowTime, text="Save", variable = cbVar, command=getCheckVal)
btnC.grid()
windowTime.mainloop()
This code works fine. Each time I tick the checkbox, I get 1, else 0.
However, when I run the same code in a function that is called from another TkInter command (when a button is pressed), it stops working. I always get 0 as the value.
class GUIMainClass:
def __init__(self):
'''Create the main window'''
self.window = Tk.Tk()
def askUser(self):
def getCheckVal():
print cbVar.get()
windowTime=Tk.Tk()
cbVar = Tk.IntVar()
btnC = Tk.Checkbutton(windowTime, text="Save", variable = cbVar,
command=getCheckVal)
btnC.grid()
windowTime.mainloop()
def cmdWindow(self):
frameShow=Tk.Frame(self.window)
frameShow.grid()
btnSwitch = Tk.Button(frameShow, text='Show Plots', command=self.askUser)
btnSwitch.grid()
self.window.mainloop()
GUIObj=GUIMainClass()
GUIObj.cmdWindow()
This is very unusual. What could be going wrong?
EDIT: I've used 2 mainloops because I want a separate window (windowTime) to open up when I click "Show Plots" button. This new window should have the checkbox in it.
Your windowTime, cbVar, etc. variables are defined in the function's local scope. When askUser() completes execution, those values are thrown away. Prepend self. to them to save them as instance variables.
There should only be one mainloop() in your program, to run the main Tkinter root object. Try putting it as the very last line in the program. I recommend doing some reading on Effbot for how to set up a Tkinter application.
I'm not sure what all you're trying to do, but one problem is that the TK.IntVar called cbVar that you create in your askUser() method will be deleted when the function returns, so you need to attach it to something that will still exist after that happens. While you could make it a global variable, a better choice would be to make it an attribute of something more persistent and has a longer "lifespan".
Another likely issue is that generally there should only be one call to mainloop() in a single Tkinter application. It appears what you want to do is display what is commonly known as a Dialog Window, which Tkinter also supports. There's some standard ones built-in, plus some more generic classes to simplify creating custom ones. Here's some documentation I found which describes them in some detail. You may also find it helpful to look at their source code.
In Python 2 it's in the /Lib/lib-tk/tkSimpleDialog.py file and
in Python 3 the code's in a file named /Lib/tkinter/simpledialog.py.
Below is code that takes the latter approach and derives a custom dialog class named GUIButtonDialog from the generic one included the Tkinter library which is simply named Dialog.
try:
import Tkinter as Tk # Python 2
from tkSimpleDialog import Dialog
except ModuleNotFoundError:
import tkinter as Tk # Python 3
from tkinter.simpledialog import Dialog
class GUIButtonDialog(Dialog):
"""Custom one Button dialog box."""
def __init__(self, btnText, parent=None, title=None):
self.btnText = btnText
Dialog.__init__(self, parent, title)
def getCheckVal(self):
print(self.cbVar.get())
def body(self, master):
"""Create dialog body."""
self.cbVar = Tk.IntVar()
self.btnC = Tk.Checkbutton(master, text=self.btnText, variable=self.cbVar,
command=self.getCheckVal)
self.btnC.grid()
return self.btnC # Return the widget to get inital focus.
def buttonbox(self):
# Overridden to suppress default "OK" and "Cancel" buttons.
pass
class GUIMainClass:
def __init__(self):
"""Create the main window."""
self.window = Tk.Tk()
def askUser(self):
"""Display custom dialog window (until user closes it)."""
GUIButtonDialog("Save", parent=self.window)
def cmdWindow(self):
frameShow = Tk.Frame(self.window)
frameShow.grid()
btnSwitch = Tk.Button(frameShow, text='Show Plots', command=self.askUser)
btnSwitch.grid()
self.window.mainloop()
GUIObj = GUIMainClass()
GUIObj.cmdWindow()