Tkinter .withdraw() strange behaviour - python

Using the following code, the Tkinter root window will be hidden:
def main():
root = Tkinter.Tk()
root.iconify()
a = open(tkFileDialog.askopenfilename(), 'r')
main()
However, using this variation, the root window will not be hidden:
class Comparison:
def __init__(self, file=open(tkFileDialog.askopenfilename(),'r')):
self.file = file
self.length = sum(1 for _ in self.file)
def main():
root = Tkinter.Tk()
root.iconify()
a = Comparison()
main()
Why does calling tkFileDialog.askopenfilename with the constructor cause this behaviour? I have tried both root.withdraw() and root.iconify() and experienced the same behaviour.
It may be worth noting that I am on OSX 10.11.6.
Thanks!

When you do this:
def __init__(self, file=open(tkFileDialog.askopenfilename(),'r')):
That immediately runs open(tkFileDialog.askopenfilename(),'r'), because default arguments are evaluated when the function is defined. Therefore, when you run the second code block, the interpreter creates a necessary Tkinter root window and opens that file chooser while it's still defining that class. After that, you define a function main. Finally, you call main(), which creates a root object, withdraws it, and instantiates an object of the Comparison class. The root window you explicitly created with root = Tkinter.Tk() is hidden. The older one, that Python was forced to create in order for the file dialog to exist, however, was not.
To fix this, put the default behavior into the method body rather than its signature:
class Comparison:
def __init__(self, file=None):
if file is None:
self.file = open(tkFileDialog.askopenfilename(),'r')
else:
self.file = file
self.length = sum(1 for _ in self.file)

Related

Python tkinter - calling a button command in a class

I'm working on functions within a class, and one of the issues I'm running into is adding a button that terminates the program. Here is the current code:
class ClassName():
def __init__(self, root):
self.root = root
def close_root(self, root):
root.destroy()
root.quit()
def addExitButton(self, root):
Button(root, text = 'Exit', width = 10, command = self.close_root).grid(row = 5,
column = 0)
Within the button arguments, I have tried command = self.close_root(root) But this gives me an error because you can't call a function if you want the button to do something (I forget the reason as to why this is). I have also tried
def close_root(self):
self.destroy()
self.quit()
def addExitButton(self, root):
Button(..., command = self.close_root,...)
And this does not work either as the class doesn't have the attribute destroy. I'm not sure how to approach this after trying a few different ways.
You need to actually access the root's functions. So using self.root.destory() or self.root.quit() will work because your root object has those methods but your class does not.
You should also only use one of them, in this case, destroy is the best option I think. And you can probably just use that when creating the button. So replace the button callback (command) with self.root.destory.
More here on how to close a Tkinter window here.

tkinter 'Tk().after()' method causing gui to not start

I am trying to text in a textbox every seconds. I have found several explanations on how to do this using the Tk().after() method. like this example
root = Tk()
def foo():
print(repeat)
root.after(5000, foo())
foo()
root.mainloop()
However, when trying this solution the main window never appears. It also does not exit with an exception. The only thing I can think of is that I am entering an infinite loop before reaching the mainloop call.
This is a condensed version of my code
def vp_start_gui():
global val, w, root
root = Tk()
top = MainWindow (root)
init(root, top)
root.mainloop()
class MainWindow():
def __init__():
self.widgets
def init(top, gui, *args, **kwargs):
global w, top_level, root
w = gui
top_level = top
root = top
root.after(15000,updateLoans(w, root))
def updateLoans(w, rt):
w.LoanOfferView.insert(END, viewModel.loanOffers())
w.LoanDemandView.insert(END, viewModel.loanDemands())
rt.after(15000,updateLoans(rt))
vp_start_gui()
The viewModel is a third module that pulls a very small amount of data from an API. `LoanDemands is a scrolledText widget.
Does anyone have any idea what might be going wrong?
python3.4
using 'page' gui designer to develop tkinter UI
You need something for the program to wait for before entering infinite loop. Try this:
Button(root, text='start', command=foo).pack()
Too replace your line:
foo()
Also, you should not put parentheses when passing functions to other functions:
root.after(5000, foo)
So I found the answer to this problem myself.
code example that was not working
root = Tk()
def foo():
print(repeat)
root.after(5000, foo())
foo()
root.mainloop()
code that does work for me
root = Tk()
def foo():
print(repeat)
root.after(5000, foo)
foo()
root.mainloop()
After looking closer at this SO answer, this rasperri pi forum answer and tkinter documentation on effbot that I quoted at the bottom. I realized that the name of the function was being referenced but the function itself is not actually called inside of the after() method
Registers an alarm callback that is called after a given time.
This method registers a callback function that will be called after a
given number of milliseconds. Tkinter only guarantees that the
callback will not be called earlier than that; if the system is busy,
the actual delay may be much longer.
The callback is only called once for each call to this method. To keep
calling the callback, you need to reregister the callback inside
itself:
class App:
def init(self, master):
self.master = master
self.poll() # start polling
def poll(self):
... do something ...
self.master.after(100, self.poll)

Tkinter Toplevel in OOP script: how?

Goal of the script:
(3) different windows, each in its own class, with its own widgets and layout, are created via Toplevel and callbacks.
When a new (Toplevel) window is created, the previous one is destroyed. Thus, only one window is visible and active at a time.
Problem?
Basically, I've tried many things and failed, so I must understand too little of ["parent", "master", "root", "app", "..."] :(
Note on raising windows:
I have implemented a successful example of loading all frames on top of each other, and controlling their visibility via the .raise method.
For this problem, however, I don't want to load all the frames at once.
This is an abstracted version of a quiz program that will require quite a lot of frames with images, which makes me reluctant to load everything at once.
Script (not working; bugged):
#!/usr/bin/env python
from Tkinter import *
import tkMessageBox, tkFont, random, ttk
class First_Window(Frame):
"""The option menu which is shown at startup"""
def __init__(self, master):
Frame.__init__(self, master)
self.gotosecond = Button(text = "Start", command = self.goto_Second)
self.gotosecond.grid(row = 2, column = 3, sticky = W+E)
def goto_Second(self):
self.master.withdraw()
self.master.update_idletasks()
Second_Window = Toplevel(self)
class Second_Window(Toplevel):
"""The gamewindow with questions, timer and entrywidget"""
def __init__(self, *args):
Toplevel.__init__(self)
self.focus_set()
self.gotothird = Button(text = "gameover", command = self.goto_Third)
self.gotothird.grid(row = 2, column = 3, sticky = W+E)
def goto_Third(self):
Third_Window = Toplevel(self)
self.destroy()
class Third_Window(Toplevel):
"""Highscores are shown with buttons to Startmenu"""
def __init__(self, *args):
Toplevel.__init__(self)
self.focus_set()
self.master = First_Window
self.gotofirst = Button(text = "startover", command = self.goto_First)
self.gotofirst.grid(row = 2, column = 3, sticky = W+E)
def goto_First(self):
self.master.update()
self.master.deiconify()
self.destroy()
def main():
root = Tk()
root.title("Algebra game by PJK")
app = First_Window(root)
root.resizable(FALSE,FALSE)
app.mainloop()
main()
The problem is not really a Tkinter problem, but a basic problem with classes vs. instances. Actually, two similar but separate problems. You probably need to read through a tutorial on classes, like the one in the official Python tutorial.
First:
self.master = First_Window
First_Window is a class. You have an instance of that class (in the global variable named app), which represents the first window on the screen. You can call update and deiconify and so forth on that instance, because it represents that window. But First_Window itself isn't representing any particular window, it's just a class, a factory for creating instances that represent particular windows. So you can't call update or deiconify on the class.
What you probably want to do is pass the first window down through the chain of windows. (You could, alternatively, access the global, or do various other things, but this seems cleanest.) You're already trying to pass it to Second_Window, you just need to stash it and pass it again in the Second_Window (instead of passing self instance, which is useless—it's just a destroyed window object), and then stash it and use it in the Third_Window.
Second:
Second_Window = Toplevel(self)
Instead of creating an instance of the Second_Window class, you're just creating an instance of the generic Toplevel class, and giving it the local name Second_Window (which temporarily hides the class name… but since you never use that class, that doesn't really matter).
And you have the same problem when you try to create the third window.
So:
class First_Window(Frame):
# ...
def goto_Second(self):
# ...
second = Second_Window(self)
class Second_Window(Toplevel):
def __init__(self, first, *args):
Toplevel.__init__(self)
self.first = first
# ...
def goto_Third(self):
third = Third_Window(self.first)
self.destroy()
class Third_Window(Toplevel):
"""Highscores are shown with buttons to Startmenu"""
def __init__(self, first, *args):
Toplevel.__init__(self)
self.first = first
# ...
def goto_First(self):
self.first.update()
self.first.deiconify()
self.destroy()

Tkinter update values from other class

I've got a tkinter window that have 3 features: background color,foreground color, and a text label. These features is in a text config file (properties.conf) in my home folder. I want to update window features when the config file changed. I watch the changes in config file with pyinotify and I want to update window when it changes. This is the code:
#!/usr/bin/python
import threading
from Tkinter import *
import os
import ConfigParser
import pyinotify
class WatchFile(threading.Thread):
def run(self):
def onChange(ev):
Gui().updateGUI()
print 2
wm = pyinotify.WatchManager()
wm.add_watch('/home/mnrl/window_configs', pyinotify.IN_CLOSE_WRITE, onChange)
notifier = pyinotify.Notifier(wm)
notifier.loop()
class ConfigParse():
def __init__(self):
self.confDir = os.path.join(os.getenv('HOME'), 'window_configs/')
self.confFile = os.path.join(self.confDir + "properties.conf")
self.config = ConfigParser.ConfigParser()
if os.path.isfile(self.confFile):
self.config.read(self.confFile)
else:
if not os.path.exists(self.confDir):
os.makedirs(self.confDir)
self.config.add_section('bolum1')
self.config.set('section1', 'setting1', 'green')
self.config.set('section1', 'setting2', 'red')
self.config.set('section1', 'setting3', 'sample text')
with open(self.confFile, 'wb') as self.confFile:
self.config.write(self.confFile)
class Gui(object):
def __init__(self):
self.root = Tk()
self.lbl = Label(self.root, text=ConfigParse().config.get('section1', 'setting3'), fg=ConfigParse().config.get('section1', 'setting1'), bg=ConfigParse().config.get('section1', 'setting2'))
self.lbl.pack()
def updateGUI(self):
self.lbl["text"] = ConfigParse().config.get('bolum1', 'ayar3')
self.lbl["fg"] = ConfigParse().config.get('bolum1', 'ayar1')
self.lbl["bg"] = ConfigParse().config.get('bolum1', 'ayar2')
self.root.update()
WatchFile().start()
Gui().root.mainloop()
But whenever properties.conf file changes a new window more appears near the old tkinter window. So tkinter window not updates, new windows open. How can I correct it?
The problem is that in WatchFile.run() you're doing this:
def onChange(ev):
Gui().updateGUI()
This does something different than what you're expecting. It creates a new GUI instance, and then immediately calls the updateGUI() method on it. Hence the two windows.
What you need to do instead, is something along the lines of:
#!/usr/bin/env python
gui = None
class WatchFile(threading.Thread):
def run(self):
def onChange(ev):
gui.updateGUI()
[...]
WatchFile().start()
gui = Gui()
gui.root.mainloop()
Here, a variable gui is created, and has an instance of your GUI class assigned to it. Later, the updateGUI() method is called on the same instance.
This problem is more or less repeated with your usage of your ConfigParser class, for example:
self.lbl["text"] = ConfigParse().config.get('bolum1', 'ayar3')
In this case, it 'works' because your ConfigParse() class can be executed twice without side-effects (such as opening windows), but it's not very efficient. You're reading the same file multiple times.
What would be better, is to just use a function (a class with only a __init__ defined is effectively the same), run this once, and return a dict.

Text Input in Tkinter

Goal
I am trying to write a basic file which I can import in all other programs that will have a simple function that will take entry from the user and then return it.
Code
For that I have the following code:
class takeInput(object):
def __init__(self,requestMessage,parent):
self.string = ''
self.frame = Frame(parent)
self.frame.pack()
self.acceptInput(requestMessage)
def acceptInput(self,requestMessage):
r = self.frame
k = Label(r,text=requestMessage)
k.pack(side='left')
self.e = Entry(r,text='Name')
self.e.pack(side='left')
self.e.focus_set()
b = Button(r,text='okay',command=self.gettext)
b.pack(side='right')
def gettext(self):
self.string = self.e.get()
self.frame.destroy()
print self.string
def getString(self):
return self.string
def getText(requestMessage,parent):
global a
a = takeInput(requestMessage,parent)
return a.getString()
And I also added some script level code so as to test this:
root = Tk()
getText('enter your name',root)
var = a.getString()
print var
root.mainloop()
And what is really baffling me is that:
var does not have the value that I entered it has the empty string ''
a.string variable has the value that I entered and I checked this from the shell.
Also When I tried to assign the string returned from a.getString() to var in the shell, then it worked.
note I am new to Tkinter programming and dont fully understand how the mainloop() works. So maybe this is were the problem is. But I am not sure.
Specs
OS:Linux Mint 14
Python IDLE 2.7
Please help me out with this issue.
As other answers tell, you print var before entering the mainloop, that is, before your window is actually running, and your program is waiting for user input.
You could rely on tkSimpleDialog family to get your input:
import Tkinter
import tkSimpleDialog
root = Tkinter.Tk()
var = tkSimpleDialog.askstring("Name prompt", "enter your name")
print var
If you want to pursue your way, you could perform your print from the "ok" button callback (gettext in your case). You could also generate a virtual event when "ok" is pressed and bind to this event in your main program (http://infohost.nmt.edu/tcc/help/pubs/tkinter/web/virtual-events.html)
The flow of your code goes like this:
the main scope calls getText.
getText creates a takeInput object a.
the takeInput object initializes itself, creating Labels & buttons etc.
getText returns a.getString(), which returns self.string, which still has its default value, the empty string.
the main scope prints var, which is empty.
So far, all of this has taken place within the span of a few nanoseconds. The user hasn't even seen the window yet.
the main scope then calls root.mainloop(), which finally gives the user the opportunity to interact with the window. But it's too late. var has already been printed.
If you want getText to not return until the user has submitted his text, then mainloop has to occur inside getText, not after it.
from Tkinter import *
class takeInput(object):
def __init__(self,requestMessage):
self.root = Tk()
self.string = ''
self.frame = Frame(self.root)
self.frame.pack()
self.acceptInput(requestMessage)
def acceptInput(self,requestMessage):
r = self.frame
k = Label(r,text=requestMessage)
k.pack(side='left')
self.e = Entry(r,text='Name')
self.e.pack(side='left')
self.e.focus_set()
b = Button(r,text='okay',command=self.gettext)
b.pack(side='right')
def gettext(self):
self.string = self.e.get()
self.root.destroy()
def getString(self):
return self.string
def waitForInput(self):
self.root.mainloop()
def getText(requestMessage):
msgBox = takeInput(requestMessage)
#loop until the user makes a decision and the window is destroyed
msgBox.waitForInput()
return msgBox.getString()
var = getText('enter your name')
print "Var:", var
The problem is that your test routine already prints out the value of var before the dialog has been shown, let alone text being entered. (You can easily validate this by adding some print statements to your test code.) This is because the call to mainloop() is at the very end. Instead, you should call mainloop after creating the frame, but before reading and returning the input, e.g. it might go to your getText method:
def getText(requestMessage,parent):
a = takeInput(requestMessage,parent)
parent.mainloop()
return a.getString()
This still does not work really well, as you have to close the dialog (click the [x]-button) even after clicking on 'okay', and I am not sure how to fix this.
However, note that there already is a module for this, tkSimpleDialog, providing methods such as askstring(title, prompt) that show just such an input dialog. So you might either use those, or look at the source code (found in /usr/lib/python2.7/lib-tk or the like) to find out how it's done.
Here's a quick snippet:
import tkinter
from tkinter import simpledialog
root = tkinter.Tk()
# withdraw() will make the parent window disappear.
root.withdraw()
# shows a dialogue with a string input field
youtube_url = simpledialog.askstring('YouTube URL', 'Enter the youtube URL of the video', parent=root)
if str(youtube_url).startswith('http'):
pass
else:
pass

Categories

Resources