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
Related
from Tkinter import *
from PIL import Image, ImageTk
import os
class App:
def __init__(self,master):
master.minsize(width=666, height=666)
master["bg"] = "black"
self.button = Button(text="Camera1",command=self.playvid)
self.button.pack(side= "top")
self.img = ImageTk.PhotoImage(Image.open("car.png"))
self.panel = Label(master, image=self.img)
self.panel.pack(side = "bottom", fill = "both", expand = "yes")
def playvid(self):
os.system("gst-launch-1.0 videotestsrc ! autovideosink")
root = Tk()
app = App(root)
root.mainloop()
root.destroy()
This is my code. If I press the s key, I want to stop the os.system process that creates the gstreamer window, i.e. if I press s then it should be equivalent to ctrl + c.
Also is it possible to play the video in tkinter itself?
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()
I am trying to make a large text-entry popup as part of a gui. The idea is to get paragraph-long user-input. The problem is that the method get_big_text() returns before the button is pushed. How can I have a separate window pop-up like this, and be able to save the user's text to a variable in my control program? Everything else in my program has been working out, until I tried to implement this. I am new to gui programming. I get the feeling that there is something fundamentally different about waiting for user input here, but I can't wrap my head around it in the functional context.
My goal is to have the line print(foo.get_big_text()) print the user's text, but of course it prints None because the get_big_text() method finishes.
I have left out the details of the rest of the gui, and wrote an __init__() that probably doesn't need to be there, but this is the basics of how my gui is coming along. The Toplevel widget is the only widget in my gui that is not somehow connected to root.
from tkinter import *
class Gui:
def __init__(self, root):
tframe = Frame(root)
tframe.pack(side='top')
bframe = Frame(root)
bframe.pack(side='bottom')
self.txt = Text(tframe)
self.txt.insert('0.0', 'Totally foobar')
self.txt.pack()
self.btn = Button(bframe, text='OK')
self.btn.pack()
def get_big_text(self, title='', text=''):
popup = Toplevel(height=160, width=180)
popup.title(title)
txtframe = Frame(popup)
txtframe.pack()
big_text = Text(txtframe)
big_text.insert('0.0',text)
big_text.pack()
btnframe = Frame(popup)
btnframe.pack()
grab_text = Button(btnframe)
grab_text.config(text="Done", command=lambda: big_text.get('0.0', 'end'))
grab_text.pack()
root=Tk()
root.title('Example')
foo = Gui(root)
print(foo.get_big_text())
root.mainloop()
You should pass the text to one function in your class and then do whatever you want with it (like printing):
from Tkinter import *
class Gui:
def __init__(self, root):
tframe = Frame(root)
tframe.pack(side='top')
bframe = Frame(root)
bframe.pack(side='bottom')
self.txt = Text(tframe)
self.txt.insert('0.0', 'Totally foobar')
self.txt.pack()
self.btn = Button(bframe, text='OK')
self.btn.pack()
def f(self, text):
print(text)
def get_big_text(self, title='', text=''):
popup = Toplevel(height=160, width=180)
popup.title(title)
txtframe = Frame(popup)
txtframe.pack()
big_text = Text(txtframe)
big_text.insert('0.0',text)
big_text.pack()
btnframe = Frame(popup)
btnframe.pack()
grab_text = Button(btnframe)
grab_text.config(text="Done", command=lambda: self.f(big_text.get('0.0', 'end')))
grab_text.pack()
root=Tk()
root.title('Example')
foo = Gui(root)
foo.get_big_text()
root.mainloop()
If you want to print the text after the gui finished you can do this modifications:
On Gui.f:
def f(self, text):
self.text = text
At the end of your code:
root.mainloop()
print(foo.text)
After some suggestions from #xndrme, and some hard thinking, I realized the solution is quite simple. It's just that I'm not used to programing in this functional style. Really fun to discover this, though.
I wanted the get_big_text() method to return the text so that I could pass it somewhere else and "do something" with it when the text comes. The solution was to pass an anonymous function to the method and "tell it" what should be done with it when it does come.
Note the new callback parameter in get_big_text()
from tkinter import *
class Gui:
def __init__(self, root):
tframe = Frame(root)
tframe.pack(side='top')
bframe = Frame(root)
bframe.pack(side='bottom')
self.txt = Text(tframe)
self.txt.insert('0.0', 'Totally foobar')
self.txt.pack()
self.btn = Button(bframe, text='OK')
self.btn.pack()
def get_big_text(self, callback, title='', text=''):
popup = Toplevel(height=160, width=180)
popup.title(title)
txtframe = Frame(popup)
txtframe.pack()
big_text = Text(txtframe)
big_text.insert('0.0',text)
big_text.pack()
btnframe = Frame(popup)
btnframe.pack()
grab_text = Button(btnframe)
grab_text.config(text="Done", command=lambda:callback(big_text.get('0.0', 'end')))
grab_text.pack()
root=Tk()
root.title('Example')
foo = Gui(root)
foo.get_big_text(lambda x:print(x))
root.mainloop()
The general flow for a dialog is to create the window, then call wait_window to wait until the window has been dismissed by the user. Your function can then return whatever you want.
There's a bit of a chicken-and-egg thing going on, in that you need to get the value from the dialog before the dialog is destroyed since the text widget will be destroyed when the toplevel is destroyed. You do this by explicitly managing the destruction of the window (read: get the value before actually destroying the window).
Here's a working example, trying to preserve as much as code as possible but without using a global import:
import Tkinter as tk
class CustomDialog(object):
def __init__(self, parent, title="Enter a paragraph", default_text=""):
self.parent = parent
self.title = title
self.default = default_text
def show(self):
self.popup = tk.Toplevel(self.parent)
self.popup.title(self.title)
txtframe = tk.Frame(self.popup)
txtframe.pack()
self.big_text = tk.Text(txtframe)
self.big_text.insert('1.0',self.default)
self.big_text.pack()
btnframe = tk.Frame(self.popup)
btnframe.pack()
grab_text = tk.Button(btnframe)
grab_text.config(text="Done", command=self.done)
grab_text.pack()
# make sure our "done" method gets called even if the
# user destroys the window
self.popup.wm_protocol("WM_DELETE_WINDOW", self.done)
# wait for the window to be destroyed
root.wait_window(self.popup)
return self.data
def done(self, *args):
# get the data from the window, then destroy
# the window and return to the caller
self.data = self.big_text.get("1.0", "end-1c")
self.popup.destroy()
class Example(tk.Frame):
def __init__(self, parent):
tk.Frame.__init__(self, parent)
b = tk.Button(self, text="Get Input", command=self.go)
b.pack()
def go(self):
dialog = CustomDialog(self, default_text="totally foobar")
result = dialog.show()
print "result:", result
if __name__ == "__main__":
root = tk.Tk()
Example(root).pack(fill="both", expand=True)
root.mainloop()
Another option that you have is to pass a callback to your dialog, and tie that callback to the "done" button. That way, whenever the user clicks the button, you execute the callback to do whatever you want with the data before destroying the window.
That is how you implement a non-modal dialog, since you don't necessarily have to destroy the window. Font dialogs are a good example of this, where you might want to keep the dialog open for quite a while, and affect whatever is currently selected.
The effbot site has a decent writeup on dialogs. See http://effbot.org/tkinterbook/tkinter-dialog-windows.htm
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()
I'm writing a program to get a video feed from a web cam and display it in a Tkinter window. I wrote the following code which I ran on Ubuntu 12.04.
#!/usr/bin/env python
import sys, os, gobject
from Tkinter import *
import pygst
pygst.require("0.10")
import gst
# Goto GUI Class
class Prototype(Frame):
def __init__(self, parent):
gobject.threads_init()
Frame.__init__(self, parent)
# Parent Object
self.parent = parent
self.parent.title("WebCam")
self.parent.geometry("640x560+0+0")
self.parent.resizable(width=FALSE, height=FALSE)
# Video Box
self.movie_window = Canvas(self, width=640, height=480, bg="black")
self.movie_window.pack(side=TOP, expand=YES, fill=BOTH)
# Buttons Box
self.ButtonBox = Frame(self, relief=RAISED, borderwidth=1)
self.ButtonBox.pack(side=BOTTOM, expand=YES, fill=BOTH)
self.closeButton = Button(self.ButtonBox, text="Close", command=self.quit)
self.closeButton.pack(side=RIGHT, padx=5, pady=5)
gotoButton = Button(self.ButtonBox, text="Start", command=self.start_stop)
gotoButton.pack(side=RIGHT, padx=5, pady=5)
# Set up the gstreamer pipeline
self.player = gst.parse_launch ("v4l2src ! video/x-raw-yuv,width=640,height=480 ! ffmpegcolorspace ! xvimagesink")
bus = self.player.get_bus()
bus.add_signal_watch()
bus.enable_sync_message_emission()
bus.connect("message", self.on_message)
bus.connect("sync-message::element", self.on_sync_message)
def start_stop(self):
if self.gotoButton["text"] == "Start":
self.gotoButton["text"] = "Stop"
self.player.set_state(gst.STATE_PLAYING)
else:
self.player.set_state(gst.STATE_NULL)
self.gotoButton["text"] = "Start"
def on_message(self, bus, message):
t = message.type
if t == gst.MESSAGE_EOS:
self.player.set_state(gst.STATE_NULL)
self.button.set_label("Start")
elif t == gst.MESSAGE_ERROR:
err, debug = message.parse_error()
print "Error: %s" % err, debug
self.player.set_state(gst.STATE_NULL)
self.button.set_label("Start")
def on_sync_message(self, bus, message):
if message.structure is None:
return
message_name = message.structure.get_name()
if message_name == "prepare-xwindow-id":
# Assign the viewport
imagesink = message.src
imagesink.set_property("force-aspect-ratio", True)
imagesink.set_xwindow_id(self.movie_window.window.xid)
def main():
root = Tk()
app = Prototype(root)
app.pack(expand=YES, fill=BOTH)
root.mainloop()
if __name__ == '__main__':
main()
My problem is neither the ButtonBox nor the VideoBox show in the output window when the program is running. How can I fix this? I did look at other sites for possible solutions (for instance http://pygstdocs.berlios.de/#projects or Way to play video files in Tkinter?) however they have very limited information on what their code means.
After making the suggested alteration and a few others to get the buttons working, I realize that the display window is different from the main window when I run the program. Is there a way to get the video to display in the main window when using tkinter??
It looks like your Prototype class is a Tkinter Frame but you don't seem to have packed/placed it anywhere.
...
app = Prototype(root)
app.pack(expand=YES, fill=BOTH)
root.mainloop()
I finally came up with a solution to the question. I realised that the error was in the line
imagesink.set_xwindow_id(self.movie_window.window.xid)
which I changed to
imagesink.set_xwindow_id(self.movie_window.winfo_id())
The mistake is that I had used window.xid which is an attribute for gtk widgets. In tkinter winfo_id() returns the window identifier for tkinter widgets. For more information http://effbot.org/tkinterbook/widget.htm#Tkinter.Widget.winfo_id-method