I'm trying to source out code for a GUI program. I made a simple test and I cannot change the text value on the GUI, no error and nothing happens. Some issue with the mainloop of Tkinter?
serial.py:
import gui
gui.chiplabel.config(text="A.3f V" )
gui.py:
from Tkinter import *
root = Tk()
chiplabel = Label(root, relief=RIDGE, width = 9 , text ="Unknown",
padx=0, pady=0).grid(row = 0,column=5, sticky =W)
root.mainloop()
You have two main problems with your code. It needs to be restructured, and you're making a very common mistake with laying out your widgets.
Organizing your code
The way you have your code structured, your call to configure happens after mainloop exits, and after the widgets have been destroyed. You need to reorganize your code so that the call to mainloop is the last line of code that is executed.
In my opinion this is best accomplished by using classes and objects, but that's not strictly necessary. You simply need to not have any code after you call mainloop.
Laying out the widgets
The problem is this line:
chiplabel = Label( root, relief=RIDGE, width = 9 , text ="Unknown", padx=0, pady=0).grid(row = 0,column=5, sticky =W)
In python, when you do x=y().z(), x is given the value of z(). So, when you do chiplabel = Label(...).grid(...), chiplabel is given the value of grid(...). Grid always returns None, so chiplabel will always be None. Because of this, you can't reconfigure it because you've lost the reference to the widget.
The solution is to create the widget and lay out the widget in two steps.
One way to do this would be to create the UI in a class, e.g.:
import Tkinter as tk # note don't use wildcard imports
class GUI(tk.Tk):
def __init__(self):
tk.Tk.__init__(self)
self.chiplabel = tk.Label(
self,
padx=0,
pady=0,
relief=tk.RIDGE,
text="Unknown",
width=9,
) # note alphabetical order and consistent spacing
self.chiplabel.grid(
column=5,
row=0,
sticky=tk.W,
) # note grid is separate step
and don't run it in-place, so that you can import the class without running anything. Then your serial.py looks more like:
from gui import GUI
interface = GUI()
interface.chiplabel.config(text="A.3f V")
interface.mainloop()
If you want multiple frames, you could do something like Switching between frames in tkinter menu.
Related
I am trying to understand a strange behavior displayed by the ttk.Entry widget. If I declare the Entry widget textvariable inside a function and call that function from main (where I create a root object and run my mainloop), for some reason the textvariable is not displayed. However if I run root.mainloop() within my function as well then the textvariable is displayed properly. Why is that? Why do I need to run mainloop() again inside my function as well when its already being run in main?
In the below code first run as is and observe Entry widget remains empty and then uncomment the root.mainloop() line within the function and then run it again - this time Entry widget textvariable will be displayed. Thank You!
from tkinter import Tk, Toplevel, StringVar, Label
from tkinter import ttk
nameVariable = 'EntryBoxTextValue'
def frameWindow(root, nameVariable):
mainFrame = Toplevel(root)
mainFrame.grid()
nameLbl = Label(mainFrame, text="Name:", bg="white")
nameLbl.grid(row=0, column=0, sticky='w', pady=(10,5))
nameSV = StringVar()
nameSV.set(nameVariable)
nameEntry = ttk.Entry(mainFrame, textvariable=nameSV)
nameEntry.grid(row=0, column=1, columnspan=4, sticky='ew', pady=(10,5))
print(nameSV)
# root.mainloop()
if __name__ == '__main__':
root = Tk()
frameWindow(root, nameVariable)
root.mainloop()
Note that other widgets like ttk.Treeview and Text update without needing to run root.mainloop() inside the function call.
It might seem a little weird at first, but I think it is the same reason as to why tkinter image does not show up when made inside a function, without keeping any reference.
So the problem might be that garbage collector sweeps nameSV once the function finishes executing because it is no longer used anywhere, but the tk widget is still using it.
The solution is simple, just like the image issue, make the variable global or keep some other reference to it.
def frameWindow(root, nameVariable):
global nameSV
...
...
Or define it in the global scope itself:
if __name__ == '__main__':
root = Tk()
nameSV = StringVar() # Remove this line from inside the function
frameWindow(root, nameVariable)
root.mainloop()
If you wish to see the problem yourself, you can freeze the script before the function ends and force the events to be processed and see for yourself:
import time
...
...
def frameWindow(root, nameVariable):
mainFrame = Toplevel(root)
root.withdraw() # Hide root, to properly see the mainFrame window without root covering it
nameLbl = Label(mainFrame, text="Name:", bg="white")
nameLbl.grid(row=0, column=0, sticky='w', pady=(10,5))
nameSV = StringVar()
nameSV.set(nameVariable)
nameEntry = ttk.Entry(mainFrame, textvariable=nameSV)
nameEntry.grid(row=0, column=1, columnspan=4, sticky='ew', pady=(10,5))
root.update() # To draw the widget to the screen
time.sleep(5)
root.deiconify() # Bring the window back to close the root window and end the process
...
...
For the first 5 seconds you can notice the entry filled with the correct data, but as soon that is over and root pops up, the entry goes to become blank.
Explanation in the case of image made inside a function, can be also used to explain the situation here(from effbot.org):
The problem is that the Tkinter/Tk interface doesn’t handle references to Image objects properly; the Tk widget will hold a reference to the internal object, but Tkinter does not. When Python’s garbage collector discards the Tkinter object, Tkinter tells Tk to release the image. But since the image is in use by a widget, Tk doesn’t destroy it. Not completely. It just blanks the image, making it completely transparent…
I am wanting to create a tkinter window where when I click a button widget it opens a new window, showing all the widgets, exactly the same, from the root/original window. Essentially creating a second instance of the root window, where the application can have multiple users, using the same GUI, in different windows.
Any help is appreciated.
An example of one of my widgets:
summary_output = Text(
master=window,
height=8,
width=78,
bg="gray95",
borderwidth=2,
relief="groove",
font=("Arial", 12))
My window layout
window = Tk()
window.title("Data Viewer")
window.geometry("750x950")
window.configure(bg='white')
window.iconphoto(False, tk.PhotoImage(file='icon.png'))
I have this but cant seem to place the widgets from the root window:
def new_window():
newWindow = Toplevel(window)
newWindow.geometry("750x950")
newWindow.configure(bg='white')
newWindow.iconphoto(False, tk.PhotoImage(file='icon.png'))
upload_button.place(x=20, y=560)
mainloop()
Is their anyway to change the master to be any window?
Edit:
from tkinter import *
class StaticFrame(Frame):
def __init__(self,master,*args,**kwargs):
Frame.__init__(self,master,*args,**kwargs)
# All your widgets
Label(self,text='This is a reusable frame',font=(0,17)).place(x=0, y=0)
Button(self,text='Click me for nothing').pack()
Label(self,text='End of page').pack()
upload_button = Button(
self,
text="Edit Data",
fg="DodgerBlue4",
font=("Graph Type", 15),
height=1, width=12,
borderwidth=2,
relief="groove")
upload_button.place(x=20, y=50)
root = Tk() # First window
top = Toplevel(root) # Second window
root.geometry("750x968")
StaticFrame(root).pack() # Put the frame on the first window
StaticFrame(top).pack() # Put the frame on the second window
root.mainloop()
Result:
The concept used here is simple, create a "custom frame" that we will put onto these new windows, so that it will create the exact same frame, and widgets within it, inside different windows.
from tkinter import *
class StaticFrame(Frame):
def __init__(self,master,*args,**kwargs):
Frame.__init__(self,master,*args,**kwargs)
# All your widgets
Label(self,text='This is a reusable frame',font=(0,17)).pack()
Button(self,text='Click me for nothing').pack()
Label(self,text='End of page').pack()
root = Tk() # First window
top = Toplevel(root) # Second window
StaticFrame(root).pack() # Put the frame on the first window
StaticFrame(top).pack() # Put the frame on the second window
root.mainloop()
Very simple to code and has been explained with comments, if you do not know what classes and inheritance is then first do go through those. There are variety of other methods that come onto mind when I read this question, like even having an option database and storing the widgets in a list and recreating it based on its order, but this seems to be the easiest in a scratch.
Hi I didn't really understand how furas made the below code work. Why didn't he get an error message about grid and pack on the same root when he added a box? In the addbox function he sets a frame to the root which is pack already and even uses the pack inside the function and then uses the grid.
Can someone please explain to me how this "magic" works?
a link to the his answer:
Creating new entry boxes with button Tkinter
from Tkinter import *
#------------------------------------
def addBox():
print "ADD"
frame = Frame(root)
frame.pack()
Label(frame, text='From').grid(row=0, column=0)
ent1 = Entry(frame)
ent1.grid(row=1, column=0)
Label(frame, text='To').grid(row=0, column=1)
ent2 = Entry(frame)
ent2.grid(row=1, column=1)
all_entries.append( (ent1, ent2) )
#------------------------------------
def showEntries():
for number, (ent1, ent2) in enumerate(all_entries):
print number, ent1.get(), ent2.get()
#------------------------------------
all_entries = []
root = Tk()
showButton = Button(root, text='Show all text', command=showEntries)
showButton.pack()
Thanks
There's no magic, it's just working as designed. The code uses pack in the root window, and uses grid inside a frame. Each widget that acts as a container for other widgets can use either grid or pack. You just can't use both grid and pack together for widgets that have the same master.
not really an answer but I think you will be helped by the link.
tkinter and it's layout is indeed a bit hard to understand.
I never understood how to deal with it until I stumbled over this presentation which explained the layout particulars in a way where I finally could get the hang of it.
Just putting it out there for others to find as well.
tkinter tutorial by beazley
I think you miss out on what pack and grid actually are. Consider such code:
import tkinter as tk
root = tk.Tk()
myFrame = tk.Frame(root)
myFrame.pack()
myButton1 = tk.Button(myFrame, text='This is button 1')
myButton2 = tk.Button(myFrame, text='This is button 2')
myButton1.grid(row=0, column=0)
myButton2.grid(row=1, column=0)
root.mainloop()
By creating root we create a new window. In this window we will put everything else. Then we create myFrame. Note, that the actual "thing" (in more adequate terms - widget) is created in line myFrame = tk.Frame(root). Note, that we have to specify where we are going to put this widget in brackets and we've written that it is going to be root - our main window. Blank frame probably isn't the best example since you can not see it being placed (not unless you use some more specifications at least), but still. We have created it, but not placed it in our user interface. The we use .pack() to place it. Now you refer to widgets as being used as packs or grids. That is not true though. Pack and grid are just the set of rules, on which the widgets are being placed inside some kind of window. Because of that, if you want to add something more to the root in our case, you will have to use .pack() again. Why? If you will give two sets of rules on how to place things on the screen for your computer - they will most likely conflict with each other. However, if we go one more level down and now want to place something inside our myFrame, we can again choose which set of rules to use. It is because it does not matter, where our frame is going to end up inside root, we now just want to specify where our Buttons 1 and 2 are going to end up inside the frame. Therefore we can again use .pack() or switch to .grid().
To conclude: .pack(), .grid() and .place() are sets of rules on how place widgets inside other widgets. In more general terms though these are rules on how place boxes in other boxes. One boxes in which we arrange other boxes can only have one set of rules.
I hope this example helps.
I am not sure if this is the right place to ask such a question but being a new wannabe Python developer i want any one to please explain Python`s Tkinter small program to me.
Program:
from Tkinter import *
master = Tk()
e = Entry(master)
e.pack()
root = Tk()
text = Text(root)
e.focus_set()
text.pack()
def callback():
text.insert(INSERT, e.get())
b = Button(master, text="Start", width=10, command=callback)
b.pack()
mainloop()
root.mainloop()
This program is working perfectly but following the documentation here i am still confused about few things.
What is Enter(master)
e.pack()
Text(root)
text.pack()
mainloop()
root.mainloop()
Thanks !!!
Entry(master) defines an entry box inside the parent window master.
e.pack defines the location in the parent window that e will appear- if we do not set its geometry it will not appear. There are several geometry managers, but pack() just fits it in wherever there is space.
Text(root) defines a simple Text widget in which... well, you store text in it. It is using the Tk() window root as its parent.
text.pack() same as 2, only with the Text widget, not an Entry one.
mainloop() Will start the event loop of a previously defined Tk() window. (See below).
root.mainloop() starts the event loop of the Tk() window root. i.e. You're blocking your main program until something happens on the UI that will trigger events.
If you think about master, from the line:
master = Tk()
as representing a kind of master 'cavity' into which all subsequent widgets will fit
e = Entry(master)
This line assigns to e, an instance of the Entry widget class. This widget will go inside master.
e.pack()
this line actually 'packs' e inside master
with text = Text(root) and text.pack(), essentially the same thing is happening, except that root is now the 'cavity' into which the Text widget is being 'packed'. I have never seen two different variables be assigned to Tk() in a program like this before, but I don't have a whole lot of experience, so this might be alright.
I suggest that for these and all the rest you read the following, it really helped me to understand tkinter:
Thinking in Tkinter
And this site is a great Tkinter reference and introduction to the basics including widgets like Entry and Text:
http://effbot.org/tkinterbook/
What is Enter(master)
as per the documnetation you're giving:
Entry(master=None, **options) (class) [#]
A text entry field.
so basically, you're binding a Text entry field to the UI, i.e. the Tk() instance you assigned to the master variable.
e.pack()
when you "pack" a widget, you actually bind it to the geometry manager that takes care of positioning it into the UI.
Text(root)
now you do the same with a multiline input
text.pack()
and again pack it to the geometry manager.
mainloop()
root.mainloop()
now you're calling the event loops for both UIs, i.e. you're blocking your main program until something happens on the UI that will trigger events. The only thing is that the second call will not work until the first window is closed, as the first one is blocking the main application thread until you close the window.
I'm adding several widgets to a Frame which is located in a tix.NoteBook. When there are too much widgets to fit in the window, I want to use a scrollbar, so I put tix.ScrolledWindow inside that Frame and add my widgets to this ScrolledWindow instead.
The problem is that when using the grid() geometry manager, the scrollbar appears, but it is not working (The drag bar occupies the whole scroll bar).
from Tkinter import *
import Tix
class Window:
def __init__(self, root):
self.labelList = []
self.notebook = Tix.NoteBook(root, ipadx=3, ipady=3)
self.notebook.add('sheet_1', label="Sheet 1", underline=0)
self.notebook.add('sheet_2', label="Sheet 2", underline=0)
self.notebook.add('sheet_3', label="Sheet 3", underline=0)
self.notebook.pack()
#self.notebook.grid(row=0, column=0)
tab1=self.notebook.sheet_1
tab2=self.notebook.sheet_2
tab3=self.notebook.sheet_3
self.myMainContainer = Frame(tab1)
self.myMainContainer.pack()
#self.myMainContainer.grid(row=0, column=0)
scrwin = Tix.ScrolledWindow(self.myMainContainer, scrollbar='y')
scrwin.pack()
#scrwin.grid(row=0, column=0)
self.win = scrwin.window
for i in range (100):
self.labelList.append((Label(self.win)))
self.labelList[-1].config(text= "Bla", relief = SUNKEN)
self.labelList[-1].grid(row=i, column=0, sticky=W+E)
root = Tix.Tk()
myWindow = Window(root)
root.mainloop()
Whenever I change at least one of the geometry managers from pack() to grid(), the problem occurs. (Actually, I'd prefer using grid() for all containers.)
When I don't use the NoteBook widget, the problem does not occur either. The other examples here all seem to rely on pack().
Any ideas?
Many thanks,
Sano
I solved it without using ´tix.scrolledWindow´. Instead, I went for the autoscrollbar suggested by Fred Lundh here.
The main problem was the adaption to the NoteBook widget. First, I tried to put the scrollbar to the root, so that they would surround the whole window. Now, I wanted to change the hook for the scrollbar whenever I changed a tab, but the ´raisecmd´ of the Notebook did not work. Next, I thought of using the configure event on each tab - whenever a new tab is raised, its size changes and configure is called.
Well, after much trying without ever being satisfied I changed my approach and put the scrollbars inside of the tabs. The tabs and all subcontainers must get the ´grid_columnconfigure(0, weight=1)´ and ´grid_rowconfigure(0, weight=1)´ settings, or else they will not grow with the tabs.