Setting multiple lines to a label - Tkinter - python

I have a label that I am setting a random number to.
output.set(randint(1,4))
output = StringVar()
ttk.Label(mainframe, textvariable=output)
I also have an Entry where the user can specify how many random numbers they want to generate.
ttk.Entry(mainframe, textvariable=numberdice, width=5)
What I am trying to do is to get the two values to work together, and have the output label print multiple lines.
I got it to print to the console using a for loop:
for x in range(int(numberdice)):
print(randint(1,4))
But I cant get it to display the same in the GUI...

How to use the print function to display to a GUI
You can create a StringVar that supports write so you can print to it like a file:
import tkinter as tk
class WritableStringVar(tk.StringVar):
def write(self, added_text):
new_text = self.get() + added_text
self.set(new_text)
def clear(self):
self.set("")
Then using print(... , file=textvar) you can add to the string variable exactly the same way as printing to the console:
root = tk.Tk()
textvar = WritableStringVar(root)
label = tk.Label(root, textvariable=textvar)
label.pack()
for i in range(4):
print("hello there", file=textvar)
root.mainloop()
note that because print statements (by default) add a newline at the end there will be an extra blank line at the bottom of the label, if it bugs you it can be worked around by removing the newline and reinserting it next addition:
class WritableStringVar(tk.StringVar):
newline = False
def write(self, added_text):
new_text = self.get()
if self.newline: #add a newline from last write.
new_text += "\n"
self.newline = False
if added_text.endswith("\n"): #remove this newline and remember to add one next write.
added_text = added_text[:-1]
self.newline = True
new_text += added_text
self.set(new_text)
def clear(self):
self.set("")
self.newline = False
Either way you can make use of prints convenient semantics to get text on your GUI.

Related

python - tkinter grid row not appearing and not passing to function correctly

Two separate issues have come up with my code
First, I can't get the fourth row of my grid to appear, although the fifth appears to be displaying just fine.
Secondly, my passVal function keeps giving me the error:
passVal() takes 1 positional argument but 2 were given
I've tried rearranging things and converting it to a string and nothing seems to work.
I figure there's a chance it's one thing causing the same issue since they're both centered around the same button but I'm not sure.
import tkinter
class AccountCreation:
def __init__(self):
self.main = tkinter.Tk()
self.main.title("Account Creation")
self.topleftLabel = tkinter.Label(text=" ")
self.topleftLabel.grid(row=1,column=1)
self.botrightLabel = tkinter.Label(text=" ")
self.botrightLabel.grid(row=5,column=5)
self.promptLabel = tkinter.Label(text="Create a password with at least nine (9)\n characters that contains at least one digit, \n one uppercase, and one lowercase letter.\n\n")
self.promptLabel.grid(row=2,column=2,columnspan=2)
self.passLabel = tkinter.Label(text="Password:")
self.passLabel.grid(row=3,column=2)
self.passEntry = tkinter.Entry(width = 18, justify='right')
self.passEntry.grid(row=3,column=3)
self.enterButton = tkinter.Button(text="Enter", \
command=self.passVal(self.passEntry.get()))
self.enterButton.grid(row=4,column=2)
self.cancelButton = tkinter.Button(text="Cancel", \
command=self.cancel)
self.cancelButton.grid(row=4,column=3)
tkinter.mainloop()
def passVal(pw):
if len(pw) < 9:
print ("no")
def cancel(self):
self.main.destroy()
my_gui = AccountCreation()
Aside from the indention issues you are having, all methods in a class you need to pass self as the first argument unless you are using special tags that can make it a stand alone function.
Change:
def passVal(pw):
To:
def passVal(self, pw):
You will also need to change the command on you Enter button to use lambda in order to prevent python from calling the passVal method on start up.
Change:
command=self.passVal(self.passEntry.get())
To:
command=lambda: self.passVal(self.passEntry.get())
You don't really need to use a lambda here or even pass the argument of self.passEntry.get(). You can get the value of the entry field in the passVal() method by use self.passEntry.get() instead of pw.
If you change this:
command=lambda: self.passVal(self.passEntry.get())
To this:
command=self.passVal
And this:
def passVal(self, pw):
if len(pw) < 9:
print ("no")
To this:
def passVal(self):
if len(self.passEntry.get()) < 9:
print ("no")
You program will work fine and you can avoid using a lambda in your command.
Note: You do not need to use labels as spacers. You can simple use padx and pady in your grid placement.
Take a look at the below code:
import tkinter
class AccountCreation:
def __init__(self):
self.main = tkinter.Tk()
self.main.title("Account Creation")
self.promptLabel = tkinter.Label(text="Create a password with at least nine (9)\n characters that contains at least one digit, \n one uppercase, and one lowercase letter.\n\n")
self.promptLabel.grid(row=2,column=2,columnspan=2,pady=(10,10))
self.passLabel = tkinter.Label(text="Password:")
self.passLabel.grid(row=3,column=2)
self.passEntry = tkinter.Entry(width = 18, justify='right')
self.passEntry.grid(row=3,column=3)
self.enterButton = tkinter.Button(text="Enter", \
command=self.passVal(self.passEntry.get()))
self.enterButton.grid(row=4,column=2)
self.cancelButton = tkinter.Button(text="Cancel", \
command=self.cancel)
self.cancelButton.grid(row=4,column=3,pady=(10,10))
tkinter.mainloop()
def passVal(self, pw):
if len(pw) < 9:
print ("no")
def cancel(self):
self.main.destroy()
my_gui = AccountCreation()
Notice that simple using pady=(10,10) we have put space at the top and bottom of the widget.

How to make expressions in Tkinter Python

I'm new in python programming and I'm having some issues in developing a specific part of my GUI with Tkinter.
What I'm trying to do is, a space where the user could enter (type) his math equation and the software make the calculation with the variables previously calculated.
I've found a lot of calculators for Tkinter, but none of then is what I'm looking for. And I don't have much experience with classes definitions.
I made this simple layout to explain better what I want to do:
import tkinter as tk
root = tk.Tk()
Iflabel = tk.Label(root, text = "If...")
Iflabel.pack()
IfEntry = tk.Entry(root)
IfEntry.pack()
thenlabel = tk.Label(root, text = "Then...")
thenEntry = tk.Entry(root)
thenlabel.pack()
thenEntry.pack()
elselabel = tk.Label(root, text = "else..")
elseEntry = tk.Entry(root)
elselabel.pack()
elseEntry.pack()
applybutton = tk.Button(root, text = "Calculate")
applybutton.pack()
root.mainloop()
This simple code for Python 3 have 3 Entry spaces
1st) If...
2nd Then...
3rd) Else...
So, the user will enter with his conditional expression and the software will do the job. In my mind, another important thing is if the user left the "if" space in blank, he will just type his expression inside "Then..." Entry and press the button "calculate" or build all expression with the statements.
If someone could give some ideas about how and what to do....
(without classes, if it is possible)
I'l give some situations for exemplification
1st using statements:
var = the variable previously calculated and stored in the script
out = output
if var >= 10
then out = 4
else out = 2
2nd Without using statement the user will type in "Then" Entry the expression that he want to calculate and that would be:
Then: Out = (((var)**2) +(2*var))**(1/2)
Again, it's just for exemplification...I don't need this specific layout. If anyone has an idea how to construct it better, is welcome.
Thanks all.
Here is a simple version of what you are trying to do.
We need to use the eval built in function to evaluate the math of a string.
We should also write our code with some error handling as there is a very good change a user will type a formula wrong and the eval statement will fail.
For more information on eval and exec take a look at this post here. I think it does a good job of explaining the two.
Here is what it would look like:
import tkinter as tk
root = tk.Tk()
math_label = tk.Label(root, text = "Type formula and press the Calculate button.")
math_label.pack()
math_entry = tk.Entry(root)
math_entry.pack()
result_label = tk.Label(root, text = "Results: ")
result_label.pack(side = "bottom")
def perform_calc():
global result_label
try:
result = eval(math_entry.get())
result_label.config(text = "Results: {}".format(result))
except:
result_label.config(text = "Bad formula, try again.")
applybutton = tk.Button(root, text = "Calculate", command = perform_calc)
applybutton.pack()
root.mainloop()
The first answer gets at the right thought, but it can also be matched a little more explicitly to the example you gave, in case you want to take this a little further.
Basically you want to use the eval statement to test your conditional, and then use the exec statement to run your python code blocks. You have to pass in the globals() argument in order to make sure your exec functions modify the correct variables in this case
See below:
import tkinter as tk
from tkinter import messagebox
var = 10
out = 0
def calculate():
global out
try:
if eval(IfEntry.get()):
exec(thenEntry.get(), globals())
else:
exec(elseEntry.get(), globals())
messagebox.showinfo(title="Calculation", message="out: " + str(out))
except:
exc_type, exc_value, exc_traceback = sys.exc_info()
msg = traceback.format_exception(exc_type, exc_value, exc_traceback)
messagebox.showinfo("Bad Entry", message=msg)
root = tk.Tk()
Iflabel = tk.Label(root, text = "If...")
Iflabel.pack()
IfEntry = tk.Entry(root)
IfEntry.insert(0, "var >= 10")
IfEntry.pack()
thenlabel = tk.Label(root, text = "Then...")
thenEntry = tk.Entry(root)
thenlabel.pack()
thenEntry.insert(0, "out = 4")
thenEntry.pack()
elselabel = tk.Label(root, text = "else..")
elseEntry = tk.Entry(root)
elselabel.pack()
elseEntry.insert(0, "out = 2")
elseEntry.pack()
applybutton = tk.Button(root, command=calculate, text = "Calculate")
applybutton.pack()
applybutton.focus_displayof
root.mainloop()

How to configure default Double Mouse Click behavior in Tkinter text widget?

On a tkinter text widget, the default behavior of double click will be to select the text under the mouse.
The event will select all characters between " " (space) char.
So - assume the text widget has:
1111111 222222
double click on over the first word (all 1) will select only it (and double clicking on 2 word will select it)
I would like to have a similar behavior, but add additional char as work seperators (e.g., ., (, ))
currently, if the text has 111111.222222 - double click anywhere over the text will highlight all characters (won't separate the words by .)
Is there a way to do it?
Changing what is a 'word'
The double click is defined to select the 'word' under the cursor. If you are wanting to change the default behavior for all text widgets, tkinter has a way to tell it what is a "word" character. If you change what tkinter thinks is a "word", you change what gets selected with a double-click. This requires that we directly call the built-in tcl interpreter upon which tkinter is based.
Note: this will affect other aspects of the widget as well, such as key bindings for moving the cursor to the beginning or ending of a word.
Here's an example:
import tkinter as tk
def set_word_boundaries(root):
# this first statement triggers tcl to autoload the library
# that defines the variables we want to override.
root.tk.call('tcl_wordBreakAfter', '', 0)
# this defines what tcl considers to be a "word". For more
# information see http://www.tcl.tk/man/tcl8.5/TclCmd/library.htm#M19
root.tk.call('set', 'tcl_wordchars', '[a-zA-Z0-9_.,]')
root.tk.call('set', 'tcl_nonwordchars', '[^a-zA-Z0-9_.,]')
root = tk.Tk()
set_word_boundaries(root)
text = tk.Text(root)
text.pack(fill="both", expand=True)
text.insert("end", "foo 123.45,678 bar")
root.mainloop()
Custom key binding
If you do not want to affect any widget except one, or do not want to affect other aspects of tkinter that depend on the definition of a 'word', you can create your own binding to select whatever you want.
The important thing to remember is that your binding should return the string "break" in order prevent the default behavior for double-click:
def handle_double_click(event):
<your code for selecting whatever you want>
return "break"
...
text.bind("<Double-1>", handle_double_click)
To facilitate this, the text widget has a search method that makes it possible to search backwards and forwards through the text for a given string or regular expression.
Is there a way to do it?
Of course, and not even one way. But anyway - we need a custom class for our Text widget, so let's start:
class CustomText(tk.Text):
def __init__(self, parent, delimiters=[]):
tk.Text.__init__(self, parent)
# test text
self.insert('1.0', '1111111 222222'
'\n'
'1111111.222222'
'\n'
'1111111.222222,333333'
'\n'
'444444444444444444')
# binds
self.bind('<Double-1>', self.on_dbl_click)
self.bind('<<Selection>>', self.handle_selection)
# our delimiters
self.delimiters = ''.join(delimiters)
# stat dictionary for double-click event
self.dbl_click_stat = {'clicked': False,
'current': '',
'start': '',
'end': ''
}
The optional delimiters leads to two options:
If delimiters are presented we can rely on RegEx search for delimiters.
If delimiters are ommited we can rely on build-in expressions, especially those two (RegEx-like word boundaries): wordstart and wordend. According to docs:
wordstart and wordend moves the index to the beginning (end) of the current word. Words are sequences of letters, digits, and underline, or single non-space characters.
The logic is simple - when double click occures - we trace this event and store indexes in a dictionary. After that we handle change of the selection, and act accordingly the choosen option (see above).
Here's a complete snippet:
try:
import tkinter as tk
except ImportError:
import Tkinter as tk
class CustomText(tk.Text):
def __init__(self, parent, delimiters=[]):
tk.Text.__init__(self, parent)
# test text
self.insert('1.0', '1111111 222222'
'\n'
'1111111.222222'
'\n'
'1111111.222222,333333'
'\n'
'444444444444444444')
# binds
self.bind('<Double-1>', self.on_dbl_click)
self.bind('<<Selection>>', self.handle_selection)
# our delimiters
self.delimiters = ''.join(delimiters)
# stat dictionary for double-click event
self.dbl_click_stat = {'clicked': False,
'current': '',
'start': '',
'end': ''
}
def on_dbl_click(self, event):
# store stats on dbl-click
self.dbl_click_stat['clicked'] = True
# clicked position
self.dbl_click_stat['current'] = self.index('#%s,%s' % (event.x, event.y))
# start boundary
self.dbl_click_stat['start'] = self.index('#%s,%s wordstart' % (event.x, event.y))
# end boundary
self.dbl_click_stat['end'] = self.index('#%s,%s wordend' % (event.x, event.y))
def handle_selection(self, event):
if self.dbl_click_stat['clicked']:
# False to prevent a loop
self.dbl_click_stat['clicked'] = False
if self.delimiters:
# Preserve "default" selection
start = self.index('sel.first')
end = self.index('sel.last')
# Remove "default" selection
self.tag_remove('sel', '1.0', 'end')
# search for occurrences
occurrence_forward = self.search(r'[%s]' % self.delimiters, index=self.dbl_click_stat['current'],
stopindex=end, regexp=True)
occurrence_backward = self.search(r'[%s]' % self.delimiters, index=self.dbl_click_stat['current'],
stopindex=start, backwards=True, regexp=True)
boundary_one = occurrence_backward + '+1c' if occurrence_backward else start
boundary_two = occurrence_forward if occurrence_forward else end
# Add selection by boundaries
self.tag_add('sel', boundary_one, boundary_two)
else:
# Remove "default" selection
self.tag_remove('sel', '1.0', 'end')
# Add selection by boundaries
self.tag_add('sel', self.dbl_click_stat['start'], self.dbl_click_stat['end'])
root = tk.Tk()
text = CustomText(root)
text.pack()
root.mainloop()
In conclusion, if you doesn't really care about delimiters, but about words - the second option is OK, otherwise - the first one.
Update:
Many thanks to #Bryan Oakley for pointing that 'break'-string prevents the default behaviour, so code can be shortened to just one callback, there's no need in <<Selection>> anymore:
...
def on_dbl_click(self, event):
if self.delimiters:
# click position
current_idx = self.index('#%s,%s' % (event.x, event.y))
# start boundary
start_idx = self.search(r'[%s\s]' % self.delimiters, index=current_idx,
stopindex='1.0', backwards=True, regexp=True)
# quick fix for first word
start_idx = start_idx + '+1c' if start_idx else '1.0'
# end boundary
end_idx = self.search(r'[%s\s]' % self.delimiters, index=current_idx,
stopindex='end', regexp=True)
else:
# start boundary
start_idx = self.index('#%s,%s wordstart' % (event.x, event.y))
# end boundary
end_idx = self.index('#%s,%s wordend' % (event.x, event.y))
self.tag_add('sel', start_idx, end_idx)
return 'break'
...

Change default tkinter entry value in python

checkLabel = ttk.Label(win,text = " Check Amount ", foreground = "blue")
checkLabel.grid(row = 0 , column = 1)
checkEntry = ttk.Entry(win, textvariable = checkVariable)
checkEntry.grid(row = 1, column = 1, sticky = 'w')
How do I change the defualt entry field from displaying 0?
Use the entry widget's function, .insert() and .delete(). Here is an example.
entry = tk.Entry(root)
entry.insert(END, "Hello") # this will start the entry with "Hello" in it
# you may want to add a delay or something here.
entry.delete(0, END) # this will delete everything inside the entry
entry.insert(END, "WORLD") # and this will insert the word "WORLD" in the entry.
Another way to do this is with the Tkinter StrVar. Here is an example of using the str variable.
entry_var = tk.StrVar()
entry_var.set("Hello")
entry = tk.Entry(root, textvariable=entry_var) # when packing the widget
# it should have the world "Hello" inside.
# here is your delay or you can make a function call.
entry_var.set("World") # this sets the entry widget text to "World"
Set the value of checkVariable to '' (the empty string) before you create the Entry object? The statement to use would be
checkVariable.set('')
But then checkVariable would have to be a StringVar, and you would have to convert the input value to an integer yourself if you wanted the integer value.

Possible to remove/deactivate variables from an executed line?

Is it possible to remove/deactivate variables from a line of code once it has been executed? If not, what are my other options? I wrote a code here to demonstrate what I mean:
from Tkinter import *
root = Tk()
ent_var1 = StringVar()
ent_var2 = StringVar()
ent_var3 = StringVar()
cbtn_var1 = BooleanVar()
cbtn_var2 = BooleanVar()
cbtn_var3 = BooleanVar()
ent1 = Entry(textvariable=ent_var1).pack()
ent2 = Entry(textvariable=ent_var2).pack()
ent3 = Entry(textvariable=ent_var3).pack()
cbtn1 = Checkbutton(text=1, variable=cbtn_var1).pack(side = LEFT)
cbtn2 = Checkbutton(text=2, variable=cbtn_var2).pack(side = LEFT)
cbtn3 = Checkbutton(text=3, variable=cbtn_var3).pack(side = LEFT)
# prints what was written in entires
def set_variables():
lbl1 = ent_var1.get()
lbl2 = ent_var2.get()
lbl3 = ent_var3.get()
print lbl1, lbl2, lbl3
return
# calls set_variables
btn1 = Button(root, text="Done!", command=set_variables).pack()
root.mainloop()
When you fill the entries and press "Done!", what was written is printed. But how do I make it so that when I press the checkboxes, the entry linked to it will not be printed the next the I press "Done!"? The checkbox with the text "1" should be linked with the first entry, and so on.
I came up with this:
def should_print():
global lbl_print
if cbtn1:
lbl_print += lbl1
if cbtn2:
lbl_print += lbl2
if cbtn3:
lbl_print += lbl3
But it would only print the values of my variables at that very moment, not the variables themselves (meaning I'd have to run this code every time a variable changes).
Thank you!
Your question is very hard to understand. I think what you want is for set_variables to only print the variables associated with a checked checkbox. If so, does the following do what you want?
def set_variables():
to_print = []
if cbtn_var1.get():
to_print.append(ent_var1.get())
if cbtn_var2.get():
to_print.append(ent_var2.get())
if cbtn_var3.get():
to_print.append(ent_var3.get())
print " ".join(to_print)
return
There are other ways to accomplish the same thing, but I'm guessing your main goal is to decide what to print based on which checkbuttons are checked. This does that, albeit in a rather ham-fisted manner.
Why don't you simply check in your set_variables function if each button is pressed? For example:
def set_variables():
if not cbtn_var1.get():
print ent_var1.get(),
if not cbtn_var2.get():
print ent_var2.get(),
if not cbtn_var3.get():
print ent_var3.get(),
print
The commas at the end of each print statement will cause it to not print a newline, which is taken care of by the print at the end. Also, this will make so that if the box is checked, the value they entered won't print. If you want it to print only if the box is checked, then remove the nots.
If you refactor your code a little bit, you can do the same thing with one line. First, add this line:
cbtn_var1 = BooleanVar()
cbtn_var2 = BooleanVar()
cbtn_var3 = BooleanVar()
buttonsAndValues = [(cbtn_var1,ent_var1), (cbtn_var2,ent_var2), (cbtn_var3,ent_var3)]
With the variables in a list, you can use a list comprehension in some Python magic:
def set_variables():
print ' '.join(value.get() for checked, value in buttonsAndValues if checked.get())
If you haven't seen list comprehensions before, I'd suggest you read up about them - they can be very handy.

Categories