Tkinter splash screen & multiprocessing outside of mainloop - python

I have implemented a splash screen that is shown while my application loads the database from remote cloud storage on startup. The splash screen is kept alive (there's a progressbar on it) with calls to .update() and is destroyed once the separate loading process ends. After this, the mainloop is started and the app runs normally.
The code below used to work fine on my Mac with python 3.6 and tcl/tk 8.5.9. However, after the update to Sierra I was forced to update tk to ActiveTcl 8.5.18. Now, the splash screen is not displayed until the separate process finishes, but then appears and stays on screen together with the root window (even though its .destroy() method is called).
import tkinter as tk
import tkinter.ttk as ttk
import multiprocessing
import time
class SplashScreen(tk.Toplevel):
def __init__(self, root):
tk.Toplevel.__init__(self, root)
self.geometry('375x375')
self.overrideredirect(True)
self.columnconfigure(0, weight=1)
self.rowconfigure(0, weight=1)
self.label = ttk.Label(self, text='My Splashscreen', anchor='center')
self.label.grid(column=0, row=0, sticky='nswe')
self.center_splash_screen()
print('initialized splash')
def center_splash_screen(self):
w = self.winfo_screenwidth()
h = self.winfo_screenheight()
x = w / 2 - 375 / 2
y = h / 2 - 375 / 2
self.geometry("%dx%d+%d+%d" % ((375, 375) + (x, y)))
def destroy_splash_screen(self):
self.destroy()
print('destroyed splash')
class App(tk.Tk):
def __init__(self):
tk.Tk.__init__(self)
self.start_up_app()
self.title("MyApp")
self.columnconfigure(0, weight=1)
self.rowconfigure(0, weight=1)
self.application_frame = ttk.Label(self, text='Rest of my app here', anchor='center')
self.application_frame.grid(column=0, row=0, sticky='nswe')
self.mainloop()
def start_up_app(self):
self.show_splash_screen()
# load db in separate process
process_startup = multiprocessing.Process(target=App.startup_process)
process_startup.start()
while process_startup.is_alive():
# print('updating')
self.splash.update()
self.remove_splash_screen()
def show_splash_screen(self):
self.withdraw()
self.splash = SplashScreen(self)
#staticmethod
def startup_process():
# simulate delay while implementation is loading db
time.sleep(5)
def remove_splash_screen(self):
self.splash.destroy_splash_screen()
del self.splash
self.deiconify()
if __name__ == '__main__':
App()
I do not understand why this is happening and how to solve it. Can anybody help? Thanks!
Update:
The splash screen is displayed correctly if you outcomment the line self.overrideredirect(True). However, I don't want window decorations and it still stays on screen at the end of the script. It is being destroyed internally though, any further method calls on self.splash (e.g. .winfo_...-methods) result in _tkinter.TclError: bad window path name ".!splashscreen".
Also, this code works fine under windows and tcl/tk 8.6. Is this a bug/problem with window management of tcl/tk 8.5.18 on Mac?

I came across this while looking for an example on how to make a tkinter splash screen that wasn't time dependent (as most other examples are). Sam's version worked for me as is. I decided to make it an extensible stand-alone class that handles all the logic so it can just be dropped into an existing program:
# Original Stackoverflow thread:
# https://stackoverflow.com/questions/44802456/tkinter-splash-screen-multiprocessing-outside-of-mainloop
import multiprocessing
import tkinter as tk
import functools
class SplashScreen(tk.Toplevel):
def __init__(self, root, **kwargs):
tk.Toplevel.__init__(self, root, **kwargs)
self.root = root
self.elements = {}
root.withdraw()
self.overrideredirect(True)
self.columnconfigure(0, weight=1)
self.rowconfigure(0, weight=1)
# Placeholder Vars that can be updated externally to change the status message
self.init_str = tk.StringVar()
self.init_str.set('Loading...')
self.init_int = tk.IntVar()
self.init_float = tk.DoubleVar()
self.init_bool = tk.BooleanVar()
def _position(self, x=.5,y=.5):
screen_w = self.winfo_screenwidth()
screen_h = self.winfo_screenheight()
splash_w = self.winfo_reqwidth()
splash_h = self.winfo_reqheight()
x_loc = (screen_w*x) - (splash_w/2)
y_loc = (screen_h*y) - (splash_h/2)
self.geometry("%dx%d+%d+%d" % ((splash_w, splash_h) + (x_loc, y_loc)))
def update(self, thread_queue=None):
super().update()
if thread_queue and not thread_queue.empty():
new_item = thread_queue.get_nowait()
if new_item and new_item != self.init_str.get():
self.init_str.set(new_item)
def _set_frame(self, frame_funct, slocx=.5, sloxy=.5, ):
"""
Args:
frame_funct: The function that generates the frame
slocx: loction on the screen of the Splash popup
sloxy:
init_status_var: The variable that is connected to the initialization function that can be updated with statuses etc
Returns:
"""
self._position(x=slocx,y=sloxy)
self.frame = frame_funct(self)
self.frame.grid(column=0, row=0, sticky='nswe')
def _start(self):
for e in self.elements:
if hasattr(self.elements[e],'start'):
self.elements[e].start()
#staticmethod
def show(root, frame_funct, function, callback=None, position=None, **kwargs):
"""
Args:
root: The main class that created this SplashScreen
frame_funct: The function used to define the elements in the SplashScreen
function: The function when returns, causes the SplashScreen to self-destruct
callback: (optional) A function that can be called after the SplashScreen self-destructs
position: (optional) The position on the screen as defined by percent of screen coordinates
(.5,.5) = Center of the screen (50%,50%) This is the default if not provided
**kwargs: (optional) options as defined here: https://www.tutorialspoint.com/python/tk_toplevel.htm
Returns:
If there is a callback function, it returns the result of that. Otherwise None
"""
manager = multiprocessing.Manager()
thread_queue = manager.Queue()
process_startup = multiprocessing.Process(target=functools.partial(function,thread_queue=thread_queue))
process_startup.start()
splash = SplashScreen(root=root, **kwargs)
splash._set_frame(frame_funct=frame_funct)
splash._start()
while process_startup.is_alive():
splash.update(thread_queue)
process_startup.terminate()
SplashScreen.remove_splash_screen(splash, root)
if callback: return callback()
return None
#staticmethod
def remove_splash_screen(splash, root):
splash.destroy()
del splash
root.deiconify()
class Screen(tk.Frame):
# Options screen constructor class
def __init__(self, parent):
tk.Frame.__init__(self, master=parent)
self.grid(column=0, row=0, sticky='nsew')
self.columnconfigure(0, weight=1)
self.rowconfigure(0, weight=1)
### Demo ###
import time
def splash_window_constructor(parent):
"""
Function that takes a parent and returns a frame
"""
screen = SplashScreen.Screen(parent)
label = tk.Label(screen, text='My Splashscreen', anchor='center')
label.grid(column=0, row=0, sticky='nswe')
# Connects to the tk.StringVar so we can updated while the startup process is running
label = tk.Label(screen, textvariable=parent.init_str, anchor='center')
label.grid(column=0, row=1, sticky='nswe')
return screen
def startup_process(thread_queue):
# Just a fun method to simulate loading processes
startup_messages = ["Reticulating Splines","Calculating Llama Trajectory","Setting Universal Physical Constants","Updating [Redacted]","Perturbing Matrices","Gathering Particle Sources"]
r = 10
for n in range(r):
time.sleep(.2)
thread_queue.put_nowait(f"Loading database.{'.'*n}".ljust(27))
time.sleep(1)
for n in startup_messages:
thread_queue.put_nowait(n)
time.sleep(.2)
for n in range(r):
time.sleep(.2)
thread_queue.put_nowait(f"Almost Done.{'.'*n}".ljust(27))
for n in range(r):
time.sleep(.5)
thread_queue.put_nowait("Almost Done..........".ljust(27))
time.sleep(.5)
thread_queue.put_nowait("Almost Done......... ".ljust(27))
def callback(text):
# To be run after the splash screen completes
print(text)
class App(tk.Tk):
def __init__(self):
tk.Tk.__init__(self)
self.callback_return = SplashScreen.show(root=self,
frame_funct=splash_window_constructor,
function=startup_process,
callback=functools.partial(callback,"Callback Done"))
self.title("MyApp")
self.columnconfigure(0, weight=1)
self.rowconfigure(0, weight=1)
self.application_frame = tk.Label(self, text='Rest of my app here', anchor='center')
self.application_frame.grid(column=0, row=0, sticky='nswe')
self.mainloop()
if __name__ == "__main__":
App()

Apparently this is due to a problem with the window stacking order when windows are not decorated by the window manager after calling overrideredirect(True). It seems to have occurred on other platforms as well.
Running the following code on macOS 10.12.5 with Python 3.6.1 and tcl/tk 8.5.18, toplevel windows do not appear after the button 'open' is clicked:
import tkinter as tk
class TL(tk.Toplevel):
def __init__(self):
tk.Toplevel.__init__(self)
self.overrideredirect(True)
# self.after_idle(self.lift)
tl_label = tk.Label(self, text='this is a undecorated\ntoplevel window')
tl_label.grid(row=0)
b_close = tk.Button(self, text='close', command=self.close)
b_close.grid(row=1)
def close(self):
self.destroy()
def open():
TL()
root = tk.Tk()
label = tk.Label(root, text='This is the root')
label.grid(row=0)
b_open = tk.Button(root, text='open', command=open)
b_open.grid(row=1)
root.mainloop()
Uncommenting the line self.after_idle(self.lift) fixes the problem (simply calling self.lift() does too. But using after_idle()prevents the window from flashing up for a fraction of a second before it is moved to its position and resized, which is another problem I have experienced repeatedly with tkinter and keeps me wondering whether I should move on to learn PyQT or PySide2...).
As to the problem with closing an undecorated window in my original question: calling after_idle(window.destroy()) instead of window.destroy() seems to fix that too. I do not understand why.
In case other people reproduce this and somebody hints me towards where to report this as a bug, I am happy to do so.

Related

tkinter pack_forget vs Window default center position

I have an app with multiple windows. I use pack_forget to eliminate the login window and invoke the main window. However this main window loses the default centered position of tkinter. The window is created at position (0 , 0).
Is there any simple way to make this main window be created in the default centered position?
example code, 3 files ->
main.py
#!/usr/bin/env python3
from tkinter import *
from frm_login import Wlogin
class Mainframe(Tk):
def __init__(self):
Tk.__init__(self)
self.frame = Wlogin(self)
self.frame.pack()
def change(self, frame):
self.frame.pack_forget() # delete currrent frame
self.frame = frame(self)
self.frame.pack() # make new frame
if __name__== '__main__':
app = Mainframe()
app.mainloop()
frm_login.py
from tkinter import *
from frm_default import Wmain
class Func(Frame):
def check(self, event=None):
if self.pwd.get() == '1':
self.master.change(Wmain)
else:
self.status.config(text='wrong password')
class Wlogin(Func):
def __init__(self, master=None, **kwargs):
Frame.__init__(self, master, **kwargs)
master.title('Enter password')
master.geometry('300x200')
self.status = Label(self, fg='red')
self.status.pack()
self.lbl = Label(self, text='Enter password')
self.lbl.pack()
self.pwd = Entry(self, show='*')
self.pwd.insert(-1, '1')
self.pwd.pack()
self.pwd.focus()
self.pwd.bind('<Return>', self.check)
self.pwd.bind('<KP_Enter>', self.check)
self.btn = Button(self, text='Done', command=self.check)
self.btn.pack()
self.btn = Button(self, text='Cancel', command=self.quit)
self.btn.pack()
frm_default.py
from tkinter import *
class Wmain(Frame):
def __init__(self, master=None, **kwargs):
Frame.__init__(self, master, **kwargs)
master.title('Main application')
master.geometry('600x400')
There is nothing about your forget / repack code that makes this unique. You can use the same commands you would otherwise. So either define the position yourself:
master.geometry('600x400+300+400')
Or use tk PlaceWindow function:
master.eval('tk::PlaceWindow . center')
Or calculate the position from the window size and monitor size:
master.geometry("600x400")
master.update_idletasks()
x = (master.winfo_screenwidth() - master.winfo_reqwidth()) // 2
y = (master.winfo_screenheight() - master.winfo_reqheight()) // 2
master.geometry(f"+{x}+{y}")
FWIW, my experience tells me that setting the window size yourself instead of letting tkinter calculate it will lead to bugs down the road.

tkinter - dock and undock frame with wm_manage and wm_forget

I found the following in the tk docs:
The wm manage and wm forget commands may be used to perform undocking
and docking of windows.
So I tried wm_manage and wm_forget in this code:
import tkinter as tk
root = tk.Tk()
class MyFigure(tk.Frame):
def __init__(self, master):
tk.Frame.__init__(self,master)
self.master = master
self.bc = tk.Button(self, text='confi',
command=lambda:self.configure(bg='red')
)
self.bmanage = tk.Button(self, text='manage',
command = lambda:self.master.wm_manage(self)
)
self.bforget = tk.Button(self, text='forget',
command = lambda:self.master.wm_forget(self)
)
self.bmanage.pack(side='left')
self.bc.pack(side='left')
self.bforget.pack(side='left')
mf = MyFigure(root)
mf.pack()
root.mainloop()
But it dosen't worked out. So I readed more and there is no way I can missunderstand this:
A toplevel widget may be used as a frame and managed with any of the
other geometry managers after using the wm forget command.
So I tried to do something like that:
def _manage(self):
top = self.master.wm_manage(self)
print(top)
def _forget(self):
frame = self.master.wm_forget(self)
print(frame)
But both return None. Am I something missing here? What am I doing wrong?
In order to make wm_forget correctly work, you should pass a toplevel window as argument. For instance, if you add the following lines in the constructor of the class:
self.top = tk.Toplevel()
self.top.title("Top level")
You can then call the method as follows:
self.master.wm_forget(self.top)
Regarding the wm_manage, you should pass as argument the widget you want to convert to a stand alone top-level window. Please keep in mind that you can only use this command with frame, labelframe and toplevel widgets. If you apply this command to your main window Tk, nothing will happen.
A full example converting a frame to a toplevel (pressing button manage) and converting it back to frame (pressing button forget):
import tkinter as tk
root = tk.Tk()
class MyFigure(tk.Frame):
def __init__(self, master):
tk.Frame.__init__(self,master)
self.master = master
self.bc = tk.Button(self, text='confi',
command=lambda:self.configure(bg='red')
)
self.bmanage = tk.Button(self, text='manage',
command = lambda:self._manage()
)
self.bforget = tk.Button(self, text='forget',
command = lambda:self._forget()
)
self.bmanage.pack(side='left')
self.bc.pack(side='left')
self.bforget.pack(side='left')
self.frame = tk.Frame(self.master, bg="red", height=100)
self.label=tk.Label(self.frame, text="hi")
self.frame.pack()
self.label.pack(expand=True, fill=tk.BOTH)
def _manage(self):
test=self.master.wm_manage(self.frame)
def _forget(self):
self.master.wm_forget(self.frame)
self.frame.pack()
mf = MyFigure(root)
mf.pack()
root.mainloop()

Tkinter load screen with updating labels

I am writing an application which involves a fair amount of data munging at launch. What I'd like to do is create a splash screen that tells the user what stage of the data loading process is happening in real time.
My plan was to create a Label and pass new text to that Label depending on what calculations were going on in that moment. However in my various attempts the best I've done is get the labels to show up only after the munging is complete.
I saw this, which helped me a bit, but still not getting all the way there:
Tkinter Show splash screen and hide main screen until __init__ has finished
Below is my current best attempt (taking all the actual dataframe stuff out to make it minimally executable)
[EDIT TO ADD] Ideally I'd like to do this in a way that doesn't require all the data munging to occur inside the class. IOW, phase 1 launches the splash screen, phase 2 runs the data munging in the main code, phase 3 launches the primary UI
import time
from tkinter import *
class LoadScreen(Toplevel):
def __init__(self, parent):
Toplevel.__init__(self, parent)
self.title('Loading')
self.update()
class UserInterface(Tk):
def __init__(self, parent):
Tk.__init__(self, parent)
self.parent=parent
self.withdraw()
loader = LoadScreen(self)
self.load_label = Label(loader, text='Loading')
self.load_label.grid(row=0, column=0, padx=20, pady=20)
self.stage_label = Label(loader, text='Preparing dataframe')
self.stage_label.grid(row=1, column=0, padx=20, pady=20)
#loader.destroy()
self.main_screen()
def main_screen(self):
self.deiconify()
self.load_label = Label(self, text='Done')
self.load_label.grid(row=0, column=0, padx=20, pady=20)
self.close_button = Button(self, text='Close',
command = lambda: self.destroy())
self.close_button.grid(row=1, column=0, padx=20, pady=20)
ui = UserInterface(None)
#Pretend I'm doing some dataframe munging
print('1')
time.sleep(2)
ui.stage_label['text'] = 'Running some calculations'
print('2')
time.sleep(2)
ui.stage_label['text'] = 'Finishing up'
print('3')
time.sleep(2)
ui.mainloop()
time.sleep will block the main thread. Here's a minimal sample on how I usually do it.
import time
from tkinter import *
root = Tk()
root.withdraw()
Label(root,text="I am main window").pack()
class SplashScreen:
def __init__(self):
self.a = Toplevel()
self.percentage = 0
Label(self.a,text="I am loading screen").pack()
self.load = Label(self.a,text=f"Loading...{self.percentage}%")
self.load.pack()
self.load_bar()
def load_bar(self):
self.percentage +=5
self.load.config(text=f"Loading...{self.percentage}%")
if self.percentage == 100:
self.a.destroy()
root.deiconify()
return
else:
root.after(100,self.load_bar)
SplashScreen()
root.mainloop()

Tkinter bind callback to activating a window

I'm writing a Tkinter application in Python 3 and I've created a custom Title Bar (the bar at the top of every Windows application with the application name, close button etc.). The way I achieved this was to create an invisible window (we'll call this window test) that controls the main application window's behavior (which we'll call Main App). Below is a piece of test code that illustrates this:
from tkinter import Tk, Toplevel
from tkinter.ttk import Button, Label, Frame, Style
class NewRoot(Tk):
def __init__(self):
Tk.__init__(self)
self.attributes('-alpha', 1.0) # This is normally set to 0.0
class MyMain(Toplevel):
def __init__(self, master):
Toplevel.__init__(self, master)
self.overrideredirect(1)
self.geometry('750x650+400+600')
self.style = Style()
self.style.configure('TTitleBar.Label',
width=8,
relief='flat',
foreground='red',
background='red',
anchor='center',
borderwidth=-1)
self.style.configure('TMainWindow.Label',
width=8,
relief='flat',
background='blue',
anchor='center',
borderwidth=-1)
self.tk_setPalette(background='green')
self.x_win = None
self.y_win = None
self.start_x = None
self.start_y = None
# make a frame for the title bar
title_bar = Frame(self, style='TTitleBar.Label')
title_bar.grid(row=0, column=0, sticky='wn')
label = Label(title_bar, text='Main App', style='TMainWindow.Label')
label.grid(row=0, column=0, padx=(4, 2), pady=(4, 0), sticky='nw')
minimize_button = Button(title_bar, text='MIN', command=self.minimize_window,
style='TMainWindow.Label', takefocus=False)
minimize_button.grid(row=0, column=2, padx=(563.5, 0), sticky='nw')
maximise_button = Button(title_bar, text='MAX', command=self.maximize_window,
style='TMainWindow.Label', takefocus=False)
maximise_button.grid(row=0, column=3, pady=(1.4, 0), sticky='nw')
close_button = Button(title_bar, text='CLOSE', command=self.close_window,
style='TMainWindow.Label', takefocus=False)
close_button.grid(row=0, column=4, sticky='nw')
window = Frame(self)
window.grid(row=1, column=0, sticky='ne')
# bind title bar motion to the move window function
title_bar.bind('<B1-Motion>', self.move_window)
title_bar.bind('<Button-1>', self.get_pos)
self.master.bind("<Map>", self.on_root_deiconify)
self.master.bind("<Unmap>", self.on_root_iconify)
self.mainloop()
def minimize_window(self):
self.master.iconify()
def maximize_window(self):
pass
def close_window(self):
self.master.destroy()
def on_root_iconify(self, event):
# print('unmap')
self.withdraw()
def on_root_deiconify(self, event):
# print('map')
self.deiconify()
def get_pos(self, event):
self.x_win = self.winfo_x()
self.y_win = self.winfo_y()
self.start_x = event.x_root
self.start_y = event.y_root
self.y_win = self.y_win - self.start_y
self.x_win = self.x_win - self.start_x
def move_window(self, event):
# print('+{0}+{1}'.format(event.x_root, event.y_root))
self.geometry('+{0}+{1}'.format(event.x_root + self.x_win, event.y_root + self.y_win))
self.start_x = event.x_root
self.start_y = event.y_root
if __name__ == '__main__':
root = NewRoot()
root.title('test')
app = MyMain(root)
In the code above, whenever the test window is minimized, the Main App window is also minimized, which works as intended.
The problem is that whenever the test window is made active, the Main App window doesn't become active also. For example, if another app covers Main App but test is not minimized, I need to click on the test icon in the Windows Task Bar three times for it to appear.
I was wondering if there is a way to fix this using something like:
self.master.bind(<some_command>, self.some_other_command)
However, I can't find a comprehensive list of bind commands anywhere.
Is this a good way of going about this, or is there something else I should be doing?
Also, I noticed that using self.overrideredirect(1) causes the shadows made by the windows to disappear, which causes overlapping windows in my application to 'merge together', since the background colors are the same. Is there a way to add the shadows back?
Thank you in advance.
I found a solution to this for anyone else with a similar problem. You can create a 'dummy' button in the invisible window that will become active when the window is in focus. You then have that call a function that places the main application window in focus.
class NewRoot(Tk):
def __init__(self):
Tk.__init__(self)
self.attributes('-alpha', 0.0)
entry = Button()
entry.pack()
entry.focus_set()
entry.pack_forget()
Then add this to __init__ in the MyMain class:
self.master.bind('<FocusIn>', self.on_root_deiconify)

IntVar().trace() not working

I'm just getting started coding in Python/Tkinter for a small Pymol plugin. Here I'm trying to have a toggle button and report its status when it is clicked. The button goes up and down, but toggleAVA never gets called. Any ideas why?
from Tkinter import *
import tkMessageBox
class AVAGnome:
def __init__(self, master):
# create frames
self.F1 = Frame(rootGnome, padx=5, pady=5, bg='red')
# checkbuttons
self.AVAselected = IntVar()
self.AVAselected.trace("w", self.toggleAVA)
self.AVAbutton = Checkbutton(self.F1, text='AVA', indicatoron=0, variable=self.AVAselected)
# start layout procedure
self.layout()
def layout(self):
self.F1.pack(side=TOP, fill=BOTH, anchor=NW)
#entry and buttons
self.AVAbutton.pack(side=LEFT)
def toggleAVA(self, *args):
if (self.AVAselected.get()):
avastatus = "selected"
else:
avastatus = "unselected"
tkMessageBox.showinfo("AVA status", avastatus)
def __init__(self):
open_GnomeUI()
def open_GnomeUI():
# initialize window
global rootGnome
rootGnome = Tk()
rootGnome.title('AVAGnome')
global gnomeUI
gnomeUI = AVAGnome(rootGnome)
I tested your code with Pymol.
Problem is because you use Tk() to create your window. You have to use Toplevel() and then it will work correctly with trace() or with command=.
Pymol is created with tkinter which can have only one window created with Tk() - it is main window in program. Every other window has to be created with Toplevel().
I have attached a working version of your code below. You can refer to it to learn where you went wrong. Generally, you have to mind how you structure your code if you are using a class format.This will help you visualize your code and debug better. You can read this discussion to help you.
from Tkinter import *
import tkMessageBox
class AVAGnome(Frame):
def __init__(self, parent):
Frame.__init__(self, parent)
# create frames
self.F1 = Frame(self, padx=5, pady=5, bg='red')
# checkbutton
self.AVAselected = IntVar()
self.AVAselected.trace("w", self.toggleAVA)
self.AVAbutton = Checkbutton(
self.F1, text='AVA', indicatoron=0, width=10,
variable=self.AVAselected)
# start layout procedure
self.F1.pack(side=TOP, fill=BOTH, anchor=NW)
self.AVAbutton.pack(side=LEFT) #entry and buttons
def toggleAVA(self, *args):
if (self.AVAselected.get()):
avastatus = "selected"
else:
avastatus = "unselected"
tkMessageBox.showinfo("AVA status", avastatus)
if __name__ == '__main__':
rootGnome = Tk()
rootGnome.title('AVAGnome')
gnomeUI = AVAGnome(rootGnome)
gnomeUI.pack(fill="both", expand=True)
gnomeUI.mainloop()
Update: The above code structure is for standalone tkinter programme. I am attempting to convert this working code to follow Pymol plugin example. Revised code is posted below and is susceptible to further revision.
# https://pymolwiki.org/index.php/Plugins_Tutorial
# I adapted from the example in the above link and converted my previous code to
#
from Tkinter import *
import tkMessageBox
def __init__(self): # The example had a self term here.
self.open_GnomeUI()
class AVAGnome(Frame):
def __init__(self, parent):
Frame.__init__(self, parent)
# create frames
self.F1 = Frame(self, padx=5, pady=5, bg='red')
# checkbutton
self.AVAselected = IntVar()
self.AVAselected.trace("w", self.toggleAVA)
self.AVAbutton = Checkbutton(
self.F1, text='AVA', indicatoron=0, width=10,
variable=self.AVAselected)
# start layout procedure
self.F1.pack(side=TOP, fill=BOTH, anchor=NW)
self.AVAbutton.pack(side=LEFT) #entry and buttons
def toggleAVA(self, *args):
if (self.AVAselected.get()):
avastatus = "selected"
else:
avastatus = "unselected"
tkMessageBox.showinfo("AVA status", avastatus)
# Note, I added a "self" term throughout function.
# Try w/ & w/o "self" to see which works.
def open_GnomeUI(self):
self.rootGnome = Tk()
self.rootGnome.title('AVAGnome')
self.gnomeUI = AVAGnome(self.rootGnome)
self.gnomeUI.pack(fill="both", expand=True)
self.gnomeUI.mainloop()

Categories