tkinter: can't enter into entry widget - python

I don't understand why the entry boxes under rackGUI.py in my code are static/won't allow anything to be entered. I believe all the Entry objects are instantiated correctly. I specified the textvariable as instances of the StringVar(). My gut tells me the problem lies in command argument in create_button instantiation but I'm not really sure why. I thought by setting command = lambda:function the function would not be called.
Upon clicking 'New' in the menu, main.py successfully calls rackGUI.create() which successfully calls input_form(). Clicking the button 'create_button' successfully calls drawRack which prints to the shell 'test'. I also added a test where I printed the type of value for each entry box i.e., print type(rack_name.get()) and this successfully returns type 'str'.
So again the main problem is that the entry box is static.
Below is my code:
config.py
"""
config.py
"""
import Tkinter as tk
import tkMessageBox as tkmb
#setup
root = tk.Tk()
root.title("TLA Database Tool")
frame = tk.Frame(height = 300, width = 250)
frame.pack()
main.py
#main.py
from config import *
import rackGUI
def createRackTemplate():
rackGUI.create()
def loadRackTemplate():
rackGUI.load()
menubar = tk.Menu(root)
filemenu = tk.Menu(menubar)
filemenu.add_command(label = "New", command = createRackTemplate)
filemenu.add_command(label = "Load", command = loadRackTemplate)
menubar.add_cascade(label = "File", menu = filemenu)
tkmb.showinfo("Welcome", "Under File click New to create a new rack template.\n\
Click load to load rack template.")
root.config(menu = menubar)
root.mainloop()
rackGUI.py
"""
rackGUI.py
"""
from config import *
def input_form():
form_frame = tk.Frame(frame)
form_frame.pack()
tk.Label(form_frame, text = "Rack Template Name (e.g., Knox Type 4)").pack()
rack_name = tk.Entry(form_frame, textvariable = tk.StringVar())
rack_name.pack()
tk.Label(form_frame, text = "Dimensions").pack()
tk.Label(form_frame, text = "#rack rows").pack()
num_rack_rows = tk.Entry(form_frame, textvariable = tk.StringVar())
num_rack_rows.pack()
tk.Label(form_frame, text = "#nodes per row").pack()
num_slots = tk.Entry(form_frame, textvariable = tk.StringVar())
num_slots.pack()
create_button = tk.Button(form_frame, text = "Create!",\
command = lambda: drawRack(rack_name, num_rack_rows, num_slots))
create_button.pack()
def drawRack(rack_name, num_rack_rows, num_slots):
print rack_name.get(), num_rack_rows.get(), num_slots.get()
def create():
input_form()
def load():
pass

For anyone who comes here after me, my solution ended up being
root.overrideredirect(True)
Working fine on Windows, but causing this text entry problem on Mac.

I actually found the problem there. The issue seems to be the focus of the windows, since you're using a messagebox.
In my script I just had put a root.update() before opening another window (in my case a filedialog) and everything worked fine. There's an already existing issue for that: https://bugs.python.org/issue42867#msg384785

Related

Problem when executing a script with two user interactions with Tkinker in Python

I made a script with two interactions with user. To do this, i use Tkinter. The first interaction is to get date while the second one is to get filename selection. The script runs fine for the first time execution. However, it seems that it hangs for the second run or more. I always need to restart Consoles in Spyder to make it runs again.
When i run in debug mode for the second run, it seems that the script stuck in root.mainloop() of the first user interaction. The script also runs fine for multiple executions if the second interaction to ask file selection is not there. Please find the code i wrote below. What could be the problem for this code?
def GetDate():
def my_function():
global date
date = my_entry.get()
root.destroy()
return()
root = tk.Tk()
my_label = tk.Label(root, text = "Enter date (YYYYMMDD): ")
my_label.grid(row = 0, column = 0)
my_entry = tk.Entry(root)
my_entry.grid(row = 0, column = 1)
my_button = tk.Button(root, text = "Submit", command = my_function)
my_button.grid(row = 1, column = 1)
root.mainloop()
GetDate()
tk.Tk().withdraw()
filenameDat = askopenfilename(initialdir = dayfold, filetypes=[("Dat", "*.xls*")], title = 'Open Input File')
'dayfold' is variable where the default initial directory will be prompted
It is because the previous instance of Tk() created by the line tk.Tk().withdraw() still exists in the second run, so root.mainloop() inside GetDate() will not return as there is still other instance of Tk().
You need to destroy the instance of Tk() as below:
...
root = tk.Tk()
root.withdraw()
filenameDat = askopenfilename(initialdir = dayfold, filetypes=[("Dat", "*.xls*")], title = 'Open Input File')
root.destroy() # destroy the root window

Python Tkinter GUI Automation

I want to get into GUI automation in order to run tests on my own program. The Program I want to test is written in Python and uses Tkinter for the GUI. The testing code though does not necessarily have to be in python, CPP would also be alright. I've done a bit of research and I am already facing a problem.
From my research, I have found that "Windows Application Driver" is a free way to test GUI. there's also "WinAppDriver UI Recorder" which seems convenient to use with it. Additionally the `Inspect.exe' program from (In my case) "C:\Program Files (x86)\Windows Kits\10\bin\10.0.19041.0\x86" is useful for getting information about the GUI elements.
Assuming I have a small python code like this (only for testing):
from Tkinter import *
import ttk
class Test():
def __init__(self):
self.root = Tk()
self.root.geometry("250x100")
self.text = StringVar()
self.text.set("Original Text")
self.buttonA = Button(self.root, textvariable=self.text)
self.buttonA.configure(text="test")
self.buttonB = Button(self.root,
text="Click to change text",
command=self.changeText
)
self.buttonA.pack(side=LEFT)
self.buttonB.pack(side=RIGHT)
self.root.mainloop()
def changeText(self):
self.text.set("Updated Text")
app=Test()
When running the code and inspecting buttonB with Inspect.exe the name I get as a result is "" (empty). what way is there to change that name to something informational and useful like in the calculator example, where the '7' button's name is "Seven". Which then is used in the tester like this:
self.driver.find_element_by_name("Seven").click()
which should look like this:
self.driver.find_element_by_name("buttonB").click()
for example in my case.
You can name tkinter widgets like:
self.buttonA = Button(self.root, textvariable=self.text,name = 'buttonA')
if WinAppDriver is not able to find tkinter widgets named this way. You can modify your code to invoke buttons (and get "hold" of other UI widgets) in a way that mimic UI automations frameworks:
I modified your sample to show how this can be done
from Tkinter import *
import ttk
def _widgets_by_name(parent,name,widgets):
if not parent.winfo_children():
if name == parent.winfo_name() :
widgets.append(parent)
else:
for child in parent.winfo_children():
_widgets_by_name(child,name,widgets)
def find_widget_by_name(parent,name):
''' ui automation function that can find a widget in an application/hierarchy of widgets by its name '''
widgets = []
_widgets_by_name(parent,name,widgets)
if len(widgets) == 0:
raise Exception(f'no widget named {name} found')
elif len(widgets) >1:
raise Exception(f'multiple widget named {name} found')
return (widgets[0])
class Test():
def __init__(self):
self.root = Tk()
self.root.geometry("250x100")
self.text = StringVar()
self.text.set("Original Text")
self.buttonA = Button(self.root, textvariable=self.text,name = 'button-a')
self.buttonA.configure(text="test")
self.buttonB = Button(self.root,
text="Click to change text",
command=self.changeText,
name = 'button-b'
)
self.buttonA.pack(side=LEFT)
self.buttonB.pack(side=RIGHT)
# self.root.mainloop() do not start the main loop for testing purpose
# can still be started outside of __init__ for normal operation
def changeText(self):
self.text.set("Updated Text")
app=Test()
# test the app step by step
# find one of the buttons and invoke it
find_widget_by_name(app.root,'button-b').invoke()
app.root.update() # replace the app mainloop: run the UI refresh once.
assert app.text.get() == "Updated Text"

OptionMenu widget not updating when StringVar is changed until hovered

I need a menu that can respond to items being clicked by running code then switch the text back to a default text.
Currently, my implementation works but the default text is only displayed when the cursor hovers over the menu after clicking.
I have searched but I could not find anything related to this problem, although maybe that is because I am unsure as to what exactly is causing this.
Here is the code to reproduce this behaviour:
from tkinter import *
root = Tk()
default_text = 'select an item'
def thing_selected(self, *args):
#other stuff happens here
var.set(default_text)
var = StringVar(root)
var.set(default_text)
var.trace('w', thing_selected)
menu = OptionMenu(root, var, *['Pizza','Lasagne','Fries','Fish'])
menu.pack()
root.mainloop()
Here is a gif representing the outcome:
I would expect the text at the top to be updated instantaneously, but it only updates when the cursor has hovered over the widget
I am looking for some way to trigger a hover event on the widget or I am open to suggestions for any other methods of accomplishing this.
You could take a different route and use the command attribute of the OptionMenu:
import tkinter as tk
root = tk.Tk()
default_text = 'select an item'
def thing_selected(selected):
#other stuff happens here
print(var.get())
var.set(default_text)
print(var.get())
var = tk.StringVar()
var.set(default_text)
options = ['Pizza','Lasagne','Fries','Fish']
menu = tk.OptionMenu(root, var, *options, command = thing_selected)
menu.pack()
root.mainloop()

Why does my tkinter window only work when created globably?

When I created this module I first made the tkinter window (all of its settings globally) it worked as intended. I could run the module and the window worked, taking the input from the entry field and displaying the welcome or error message. But when I put them into a function, it stopped working correctly, as shown.
How the window looks when created globally, with the button and input working:
https://gyazo.com/ffcb16416b8a971c09bfa60ee9367bbd
How it looks when created inside the function:
https://gyazo.com/c8858a2793befafa41e71d1099f021d3
The error message pops up straight away, then the main window with the entry field but not the button.
Here's the code where I created the window and settings inside a function:
def userSign(userEntry):
userId = userEntry.get()
if userId.isdigit() == True and len(userId) == 4:
welcomeWindow = tkinter.Tk()
welcomeWindow.title("Welcome")
welcomeWindow.geometry("200x50")
welcome = tkinter.Label(master=welcomeWindow, text="Welcome "+userId,font=("Helvetica", 18, "bold"))
welcome.grid()
welcomeWindow.mainloop()
else:
errorWindow = tkinter.Tk()
errorWindow.title("ERROR")
errorWindow.geometry("500x50")
error = tkinter.Label(master=errorWindow, text="ERROR: "+userId +" DOES NOT MEET CRITERIA", font=("Helvetica", 18, "bold"))
error.grid()
userId=""
errorWindow.mainloop()
def show():
window = tkinter.Tk()
window.title("Sign In")
window.geometry("250x100")
signInPrompt = tkinter.Label(master = window, text = "Enter your ID to sign in")
signInPrompt.grid(column=0,row=2)
userEntry = tkinter.Entry(master = window)
userEntry.grid(column=0,row=4)
enterButton = tkinter.Button(master = window, text="Sign in", command=userSign(userEntry))
enterButton.grid(column=0,row=6)
window.mainloop()
How do I get it so that my window works correctly when created inside functions as this module needs to be called by a different, main module.
You are creating two instances of Tk() which is a bad idea. Instead use Toplevel() for additional windows.
When you create variables or widgets inside a function the names are in the local scope and not available outside the function. And whan the function ends they will be garbage colletced.
Also, as #fhdrsdg points out, problems in the button command.

Window with tk to get text

I'm building a small educational app.
I already have all the code done, all I'm missing is a way is getting a window to open with TK displaying a textbox, an image and a button.
All it should do, it return the text inserted in the textbox after clicking the button and closing the windows.
So, how do I do this?
I have been looking at code, but nothing I did worked, I almost fell ashamed being this so basic.
Thanks
An easy way to write GUIs is using Tkinter. There's an example that display a windows with a text and a button:
from Tkinter import*
class GUI:
def __init__(self,v):
self.entry = Entry(v)
self.entry.pack()
self.button=Button(v, text="Press the button",command=self.pressButton)
self.button.pack()
self.t = StringVar()
self.t.set("You wrote: ")
self.label=Label(v, textvariable=self.t)
self.label.pack()
self.n = 0
def pressButton(self):
text = self.entry.get()
self.t.set("You wrote: "+text)
w=Tk()
gui=GUI(w)
w.mainloop()
You may look at the Tkinter documentation, the label widget also supports to include pictures.
Regards
This is a simple code that get the input from the inputBox to myText. It should get you started on the right direction. Depending on what else you need to check or do, you can add more functions to it. Notice you might have to play around with the order of the line image = tk.PhotoImage(data=b64_data). Because if you put it right after b64_data = .... It will gives you error. (I am running MAC 10.6 with Python 3.2). And the picture only works with GIF at the moment. See reference at the bottom if you want to learn more.
import tkinter as tk
import urllib.request
import base64
# Download the image using urllib
URL = "http://www.contentmanagement365.com/Content/Exhibition6/Files/369a0147-0853-4bb0-85ff-c1beda37c3db/apple_logo_50x50.gif"
u = urllib.request.urlopen(URL)
raw_data = u.read()
u.close()
b64_data = base64.encodestring(raw_data)
# The string you want to returned is somewhere outside
myText = 'empty'
def getText():
global myText
# You can perform check on some condition if you want to
# If it is okay, then store the value, and exist
myText = inputBox.get()
print('User Entered:', myText)
root.destroy()
root = tk.Tk()
# Just a simple title
simpleTitle = tk.Label(root)
simpleTitle['text'] = 'Please enter your input here'
simpleTitle.pack()
# The image (but in the label widget)
image = tk.PhotoImage(data=b64_data)
imageLabel = tk.Label(image=image)
imageLabel.pack()
# The entry box widget
inputBox = tk.Entry(root)
inputBox.pack()
# The button widget
button = tk.Button(root, text='Submit', command=getText)
button.pack()
tk.mainloop()
Here is the reference if you want to know more about the Tkinter Entry Widget: http://effbot.org/tkinterbook/entry.htm
Reference on how to get the image: Stackoverflow Question

Categories

Resources