How to call a function from a class that needs "root" - python

I am trying to run a script in python that is using multiprocessing. Everything works ok, but I want it to make a bit more complex. In the example below, I don't know how to call a function from the class that needs the root as an argument as the root is defined in another function. I would appreciate some help or advice.
The code that I am using is the following one:
import tkinter as tk, time
from multiprocessing import Process
from tkinter import filedialog, ttk
class Example():
def __init__ (self, parent)
self.parent = parent
canvas = tk.Canvas(self.parent)
self.button = tk.Button(self.parent, command = self.onStart)
self.pbar = ttk.Progressbar(self.parent, mode = 'indeterminate', length = 100)
def pbar_start(self):
self.pbar.pack()
self.pbar.start()
def files(self):
self.filena = filedialog.askopenfilenames(initialdir="/", title = "Select file", filetypes = (("comma separated file","*.csv"), ("all files", "*.*")))
return filena
def onStart():
self.p1 = Process(target = work)
self.p1.start()
def work():
time.sleep(5) # I put time just to show that I don't want the progressbar to start immediately
app = Example(root) # here if I call root is giving me an error as expected, but I don't know how I can do it
app.pbar_start()
file = app.filena()
#do some work
def main():
root = tk.Tk()
app = Example(root)
root.mainloop()
if __name__ == "__main__":
main()
I have tried to place work under the main but it didn't work because when I tried to click the button another window popped up.
Thank you for your help.

Related

Passing on an object from one class to another class that is called with it's main() function

My program starts with a 'Loading_UI' class that shows a simple loading bar and creates an object that contains data fetched from .csv files.
When the loading (= data fetching) is completed, the loading bar destroys itself (root.destroy()) and then I want to pass on the object that I created to another class that will show the main UI of the program. To keep things neat, I want to call the main UI from its own .py file and own Main_UI class.
Using the idea above, my question is: how can I pass the object on from the Loading_UI class to the Main_UI class?
(Run the program from RUN_loading.py)
(DataBase.py) Simple object creation:
class DataBase:
def __init__(self, name):
self.name = name
def show_content(self):
print(self.name)
And in the other .py file with class Loading_UI, something like this:
(RUN_Loading.py) UI with loading bar simulation:
from tkinter import *
from DataBase import DataBase
class Loading_UI:
def __init__(self, master):
self.master = master
self.DataBase = DataBase("This is our object.")
self.load_pct = DoubleVar()
self.load_pct.set(0)
loading_bar = Label(textvariable = self.load_pct)
loading_bar.grid()
self.handle_loading_progress()
def handle_loading_progress(self):
if self.load_pct.get() < 10:
self.load_pct.set(self.load_pct.get() + 1)
self.master.after(200, self.handle_loading_progress)
else:
self.show_main()
def show_main(self):
root.destroy()
from UI_Main import Main_UI
Main = Main_UI(self.DataBase)
Main.main()
root = Tk()
root.geometry("100x100+300+300")
app = Loading_UI(root)
root.mainloop()
And the main UI which would need to process the object would look like this.
(UI_Main.py) Main UI Window that pops up after loading is complete.
from tkinter import *
class Main_UI:
def __init__(self, database_object):
self.DataBase = database_object
# Run this function to check that the
# data object is properly received.
self.DataBase.show_content()
root = Tk()
app = Main_UI(root, database_object)
root.mainloop()
if __name__ == '__main__':
main()
The above is obviously wrong, but I just want to present the idea what I am after. Running the code above gives:
NameError: name 'database_object' is not defined
EDIT: Edited the code with a practical (simplified) example.
The best way to do this might be to create a Main_UI object in Loading_UI file.
Loading_UI.py:
import Main_UI
# Create your progress bar
data = getDataFromCSV('file.csv')
# Delete your progress bar
main_ui = Main_Ui(data)
I'm no expert in tkinter but I've researched its documentation a little bit and this is how I'd do it.
Tkinter(as pygtk for that matter) has the concept of events and signals that are fired upon them. You can bind an event in any tkinter widget (such as a progressbar) using the bind function and have a callback function that will act on this. Apparently there's a predefined list of tkinter events that are available and in your case I think the <Destroy> A widget is being destroyed. will be of use.
So in your loading_ui.py file:
widget = ttk.Progressbar(parent, option=value, ...) # create your progressbar
#Bind it to the destroy event. Once destroyed call the proceed_with_main function and pass your object
widget.bind('<Destroy> ', lambda event, db=self.DataBase:
self.proceed_with_main(db))
def proceed_with_main(db):
from UI_Main import Main_UI
Main = Main_UI(db)
Main.main()
Ok multiple things are not done right here. Corrections are posted in the code directly.
(Run_loading.py)
from tkinter import *
from DataBase import DataBase
class Loading_UI:
def __init__(self, master):
self.master = master
self.DataBase = DataBase("This is our object.")
self.load_pct = DoubleVar()
self.load_pct.set(0)
loading_bar = Label(textvariable = self.load_pct)
loading_bar.grid()
self.handle_loading_progress()
def handle_loading_progress(self):
if self.load_pct.get() < 10:
self.load_pct.set(self.load_pct.get() + 1)
self.master.after(200, self.handle_loading_progress)
else:
self.show_main()
def show_main(self):
root.destroy()
from UI_Main import Main_UI
Main = Main_UI(self.DataBase)
# Main.main() This line is wrong, your object Main_UI() does not have a defined 'main()' function
root = Tk()
root.geometry("100x100+300+300")
app = Loading_UI(root)
root.mainloop()
(UI_Main.py)
from tkinter import *
class Main_UI:
def __init__(self, database_object):
self.DataBase = database_object
# Run this function to check that the
# data object is properly received.
self.DataBase.show_content()
self.tkWindow = Tk() # if you want to keep an new window you could keep this
self.tkWindow.mainloop()
""" You shouldn't do things outside of the __init__() function of this class.
root = Tk() lines to delete
app = Main_UI(root, database_object) lines to delete
root.mainloop() lines to delete """
""" This part is not necessary
if __name__ == '__main__':
main() """

tkinter py script gives different output when called from another script

I wrote a script in a .py file that I would like to call from another main file of my program. But when I do that, it does not initialize and give the same output as when runned directly.
here's the code of the main file where I import the subTest file as a module and call it when the user clicks on a button:
#!/usr/bin/python
import tkinter as tk
from tkinter import ttk
import subTest
from subTest import SubTest
class Window(tk.Tk):
def __init__(self, parent):
tk.Tk.__init__(self, parent)
tk.Tk.wm_title(self, "Test")
self.parent = parent
self.initialize()
def initialize(self):
self.geometry("600x300+30+30")
label = tk.Label(self, text="Test")
label.pack(pady=20,padx=10)
self.button = ttk.Button(self, text='gotosubtest', command = self.callsubtest)
self.button.pack()
def callsubtest(self):
app = SubTest(None)
app.mainloop()
if __name__ == "__main__":
window = Window(None)
window.mainloop()
and here's the code of the subTest file containing an Entry text box which should be initialized at 320. This is a simplified example of the problem. when subTest is executed directly, this Entry default value is shown in the text box. But when subTest is called from the main, the value is not shown.
Any idea what's wrong with my code? thanks in advance to all useful tips for a python beginner ;)
#!/usr/bin/python
import tkinter as tk
from tkinter import Entry
class SubTest(tk.Tk):
def __init__(self, parent):
tk.Tk.__init__(self, parent)
tk.Tk.wm_title(self, "SubTest")
self.parent = parent
self.initializesubtest()
def initializesubtest(self):
self.geometry("510x480")
self.minx = tk.DoubleVar()
self.minx.set(320)
Entry(self, textvariable=self.minx,width=5).grid(row=21,column=1)
tk.Label(self, text="Min X").grid(row=22,column=1)
if __name__ == "__main__":
app = SubTest(None)
app.mainloop()
You can only have a single instance of Tk in a program. When you use that other module you are creating a second root window. For you to be able to use that second module in the first, it cannot create its own root window.

Tkinter windows popping up and not able to close them

I have a tkinter GUI python code that creates a gui interface to my code, in the code later snack sound toolkit is used (which also uses Tk and creates an instance using root = Tk()). As, mainloop of the previously GUI application is already running to everytime snack function is called a new empty default tk window pops up. As this happens quite a lot, there are hundreds of empty tk windows on screen when this code executes. I have tried to close them using numerous methods root.destroy,root.withdraw, WM_DELETE_WINDOW etc. but to no solution.
Is there any way this can be done in tkinter?
import tkSnack
import thread
import Tkinter as tk
class my_gui(tk.Tk):
def __init__(self):
tk.Tk.__init__(self)
self.button = tk.Button(self, text="Get", command=self.on_button)
self.button.grid(row=8)
def on_button(self):
thread1 = thread.start_new_thread(run, (PATH_TO_WAVE_FILE,))
def run(path):
for k in range(10):
PITCH_VALUES = snack_work(path)
print PITCH_VALUES
def snack_work(INPUT_WAVE_FILE):
# initializing the snack tool
root = tk.Tk()
tkSnack.initializeSnack(root)
# root.withdraw()
mysound = tkSnack.Sound()
# processing original wave file
mysound.read(INPUT_WAVE_FILE)
PITCH_VALUES = mysound.pitch()
return PITCH_VALUES
app = my_gui()
app.mainloop()
Make run() and snack_work() into instance methods of your app object, so that they can easily access that object's attributes. To work with a more minimal MCVE that doesn't rely on external libraries or files, I tested the following with simple print() (I'm on Python 3) and after() calls rather than snack stuff, as all I wanted to check was that the other functions could access a tkinter object.
import tkSnack
import thread
import Tkinter as tk
class my_gui(tk.Tk):
def __init__(self):
tk.Tk.__init__(self)
self.button = tk.Button(self, text="Get", command=self.on_button)
self.button.grid(row=8)
def on_button(self):
thread1=thread.start_new_thread(self.run,(PATH_TO_WAVE_FILE,))
def run(self, path):
for k in range(10):
PITCH_VALUES = self.snack_work(path)
print PITCH_VALUES
def snack_work(self, INPUT_WAVE_FILE):
## initializing the snack tool
tkSnack.initializeSnack(self) # use self instead of the separate root object
# self.withdraw()
mysound=tkSnack.Sound()
## processing original wave file
mysound.read(INPUT_WAVE_FILE)
PITCH_VALUES= mysound.pitch()
return PITCH_VALUES
app = my_gui()
app.mainloop()

Get command window output to display in widget with tkinter

Quick Project Summary: Make a python widget using Tkinter that displays data from several json and txt files. Needs to work in Windows.
Where I'm At: Everything is going great with the json files. But I'm running into trouble with the txt files. I can parse the information I need out of the necessary files with this code:
from Tkinter import *
import re
results = open("sample_results.txt", "r")
for line in results:
if re.match("(.*)test(.*)", line):
print line
if re.match("(.*)number(.*)", line):
print line
if re.match("(.*)status(.*)", line):
print line
if re.match("(.*)length(.*)", line):
print line
Problem: It displays all the data in the command shell NOT in a seperate widget.
I would like to simply move all of this information from the command shell to a text box widget (or a tkmessage widget but I feel that the text box would be more appropriate). A very long google search process gave me lots of code that doesn't work - any tips? Thanks!
Note: This is NOT all the code - just the part I need help fixing
Here's what I think you want. You want your application to open files and parse them out. For each parsed line, you would like it to insert the text (or append the text) to a text control. I would create a method for each file type to do the parsing. Then I would loop over each file and call the parser as appropriate. When you are finished parsing, you can call
self.textbox.insert(tkinter.END, parsed_text)
Another way to do this is to redirect stdout to your text control and then print the parsed line. I find this latter approach to be a bit more flexible, especially when I want to call a separate program with subprocess and read its output bit by bit. Here's one way to do it with Tkinter:
import ScrolledText
import sys
import tkFileDialog
import Tkinter
########################################################################
class RedirectText(object):
""""""
#----------------------------------------------------------------------
def __init__(self, text_ctrl):
"""Constructor"""
self.output = text_ctrl
#----------------------------------------------------------------------
def write(self, string):
""""""
self.output.insert(Tkinter.END, string)
########################################################################
class MyApp(object):
""""""
#----------------------------------------------------------------------
def __init__(self, parent):
"""Constructor"""
self.root = parent
self.root.title("Redirect")
self.frame = Tkinter.Frame(parent)
self.frame.pack()
self.text = ScrolledText.ScrolledText(self.frame)
self.text.pack()
# redirect stdout
redir = RedirectText(self.text)
sys.stdout = redir
btn = Tkinter.Button(self.frame, text="Open file", command=self.open_file)
btn.pack()
#----------------------------------------------------------------------
def open_file(self):
"""
Open a file, read it line-by-line and print out each line to
the text control widget
"""
options = {}
options['defaultextension'] = '.txt'
options['filetypes'] = [('all files', '.*'), ('text files', '.txt')]
options['initialdir'] = '/home'
options['parent'] = self.root
options['title'] = "Open a file"
with tkFileDialog.askopenfile(mode='r', **options) as f_handle:
for line in f_handle:
print line
#----------------------------------------------------------------------
if __name__ == "__main__":
root = Tkinter.Tk()
root.geometry("800x600")
app = MyApp(root)
root.mainloop()
One approach is to use a simple tkinter label:
# somewhere in your main class, I suppose:
self.log_txt = tkinter.StringVar()
self.log_label = tkinter.Label(self.inputframe, textvariable=self.log_txt, justify=tkinter.LEFT)
self.log_label.pack(anchor="w")
Then, a really simple approach for putting text into that label:
def log(self, s):
txt = self.log_txt.get() + "\n" + s
self.log_txt.set(txt)
Alternatively, you can use a tkinter.Text widget. In that case you can insert text with the insert method:
self.textbox = tkinter.Text(parent)
self.textbox.insert(tkinter.END, "some text to insert")
One resource I like is http://effbot.org/tkinterbook/text.htm. Unfortunately it is quite hard to go from that text to working Python code :(
Here is a little example program with an ugly little tkinter GUI that adds text to a textbox:
#!/usr/bin/env python
try:
import tkinter
except ImportError:
import Tkinter as tkinter
import _tkinter
import platform
class TextBoxDemo(tkinter.Tk):
def __init__(self, parent):
tkinter.Tk.__init__(self, parent)
self.parent = parent
self.wm_title("TextBoxDemo")
self.textbox = tkinter.Text(self)
self.textbox.pack()
self.txt_var = tkinter.StringVar()
self.entry = tkinter.Entry(self, textvariable=self.txt_var)
self.entry.pack(anchor="w")
self.button = tkinter.Button(self, text="Add", command=self.add)
self.button.pack(anchor="e")
def add(self):
self.textbox.insert(tkinter.END, self.txt_var.get())
if __name__ == '__main__':
try:
app = TextBoxDemo(None)
app.mainloop()
except _tkinter.TclError as e:
if platform.system() == 'Windows':
print(e)
print("Seems tkinter will not run; try running this program outside a virtualenv.")

Splitting a TKInter function

I am new to programming in all languages and am having a crack at some python. I have a collection of the functions I've written so far which work so I can refer back to them for when I get stuck. In order to collect them together I have used tkinter to do this for me 'AddToCollection.py'. I can get it working when I run the .py file I created it in, however I would like to import it to any function I wish. Whenever I run the code with the AddToCollection imported it runs immediately. I have tried to split it into functions so that the new window will only open when I call a function but then it can't access the Entry to get the file name. Help would be appreciated. TL;DR how do I stop this code from running when it's imported?
from tkinter import *
from tkinter import messagebox as box
#pops up a box to confirm you want to save it
def SaveConf():
var = box.askokcancel('Save', 'Are you sure you want to save?')
if var == 1:
Save(FileName.get())
#Does the actual saving
def Save(Oldfile):
file = open(Oldfile+".py", 'r')
ToAdd = '\n#- '+ Oldfile +' -------------\n' + file.read() + '\n#-----------'
file.close()
newfile = open("/New.py", 'a+')
newfile.write(ToAdd)
newfile.close()
newwind.destroy()
#setting up items in window
#Initialising window
newwind = Tk()
newwind.title('Save a file')
#to enter filename
Frame3 = Frame()
Frame3.pack(padx=5, pady=5)
FileName = Entry(Frame3)
FileName.pack(side = LEFT)
#click button
SaveBtn2 = Button(Frame3, text = 'Save to Testicles.py', command = SaveConf)
SaveBtn2.pack(side=RIGHT,padx=2)
A common way to structure tkinter application is to subclass Tk and create your widget in the constructor. Here is an example of how you could architecture for your code. It pack your application in a class (subclass of Tk) and provide an helper function launch_app to initialise your class and run mainloop on it.
The point with __name__ == "__main__" is to segregate code executed when the script is run #> python foo.py from code executed when the module is imported. If you want to provide a default behavior when used as script, as well as the ability to use that functionality from another module, put it in a function and call this function from if __name__ == "__main__" block.
I also took the liberty to transform your code toward python coding standard (described in PEP 8)
import tkinter as tk
from tkinter import messagebox as box
#Does the actual saving
def save(oldfile):
file_ = open(oldfile+".py", 'r')
#[...]
newfile.close()
#do not destroy window here
class App(tk.Tk):
def __init__(self):
tk.Tk.__init__(self)
self.title('Save a file')
frame3 = tk.Frame()
frame3.pack(padx=5, pady=5)
self.filename = tk.Entry(frame3)
self.filename.pack(side=tk.LEFT)
#click button
save_btn2 = tk.Button(frame3, text='Save to Testicles.py', command=self.save_conf)
save_btn2.pack(side=tk.RIGHT, padx=2)
def save_conf(self):
var = box.askokcancel('Save', 'Are you sure you want to save?')
if var == 1:
save(self.FileName.get())
self.destroy() #<-- here come the destroy
def launch_app():
app = App()
app.mainloop()
if __name__ == "__main__":
launch_app()
If I'm understanding this correctly, you just want to import and use the functions you've written? I think part of what you're missing is this:
if __name__ == "__main__":
#setting up items in window
#Initialising window
newwind = Tk()
newwind.title('Save a file')
#to enter filename
Frame3 = Frame()
Frame3.pack(padx=5, pady=5)
FileName = Entry(Frame3)
FileName.pack(side = LEFT)
That will prevent this code from running when the file is imported as a module.

Categories