Python and Tkinter - python

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.

Related

Why does ttk.Entry widget not update textvariable if called inside a function

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…

How to get a callback of when a tkinter text widget is changed?

I want to create a callback function that is gonna run when a text widget is modified. I've looked all over the internet and only came across how people did so with entry widgets and StringVar. I've also found out that a text widget doesn't have a textvariable attribute, so I can't use StringVar with it. So how can I can I detect when a text widget is modified?
I've found an answer to this shortly after I posted this question, so I thought I'd post an answer to help other people who encounter this problem. My solution:
from tkinter import *
def get_stringvar(event):
sv.set(text1.get("1.0", END))
text2.replace("1.0", END, sv.get())
root = Tk()
sv = StringVar()
text1 = Text(root)
text1.pack()
text1.bind('<KeyRelease>', get_stringvar)
text2 = Text(root)
text2.pack()
root.mainloop()
I basically bound the first text widget to the get_stringvar function and updated the StringVar in that function. Then, I displayed the first text widget's input in the second text widget

How can I combine this tkinter label and string into one window?

In a program I am working on there is a tkinter label/button which starts the card game (the program I am using) and a other window that has a string stating 'Welcome to the card game'.
Here is the tkinter section of the code:
import tkinter
window = tkinter.Tk()
print()
from tkinter import *
def quit():
global root
root.quit()
root = Tk()
while True:
label = tkinter.Label(window, text = "Welcome to the card game! (During name registration only use characters)").pack()
Button(root, text="Start Game", command=quit).pack()
root.mainloop()
However when I run the program they each appear in their own window screens when it would be more convenient for the user to have the options in one single window.
Is there anyway to merge them?
EDIT - (Having the button and text using root has fixed the problem.)
There is a lot going on here that should not be in such a small set of code.
Lets break it down.
First your imports. You are importing from tkinter multiple times. You only need to import once and you can use everything with the proper prefix. The preferred method is import tkinter as tk this way you don't overwrite any other imports or built in methods.
Next we need to get rid of one of your instances of Tk() as tkinter should only ever have one. For other windows use Toplevel().
In your quit function you do not need to define global as you are not assigning values here so the function will look in the global namespace for root.
Next Lets delete the empty print statement.
Next make sure both your label and button have the same container assigned to them. This is the reason why you are seeing them in different windows.
Next rename your function as quit is a built in method and should not be overwritten.
Lastly we remove the while statement as the mainloop() is already looping the Tk instance. You do not need to manage this yourself.
Here is what your code should look like (a 2nd window serves no purpose here):
import tkinter as tk
def root_quit():
root.quit()
root = tk.Tk()
tk.Label(root, text="Welcome to the card game! (During name registration only use characters)").pack()
tk.Button(root, text="Start Game", command=root_quit).pack()
root.mainloop()
Here is an example using Toplevel just so you can get an idea of how it is used.
import tkinter as tk
def root_quit():
root.quit()
def game_window():
top = tk.Toplevel(root)
tk.Button(top, text='exit', command=root_quit).pack()
root = tk.Tk()
tk.Label(root, text="Welcome to the card game! (During name registration only use characters)").pack()
tk.Button(root, text="Start Game", command=game_window).pack()
root.mainloop()

I cannot change the text of a Label

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.

In Tkinter how do i remove focus from a widget?

I'd like to remove focus from a widget manually.
You can focus to another dummy widget.
Edit
from Tkinter import *
def callback():
print master.focus()
master = Tk()
e = Entry(master)
e.pack()
e.focus()
b = Button(master, text="get", width=10, command=callback)
b.pack()
master.mainloop()
Focusing on a non-'focusable' widget will remove focus from another widget.
Set focus to another widget to remove focus from the target widget is a good idea. There are two methods for this: w.focus_set() and w.focus_force(). However, method w.focus_force() is impolite. It's better to wait for the window manager to give you the focus. Setting focus to parent widget or to the root window removes focus from the target widget.
Some widgets have takefocus option. Set takefocus to 0 to take your widget out of focus traversal (when user hits <Tab> key).
My solution is root.focus() it will remove widget focus.
If the dummy widget is Canvas then c.focus() will not work.
use c.focus_set() or c.tk.call('focus',c) to first focus on the canvas window itself.
That's because
c.focus()
... returns the id for the item that currently has the focus, or an empty string if no item has the focus. Reference
c.focus(id_) will focus on the item having id id_ within the canvas.
c.focus("") will remove the focus from any item in the canvas.
Hence (within some callback)
c.config(highlightthickness = 0) # to remove the highlight border on focus
c.foucs_set()
c.focus("") # just to be sure
The reason c.focus() functions differently is that within Tcl/Tk's Commands there's the "Primary" Command focus
as well as the Canvas-specific Widget Command focus
That's not an issue within the Tcl/Tk syntax but in the tkinter module c.focus() will call the underlying canvas-specific foucs.
From tkinter.py within the Canvas class Line 2549
def focus(self, *args):
"""Set focus to the first item specified in ARGS."""
return self.tk.call((self._w, 'focus') + args)
So the question may be a duplicate here, but the answer from #Bryan Oakley works perfectly for me in Python 3.8
root.focus_set()
Too easy...
If you use ttk widgets you can "remove" the focus ring by removing the color; for example on a button:
style = ttk.Style()
style.configure('TButton', focuscolor='')

Categories