I'm trying to redirect the stdout of a function to a tkinter text widget.
The problem I am running into is that it writes each line to a new window instead of listing everything in one.
The function scans a directory and lists any file that is 0k. If no files are 0k it prints that. So, the problem is that if there are 30 0k files in a directory, it will open 30 windows with a single line in each.
Now, I know what the problem is. If you look in my function code Zerok() I am telling it:
if os.stat(filename).st_size==0:
redirector(filename)
I know that every time os.stat sees a file that is 0k it is then sending that to redirector, that's why its a new window for each file.
I just have no idea how to fix it.
Complete code below.
Thanks for the help.
import Tkinter
from Tkinter import *
import tkFileDialog
class IORedirector(object):
'''A general class for redirecting I/O to this Text widget.'''
def __init__(self,text_area):
self.text_area = text_area
class StdoutRedirector(IORedirector):
'''A class for redirecting stdout to this Text widget.'''
def write(self,str):
self.text_area.write(str,False)
def redirector(inputStr):
import sys
root = Tk()
sys.stdout = StdoutRedirector(root)
T = Text(root)
T.pack()
T.insert(END, inputStr)
####This Function checks a User defined directory for 0k files
def Zerok():
import os
sys.stdout.write = redirector #whenever sys.stdout.write is called, redirector is called.
PATH = tkFileDialog.askdirectory(initialdir="/",title='Please select a directory')
for root,dirs,files in os.walk(PATH):
for name in files:
filename=os.path.join(root,name)
if os.stat(filename).st_size==0:
redirector(filename)
else:
redirector("There are no empty files in that Directory")
break
#############################Main GUI Window###########################
win = Tk()
f = Frame(win)
b1 = Button(f,text="List Size")
b2 = Button(f,text="ZeroK")
b3 = Button(f,text="Rename")
b4 = Button(f,text="ListGen")
b5 = Button(f,text="ListDir")
b1.pack()
b2.pack()
b3.pack()
b4.pack()
b5.pack()
l = Label(win, text="Select an Option")
l.pack()
f.pack()
b2.configure(command=Zerok)
win.mainloop()
The fix is simple: don't create more than one redirector. The whole point of the redirector is that you create it once, and then normal print statements will show up in that window.
You'll need to make a couple of small changes to your redirector function. First, it shouldn't call Tk; instead, it should create an instance of Toplevel since a tkinter program must have exactly one root window. Second, you must pass a text widget to IORedirector since it needs to know the exact widget to write to.
def redirector(inputStr=""):
import sys
root = Toplevel()
T = Text(root)
sys.stdout = StdoutRedirector(T)
T.pack()
T.insert(END, inputStr)
Next, you should only call this function a single time. From then on, to have data appear in the window you would use a normal print statement.
You can create it in the main block of code:
win = Tk()
...
r = redirector()
win.mainloop()
Next, you need to modify the write function, since it must write to the text widget:
class StdoutRedirector(IORedirector):
'''A class for redirecting stdout to this Text widget.'''
def write(self,str):
self.text_area.insert("end", str)
Finally, change your Zerok function to use print statements:
def Zerok():
...
if os.stat(filename).st_size==0:
print(filename)
else:
print("There are no empty files in that Directory")
break
The above solution is very complete; I was able to essentially copy and paste it into my code with only one small modification. I'm not entirely sure why, but the StdoutRedirector requires a flush method.
I'm guessing it's because sys.stdout calls a flush() method when it exits, but I'm haven't waded into the docs deep enough to actually understand what that means.
Running the above code in the Jupyter environment caused the code to hang indefinitely until the kernel was restarted. The console kicks the following errors:
sys.stdout.flush()
AttributeError: 'StdoutRedirector' object has no attribute 'flush'
ERROR:tornado.general:Uncaught exception, closing connection.
The simple solution is to make a small change to the StdoutRedirector class by adding a flush method.
class StdoutRedirector(IORedirector):
'''A class for redirecting stdout to this Text widget.'''
def write(self,str):
self.text_area.insert("end", str)
def flush(self):
pass
Thanks to the giants that came before me and offered this very clear explanation.
Related
I'm working on a program that takes data from a spreadsheet and enters it into a contract template, then converts it into a PDF. I've created a simple GUI using tkinter, and I'd like to have a button that opens a new window, allowing users to select the .xlsx file they'd like to work from. I can't seem to figure out how to get my program to run without calling the importSpread method right when the program starts.
Here are the relevant portions of my code:
import os
from tkinter import *
from tkinter import filedialog
class GUI:
def __init__(self, master):
self.master = master
self.root = os.path.dirname(os.path.abspath(__file__)) + '\\'
self.importButton = Button(master, text = 'Import Spreadsheet',
command = self.importSpread,
height = 1, width = 20)
self.importButton.grid(row = 1, column = 2, sticky = W)
def importSpread(self):
my_filetypes = [('excel files', '.xlsx')]
fileName = filedialog.askopenfilename(parent = self.master,
initialdir = self.root,
title = "Please select a file:",
filetypes = my_filetypes)
return fileName
window = Tk()
myGUI = GUI(window)
fileName = self.importSpread()
window.mainloop()
It seems as though as soon as I instantiate the myGUI object by saying myGUI = GUI(window), the importSpread method is automatically called. I'd also like importSpread to return the fileName to be used later. How do I retain the value the method returns without calling the method itself? I want the program to start and remain static until the user presses the "Import Spreadsheet" button. I'm fairly new to python and GUIs in general, so I could be missing something basic. Any guidance would be much appreciated.
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()
I am new to programming in all languages and am having a crack at some python. I have a collection of the functions I've written so far which work so I can refer back to them for when I get stuck. In order to collect them together I have used tkinter to do this for me 'AddToCollection.py'. I can get it working when I run the .py file I created it in, however I would like to import it to any function I wish. Whenever I run the code with the AddToCollection imported it runs immediately. I have tried to split it into functions so that the new window will only open when I call a function but then it can't access the Entry to get the file name. Help would be appreciated. TL;DR how do I stop this code from running when it's imported?
from tkinter import *
from tkinter import messagebox as box
#pops up a box to confirm you want to save it
def SaveConf():
var = box.askokcancel('Save', 'Are you sure you want to save?')
if var == 1:
Save(FileName.get())
#Does the actual saving
def Save(Oldfile):
file = open(Oldfile+".py", 'r')
ToAdd = '\n#- '+ Oldfile +' -------------\n' + file.read() + '\n#-----------'
file.close()
newfile = open("/New.py", 'a+')
newfile.write(ToAdd)
newfile.close()
newwind.destroy()
#setting up items in window
#Initialising window
newwind = Tk()
newwind.title('Save a file')
#to enter filename
Frame3 = Frame()
Frame3.pack(padx=5, pady=5)
FileName = Entry(Frame3)
FileName.pack(side = LEFT)
#click button
SaveBtn2 = Button(Frame3, text = 'Save to Testicles.py', command = SaveConf)
SaveBtn2.pack(side=RIGHT,padx=2)
A common way to structure tkinter application is to subclass Tk and create your widget in the constructor. Here is an example of how you could architecture for your code. It pack your application in a class (subclass of Tk) and provide an helper function launch_app to initialise your class and run mainloop on it.
The point with __name__ == "__main__" is to segregate code executed when the script is run #> python foo.py from code executed when the module is imported. If you want to provide a default behavior when used as script, as well as the ability to use that functionality from another module, put it in a function and call this function from if __name__ == "__main__" block.
I also took the liberty to transform your code toward python coding standard (described in PEP 8)
import tkinter as tk
from tkinter import messagebox as box
#Does the actual saving
def save(oldfile):
file_ = open(oldfile+".py", 'r')
#[...]
newfile.close()
#do not destroy window here
class App(tk.Tk):
def __init__(self):
tk.Tk.__init__(self)
self.title('Save a file')
frame3 = tk.Frame()
frame3.pack(padx=5, pady=5)
self.filename = tk.Entry(frame3)
self.filename.pack(side=tk.LEFT)
#click button
save_btn2 = tk.Button(frame3, text='Save to Testicles.py', command=self.save_conf)
save_btn2.pack(side=tk.RIGHT, padx=2)
def save_conf(self):
var = box.askokcancel('Save', 'Are you sure you want to save?')
if var == 1:
save(self.FileName.get())
self.destroy() #<-- here come the destroy
def launch_app():
app = App()
app.mainloop()
if __name__ == "__main__":
launch_app()
If I'm understanding this correctly, you just want to import and use the functions you've written? I think part of what you're missing is this:
if __name__ == "__main__":
#setting up items in window
#Initialising window
newwind = Tk()
newwind.title('Save a file')
#to enter filename
Frame3 = Frame()
Frame3.pack(padx=5, pady=5)
FileName = Entry(Frame3)
FileName.pack(side = LEFT)
That will prevent this code from running when the file is imported as a module.
Hi I am trying to create a tool that browses a time machine image with Tkinter in python. I plan on using the code from here: http://code.google.com/p/python-ttk/source/browse/trunk/pyttk-samples/dirbrowser.py?r=21 for the directory browser. I have written a start menu and upon clicking on the 'browse' button I want the directory browser to open where the user can select a file and the path is then passed back to the Label (I need to add this as its not in the directory browser code yet). Below is the code for my start menu:
#!/usr/bin/python
from Tkinter import *
import ttk
class App:
def __init__(self,master):
frame = Frame(master)
frame.pack()
self.label = Label(frame, text="Please enter file path or browse to a file")
self.label.pack(side=TOP)
self.button = Button(frame, text="OK", command=messageWindow)
self.button.pack(side=BOTTOM)
self.hi_there = Button(frame, text="Browse")
self.hi_there.pack(side=BOTTOM)
self.entry = Entry(frame, width = 30)
self.entry.pack(side=LEFT)
root = Tk()
app = App(root)
root.mainloop()
I have read that you cannot have two root frames at once with Tkinter but I am struggling to find an alternative as the directory browser also has a root frame. I am not sure if what I am doing is correct but on the button for browse I have added:
self.hi_there = Button(frame, text="Browse", command=dir)
I have put the directory browser code inside of a class and called it dir. So my thinking is that I should be calling the entire class? But then I get an error stating that the name dir is not defined. What ways can I go about getting this right?
I don't quite understand what you mean by "a time machine image", but I've got a few things that might help you: don't use the variable name dir, as that's a builtin keyword and you're bound to run into problems. If you're having trouble finding the method called dir which is inside a class, make sure you're telling it to look inside the class.
def sayHello():
print "Hello!"
class Person:
def sayHello():
print "Hello from Person"
a_person = Person()
sayHello()
##"Hello"
a_person.sayHello()
## "Hello from Person"
Calling printHello and class_instance.printHello are two different functions, and you'll want to pass class_instance.dir to the button.
I'm sure you know about them, but there are premade file dialogs to help with getting filepaths, filenames etc.
Another thing is you don't want a new root instance, you're looking for a new TopLevel instance, which is essentially the same thing as a new root, but not quite.
I'm using tkinter with Python to create a user interface for a program that converts Excel files to CSV.
I created a label to act as a status bar, and set statusBarText as a StringVar() as the textvariable. inputFileEntry and outputFileEntry are textvariables that contain the input and output file paths.
def convertButtonClick():
statusBarText.set('Converting...')
if inputFileEntry.get() == '' or outputFileEntry.get() == '':
statusBarText.set('Invalid Parameters.')
return
retcode = subprocess.('Program.exe' ,shell=true)
if retcode == 0:
statusBarText.set('Conversion Successful!')
else:
statusBarText.set('Conversion Failed!')
This function gets called when you click the convert button, and everything is working fine EXCEPT that the status bar never changes to say 'Converting...'.
The status bar text will get changed to invalid parameters if either the input or output are empty, and it will change to success or failure depending on the return code. The problem is it never changes to 'Converting...'
I've copied and pasted that exact line into the if statements and it works fine, but for some reason it just never changes before the subprocess runs when its at the top of the function. Any help would be greatly appreciated.
Since you're doing all of this in a single method call, the GUI never gets a chance to update before you start your sub process. Check out update_idletasks() call...
from http://infohost.nmt.edu/tcc/help/pubs/tkinter/universal.html
w.update_idletasks()
Some tasks in updating the display, such as resizing and redrawing widgets, are called idle tasks because they are usually deferred until the application has finished handling events and has gone back to the main loop to wait for new events.
If you want to force the display to be updated before the application next idles, call the w.update_idletasks() method on any widget.
How are you creating your Label?
I have this little test setup:
from Tkinter import *
class LabelTest:
def __init__(self, master):
self.test = StringVar()
self.button = Button(master, text="Change Label", command=self.change)
self.button.grid(row=0, column=0, sticky=W)
self.test.set("spam")
self.testlabel = Label(master, textvariable = self.test).grid(row = 0,column = 1)
def change(self):
self.test.set("eggs")
root = Tk()
root.title("Label tester")
calc = LabelTest(root)
root.mainloop()
And it works.
Did you make sure to use "textvariable = StatusBarText" instead of "text=StatusBarText.get()"?