How to get progressbar start() info from one window (class) to other? - python

There is a main window with menu and the progressbar. A correspondence window with OK button opens upon menu command and the OK button starts the process (here: 3 sec. sleep).
The correspondence window is created via inheritance from a class I have not provided here (If required for answer, please let me know). The methods apply and ok override existing methods in the mother class.
Now my problem: Since the progressbar sits in the main window (class App) and progressbar(start) and progressbar(stop) in the correspondence window I somehow have to pass (start) and (stop) via the mother class tkSimpleDialog.Dialog to class App. So I thought I also override the __init__(self..) method, provide self. to progressbar.
How can I make this work?
import Tkinter, ttk, tkFileDialog, tkSimpleDialog, time, threading
class App:
def __init__(self, master, progressbar):
self.progress_line(master)
def progress_line (self, master):
self.progressbar = ttk.Progressbar(master, mode='indeterminate')
self.progressbar.place(anchor = 'ne', height = "20", width = "150", x = "175", y = "30")
class AppMenu(object):
def __init__(self, master, progressbar):
self.master = master
self.menu_bar()
def menu_bar(self):
menu_bar = Tkinter.Menu(self.master)
self.menu_bar = Tkinter.Menu(self.master)
self.master.config(menu=self.menu_bar)
self.create_menu = Tkinter.Menu(self.menu_bar, tearoff = False)
self.create_menu.add_command(label = "do", command = self.do)
self.menu_bar.add_cascade(label = "now", menu = self.create_menu)
def do(self):
do1 = Dialog(self.master, progressbar)
class Dialog(tkSimpleDialog.Dialog):
def __init__(self, parent, progressbar):
tkSimpleDialog.Dialog.__init__(self, parent, progressbar)
self.transient(parent)
self.parent = parent
self.result = None
self.progressbar = progressbar
body = Frame(self)
self.initial_focus = self.body(body)
body.pack(padx=5, pady=5)
self.buttonbox()
self.grab_set()
if not self.initial_focus:
self.initial_focus = self
self.protocol("WM_DELETE_WINDOW", self.cancel)
self.geometry("+%d+%d" % (parent.winfo_rootx()+50, parent.winfo_rooty()+50))
self.initial_focus.focus_set()
self.wait_window(self)
def ok(self, event=None):
self.withdraw()
self.start_foo_thread()
self.cancel()
def apply(self):
time.sleep(5)
def start_foo_thread(self):
global foo_thread
self.foo_thread = threading.Thread(target=self.apply)
self.foo_thread.daemon = True
self.progressbar.start()
self.foo_thread.start()
master.after(20, check_foo_thread)
def check_foo_thread(self):
if self.foo_thread.is_alive():
root.after(20, self.check_foo_thread)
else:
self.progressbar.stop()
master = Tkinter.Tk()
progressbar = None
app = App(master, progressbar)
appmenu = AppMenu(master, progressbar)
master.mainloop()
error messages:
first after clicking ok:
Exception in Tkinter callback
Traceback (most recent call last):
File "/System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/lib-tk/Tkinter.py", line 1410, in __call__
File "ask-progressbar.py", line 57, in ok
self.start_foo_thread()
File "ask-progressbar.py", line 66, in start_foo_thread
self.progressbar.start()
AttributeError: Dialog2 instance has no attribute 'progressbar'
second: after closing app
Exception in Tkinter callback
Traceback (most recent call last):
File "/System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/lib-tk/Tkinter.py", line 1410, in __call__
File "ask-progressbar.py", line 26, in do
do1 = Dialog2(self.master, progressbar)
File "ask-progressbar.py", line 33, in __init__
self.transient(parent)
File "/System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/lib-tk/Tkinter.py", line 1652, in wm_transient
TclError: can't invoke "wm" command: application has been destroyed

Below is a working version of your code. There were a number of issues I had to fix because you didn't change a number of things in the code from my answer to your other question about progressbars.
The answer to your main question here is basically that you have to pass the instance around and remember it when necessary in the various class instances involved so that their methods will have it available through theself argument when they need it. Also, the way you were trying to derive and override the tkSimpleDialog.Dialog base class methods was both over-complicated and incorrect as well.
Usually the best (and simplest) thing to do is just supply your own validate() and apply() methods since that's how it was designed to work. If you also need your own __init__() constructor, it's important to only pass parameters to the base class' method that it understands from within the one in the subclass. If you need more functionality, it can usually be provided via additional derived-class-only methods, that only it or other classes you've also created know about.
Anyway, here's what I ended-up with:
import Tkinter, ttk, tkFileDialog, tkSimpleDialog, time, threading
class App:
def __init__(self, master):
self.progress_line(master)
def progress_line(self, master):
# the value of "maximum" determines how fast progressbar moves
self._progressbar = ttk.Progressbar(master, mode='indeterminate',
maximum=4) # speed of progressbar
self._progressbar.place(anchor='ne', height="20", width="150",
x="175", y="30")
#property
def progressbar(self):
return self._progressbar # return value of private member
class AppMenu(object):
def __init__(self, master, progressbar):
self.master = master
self.menu_bar()
self.progressbar = progressbar
def menu_bar(self):
self.menu_bar = Tkinter.Menu(self.master)
self.master.config(menu=self.menu_bar)
self.create_menu = Tkinter.Menu(self.menu_bar, tearoff=False)
self.create_menu.add_command(label="do", command=self.do)
self.menu_bar.add_cascade(label="now", menu=self.create_menu)
def do(self):
Dialog(self.master, self.progressbar) # display the dialog box
class Dialog(tkSimpleDialog.Dialog):
def __init__(self, parent, progressbar):
self.progressbar = progressbar
tkSimpleDialog.Dialog.__init__(self, parent, title="Do foo?")
def apply(self):
self.start_foo_thread()
# added dialog methods...
def start_foo_thread(self):
self.foo_thread = threading.Thread(target=self.foo)
self.foo_thread.daemon = True
self.progressbar.start()
self.foo_thread.start()
master.after(20, self.check_foo_thread)
def check_foo_thread(self):
if self.foo_thread.is_alive():
master.after(20, self.check_foo_thread)
else:
self.progressbar.stop()
def foo(self): # some time-consuming function...
time.sleep(3)
master = Tkinter.Tk()
master.title("Foo runner")
app = App(master)
appmenu = AppMenu(master, app.progressbar)
master.mainloop()
Hope this helps.

Here's another, simpler, solution that doesn't require the use of threading -- so could be easier to use/adapt in your case. It calls the progressbar widget's update_idletasks() method multiple times during the time-consuming foo() function. Again, it illustrates how to pass the progressbar around to the various parts of the code that need it.
import Tkinter, ttk, tkFileDialog, tkSimpleDialog, time
class App:
def __init__(self, master):
self.progress_line(master)
def progress_line(self, master):
self._progressbar = ttk.Progressbar(master, mode='indeterminate')
self._progressbar.place(anchor='ne', height="20", width="150",
x="175", y="30")
#property
def progressbar(self):
return self._progressbar # return value of private member
class AppMenu(object):
def __init__(self, master, progressbar):
self.master = master
self.menu_bar()
self.progressbar = progressbar
def menu_bar(self):
self.menu_bar = Tkinter.Menu(self.master)
self.master.config(menu=self.menu_bar)
self.create_menu = Tkinter.Menu(self.menu_bar, tearoff=False)
self.create_menu.add_command(label="do foo", command=self.do_foo)
self.menu_bar.add_cascade(label="now", menu=self.create_menu)
def do_foo(self):
confirm = ConfirmationDialog(self.master, title="Do foo?")
self.master.update() # needed to completely remove conf dialog
if confirm.choice:
foo(self.progressbar)
class ConfirmationDialog(tkSimpleDialog.Dialog):
def __init__(self, parent, title=None):
self.choice = False
tkSimpleDialog.Dialog.__init__(self, parent, title=title)
def apply(self):
self.choice = True
def foo(progressbar):
progressbar.start()
for _ in range(50):
time.sleep(.1) # simulate some work
progressbar.step(10)
progressbar.update_idletasks()
progressbar.stop()
master = Tkinter.Tk()
master.title("Foo runner")
app = App(master)
appmenu = AppMenu(master, app.progressbar)
master.mainloop()

Related

Open existing class with button in another class

I have a problem with opening existing class from button which is in another class.
I don't have any idea how to make it working because I know I should in function add (Toplevel) but it's also not working.
class na_dane(object):
def __init__(self, master):
self.master = master
self.frame = tk.Frame(self.master)
self.frame.pack()
# CODE ........
def close_windows(self):
self.master.destroy()
# this code opens another window (class) which I have defined but not included here...
def new_window(self):
self.newWindow = tk.Toplevel.iconify(self.master)
self.app = sinus(self.newWindow)
# here is the main class where when I click button it should open na_dane Class
class menu(na_dane):
def __init__(self, master):
self.master = master
przycisk_Jednostkowo_Liniowa = tk.Button(
text="Funkcja jednostkowo-liniowa",
width=250,
height=2,
bg="blue",
fg="yellow"
).pack()
# HERE IS THE PROBLEM WHERE I TRY TO BIND BUTTON TO CLASS
przycisk_Jednostkowo_Liniowa.bind("<Button>", lambda e: na_dane(master))
przycisk_Jednostkowo_Liniowa.pack()
def main():
root = tk.Tk()
root.geometry("250x200")
root.title("GRUPA 3")
root['bg'] = '#FFFFFF'
app = menu(root)
root.mainloop()
if __name__ == '__main__':
main()
The problem seems to be that you attempt to pack przycisk_Jednostkowo_Liniowa in the same line as it is created. What this line of code is doing is creating a button with the characteristics described in your arguments, applying .pack() to it, then assigning the output of that .pack() execution to przycisk_Jednostkowo_Liniowa. Because .pack() doesn't have a return value, przycisk_Jednostkowo_Liniowa becomes None, so when you later attempt to bind a lambda to it, it doesn't recognize .bind() as a valid method. I tested the code with that .pack() removed and it worked for me.

Tkinter event not trigerred in all child windows.

in my code, the main window is emitting event signals, which are to be caught by the child windows, which will show the change in a label. But only the last child window catches the event signal, and changes it's label. what's wrong?
from Tkinter import *
from threading import Timer as tt
class main(Tk):
def __init__(self):
Tk.__init__(self)
tt(.5,self.timedsig).start()
for i in range (5):
child(self,i)
def timedsig(self):
self.event_generate("<<timedsig>>")
tt(.5,self.timedsig).start()
class child(Toplevel):
def __init__(self,master,num):
Toplevel.__init__(self)
self.title(str(num))
self.num=num
self.var=IntVar()
self.var.set(0)
Label(self,textvariable=self.var).pack()
self.master=master
self.master.bind("<<timedsig>>",self.changelabel)
def changelabel(self,e):
print self.num,self.var.get()
self.var.set(self.var.get()+1)
if __name__=="__main__":
main().mainloop()
Calling bind also unbinds all previous functions. To bind an additional function, you need to use the optional 3rd argument:
self.master.bind("<<timedsig>>",self.changelabel, '+')
But that's pretty silly to do in your case when you can simply pass the variable to the instances:
import Tkinter as tk
class Main(tk.Tk):
def __init__(self):
tk.Tk.__init__(self)
self.var = tk.IntVar(value=0)
for i in range (5):
Popup(self, i, self.var)
self.timedsig() # start the looping
def timedsig(self):
self.var.set(self.var.get() + 1)
self.after(500, self.timedsig) # call this again in 500 ms
class Popup(tk.Toplevel):
def __init__(self, master, num, var):
tk.Toplevel.__init__(self)
self.title(str(num))
lbl = tk.Label(self,textvariable=var)
lbl.pack()
if __name__=="__main__":
root = Main()
root.mainloop()
Also, that's not what we usually call a "child". It's just a different object.

Why does adding a custom menu break keyboard event binding in Python3/tkinter?

In the following code, when I press the button to add some secondary windows, and then try to close a window by using "Command-w" it does not always close the active window. But if I disable the menu creation by commenting the line self.gerar_menu(), windows are opened and closed as expected (I mean, by clicking the red 'x' button or by pressing Command-W in OS X). Any idea about what is wrong here?
Here is my current test code:
#!/usr/bin/env python3.6
# encoding: utf-8
import tkinter as tk
import tkinter.font
from tkinter import ttk
class baseApp(ttk.Frame):
"""
Parent classe for main app window (will include some aditional methods and properties).
"""
def __init__(self, master, *args, **kwargs):
super().__init__(master, *args, **kwargs)
self.master = master
self.mainframe = ttk.Frame(master)
self.mainframe.pack()
class App(baseApp):
""" Base class for the main application window """
def __init__(self, master, *args, **kwargs):
super().__init__(master, *args, **kwargs)
self.master = master
#self.gerar_menu() # This line breaks "Command-w" functionality
self.lbl_text = ttk.Label(self.mainframe, text="This is the Main Window")
self.lbl_text.pack()
self.btn = ttk.Button(self.mainframe, text="Open Second window",
command=lambda: self.create_detail_window(self, number=0))
self.btn.pack()
self.newDetailsWindow = {}
self.windows_count=0
def gerar_menu(self):
""" generate the application menu """
self.menu = tk.Menu(root)
root.config(menu=self.menu)
self.fileMenu = tk.Menu(self.menu)
self.menu.add_cascade(label="File", menu=self.fileMenu)
self.fileMenu.add_command(label="New Document", command=None, accelerator="Command+n")
def create_detail_window(self, *event, number=None):
self.windows_count += 1
self.newDetailsWindow[self.windows_count]=tk.Toplevel()
self.newDetailsWindow[self.windows_count].geometry('900x600+80+130')
self.newDetailsWindow[self.windows_count].title(f'Detail: {self.windows_count}')
self.newDetailsWindow[self.windows_count].wm_protocol("WM_DELETE_WINDOW", self.newDetailsWindow[self.windows_count].destroy)
self.newDetailsWindow[self.windows_count].bind("Command-w", lambda event: self.newDetailsWindow[-1].destroy())
self.detail_window = detailWindow(self.newDetailsWindow[self.windows_count], self.windows_count)
self.newDetailsWindow[self.windows_count].focus()
print(self.newDetailsWindow)
class detailWindow(ttk.Frame):
""" Base class for secondary windows """
def __init__(self, master, rep_num, *args,**kwargs):
super().__init__(master,*args,**kwargs)
self.num_rep = rep_num
self.master.minsize(900, 600)
self.master.maxsize(900, 600)
print(f"Showing details about nr. {self.num_rep}")
self.mainframe = ttk.Frame(master)
self.mainframe.pack()
self.lbl_text = ttk.Label(self.mainframe,
text=f"Showing details about nr. {self.num_rep}")
self.lbl_text.pack()
if __name__ == "__main__":
root = tk.Tk()
janela_principal = App(root)
root.title('Main Window')
root.bind_all("<Mod2-q>", exit)
root.mainloop()
If you want to create a binding for closing a window, you need the function to act upon the actual window that received the event. Your code is always deleting the last window that was opened no matter which window received the event.
The first step is to bind to a function rather than using lambda. While lambda has its uses, binding to a named function is much easier to debug and maintain.
Once the function is called, the event object can tell you which window got the event via the widget attribute of the event object. Given this window, you can get the toplevel window that this window is in (or itself, if it's a toplevel window) via the winfo_toplevel command.
For example:
window = tk.Toplevel(...)
...
window.bind("<Command-w>", self.destroy_window)
...
def destroy_window(self, event):
window = event.widget.winfo_toplevel()
window.destroy()

Close a tkinter GUI after a period of time

I want to create a GUI that shows a message and it is automatically destroyed after some time. I saw this question in different posts but none of the solutions proposed worked out for my App. Here a small part of the code
class MessageShort(tkSimpleDialog.Dialog):
def __init__(self, parent, text, time):
self.top=Toplevel.__init__(self, parent)
self.transient(parent)
self.parent = parent
self.text=text
self.time=time
body = Frame(self)
self.initial_focus = self.body(body)
body.pack(padx=10, pady=10)
if not self.initial_focus:
self.initial_focus = self
self.geometry("+%d+%d" % (parent.winfo_rootx()+200,
parent.winfo_rooty()+75))
self.initial_focus.focus_set()
self.wait_window(self)
def body(self, master):
m=Message(master, text=self.text).grid(row=0,column=0,sticky=W)
master.after(self.time,master.destroy())
MessageShort(root,"Select date and decimal format",2000)#call from another part to the class to create the GUI message
root = Tk()
app = App(root) #main App
root.mainloop()
The App have different Menus and Tkinter classes to display the different tools
With the current code I close the App and I just want to close the message but not the App
Create a timer and set destroying the root as it's callback:
from threading import Timer
import time
def called_after_timer():
if(root != None):
root.destroy()
t = Timer(20 * 60, called_after_timeout)
t.start()
# do something else, such as
time.sleep(1500)
Finally I got something that seems to work
class MessageShort(tkSimpleDialog.Dialog):
def __init__(self, parent, text, time):
self.top=Toplevel.__init__(self, parent)
self.transient(parent)
self.parent = parent
self.text=text
self.time=time
body = Frame(self)
self.initial_focus = self.body(body)
body.pack(padx=10, pady=10)
if not self.initial_focus:
self.initial_focus = self
self.geometry("+%d+%d" % (parent.winfo_rootx()+200,
parent.winfo_rooty()+75))
self.initial_focus.focus_set()
self.wait_window(self)
def body(self, master):
m=Message(master, text=self.text).grid(row=0,column=0,sticky=W)
master.after(self.time,self.destroy)
MessageShort(root,"Select date and decimal format",2000)#call from another part to the class to create the GUI message
root = Tk()
app = App(root) #main App
root.mainloop()
In the last line self.master.destroy destroys m but not the container itself. So it has to call to self.destroy

Declared variables not initialised when using 'simpledialog'

I have a problem with some code that I have been working on where I am trying to pass a variable to a 'simpledialog' box. However, when I declare the variable in the __init__ section, the variable cannot be accessed from any other method in the class.
I have created a simplified working example in which I am trying to pass a string to an Entry box so that when the 'simpledialog' is created, the Entry box is already populated. The value can then be changed and the new value is printed to the console.
from tkinter import *
from tkinter.simpledialog import Dialog
class App(Frame):
def __init__(self, parent):
Frame.__init__(self, parent)
self.parent = parent
Button(parent, text="Press Me", command=self.run).grid()
def run(self):
number = "one"
box = PopUpDialog(self, title="Example", number=number)
print(box.values)
class PopUpDialog(Dialog):
def __init__(self, parent, title, number, *args, **kwargs):
Dialog.__init__(self, parent, title)
self.number = number
def body(self, master):
Label(master, text="My Label: ").grid(row=0)
self.e1 = Entry(master)
self.e1.insert(0, self.number) # <- This is the problem line
self.e1.grid(row=0, column=1)
def apply(self):
self.values = (self.e1.get())
return self.values
if __name__ == '__main__':
root = Tk()
app = App(root)
root.mainloop()
When the code is run, and the 'Press Me' button is pressed, I get the following error message:
Exception in Tkinter callback
Traceback (most recent call last):
File "C:\Python34\lib\tkinter\__init__.py", line 1533, in __call__
return self.func(*args)
File "C:/Python/scratch.py", line 14, in run
box = PopUpDialog(self, title="Example", number=number)
File "C:/Python/scratch.py", line 20, in __init__
Dialog.__init__(self, parent, title)
File "C:\Python34\lib\tkinter\simpledialog.py", line 148, in __init__
self.initial_focus = self.body(body)
File "C:/Python/scratch.py", line 26, in body
self.e1.insert(0, self.number)
AttributeError: 'PopUpDialog' object has no attribute 'number'
If I comment out the self.e1.insert(0, self.number), the code will otherwise work.
There seems to be little documentation on the 'simpledialog', and I've been using the examples on effbot.org to try and learn more about dialog boxes.
As a side note, if I insert a print(number) line in the __init__ method of the PopUpDialog class, the number will print to the console. Also, if I initialise the self.number variable (eg, self.number = "example") in the body() method, the code works as expected.
I'm sure I'm missing something silly here, but if you could offer any suggestions as to what might be happening, it would be most appreciated.
The problem is in your PopUpDialog class, At the function __init__ you call the line Dialog.__init__(self, parent, title) that calls the body method. The problem is that you initialize the self.number at the next line and that's why self.number is not initialized yet at the body method.
If you switch the lines it will work for you, just like this:
class PopUpDialog(Dialog):
def __init__(self, parent, title, number, *args, **kwargs):
self.number = number
Dialog.__init__(self, parent, title)
EDIT:
As you can see at the __init__ method of the Dialog there is the above line :
self.initial_focus = self.body(body) that calls your body method.

Categories

Resources