I was writing a pixel editor in Tkinter, to practice Tkinter and GUI programming. And I was thinking to add a "bottom frame" to show informations like the current tool, the canvas size, etc. But for some reason, the second defined StringVar doesn't seems to work, while the first one works just fine. By this, I mean the code runs just fine but the second StringVar doesn't show anything.
Here is the code, for the bottom frame so far:
# Bottom frame
ttk.Style().configure('Interface.TFrame', background='#dbdbdb')
bottomframe = ttk.Frame(root, style='Interface.TFrame')
bottomframe.grid(column=0, row=2, sticky=(N, S, W, E), pady=4)
# An indicator that shows the current selected tool.
toolname = StringVar()
toolname.set('paint tool')
tool_label = ttk.Label(bottomframe, textvariable=toolname, width=11, background='#dbdbdb', anchor='center')
tool_label.grid(column=0, row=0, sticky=(W, E), padx=2)
# A thin separator.
ttk.Separator(bottomframe, orient=VERTICAL).grid(column=1, row=0, sticky=(N, S, W, E))
# An indicator that shows the canvas size.
cvsize = StringVar()
cvsize.set('50x50 px')
cv_size_label = ttk.Label(bottomframe, textvariable=cvsize, width=11, background='#dbdbdb', anchor='center')
cv_size_label.grid(column=2, row=0, sticky=W, padx=2)
So, why it's not working? The first and the second indicator look nearly same (except the bind operation) and the second one still fails. I also tried removing the first, but it also failed.
I have no idea how can I fix it. I think, I am either missing something and using the StringVar wrong, or there is something in my code that causes this behavior.
So, how can I fix it? And also, why it's happening?
EDIT: Removed the function definiton part. It wasn't really part of the question.
So, I finally understood the reason behind this behavior, thanks to #BryanOakley. Python was removing those unusued variables to free up memory. Because even though they are used in tkinter window, it doesn't really matter for Python, since the mainloop() is what organizing the window. So, it is necessary to save a reference of the variable somewhere, to prevent Python from removing it. And the easiest way of doing this, is saving the variable with self. Which won't get removed even after the class definiton ends.
Related
If I were to make a button or label that won't be called or re-configed anywhere else in the code should I make it a variable or just create that instance? Just instantiating it makes it more readable IMO and uses less memory (not that I have to think about it using python), but I see most people creating a instance variable. Which one is the "better" way of writing it (assuming I don't need to call it later)?
Button(root, text="Button").grid(row=0, column=0)
or
self.my_button = Button(root, text="Button")
self.my_button.grid(row=0, column=0)
I always assign widgets to variables. They aren't always instance variables; sometimes they are local variables. I'll use instance or global variables if I ever need to reference the widget outside of the function where it is defined.
In my opinion, tkinter code is much easier to understand when widgets are created and laid out in separate blocks and grouped together logically. When all of the layout code is in a block it's much easier to visualize the layout.
In other words, instead of this:
root = Tk()
Canvas(root, ...).grid(...)
toolbar = Frame(root,...).grid(...)
Label(toolbar,...).pack(...)
Button(toolbar, ...).pack(...)
... I prefer this:
toolbar = Frame(root, ...)
canvas = Canvas(root, ...)
app_icon = Label(toolbar,...)
save_button = Button(toolbar, ...)
toolbar.grid(...)
canvas.grid(...)
app_icon.pack(...)
save_button.pack(...)
I think it becomes much more clear that the toolbar and canvas are laid out together in the window, and that the label and icon are part of the toolbar.
I have a scale and an input field which both control the same variable to give the user choice of which one they'd like to use. I've coded it a bit like this:
def scale_has_moved(value):
entry_field.delete(0, END)
entry_field.insert(0, str(float(value)))
# Other functions I want the code to do
def entry_field_has_been_written(*args):
value = float( entry_field.get() )
scale.set(value)
This works, when I move the scale the entry_field gets written in and vice versa, and the other functions I want the code to do all happen. The obvious problem is the functions call each other in a loop, so moving the scale calls scale_has_moved() which calls the additional functions within and writes in the entry field, then because the entry field has been written in entry_field_has_been_written() gets called which in turn calls scale_has_moved() again, it doesn't go in an endless loop but it does everything at least twice everytime which affects performance.
Any clue how I'd fix this? Thank you
If you use the same variable for both widgets, they will automatically stay in sync. You don't need your two functions at all. The following code illustrates the technique.
import tkinter as tk
root = tk.Tk()
var = tk.IntVar(value=0)
scale = tk.Scale(root, variable=var, orient="horizontal")
entry = tk.Entry(root, textvariable=var)
scale.pack(side="top", fill="x")
entry.pack(side="top", fill="x")
root.mainloop()
I am trying to create a virtual keyboard using Tkinter and I am trying to do something
analogous to the code supplied below. I am having trouble with understanding the lambda construct in the code below, which from what I understand is the only way to go without having to write a button widget for every single letter that exists on the keyboard.
I have always understood that when you are using lambda, you do something analogous to:
variable = lambda x: x+5
but in the supplied code further down, the Button widget has a lambda I have not seen before written like this:
command=lambda value=text: select(entry, value)
I have spent all day reading about lambda, and I still cannot get this.
This is the code link:
How to call and close a virtual keyboard made by Tkinter using touchscreen display
Specifically this is the line I am having problems with:
tk.Button(window, text=text, width=width,
command=lambda value=text: select(entry, value),
padx=3, pady=3, bd=12, bg="black", fg="white", takefocus = False
).grid(row=y, column=x, columnspan=columnspan)
My problem was that i did not know you could use the default argument on lambda....i am surprised not many picked up how dumb i was though.
command=lambda value=text: select(entry, value)
value or value=text is the same thing if text is a variable. That was
my problem
I am trying to create a GUI as shown in the attached picture
I wrote the following code which does the job but not the way I need it to.
try:
import Tkinter as tk
import tkMessageBox as mb
except ImportError:
import tkinter as tk
import tkinter.messagebox as mb
root = tk.Tk()
root.geometry("500x300")
tk.Label(root, text="Python First GUI Template", bg="goldenrod", font="bold").pack()
tk.Label(root, text="").pack()
def addFn():
a = int(input('enter first number'))
b = int(input('enter second number'))
mb.showinfo('showinfo', a+b)
def subtractFn():
a = int(input('enter first number'))
b = int(input('enter second number'))
mb.showinfo('showinfo', a - b)
tk.Button(root, text="Add Function", bg="SkyBlue1", command=addFn).pack()
tk.Label(root, text="").pack()
tk.Button(root, text="Subtract Function", bg="SkyBlue1", command=subtractFn).pack()
root.mainloop()
So, I have the following problems:
(1) I am not able to create the design as I want in the attached picture in terms of relative color and relative location of "add" and "subtract" buttons.
(2) When I hit the buttons to activate "add" or "subtract" functions, the inputs are required on the console. I need a pop up with input box and drop down for two numbers I want to add. I am looking for following format for "add" function.
(3) I want to add a "quit" button to close the console when I am done
I'm new to this myself, and unfortunately can't answer most of your questions, but regarding the quit button, I'm thinking you can write a function that calls quit(), just like you would type in order to exit the Python interactive interpreter. Then you link that function to a button just as you did for the first two buttons. This is the same idea with a lambda expression:
from tkinter import *
root =Tk()
root.geometry("500x300")
Button(root,text="QUIT",bg='Red',command=lambda:(quit())).pack(side=BOTTOM)
root.mainloop()
This here is a TKinter frame that gives you a red quit button at the bottom whose sole reason for existence is to quit the frame it's in.
Regarding the layout, I think the pack method requires you to indicate where pack should prefer to put the widget, but doesn't give you much absolute control. Wouldn't grid method allow for better alignment?
Why do your input boxes have to pop out? Why can't they be embedded into the app frame? I would think that would eliminate some difficulty with the issue, no?
Sorry this isn't the most helpful answer ever... but I hope it gives you something to work with until someone more knowledgeable happens by. Cheers.
There are a couple of ways you could do this, the simplest being using .grid() instead of .pack():
from tkinter import *
root = Tk()
title = Label(root, text="Python First GUI Template")
add = Button(root, text="Add")
subtract = Button(root, text="Subtract")
_quit = Button(root, text="Quit")
title.grid(row=0, column=1, padx=5, pady=5)
add.grid(row=1, column=0, padx=5, pady=5)
subtract.grid(row=1, column=2, padx=5, pady=5)
_quit.grid(row=2, column=3, padx=5, pady=5)
root.mainloop()
.grid() allows you to place widgets on the window in a grid fashion, imagine there are cells which you are placing each widget into, whereas .pack() by default will place items stacked on top of eachother unless certain attributes are given values.
You could also use .place() which allows you to place the widgets based on coordinates but this requires a lot more effort make responsive to window size changes or adding new widgets and the like.
On a side note, Stack Overflow is not a free programming resource, we will not write programs for you based on a list of demands. There are plenty of freelance programmers who are happy to do that in exchange for money. I would recommend in future that rather than asking a question about an incredibly well documented library with over 17000 questions on Stack Overflow, a large number of which are about the difference in the three geometry managers you instead find a tutorial or ask a colleague, schoolmate, teacher or friend for help.
I can't figure out how to get tkinter to put checkbuttons and radiobuttons closer together. Even specifying pady=0 anyplace it seems valid has no effect. The vertical distance between the buttons is unnaturally large, ugly, and just wastes space. In order to make a set of buttons appear as a group separate from other controls requires that I add extra space elsewhere, which just gets out of control.
Here's a working example I extracted:
from Tkinter import *
def rbtest(frame):
group = LabelFrame( frame, text="Target", padx=0, pady=0)
btnVal = StringVar(frame,' ')
for b in ( "option1", "another option", "Someth Else", "go away"):
rb=Radiobutton( group, text=b, value=b, variable=btnVal)
rb.pack( anchor=W, pady=2)
boardname = StringVar()
Label( group, text="Name").pack( anchor=W)
Entry( group, text=boardname).pack()
group.pack( side=LEFT, fill=Y, padx=0, pady=0)
tk = Tk()
rbtest(tk)
tk.mainloop()
[Well, I can't post an image showing what it produces because I don't have enough reputation, so sorry about that... I tried.]
Edit: I'm using Python 2.6.6 and Windows 7.
Considering how closely together it packs other elements, I'm surprised this is normal behavior, but all the examples I've found on the web look similar...
I ran your code on a Mac and on a windows box, and in both cases it looked about right to me. If you want them literally as close as possible, set the borderwidth to zero and the pady option to zero.
To eek out another two pixels between each button, set the highlightthickness to zero, though that affects the user experience when they are using keyboard traversal.
It looks pretty normal to me:
If it really needs to be made super-compact, there are ways of doing so, but I suspect your system is rendering the window differently.