I'm trying to launch a threaded window because I have background processes that are running, but two identical windows are launching under the same thread and I'm not sure why. I'm guessing this code can be optimized! thx for any suggestions.
6/1: I made the modification as suggested to not run two windows in the main loop, and that works. The 2nd piece, is that once the button is clicked, the window is destroyed "self.root.destroy()", but if I try to open another window it will not open. I do a check before trying to launch the new window and there is only the main thread running, so the first thread is gone. Not sure what's going on and I'm not able to debug with threads running.
from Tkinter import *
import ttk
import threading
import thread
from pdctest.test_lib.hid_util import *
class GUI():
def __init__(self, root):
self.root = root # root is a passed Tk object
self.root.title("GUI--->user instruction window")
self.frame = Frame(self.root)
self.frame.pack()
self.label0 = Label(self.frame, text=" ")
self.label0.configure(width=50, height=1)
self.label0.pack()
self.label = Label(self.frame, text="GUI--->execute a SKP on HS within 3s...")
self.label.configure(width=50, height=1)
self.label.pack()
self.button = Button(self.root, text=" GUI--->then click this button ", command=self.buttonWasPressed, fg = 'black', bg = 'dark orange')
self.button.configure(width=40, height=5)
self.button.pack(pady=25)
def removethis(self):
print("GUI--->in removethis")
self.root.destroy()
def buttonWasPressed(self):
print( "GUI--->inside buttonWasPressed: the button was pressed! " + '\r')
self.removethis()
print("GUI--->leaving buttonWasPressed")
def guiSKP(self):
#root = Tk()
#window = GUI(root)
#root.mainloop()
# modified 6/1, fixes multi-window issue
self.root.mainloop()
class GUI_SKP_Thread(threading.Thread):
def __init__(self, name):
threading.Thread.__init__(self)
self.name = name
def run(self):
root = Tk()
window = GUI(root)
window.guiSKP()
def launch_SKP_thread():
print("beginning launch_SKP_thread" + '\r')
thread_SKP = GUI_SKP_Thread("GUI_SKP_Thread")
thread_SKP.start()
print("exiting launch_SKP_thread" + '\r')
def whatsRunning():
t = threading.enumerate()
print("--------------->whatsRunning")
print t
if __name__ == '__main__':
launch_SKP_thread()
# trying to launch 2nd window
whatsRunning()
time.sleep(4)
whatsRunning()
launch_SKP_thread()
I't not really sure what you're trying to do with the program. But I'm sure about one thing, that is, you are trying to run two frames in the same mainloop. You defined window twice in different locations, meaning that there are two frames/windows. Once in guiSKP in GUI, and once in run in GUI_SKP_Thread.
root = Tk()
window = GUI(root)
So, you should change the text in guiSKP from:
root = Tk()
window = GUI(root)
root.mainloop()
To simply:
self.root.mainloop()
I added the self to make sure it runs its own root.mainloop()
Related
I'm trying to make it so that new information shows in in a new window, but I want the new window to be connected to the parent window, even when the parent window is clicked the new window should still show up similar to how a dropdown menu works. I'm also planning on having some of the new windows have treeviews later on.
from tkinter import *
win = Tk()
win.geometry("500x500+0+0")
def button_function ():
win2 = Toplevel()
label = Label(win2,text='dropdown', width=7)
label.pack()
win2.geometry(f"+{win.winfo_x()}+{win.winfo_y()+30}")
button = Button(win, command=lambda: button_function (), width=12)
button.pack()
win.mainloop()
Ok so with a little bit of googling I came across this post: tkinter-detecting-a-window-drag-event
In that post they show how you can keep track of when the window has moved.
By taking that code and making some small changes we can use the dragging() and stop_drag() functions to move the top level window back to where it was set to relative to the main window.
That said this will only work in this case. You will need to write something more dynamic to track any new windows you want so they are placed properly and on top of that you will probably want to build this in a class so you do not have to manage global variables.
With a combination of this tracking function and using lift() to bring the window up we get closer to what you are asking to do.
That said you will probably want remove the tool bar at the top of the root window to be more clean. I would also focus on using a dictionary or list to keep track of open and closed windows and their locations to make the dynamic part of this easier.
import tkinter as tk
win = tk.Tk()
win.geometry("500x500+0+0")
win2 = None
drag_id = ''
def dragging(event):
global drag_id
if event.widget is win:
if drag_id == '':
print('start drag')
else:
win.after_cancel(drag_id)
print('dragging')
drag_id = win.after(100, stop_drag)
if win2 is not None:
win2.lift()
win2.geometry(f"+{win.winfo_x()}+{win.winfo_y() + 30}")
def stop_drag():
global drag_id, win2, win
print('stop drag')
drag_id = ''
if win2 is not None:
win2.lift()
win2.geometry(f"+{win.winfo_x()}+{win.winfo_y() + 30}")
win.bind('<Configure>', dragging)
def button_function():
global win2
win2 = tk.Toplevel()
label = tk.Label(win2, text='drop down', width=7)
label.pack()
win2.geometry(f"+{win.winfo_x()}+{win.winfo_y()+30}")
tk.Button(win, command=button_function, width=12).pack()
win.mainloop()
EDIT:
Ok so I took some time to write this up in a class so you could see how it could be done. I have also added some level of dynamic building of the buttons and pop up windows.
We use a combination of lists and lambdas to perform a little bit of tracking and in the end we pull off exactly what you were asking for.
let me know if you have any questions.
import tkinter as tk
class Main(tk.Tk):
def __init__(self):
super().__init__()
self.geometry('500x500')
self.pop_up_list = []
self.drag_id = ''
self.button_notes = ['Some notes for new window', 'some other notes for new window', 'bacon that is all!']
self.bind('<Configure>', self.dragging)
for ndex, value in enumerate(self.button_notes):
print(ndex)
btn = tk.Button(self, text=f'Button {ndex+1}')
btn.config(command=lambda b=btn, i=ndex: self.toggle_button_pop_ups(i, b))
btn.grid(row=ndex, column=0, padx=5, pady=5)
self.pop_up_list.append([value, 0, None, btn])
def dragging(self, event):
if event.widget is self:
if self.drag_id == '':
pass
else:
self.after_cancel(self.drag_id)
self.drag_id = self.after(100, self.stop_drag)
for p in self.pop_up_list:
if p[1] == 1:
p[2].lift()
p[2].geometry(f"+{p[3].winfo_rootx() + 65}+{p[3].winfo_rooty()}")
def stop_drag(self):
self.drag_id = ''
for p in self.pop_up_list:
if p[1] == 1:
p[2].lift()
p[2].geometry(f"+{p[3].winfo_rootx() + 65}+{p[3].winfo_rooty()}")
def toggle_button_pop_ups(self, ndex, btn):
p = self.pop_up_list
if p[ndex][1] == 0:
p[ndex][1] = 1
p[ndex][2] = tk.Toplevel(self)
p[ndex][2].overrideredirect(1)
tk.Label(p[ndex][2], text=self.pop_up_list[ndex][0]).pack()
p[ndex][2].geometry(f"+{btn.winfo_rootx() + 65}+{btn.winfo_rooty()}")
else:
p[ndex][1] = 0
p[ndex][2].destroy()
p[ndex][2] = None
if __name__ == '__main__':
Main().mainloop()
Creates two windows and gridding is not correct. Some additional comments in the code initiation.
I have used this approach, without the super init with no problem, many times.
Advice appreciated.
Thanks
# timhockswender#gmail.com
import tkinter as tk
from tkinter import ttk
class constants_page(tk.Frame):
def __init__(self):
super(constants_page, self).__init__() # from stackoverflow
# if not used error = 'constants_page' object has no attribute 'tk'
# if used, another tiny window is opened
# in addtion to the constants_page
self.constants_page = tk.Tk()
self.constants_page.geometry("1000x500") #width*Length
self.constants_page.title("Owen's Unit Conversion App")
self.constants_page.configure(background='light blue')
self.CreateWidgets()
def CreateWidgets(self):
self.value_label = ttk.Label(self.constants_page,text="Value----->" , width =10 )
self.value_label.grid(row=0, column=1, columnspan=1, sticky='nse')
# Problem: not gridding properly
self.title_label = ttk.Label(self.constants_page, text="Important Physical Constants",
anchor=tk.CENTER, font=("Arial",20)).grid(row=2, columnspan=2)
for r in range(2):
self.constants_page.rowconfigure(r, weight=1, uniform='row')
for c in range(2):
self.constants_page.columnconfigure(c, weight=1 )
def Show_Page():
# Create the entire GUI program
program = constants_page()
program.mainloop()
if __name__ == "__main__":
Show_Page()
The super call expects you to provide a root window (an instance of tk.Tk()). If you don't provide one it defaults to the first root window opened, and if none has been opened yet then it helpfully opens one for you. A few lines later you open a second one yourself.
The easy fix is to remove the self.constants_page = tk.Tk() line. The proper fix is to make the Tk() instance outside of the class and pass it in. This allows you to use the Frame class itself to lay out widgets (use self instead of self.constants_page). Try this:
import tkinter as tk
from tkinter import ttk
class constants_page(tk.Frame):
def __init__(self, master=None, **kwargs):
super().__init__(master, **kwargs)
master.geometry("1000x500") #width*Length
master.title("Owen's Unit Conversion App")
self.configure(background='light blue')
self.CreateWidgets()
def CreateWidgets(self):
self.value_label = ttk.Label(self,text="Value----->" , width =10 )
self.value_label.grid(row=0, column=1, columnspan=1, sticky='nse')
self.title_label = ttk.Label(self, text="Important Physical Constants",
anchor=tk.CENTER, font=("Arial",20)).grid(row=2, columnspan=2)
for r in range(2):
self.rowconfigure(r, weight=1, uniform='row')
for c in range(2):
self.columnconfigure(c, weight=1 )
def Show_Page():
# Create the entire GUI program
program = tk.Tk()
win = constants_page(program)
win.pack()
program.mainloop()
if __name__ == "__main__":
Show_Page()
I have a gui in python3 that I want to call another python script using subprocess (or something better?). I have the main gui full screen, which is what I want. The problem is, when I launch the subprocess, it initially becomes the topmost window on LXDE, so far so good. You can click on the main gui in the background which brings the main gui to topmost. This is what is expected from the window manager, but this covers the subprocess that I currently have blocking the main gui. I need to prevent the main gui from accepting focus while the subprocess is running or keeping the subprocess as topmost window.
maingui.py
#!/usr/bin/env python3
import tkinter as tk
import subprocess
def on_escape(event=None):
print("escaped")
root.destroy()
def do_something():
child = subprocess.run(["python3", "childgui.py"], shell=False)
######################################################################
busy=False
root = tk.Tk()
root.title("My GUI")
screen_width = root.winfo_screenwidth()
screen_height = root.winfo_screenheight()
#don't run this as the subprocess blocks #root.attributes("-fullscreen", True) # run fullscreen
root.focus_set()
root.bind("<Escape>", on_escape)
#doesn't work#root.protocol("WM_TAKE_FOCUS", on_focus)
canvas = tk.Canvas(root)
canvas.grid(row=0)
cbutton = tk.Button(root, text = "Run Child", width=50, height=50, bg = "green", compound = "top", command = do_something)
lab = tk.Label(canvas, text = 'Output Here')
lab.grid(row=0, column=1)
cbutton.grid(row=1, column=0)
# --- start ---
root.mainloop()
childgui.py
from tkinter import *
class app(Frame):
def __init__(self, master):
Frame.__init__(self, master=None)
self.master.title("Child Process")
self.master.geometry("500x200")
Button(self.master, text="Grandchild", command=self.dialog).pack()
self.data = StringVar()
self.data.set("Here is the data")
Label(self.master, textvariable=self.data).pack()
def dialog(self):
d = MyDialog(self.master, self.data, "Grandchild", "Enter Data")
self.master.wait_window(d.top)
class MyDialog:
def __init__(self, parent, data, title, labeltext = '' ):
self.data = data
self.rt = parent
self.top = Toplevel(parent)
self.top.transient(parent)
self.top.grab_set()
self.top.geometry("300x100")
if len(title) > 0: self.top.title(title)
if len(labeltext) == 0: labeltext = 'Data'
Label(self.top, text=labeltext).pack()
self.top.bind("<Return>", self.ok)
self.e = Entry(self.top, text=data.get())
self.e.bind("<Return>", self.ok)
self.e.bind("<Escape>", self.cancel)
self.e.pack(padx=15)
self.e.focus_set()
b = Button(self.top, text="OK", command=self.ok)
b.pack(pady=5)
def ok(self, event=None):
print ("The data:", self.e.get())
self.data.set(self.e.get())
self.top.destroy()
def cancel(self, event=None):
self.top.destroy()
def main():
root = Tk()
a = app(root)
root.mainloop()
if __name__ == '__main__':
main()
Edit: I should have mentioned the child gui is an app that is not mine. I only created this one for the example code to show the issue. While self.top.attributes may work, the actual child app is pretty large and I don't want to change it if I can avoid it. I don't have problems getting the focus, which is actually the problem. The main gui gets the focus back causing the subprocess to go behind. When the main gui is set to fullscreen (delete this to see #don't run this as the subprocess blocks #) the main gui is now stuck waiting for the child to close which you can't get to with the mouse
I am totally new in python GUI and Tkinter. Now i want an entry field where i can change the value or time of self.hide when i will execute this code. that means self.hide value will change from Entry field. In this code this value is statically set to 1 minute. need help from experts.
import Tkinter as Tk
import time
import tkMessageBox
class Window:
def __init__(self):
self.root = None
self.hide = 1 #minutes
self.show = 3 #seconds
def close(self):
self.root.destroy()
return
def new(self):
self.root = Tk.Tk()
self.root.overrideredirect(True)
self.root.geometry("{0}x{1}+0+0".format(self.root.winfo_screenwidth(), self.root.winfo_screenheight()))
self.root.configure(bg='black')
Tk.Label(self.root, text='Hello', fg='white', bg='black', font=('Helvetica', 30)).place(anchor='center', relx=0.5, rely=0.5)
#tkMessageBox.showinfo("Notification", "Your time is up. Time to do next job. . .")
Tk.Button(text = 'Close', command = self.close).pack()
self.root.after(self.show*1000, self.pp)
def pp(self):
if self.root:
self.root.destroy()
time.sleep(self.hide*60)
self.new()
self.root.mainloop()
return
Window().pp()
Try This. It may help you.
from Tkinter import *
import time
root = Tk()
def close():
root.destroy()
def show():
root.deiconify()
button.config(text = 'Close', command = close)
root.after(1000, hide)
def hide():
root.withdraw()
time_to_sleep = set_time_to_sleep.get()
time_to_sleep = float(time_to_sleep)
#print time_to_sleep
time.sleep(time_to_sleep)
show()
set_time_to_sleep = Entry(root)
set_time_to_sleep.pack(side=LEFT)
button = Button(text = 'Set Time', command = hide)
button.pack()
root.mainloop()
To summarise:
Instead of using the sleep function, use the after function. This will not freeze the GUI.
Set the "wait" time of the after function self.Entry.get(). This will collect the info you have put into the Entry.
For more info, look at these links. People smarter than myself give a very clear explication on how to use the functions.
Tkinter, executing functions over time
tkinter: how to use after method
I'm trying to get a progress bar to appear on a Toplevel widget, then incrementally increase every few seconds until complete.
When I click "Start", there is delay of a few seconds before the progressbar widget appears. When it does appear, the progress bar does not increment at all.
Here is what I've tried so far:
class MainUI:
def __init__(self, parent):
self.parent = parent
self.counter = IntVar()
self.main_container = Frame(self.parent)
self.main_container.pack()
self.btn_start = Button(self.main_container, command=self.btn_start_click)
self.btn_start.configure(
text="Start", background="Grey",
padx=50
)
self.btn_start.pack(side=LEFT)
def progress_bar(self):
self.pbar_top = Toplevel(self.main_container)
self.download_label = Label(
self.pbar_top,
text="Download Bar"
)
self.download_label.pack(side=TOP)
self.download_bar = ttk.Progressbar(
self.pbar_top, orient="horizontal",
length=400, mode="determinate",
variable=self.counter, maximum=5
)
self.download_bar.pack(side=TOP)
def btn_start_click(self):
self.progress_bar()
for i in range(4):
self.counter = i
time.sleep(1)
root = Tk()
root.title("Progress Bar Test")
main_ui = MainUI(root)
root.mainloop()
I found that commenting out the for loop inside btn_start_click causes the progress bar to appear immediately after clicking "Start". However, as before the actual bar does not increment.
Could someone please point out what I'm doing wrong?
The problem is that you are calling time.sleep(1) in the same thread than your Tkinter code. It makes your GUI unresponsive until the task (in this case, the call to btn_start_click) finishes. To solve this, you can start a new thread which executes that function, and update the progress bar in the GUI thread by using a synchronized object like Queue. This is a working example I wrote for a similar question.
Besides, you should call self.counter.set(i) instead of self.counter = i to update the value of the IntVar. Another solution more explicit is self.download_bar.step() with the appropiate increment.
from tkinter import *
from tkinter import ttk
class MainUI:
def __init__(self, parent):
self.parent = parent
self.counter = IntVar()
self.main_container = Frame(self.parent)
self.main_container.pack()
self.btn_start = Button(self.main_container, command=self.startThread)
self.btn_start.configure(
text="Start", background="Grey",
padx=50
)
self.btn_start.pack(side=LEFT)
def progress_bar(self):
self.pbar_top = Toplevel(self.main_container)
self.download_label = Label(
self.pbar_top,
text="Download Bar"
)
self.download_label.pack(side=TOP)
self.download_bar = ttk.Progressbar(
self.pbar_top, orient="horizontal",
length=400, mode="determinate",
variable=self.counter, maximum=5
)
self.download_bar.pack(side=TOP)
def startThread(self):
import threading
def btn_start_click():
self.progress_bar()
for i in range(6):
self.counter.set(i)
import time
time.sleep(1)
t = threading.Thread(None, btn_start_click, ())
t.start()
root = Tk()
root.title("Progress Bar Test")
main_ui = MainUI(root)
root.mainloop()