I'm new to python programming, and I have a bit of trouble with the program structure:
When I make my GUI in the main python part then the code works:
import tkinter as tk
root = tk.Tk()
root.overrideredirect(True)
root.geometry("800x480")
def cb_Gebruiker():
btnUser["text"]= "changed"
btnUser = tk.Button(root, text="User",command = cb_Gebruiker)
btnUser.place(x=1,y=1,width="300",height="73")
root.mainloop()
When I make my GUI in a function, the btn variable is local, so this doesn't work
def MakeBtn():
btnUser = tk.Button(root, text="User",command = cb_Gebruiker)
btnUser.place(x=1,y=1,width="300",height="73")
def cb_Gebruiker():
btnUser["text"]= "changed"
MakeBtn()
root.mainloop()
Now I have a program that's rather large and I want my GUI in a separate file, but then I can't access my GUI components...
And I can't seem to be able to find a tutorial on how to structure a program (python has to many possibilities: script, module, object oriented,..)
How do I solve this?
You will need a lambda to delay the call to the command with a parameter.
def MakeBtn():
btnUser = tk.Button(root, text="User", command = lambda: cb_Gebruiker(btnUser))
btnUser.place(x=1, y=1, width="300", height="73")
def cb_Gebruiker(btnUser):
btnUser["text"] = "changed"
MakeBtn()
root.mainloop()
Related
I have been searching for a solution for this issue, but I can't seem to find a viable answer.
I have the following code to open another tkinter script on button click:
# Program to Open on Button Click
def tkinter1():
ret = os.system('python C:\filepath\new_script.py"')
if ret:
# record updated, reload data
treeview_preview()
b1 = Button(master, text="New Window", command=tkinter1)
My problem I am facing is that I want the current window to close on the button click and only keep the new window open.
I know it is possible. I have this instance with many different windows and I seem to be stuck.
The unfortunate thing is that I have an entire different script for different parts of the beta software and the only way I could successfully run all of them is to access them as stated above.
I tried using the if ret: exit() command at the end with the same result. I have windows opening over and over again.
It seems simple, but I haven't been programming tkinter script for too long.(I still have a lot to learn)
All help is appreciated.
You can use master.withdraw(). I
made your code runnable with a few lines. Ok lets say your fixed main code is this:
import tkinter as tk
from tkinter import *
from seccond import *
from multiprocessing import Process
import os
def main():
master = tk.Tk()
# Program to Open on Button Click
def tkinter1():
master.withdraw()
master.destroy()
ret = Process(target=treeview_preview())
ret.start()
#if ret:
# # record updated, reload data
# treeview_preview()
#commented out bc i don't know what it is
b1 = Button(master, text="New Window", command=tkinter1)
b1.pack()
master.mainloop()
if __name__ == '__main__':
main()
the secondary code name seccond.py in the same folder as the main code is called as an external function but also as a separate process.
import tkinter as tk
def treeview_preview():
root = tk.Tk()
S = tk.Scrollbar(root)
T = tk.Text(root, height=4, width=50)
S.pack(side=tk.RIGHT, fill=tk.Y)
T.pack(side=tk.LEFT, fill=tk.Y)
S.config(command=T.yview)
T.config(yscrollcommand=S.set)
quote = """this is the seccond window."""
T.insert(tk.END, quote)
tk.mainloop()
The code will remove the window but not the terminal box as it will use it for the second script.
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"
I created a couple of GUIs using tkinter. But now I am interested in combining them into one caller GUI. So the caller GUI would have buttons that, when clicked, would open the other GUIs. However, I cannot get it to work. I've done the imports correctly (I think), edited the main functions in the subGUIs, and added the command=GUI.main in my buttons. I get it to load but I get errors about missing files...but when I run a GUI by itself it works fine.
In my research, I read that there can only be one mainloop in a Tkinter program. Basically, I cannot use a Tkinter GUI to call another Tkinter GUI. Do you know what I can do different, for instance, can I create the caller GUI using wxPython and have it call all other GUIs that use Tkinter?
Thank you!
You can't "call" another GUI. If this other GUI creates its own root window and calls mainloop(), your only reasonable option is to spawn a new process. That's a simple solution that requires little work. The two GUIs will be completely independent of each other.
If you have control over the code in both GUIs and you want them to work together, you can make the base class of your GUI a frame rather than a root window, and then you can create as many windows as you want with as many GUIs as you want.
For example, let's start with a simple GUI. Copy the following and put it in a file named GUI1.py:
import tkinter as tk
class GUI(tk.Frame):
def __init__(self, window):
tk.Frame.__init__(self)
label = tk.Label(self, text="Hello from %s" % __file__)
label.pack(padx=20, pady=20)
if __name__ == "__main__":
root = tk.Tk()
gui = GUI(root)
gui.pack(fill="both", expand=True)
tk.mainloop()
You can run that GUI normally with something like python GUI1.py.
Now, make an exact copy of that file and name it GUI2.py. You can also run it in the same manner: python GUI2.py
If you want to make a single program that has both, you can create a third file that looks like this:
import tkinter as tk
import GUI1
import GUI2
# the first gui owns the root window
win1 = tk.Tk()
gui1 = GUI1.GUI(win1)
gui1.pack(fill="both", expand=True)
# the second GUI is in a Toplevel
win2 = tk.Toplevel(win1)
gui2 = GUI2.GUI(win2)
gui2.pack(fill="both", expand=True)
tk.mainloop()
Depending on your OS and window manager, one window might be right on top of the other, so you might need to move it to see both.
Thank you for the ideas. At first, your code wouldn't print the text on the toplevel window. So I edited it a little and it worked! Thank you. GUI1 and GUI2 look like:
import tkinter as tk
def GUI1(Frame):
label = tk.Label(Frame, text="Hello from %s" % __file__)
label.pack(padx=20, pady=20)
return
if __name__ == "__main__":
root = tk.Tk()
GUI1(root)
root.mainloop()
And then the caller looks like this:
from tkinter import *
import GUI1
import GUI2
def call_GUI1():
win1 = Toplevel(root)
GUI1.GUI1(win1)
return
def call_GUI2():
win2 = Toplevel(root)
GUI2.GUI2(win2)
return
# the first gui owns the root window
if __name__ == "__main__":
root = Tk()
root.title('Caller GUI')
root.minsize(720, 600)
button_1 = Button(root, text='Call GUI1', width='20', height='20', command=call_GUI1)
button_1.pack()
button_2 = Button(root, text='Call GUI2', width='20', height='20', command=call_GUI2)
button_2.pack()
root.mainloop()
So for some reason the scale function in tkinter doesn't want to output the number on the scale. All i receive is either 0.0 or nothing. It seems to be to do with the GUI and calling functions through the GUI. Written and run in python 3.4.
from tkinter import *
from tkinter import ttk
from tkinter import messagebox
iterations=30
def settings():
global itervar, iterscale
sGui = Tk()
itervar = DoubleVar()
iterscale = Scale(sGui, orient="horizontal", from_=1, to=1000, variable=itervar)
iterscale.pack()
iterbutton = Button(sGui, text="Save Settings", command=saveSettings)
iterbutton.pack()
sGui.mainloop()
def saveSettings():
global iterations
iterations = itervar.get()
print(iterations)
def doNothing():
pass
def main():
global root, version
root= Tk()
menu = Menu(root)
root.config(menu=menu)
fileMenu = Menu(menu)
menu.add_cascade(label="File", menu=fileMenu)
fileMenu.add_command(label="Quit", command=quit)
benchmarkMenu = Menu(menu)
menu.add_cascade(label="Benchmark", menu=benchmarkMenu)
benchmarkMenu.add_command(label="Run [All]", command=doNothing)
benchmarkMenu.add_separator()
benchmarkMenu.add_command(label="Settings", command=settings)
root.mainloop()
#Main
main()
I have tried the functions settings and saveSettings on their own and they work fine, but when i call it through the GUI it doesn't seem to work.
Any ideas on the problem, my only solution would be have the settings function and saveSettings function in a different file and then run that file externally with os.startfile("etc...")
Minimal fix: change this
itervar = DoubleVar()
to this:
itervar = DoubleVar(sGui)
Because you have two root applications (root and sGui are both instances of Tk) the implied parent widget for itervar is the first one created, being root so tkinter gets confused when you specify it as the variable for a completely different application.
But I would highly recommend using a Toplevel instance to keep the windows a part of the same program:
sGui = Toplevel(root)
...
#sGui.mainloop() #no longer need this
although if you want to be able to run the setting window without the main one you might consider making all your visible windows Toplevels and make the actual root hidden:
# I'm not sure if you want to call it this
abs_root = Tk() # but you are already using root
abs_root.withdraw() #hide the window
Then make root = Toplevel(abs_root)
You coud phase out the variable all together by using .geting the scale directly:
iterations = iterscale.get()
from Tkinter import *
import webbrowser
root = Tk()
frame = Frame(root)
frame.pack()
url = 'http://www.sampleurl.com'
def OpenUrl(url):
webbrowser.open_new(url)
button = Button(frame, text="CLICK", command=OpenUrl(url))
button.pack()
root.mainloop()
My goal is to open a URL when I click the button in the GUI widget. However, I am not sure how to do this.
Python opens two new windows when I run the script without clicking
anything. Additionally, nothing happens when I click the button.
You should use
button = Button(root, text="CLCK", command=lambda aurl=url:OpenUrl(aurl))
this is the correct way of sending a callback when arguments are required.
From here:
A common beginner’s mistake is to call the callback function when
constructing the widget. That is, instead of giving just the
function’s name (e.g. “callback”), the programmer adds parentheses and
argument values to the function:
If you do this, Python will call the callback function before creating
the widget, and pass the function’s return value to Tkinter. Tkinter
then attempts to convert the return value to a string, and tells Tk to
call a function with that name when the button is activated. This is
probably not what you wanted.
For simple cases like this, you can use a lambda expression as a link
between Tkinter and the callback function:
Alternatively, you don't have to pass the URL as an argument of the command. Obviously your OpenUrl method would be stuck opening that one URL in this case, but it would work.
from Tkinter import *
import webbrowser
url = 'http://www.sampleurl.com'
root = Tk()
frame = Frame(root)
frame.pack()
def OpenUrl():
webbrowser.open_new(url)
button = Button(frame, text="CLICK", command=OpenUrl)
button.pack()
root.mainloop()