Tkinter: Grab content of a ScrolledText text pad - python

all. I'm working on a simple Notepad-like program that saves files and closes the program when the escape key is pressed. I mention this because it is in this method that the program runs into problems. textpad is a ScrolledText object.
This line:
`contents = self.textPad.get(self, 1.0, END)`
results in the following error:
Exception in Tkinter callback
Traceback (most recent call last):
File "/usr/lib/python2.7/lib-tk/Tkinter.py", line 1535, in __call__
return self.func(*args)
File "todopad.py", line 24, in save_and_quit
contents = self.textPad.get(self, 1.0, END)
AttributeError: Event instance has no attribute 'textPad'
I know this is the problem, because the program executes and terminates without issue when this line is commented out. Although I don't understand the error at all.
This has been a very long-winded way of asking: How can I retrieve the contents of a ScrolledText text pad and save it to a variable or directly write it to a file? And also an explanation about the error message?
Thank you in advance.
EDIT: As requested, here is the code for the entire thing.
import sys
import Tkinter
from Tkinter import *
from ScrolledText import *
root = Tkinter.Tk(className = "TodoPad");
textPad = ScrolledText(root, width = 80, height = 20)
def start_and_open():
textFile = open('/home/colin/documents/prog/py/todopad/todo', 'r')
contents = textFile.read()
textPad.insert('1.0', contents)
textFile.close()
def save_and_quit(self):
textFile = open('/home/colin/documents/prog/py/todopad/todo', 'w')
#contents = self.textPad.get(self, 1.0, END) # The line in question
#textFile.write(contents)
textFile.close()
root.destroy()
textPad.pack()
root.bind('<Escape>', save_and_quit)
root.after(1, start_and_open)
root.mainloop()
Since I have posted the whole thing I may as well explain the rationale behind everything. It's supposed to be a fast little thing that opens a to-do list and displays what's already on the list in the text box. I make whatever edits I like, then it saves before closing when I hit escape, problem being is that it doesn't like closing because of the line that I mentioned previously in my post.

First of all, kudos on identifying the problem.
Placing the Widget
To answer what is going wrong: you need to actually place the widget into the window frame. You have a choice between .grid() and .pack(). The first allows you to pick exactly where you want it to go, the second puts in a (technically) default location.
Right now, the instance of your widget is not preset, so your program has no idea where to pull the value from. You have to set a location. i would recommend using .grid(), but for the example .pack() will work as well.
textPad = ScrolledText(root, width = 80, height = 20)
textPad.pack()
Try this, and see if it works. This should fix it, but I could be wrong.
Do NOT just do
textPad = ScrolledText(root, width = 80, height = 20).pack()
The pack() function returns a NULL and will nullify your widget.
Your Issue With Self
Finally, why are you using self at all? You are not using any classes--you need to globalize the variable. The error that is thrown is a result of your program not knowing what class you are pulling the self instance from. Remove the self variables from the program, and put this into the function:
global textPad
This will make it global and all functions will be able to use it.
This should solve all the problems you have right now. However, give it a try and report what happens.
Here are some resources on global variables, getting input from widgets, and saving to files;
http://www.python-course.eu/python3_global_vs_local_variables.php
http://effbot.org/tkinterbook/text.htm
http://www.afterhoursprogramming.com/tutorial/Python/Writing-to-Files/
Happy coding, and best of luck!!

Related

Multiple After on Tkinter made the GUI frozen

recently im working using Python with Tkinter gui. The problem is , i want to show some lines by changing its state from hidden to normal within some interval, let's say it 1s.
So , the line will show up 1 by 1 within 1 second.
When i tried, the commandline works perfectly(i print some text on windows cmd) But, the gui frozen until the end of the computation then all of the lines showed up(not 1 by 1), idk why? im new with python :(
here's my dummy code
def delay():
allline=mainframe.find_withtag('line')
for i in allline:
tags=mainframe.gettags(i)
print(tags[0])
root.after(1000, mainframe.itemconfigure(tags[0],state='normal'))
......
mainframe.create_line((50,50,100,100),...,tags=('line1','line'),state='hidden')
mainframe.create_line((150,150,100,100),...,tags=('line2','line'),state='hidden')
let's say i have a button which trigger delay function.
Thanks for ur help! sorry for my bad english :)
Rather than using a for loop like this, get all the lines into a list and call a function which removes the first line from the list, shows the line and then have the function call itself using after. Next time the function is called the next item is removed from the list, line made visible etc etc. This way tkinter has time to update the screen.
import tkinter as tk
lines = []
def showNextLine():
global lines
try:
nextLine = lines.pop(0)
canvas.itemconfigure(nextLine,state='normal')
root.after(1000,showNextLine)
except IndexError:
pass
#No more items in list.
root = tk.Tk()
canvas = tk.Canvas()
canvas.grid()
canvas.create_line((50,50,100,100),fill='red',tags=('line1','line'),state='hidden')
canvas.create_line((50,60,100,110),fill='blue',tags=('line2','line'),state='hidden')
canvas.create_line((50,70,100,120),fill='yellow',tags=('line3','line'),state='hidden')
canvas.create_line((50,80,100,130),fill='green',tags=('line4','line'),state='hidden')
lines = list(canvas.find_withtag('line'))
print(type(lines),lines)
root.after(1000,showNextLine)
root.mainloop()

TkInter Menubar causes program to not run

I am trying to write a simple text editor using TkInter. I want it to have a menu bar like other text editors, where you can save, open another file, etc.
However, whenever I try to add a menu bar to my class, the program simply starts, hangs for about half a second, then exits. I have no idea why this is happening, or how to debug it. Here is my code.
#!/usr/bin/env python3
import functools
from tkinter import *
class mainWindow(Tk):
def initiate(self):
menuBarFrame = Frame(self).pack(side=TOP)
menubar = Menu(menuBarFrame)
menubar.add_command(label='Exit', command=quit())
root.config(menu=menubar)
mainloop()
win = mainWindow().initiate()
I tried adding .pack() to the line
menubar = Menu(menuBarFrame)
but it gives me the following traceback:
File "XML.py", line 14, in <module>
win = mainWindow().initiate()
File "XML.py", line 9, in initiate
menubar = Menu(menuBarFrame).pack()
File "/usr/lib/python3.4/tkinter/__init__.py", line 1977, in pack_configure
+ self._options(cnf, kw))
_tkinter.TclError: can't pack ".140664986043280": it's a top-level window
When I remove the code for the menubar, and just replace it with a simple button, the application works and starts fine. What could be causing the problem?
The menu needs to be a child of the root window, rather than a child of a frame. You don't need MenuBarFrame at all.
Also, take a look at this line:
menubar.add_command(label='Exit', command=quit())
You are instructing Tkinter to immediately call the quit() function, and assign the result to the command attribute of the menu command. I'm guessing that quit() actually quits rather than returning a reference to some other function. You need to change it to this:
menubar.add_command(label='Exit', command=quit)
Of course, the other glaring problem is that you don't actually define root anywhere.
You definitely don't want to call pack() on the instance of Menu. The correct way to attach the menu to the window is with root.config(menu=menubar), like you're already doing.

How to display line numbers in tkinter.Text widget?

I'm writing my own code editor and I want it to have numbered lines on left side. Based on this answer I wrote this sample code:
#!/usr/bin/env python3
import tkinter
class CodeEditor(tkinter.Frame):
def __init__(self, root):
tkinter.Frame.__init__(self, root)
# Line numbers widget
self.__line_numbers_canvas = tkinter.Canvas(self, width=40, bg='#555555', highlightbackground='#555555', highlightthickness=0)
self.__line_numbers_canvas.pack(side=tkinter.LEFT, fill=tkinter.Y)
self.__text = tkinter.Text(self)
self.__text['insertbackground'] = '#ffffff'
self.__text.pack(side=tkinter.LEFT, fill=tkinter.BOTH, expand=True)
def __update_line_numbers(self):
self.__line_numbers_canvas.delete("all")
i = self.__text.index('#0,0')
self.__text.update() #FIX: adding line
while True:
dline = self.__text.dlineinfo(i)
if dline:
y = dline[1]
linenum = i[0]
self.__line_numbers_canvas.create_text(1, y, anchor="nw", text=linenum, fill='#ffffff')
i = self.__text.index('{0}+1line'.format(i)) #FIX
else:
break
def load_from_file(self, path):
self.__text.delete('1.0', tkinter.END)
f = open(path, 'r')
self.__text.insert('0.0', f.read())
f.close()
self.__update_line_numbers()
class Application(tkinter.Tk):
def __init__(self):
tkinter.Tk.__init__(self)
code_editor = CodeEditor(self)
code_editor.pack(fill=tkinter.BOTH, expand=True)
code_editor.load_from_file(__file__)
def run(self):
self.mainloop()
if __name__ == '__main__':
app = Application()
app.run()
Unfortunately something is wrong inside __update_line_numbers. This method should write line numbers from top to bottom on my Canvas widget but it prints only the number for the first line (1) and then exits. Why?
The root problem is that you're calling dlineinfo before returning to the runloop, so the text hasn't been laid out yet.
As the docs explain:
This method only works if the text widget is updated. To make sure this is the case, you can call the update_idletasks method first.
As usual, to get more information, you have to turn to the Tcl docs for the underlying object, which basically tell you that the Text widget may not be correct about which characters are and are not visible until it's updated, in which case it may be returning None not because of any problem, but just because, as far as it's concerned, you're asking for the bbox of something that's off-screen.
A good way to test whether this is the problem is to call self.__text.see(i) before calling dlineinfo(i). If it changes the result of dlineinfo, this was the problem. (Or, if not that, at least something related to that—for whatever reason, Tk thinks everything after line 1 is off-screen.)
But in this case, even calling update_idletasks doesn't work, because it's not just updating the line info that needs to happen, but laying out the text in the first place. What you need to do is explicitly defer this call. For example, add this line to the bottom of load_from_file and now it works:
self.__text.after(0, self.__update_line_numbers)
You could also call self.__text.update() before calling self.__update_line_numbers() inline, and I think that should work.
As a side note, it would really help you to either run this under the debugger, or add a print(i, dline) at the top of the loop, so you can see what you're getting, instead of just guessing.
Also wouldn't it be easier to just increment a linenumber and use '{}.0'.format(linenumber) instead of creating complex indexes like #0,0+1line+1line+1line that (at least for me) don't work. You can call Text.index() to convert any index to canonical format, but why make it so difficult? You know that what you want is 1.0, 2.0, 3.0, etc., right?
The root cause of the problem is that the text hasn't been drawn on the screen yet, so the call to dlineinfo will not return anything useful.
If you add a call to self.update() before drawing the line numbers, your code will work a little better. It won't work perfectly, because you have other bugs. Even better, call the function when the GUI goes idle, or on a Visibility event or something like that. A good rule of thumb is to never call update unless you understand why you should never call update(). In this case, however, it's relatively harmless.
Another problem is that you keep appending to i, but always use i[0] when writing to the canvas. When you get to line 2, i will be "1.0+1line". For line three it will be "1.0+1line+1line", and so on. The first character will always be "1".
What you should be doing is asking tkinter to convert your modified i to a canonical index, and using that for the line number. For example:
i = self.__text.index('{0}+1line'.format(i))
This will convert "1.0+1line" to "2.0", and "2.0+1line" to "3.0" and so on.

Python Tkinter Calculator wont evaluate text from entry widget

I am trying to make a simple calculator app using tkinter, but everytime I run the code below i get an error message saying
Traceback (most recent call last):
File "C:\Python33\Lib\site-packages\pythonwin\pywin\framework\scriptutils.py", line 326, in RunScript
exec(codeObject, __main__.__dict__)
File "C:\Users\csp\Python\Calculator App.py", line 17, in <module>
solved = eval(expression)
File "<string>", line 0
^
SyntaxError: unexpected EOF while parsing
CODE:
from tkinter import *
tk = Tk()
tk.title('Calculator')
inp = Entry(tk,text="Enter Expression Here",width=20)
inp.pack()
exit = False
def exitbtn():
global exit
exit = True
return exit
btn = Button(tk,text="Quit?",command=exitbtn)
btn.pack
canvas = Canvas(tk,width=200,height=200)
canvas.pack()
while not exit:
expression = inp.get()
solved = eval(expression)
canvas.create_text(100,100,text=expression,font=('Times', 15))
canvas.create_text(100,150,text=solved,font=('Times', 15))
if exit == True:
break
tk.destroy()
i am really new to Python and dont understand why the "solved = eval(expression)" line wont work. please help
So, the reason why eval is not working is because when you first start your program, expression is just an empty string. If you go to the python shell, and type in eval(''), you'll see the same error appear.
One solution would be to check if expression is an empty string or not, and do something like this:
expression = inp.get()
if expression != '':
solved = eval(expression)
else:
solved = '?'
However, even after you apply this fix, your program won't work, for unrelated reasons. The primary reason is that you never call tk.mainloop() (or whatever it's called), so the window will not show up.
This is because of your while loop -- what you wanted to do was to constantly check the input field and update your canvas whenever you get new input after running it through eval.
However, GUI programs, in general, don't work that way and require a different mindset and approach while writing them. Instead of writing loops to check and update program state, you write functions that will automatically be called whenever the program state changes (which are called events). It'll feel a bit backwards at first, but over time it'll help make your code cleaner and easier to manage.
You're actually already doing this in one part of your program -- with your exitbtn function. Now, you just need to convert your while loop into a similar function and bind it to the Entry object.
EDIT:
Here's some example code that does what you want:
import sys
from tkinter import *
# Create the GUI
tk = Tk()
tk.title('Calculator')
inp = Entry(tk, text="Enter Expression Here", width=20)
inp.pack()
btn = Button(tk, text="Quit?")
btn.pack()
canvas = Canvas(tk, width=200, height=200)
canvas.pack()
# Create callback functions
def end_program(event):
'''Destroys the window and ends the program without needing
to use global variables or a while loop'''
tk.destroy()
sys.exit() # Automatically ends any Python program
def update_canvas(event):
'''Gets the input, tries to eval it, and displays it to the canvas'''
expression = inp.get()
try:
solved = eval(expression)
except SyntaxError:
# The expression wasn't valid, (for example, try typing in "2 +")
# so I defaulted to something else.
solved = '??'
canvas.delete('all') # remove old text to avoid overlapping
canvas.create_text(100, 100, text=expression,font=('Times', 15))
canvas.create_text(100, 150, text=solved,font=('Times', 15))
# Bind callbacks to GUI elements
btn.bind('<Button-1>', end_program)
inp.bind('<KeyRelease>', update_canvas)
# Run the program
tk.mainloop()
Some things to note:
I moved your code for checking inp and writing to the canvas to the update_canvas function, and got rid of the while loop.
The update_canvas function will automatically be called whenever somebody lets go of a key while typing in the inp object (the <KeyRelease> event).
This can cause some problems -- this will mean your update_canvas function will be called while the user is in the process of typing text into your calculator. For example, what if the user types in 2 + 2 *? It's not a complete expression, so can't be parsed by eval.
To solve this, I just wrapped eval in a try-except to prevent any bad input from mucking up the program.
Similarly, end_program will be called whenever somebody left-clicks on the btn object (the <Button-1> event).

Python Tkinter Entry Values Method Errors

The code is intended to write a few things to a line in a text file.
from tkinter import *
Tag=0
x="txt.txt"
w=open(x,"w")
root=Tk()
win1=Frame(root)
Label(root,text="Tag").pack()
tagE=Entry(root)
tagE.pack()
def get_it():
Tag=(tagE.get())
v=Button(root,text="Submit",command=get_it)
v.pack()
win1.pack()
w.write("%s var=%s"%(Tag,"text"))
w.close()
root.mainloop()
The Tag=(tagE.get()) is indented for more spaces than it is. When i run this code i will either get a "AttributError: 'NoneType' object has no attribute 'get' or the tag value will equal its original value of 0. Help is very much appreciated.
Apart from getting the value of the entry, you have to write the value in the file in the same function:
from tkinter import *
filename = "txt.txt"
root=Tk()
Label(root,text="Tag").pack()
tagE=Entry(root)
tagE.pack()
def get_it():
w=open(filename, "w")
tag = tagE.get()
w.write("%s var=%s"%(tag,"text"))
w.close()
v=Button(root,text="Submit",command=get_it)
v.pack()
root.mainloop()
Since you don't use the Frame as parent of any of your widgets, you can use the root element directly. As a side note, I recommend you to use lowercase notation for your variables as suggested in the PEP8, and try to use clearer names.
You misunderstand how Tkinter works. Your print statement will execute before you have a chance to click the button. You need to put your print statement inside get_it.

Categories

Resources