I'm creating a GUI to allow users to see a "live view" of a spectrometer where data is taken from the spectrometer and plotted in Matplotlib to be displayed in the GUI window. The GUI also has a few other buttons which allow the user to go through other functions (irrelevant but just background).
I've gotten the live view to work in matplotlib using a while loop and clearing the data to re-plot:
while True:
data = ccs.take_data(num_avg=3) # spectrometer function
norm = (data[0]-dark[0])/(light[0]-dark[0]) # some calcs.
plt.plot(data[1],norm)
plt.axis([400,740,0,1.1])
plt.grid(color='w', linestyle='--')
plt.xlabel('Wavelength [nm]')
plt.ylabel('Normalized Intesity')
plt.pause(0.1)
plt.cla()
Next step was to show this figure in PySimpleGUI. Harder than expexted... I was able to use a few demo codes from PySimpleGUI to get a single figure to appear and update if user presses 'update' button:
from instrumental.drivers.spectrometers import thorlabs_ccs
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
import PySimpleGUI as sg
import matplotlib, time, threading
matplotlib.use('TkAgg')
import matplotlib.pyplot as plt
def fig_maker(ccs, dark, sub):
plt.clf()
plt.close()
data = ccs.take_data(num_avg=3)
norm = (data[0]-dark[0])/(sub[0]-dark[0])
plt.plot(data[1],norm,c='r')
plt.axis([400,750,0,1.1])
plt.grid(color='w', linestyle='--')
plt.xlabel('Wavelength [nm]')
plt.ylabel('Normalized Intesity')
return plt.gcf()
def draw_figure(canvas, figure, loc=(0, 0)):
figure_canvas_agg = FigureCanvasTkAgg(figure, canvas)
figure_canvas_agg.draw()
figure_canvas_agg.get_tk_widget().pack(side='top', fill='both', expand=1)
return figure_canvas_agg
def delete_fig_agg(fig_agg):
fig_agg.get_tk_widget().forget()
plt.close('all')
if __name__ == '__main__':
... some code ...
# define the window layout
layout = [[sg.Button('update')],
[sg.Text('Plot test', font='Any 18')],
[sg.Canvas(size=(500,500), key='canvas')] ]
# create the form and show it without the plot
window = sg.Window('Demo Application - Embedding Matplotlib In PySimpleGUI',
layout, finalize=True)
fig_agg = None
while True:
event, values = window.read()
if event is None: # if user closes window
break
if event == "update":
if fig_agg is not None:
delete_fig_agg(fig_agg)
fig = fig_maker(ccs,dark,sub)
fig_agg = draw_figure(window['canvas'].TKCanvas, fig)
window.close()
Now for the fun part (I can't seem to get it to work). I would like the plot to always be updating similar to how I did it using just matplotlib so that the user doesn't have to press 'update'. Using PySimpleGUI long_task threaded example is where my program starts to fail. I don't actually get any errors thrown except for a print to the Debug I/O stating *** Faking Timeout *** before Python closes the script.
I even just tried to do a for loop of 10 iterations instead of continuous while loop:
from instrumental.drivers.spectrometers import thorlabs_ccs
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
import PySimpleGUI as sg
import matplotlib, time, threading
matplotlib.use('TkAgg')
import matplotlib.pyplot as plt
def long_function_thread(window, ccs, dark, sub):
for i in range(10):
fig = fig_maker(ccs, dark, sub)
fig_agg = draw_figure(window['canvas'].TKCanvas, fig)
window.write_event_value('-THREAD PROGRESS-', i)
time.sleep(1)
delete_fig_agg(fig_agg)
time.sleep(0.1)
window.write_event_value('-THREAD DONE-', '')
def long_function(window, ccs, dark, sub):
print('In long_function')
threading.Thread(target=long_function_thread, args=(window, ccs, dark, sub), daemon=True).start()
def fig_maker(ccs, dark, sub):
plt.clf()
plt.close()
data = ccs.take_data(num_avg=3)
norm = (data[0]-dark[0])/(sub[0]-dark[0])
plt.plot(data[1],norm,c='r')
plt.axis([400,750,0,1.1])
plt.grid(color='w', linestyle='--')
plt.xlabel('Wavelength [nm]')
plt.ylabel('Normalized Intesity')
return plt.gcf()
def draw_figure(canvas, figure, loc=(0, 0)):
figure_canvas_agg = FigureCanvasTkAgg(figure, canvas)
figure_canvas_agg.draw()
figure_canvas_agg.get_tk_widget().pack(side='top', fill='both', expand=1)
return figure_canvas_agg
def delete_fig_agg(fig_agg):
fig_agg.get_tk_widget().forget()
plt.close('all')
if __name__ == '__main__':
... some code ...
# define the window layout
layout = [[sg.Button('Go')],
[sg.Text('Plot test', font='Any 18')],
[sg.Canvas(size=(500,500), key='canvas')] ]
# create the form and show it without the plot
window = sg.Window('Demo Application - Embedding Matplotlib In PySimpleGUI',
layout, finalize=True)
fig_agg = None
while True:
event, values = window.read()
if event is None or event == 'Exit':
break
if event == 'Go':
print('Calling plotter')
long_function(window, ccs, dark, sub)
print('Long function has returned from starting')
elif event == '-THREAD DONE-':
print('Your long operation completed')
window.close()
Appologies on the long description and code dump but I thought this is the easiest way to explain. Any help or links on this issue would be greatly appreciated.
If someone wants to try and run my script this should just produce a random plot instead
def random_fig_maker():
plt.scatter(np.random.rand(1,10),np.random.rand(1,10))
return plt.gcf()
You need to use two additional PySimpleGUI features: window.Refresh() and window.write_event_value(). When you deleted figg_agg and the new plot is ready, call window.Refresh(). This will redraw the window, but also introduces a problem: the main event (while) loop will keep running forever. To address this, you also need to add window.write_event_value('-THREAD-', 'some message.') to one of the functions that is called from within the event loop. This will act as an external trigger for the event loop to keep running, but this will also keep the window responsive, so you can change some other window element (here I used a radio switch) to stop the loop.
For bonus points, you can also run the "trigger function" as a separate thread. Then, time.sleep() in that function will not affect GUI responsiveness. Because of this, I would use some data gathering function that only returns some lists or tuples as the trigger for restarting the loop. In this case, matplotlib was unhappy about being called from an external thread, so I just added a delay in the event loop to keep the plot visible.
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
import PySimpleGUI as sg
import matplotlib, time, threading
matplotlib.use('TkAgg')
import matplotlib.pyplot as plt
import numpy as np
def fig_maker(window): # this should be called as a thread, then time.sleep() here would not freeze the GUI
plt.scatter(np.random.rand(1,10),np.random.rand(1,10))
window.write_event_value('-THREAD-', 'done.')
time.sleep(1)
return plt.gcf()
def draw_figure(canvas, figure, loc=(0, 0)):
figure_canvas_agg = FigureCanvasTkAgg(figure, canvas)
figure_canvas_agg.draw()
figure_canvas_agg.get_tk_widget().pack(side='top', fill='both', expand=1)
return figure_canvas_agg
def delete_fig_agg(fig_agg):
fig_agg.get_tk_widget().forget()
plt.close('all')
if __name__ == '__main__':
# define the window layout
layout = [[sg.Button('update'), sg.Button('Stop', key="-STOP-"), sg.Button('Exit', key="-EXIT-")],
[sg.Radio('Keep looping', "RADIO1", default=True, size=(12,3),key="-LOOP-"),sg.Radio('Stop looping', "RADIO1", size=(12,3), key='-NOLOOP-')],
[sg.Text('Plot test', font='Any 18')],
[sg.Canvas(size=(500,500), key='canvas')]]
# create the form and show it without the plot
window = sg.Window('Demo Application - Embedding Matplotlib In PySimpleGUI',
layout, finalize=True)
fig_agg = None
while True:
event, values = window.read()
if event is None: # if user closes window
break
if event == "update":
if fig_agg is not None:
delete_fig_agg(fig_agg)
fig = fig_maker(window)
fig_agg = draw_figure(window['canvas'].TKCanvas, fig)
if event == "-THREAD-":
print('Acquisition: ', values[event])
time.sleep(1)
if values['-LOOP-'] == True:
if fig_agg is not None:
delete_fig_agg(fig_agg)
fig = fig_maker(window)
fig_agg = draw_figure(window['canvas'].TKCanvas, fig)
window.Refresh()
if event == "-STOP-":
window['-NOLOOP-'].update(True)
if event == "-EXIT-":
break
window.close()
It's not exactly connected but I had a similar problem. Does this help....
import PySimpleGUI as sg
import matplotlib.pyplot as plt
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
import numpy as np
class updateable_matplotlib_plot():
def __init__(self, canvas) -> None:
self.fig_agg = None
self.figure = None
self.canvas = canvas
def plot(self, data):
self.data = data
self.figure_controller()
self.figure_drawer()
#put all of your normal matplotlib stuff in here
def figure_controller(self):
#first run....
if self.figure is None:
self.figure = plt.figure()
self.axes = self.figure.add_subplot(111)
self.line, = self.axes.plot(self.data)
self.axes.set_title("Example of a Matplotlib plot updating in PySimpleGUI")
#all other runs
else:
self.line.set_ydata(self.data)#update data
self.axes.relim() #scale the y scale
self.axes.autoscale_view() #scale the y scale
#finally draw the figure on a canvas
def figure_drawer(self):
if self.fig_agg is not None: self.fig_agg.get_tk_widget().forget()
self.fig_agg = FigureCanvasTkAgg(self.figure, self.canvas.TKCanvas)
self.fig_agg.get_tk_widget().pack(side='top', fill='both', expand=1)
self.fig_agg.draw()
def getGUI():
# All the stuff inside your window.
layout = [ [sg.Canvas(size=(500,500), key='canvas')],
[sg.Button('Update', key='update'), sg.Button('Close')] ]
# Create the Window
window = sg.Window('Updating a plot example....', layout)
return window
if __name__ == '__main__':
window = getGUI()
spectraPlot = updateable_matplotlib_plot(window['canvas']) #what canvas are you plotting it on
window.finalize() #show the window
spectraPlot.plot(np.zeros(1024)) # plot an empty plot
while True:
event, values = window.read()
if event == "update":
some_spectrum = np.random.random(1024) # data to be plotted
spectraPlot.plot(some_spectrum) #plot the data
if event == sg.WIN_CLOSED or event == 'Close': break # if user closes window or clicks cancel
window.close()
hey i am making a program which records desktop screen.
So what i want to do is when ever i click on my start button(tkinter Gui) in my gui window.
It should start a timer like 3.... ,2.... ,1.... in big font directly on my desktop screen and not on my tkinter window. and then my function should start.
How can i do that ..
import tkinter as tk
from tkinter import *
root = Tk()
root.title("our program")
start_cap =tk.button(text='start recording' command=start_capute)
start_cap.pack()
root.mainloop()
Not mentioning the functions and the entire code here as not necessary the code is working fine and i just want to add a new feature of the timer in it.
An minimal example:
import tkinter as tk
# from tkinter import *
def Start():
def Count(Number):
if Number == -1:
win.withdraw()
print("Start") # what you want to do
return False
NumberLabel["text"] = Number
win.after(1000,Count,Number-1)
screen_width = root.winfo_screenwidth()
screen_height = root.winfo_screenheight()
win = tk.Toplevel()
win.geometry("+%d+%d"%((screen_width-win.winfo_width())/2,(screen_height-win.winfo_height())/2)) # make it in the center.
win.overrideredirect(1)
win.wm_attributes('-topmost',1) # top window
win.wm_attributes('-transparentcolor',win['bg']) # background transparent.
NumberLabel = tk.Label(win,font=("",40,"bold"),fg='white')
NumberLabel.pack()
win.after(0,lambda :Count(3))
win.mainloop()
root = tk.Tk()
root.title("our program")
start_cap = tk.Button(text='start recording',command=Start)
start_cap.pack()
root.mainloop()
I'm working at a project in Python3 in which i have both tkinter and a frame in tkinter with cef browser.
This is the code.
from tkinter import messagebox
#import threading
from cefpython3 import cefpython as cef
import platform
import sys
from tkinter import *
import time
def on_closing ():
print('closing')
r.destroy()
cef.Shutdown()
r = Tk()
r.geometry('800x600')
r.protocol('WM_DELETE_WINDOW', on_closing)
f = Frame(r, bg = 'blue', height = 200)
f.pack(side = TOP, fill = 'x')
g=Frame(r,bg = 'white',height = 200)
g.pack(side = TOP, fill = 'x')
b1 = Button (g,text='Exit',command = on_closing)
b1.pack (side = LEFT)
b2 = Button (g,text='Show something',command = lambda:messagebox.showinfo('TITLE', 'Shown something'))
b2.pack (side = RIGHT)
sys.excepthook = cef.ExceptHook
rect = [0, 0, 800, 200]
print('browser: ', rect[2],'x',rect[3])
window_info=cef.WindowInfo(f.winfo_id())
window_info.SetAsChild(f.winfo_id(),rect)
cef.Initialize()
browser = cef.CreateBrowserSync(window_info, url='http://www.google.com')
r.update()
cef.MessageLoop()
##_thread = threading.Thread(target=cef.MessageLoop)
##
##_thread.start()
##
##_thread.join()
r.mainloop()
print('end')
The problem is:
I leave cef.MessageLoop() and the browser works but buttons don't.
I comment out cef.MessageLoop() and the browser doesn't work but
tkinter window does.
I was thinking that maybe threading module wuold help but i tried (you can see the commented lines) and doesn't work (i get no exceptions but browser don't work).
How can i sort this out?
Tkinter runs in a single thread so when you write what is basically an infinite loop inside of it then you will block Tkinter from working. The only reason you screen is coming up at all is because you used update() but that will not fix the issue here.
The solution will be to use threading to manage the MessageLoop in a separate thread while also passing the frame to the function to allow for some interaction between Tkinter and cef.
Note: I also cleaned up your code a bit to better follow PEP8 standards.
import tkinter as tk
from tkinter import messagebox
from cefpython3 import cefpython as cef
import threading
import sys
def test_thread(frame):
sys.excepthook = cef.ExceptHook
window_info = cef.WindowInfo(frame.winfo_id())
window_info.SetAsChild(frame.winfo_id(), rect)
cef.Initialize()
browser = cef.CreateBrowserSync(window_info, url='http://www.google.com')
cef.MessageLoop()
def on_closing():
print('closing')
root.destroy()
root = tk.Tk()
root.geometry('800x600')
root.protocol('WM_DELETE_WINDOW', on_closing)
frame = tk.Frame(root, bg='blue', height=200)
frame2 = tk.Frame(root, bg='white', height=200)
frame.pack(side='top', fill='x')
frame2.pack(side='top', fill='x')
tk.Button(frame2, text='Exit', command=on_closing).pack(side='left')
tk.Button(frame2, text='Show something',
command=lambda: messagebox.showinfo('TITLE', 'Shown something')).pack(side='right')
rect = [0, 0, 800, 200]
print('browser: ', rect[2], 'x', rect[3])
thread = threading.Thread(target=test_thread, args=(frame,))
thread.start()
root.mainloop()
Results:
I am using tkinter and winsound.
I want the sound and the countdown timer to work simultaneously.
Right now, once the clicking sound is over the timer appears.
I have seen some countdown timers examples which use "after". Ex: self.after(1000, self.countdown). But I need both in simoultaneous.
import tkinter as tk
from tkinter import Tk
from nBackTools.NBackTools import *
from nBackTools.ZBack import *
#To play sounds
import winsound
from winsound import *
import numpy as np
class NBack:
def __init__(self, master):
##Title of the window
self.master = master
master.title("N-Back")
##It measures the screen size (width x height + x + y)
##The opened window will be based on the screen size
master.geometry("{0}x{1}-0+0".format(master.winfo_screenwidth(), master.winfo_screenheight()))
self.canvas = tk.Canvas(master, width=master.winfo_screenwidth(), height=master.winfo_screenheight(), \
borderwidth=0, highlightthickness=0, bg="grey")
self.canvasWidth = master.winfo_screenwidth()
self.canvasHeight = master.winfo_screenheight()
##If removed, a white screen appears
self.canvas.grid()
"""
BREAK TIMER
"""
self.play()
self.canvas.create_text(((self.canvasWidth/2), (self.canvasHeight/2)-130), text="LET'S TAKE A BREAK!", font=(None, 90))
self.display = tk.Label(master, textvariable="")
self.display.config(foreground="red", background = "grey", font=(None, 70), text= "00:00")
self.display.grid(row=0, column=0, columnspan=2)
def play(self):
return PlaySound('clock_ticking.wav', SND_FILENAME)
root = Tk()
my_gui = NBack(root)
root.mainloop()
Doing 2 things at once is called "asynchronous". To enable that mode in winsound you need the ASYNC flag:
def play(self):
PlaySound('clock_ticking.wav', SND_FILENAME | SND_ASYNC)
You will still need to use after to get the countdown to work.
I am starting a QProcess and would like to bring the resulting window on top everytime a command is written to QProcess.
However, if my process is "gnuplot.exe" (Win7) the plot window will be updated but always stays behind the PyQt window. I haven't yet found a way to bring it to front, or make it active, or put the focus on, or however you would call it.
Something like
self.process. ...
raise(), show(), activateWindow(), SetForegroundWindow, WindowStaysOnTopHint ... ???
These posts didn't help me further
How to find the active PyQt window and bring it to the front
https://forum.qt.io/topic/30018/solved-bring-to-front-window-application-managed-with-qprocess/3
Bring QProcess window to front (running Qt Assistant)
Here is the code:
import sys
from PyQt5.QtCore import QProcess, pyqtSlot
from PyQt5.QtWidgets import QApplication, QWidget, QPushButton
import subprocess
class MyWindow(QWidget):
def __init__(self):
super(MyWindow,self).__init__()
self.setGeometry(100,100,500,500)
self.button1 = QPushButton('New plot',self)
self.button1.clicked.connect(self.button1_clicked)
self.process = QProcess(self)
self.process.start(r'C:\Programs\gnuplot\bin\gnuplot.exe', ["-p"])
self.n = 1
def button1_clicked(self):
plotcmd = "plot x**"+str(self.n)+"\n"
self.process.write(plotcmd.encode())
self.n += 1
response = self.process.readAllStandardOutput()
print(str(response, encoding="utf-8"))
if __name__ == '__main__':
app = QApplication(sys.argv)
app.setStyle("plastique")
window = MyWindow()
window.show()
sys.exit(app.exec_())
Based on the comment and links from #buck54321, I got to the following version which seems to (almost) work.
The solution in the above links seem to be more general and a bit too complicated for my case, since I know the exact name of my window which I want to bring to front.
To my original code I just had to add three lines. I was not familiar with win32gui.
However, with my code, only after the button is pressed the second time the window will be brought to front. After the first button press hwnd is still 0 which leads to a crash. I guess this is because gnuplot.exe will start without opening a window, and opens a window only after the first command has been sent.
If anybody can show me how to bring the window to front even after the first button click that would be great.
If there are limitation or improvements I would be happy to learn about them.
Here is the code:
import sys
from PyQt5.QtCore import QProcess, pyqtSlot
from PyQt5.QtWidgets import QApplication, QWidget, QPushButton
import subprocess
import win32gui ####### new line
class MyWindow(QWidget):
def __init__(self):
super(MyWindow,self).__init__()
self.setGeometry(100,100,500,500)
self.button1 = QPushButton('New plot',self)
self.button1.clicked.connect(self.button1_clicked)
self.process = QProcess(self)
self.process.start(r'C:\Programs\gnuplot\bin\gnuplot.exe', ["-p"])
self.n = 1
def button1_clicked(self):
plotcmd = "set term wxt 999\n plot x**"+str(self.n)+"\n"
self.process.write(plotcmd.encode())
self.n += 1
hwnd = win32gui.FindWindow(None, "Gnuplot (window id : 999)") ####### new line
response = self.process.readAllStandardOutput()
print(str(response, encoding="utf-8"))
if hwnd != 0: win32gui.SetForegroundWindow(hwnd) ####### new line
if __name__ == '__main__':
app = QApplication(sys.argv)
app.setStyle("plastique")
window = MyWindow()
window.show()
sys.exit(app.exec_())