I'm using TKinter to draw a GUI for a python program im making, and I have it updating at about 200ms, but when the program queries the data it locks the program because it takes a second to get the data. I tried to write it into multi processing so each query would be its own process and just share the info with global variables because my program is a real time program that uses wmi to get performance data. At least thats what I have so far. Not the end goal just the start. So if you could help me figure out why even with multiprocessing if it queries the info while I'm dragging the app across the screen it will freeze for a second.
import wmi
import time
import Tkinter as tk
from multiprocessing import cpu_count
import Image
from PIL import ImageTk
from Tkinter import Button, Label
import threading
from multiprocessing import Process, Value, Array
window = Tk();
global pct_in_use
global available_mbytes
global utilization
global hours_up
a= 0
b=0
def build_labels(gui, string):
var = StringVar()
label = Label( gui, textvariable=var, relief=RAISED )
var.set(string)
return label
def get_uptime():
global hours_up
c = wmi.WMI()
secs_up = int([uptime.SystemUpTime for uptime in c.Win32_PerfFormattedData_PerfOS_System()][0])
hours_up = secs_up / 3600
return hours_up
def get_cpu():
global utilization
c = wmi.WMI()
utilizations = [cpu.LoadPercentage for cpu in c.Win32_Processor()]
utilization = int(sum(utilizations) / len(utilizations)) # avg all cores/processors
return utilization
def get_mem_mbytes():
global available_mbytes
c = wmi.WMI()
available_mbytes = int([mem.AvailableMBytes for mem in c.Win32_PerfFormattedData_PerfOS_Memory()][0])
return available_mbytes
def get_mem_pct():
global pct_in_use
c = wmi.WMI()
pct_in_use = int([mem.PercentCommittedBytesInUse for mem in c.Win32_PerfFormattedData_PerfOS_Memory()][0])
return pct_in_use
def Draw():
global mem_per_lb
global cpu_lb
global up_time_lb
global mb_used_lb
mem_pct = 0
mem_per_lb = tk.Label(text='Memory % ' + str(mem_pct))
mem_per_lb.place(x=10, y=10)
cpu = 0
cpu_lb = tk.Label(text='CPU % ' + str(cpu))
cpu_lb.place(x=10, y=30)
mem_pct = 0
up_time_lb = tk.Label(text='UP Time % ' + str(mem_pct))
up_time_lb.place(x=10, y=50)
mem_pct = 0
mb_used_lb = tk.Label(text='Memory MB ' + str(mem_pct))
mb_used_lb.place(x=10, y=70)
def Refresher():
global mem_per_lb
global cpu_lb
global up_time_lb
global mb_used_lb
mem_pct = get_mem_pct()
cpu = get_cpu()
up_time = get_uptime()
mbused = get_mem_mbytes()
window.wm_title('Vision' + time.asctime())
mem_per_lb.configure(text='Memory % ' + str(pct_in_use))
cpu_lb.configure(text='CPU ' + str(utilization))
up_time_lb.configure(text='UP Time ' + str(hours_up))
mb_used_lb.configure(text='Memory MB ' + str(available_mbytes))
window.after(200, Refresher) # every second...
def draw_window(): #creates a window
window.geometry('704x528+100+100')
image = Image.open('bg.jpg') #gets image (also changes image size)
image = image.resize((704, 528))
imageFinal = ImageTk.PhotoImage(image)
label = Label(window, image = imageFinal) #creates label for image on window
label.pack()
label.place(x = a, y = b) #sets location of label/image using variables 'a' and 'b'
Draw()
Refresher()
window.mainloop()
up_time_p = Process(target=get_uptime())
cpu_p = Process(target=get_cpu())
mb_p = Process(target=get_mem_mbytes())
pct_p = Process(target=get_mem_pct())
win_p = Process(target=draw_window())
up_time_p.start()
mb_p.start()
pct_p.start()
cpu_p.start()
win_p.start()
up_time_p = Process(target=get_uptime())
cpu_p = Process(target=get_cpu())
mb_p = Process(target=get_mem_mbytes())
pct_p = Process(target=get_mem_pct())
win_p = Process(target=draw_window())
I don't think you're supposed to include parentheses when you supply targets to a process. If you do that, the functions will execute in the main thread, and whatever those functions return will become the target.
up_time_p = Process(target=get_uptime)
cpu_p = Process(target=get_cpu)
mb_p = Process(target=get_mem_mbytes)
pct_p = Process(target=get_mem_pct)
win_p = Process(target=draw_window)
As per Kevin's answer, you're calling the functions when you create each process instance. So they are all actually running in the main process.
However, once you fix that problem your 'global' variables aren't going to work as you expect. When a process is created it takes a COPY of the parent processes memory. Any changes to that memory are not shared between the processes.
To achieve the result you want you'll have to use Python's threading library. Not the multiprocess library.
Threads share the same memory space as the parent process. Which can lead to its own problems. Though in your case the global variables you're changing are just integer constants so it should be okay.
from threading import Thread
data_funcs = (
get_uptime,
get_cpu,
get_mem_mbytes,
get_mem_pct,
draw_window
)
threads = [Thread(target=f) for f in data_funcs]
for t in threads:
t.start()
Is the general pattern you should use. You'll then have to figure out a way of killing those threads when you shut down the main process or it will hang.
Related
Title, it returns the error "_tkinter.TclError: image "pyimage3" doesn't exist" I have tried changing the root = tk.Tk
() to root = tk.TopLevel() as suggested by previous users who received the same error.
The script goes through a list of image files on a CSV file, the idea is to create a slideshow and the CSV file saves the formats of the different slides.
The "iam" if statement refers to an "image and message" slide which is what's currently drawing the errors. I have tried opening the image using the same calls as the regular image slide type but it crashes in a new and unique way every time.
I can post more information as needed but if anybody has any ideas as to how I could fix this I would love to hear them.
# import required modules
import tkinter as tk
from tkinter import *
from PIL import Image
from PIL import ImageTk
import pandas as pd
import datetime
import display_save
# Initialize tkinter window
root = tk.Tk()
# Retreive data table
frame = pd.read_csv("data.csv")
# Establish variables
show_size = len(frame.index)
img = ImageTk.PhotoImage(Image.open("teapot.png"))
bg_img = ImageTk.PhotoImage(Image.open("teapot.png"))
time_step = 1
# Initialize image label as empty
img_lbl = Label(root)
img_lbl.pack()
# Initialize text label
txt_lbl=Label(root)
txt_lbl.pack()
img_txt_lbl = Label(root)
img_txt_lbl.pack()
# Keypress event management
res_lbl = Label(root)
def keypress(event):
global res_lbl
if(event.char == "f"):
root.attributes('-fullscreen', False)
elif(event.char == "r"):
res_lbl.pack()
res_lbl.config(text= str(root.winfo_width()) + " x " + str(root.winfo_height()))
def keyrelease(event):
global res_lbl
if (event.char == "f"):
root.attributes('-fullscreen', True)
elif (event.char == "r"):
res_lbl.pack_forget()
# bind key events
root.bind("<KeyPress>",keypress)
root.bind("<KeyRelease>",keyrelease)
x = 0
# Function to rotate images
def runtime():
global x
global img
global img_lbl
global txt_lbl
global img_txt_lbl
global bg_img
if(x <= show_size):
df = pd.read_csv('data.csv')
df = df.iloc[[x - 1]]
t = df.iloc[0]['type']
date_remv = df.iloc[0]['date_remove']
# If type is image, initialize
if(t == "img"):
img_lbl.pack()
txt_lbl.pack_forget()
img_txt_lbl.pack_forget()
root.config(bg='white')
p = df.iloc[0]['data']
temp = Image.open(p)
temp = temp.resize((root.winfo_width(), root.winfo_height()))
img = ImageTk.PhotoImage(temp)
# If type is message, initialize
elif (t == "msg"):
txt_lbl.pack()
img_lbl.pack_forget()
img_txt_lbl.pack_forget()
m = df.iloc[0]['data']
c = df.iloc[0]['data2']
txt_lbl.config(bg =c, text=m, anchor=CENTER, height=20, wraplength=1000, font=("Arial", 50))
root.config(bg=c)
# If type is an image and a message, initialize
elif (t == "iam"):
img_txt_lbl.pack()
txt_lbl.pack_forget()
img_lbl.pack_forget()
p = df.iloc[0]['data']
temp = Image.open("teapot.png")
temp = temp.resize((root.winfo_screenwidth(), root.winfo_screenheight()))
temp = ImageTk.PhotoImage(temp)
bg_img = temp
m = df.iloc[0]['data2']
img_txt_lbl.config(text=m, height=root.winfo_screenheight(), width=root.winfo_screenwidth(), wraplength=1000, font=("Arial", 50), compound='center')
root.config(bg='white')
# Check to make sure the slides list is up-to date
if(datetime.datetime.strptime(date_remv, display_save.format) <= datetime.datetime.now()):
index = df.iloc[0]['id']
display_save.delete_row(index)
root.after(time_step * 1000, runtime)
else:
x = 0
root.after(0, runtime)
x = x + 1
img_lbl.config(image=img)
img_txt_lbl.config(image=bg_img)
runtime()
root.attributes('-fullscreen', True)
root.mainloop()
Whenever you create a new image using ImageTk.PhotoImage() you have to make sure that image variable stays during the entire time your code runs. This means for example if you create the first image in a variable img then you must not use the SAME variable for a new image because once the old image is rewritten it's gone. So the trick here is to store the old image somewhere where it doesn't change. To do this simply append the old image to a new list where you can store all PHOTOIMAGEs as a memory. I recently answered the same type of question here and there might be a better explanation: How to create multiple images in tkinter that can scale.
I haven't tested if this works but I believe it should work.
I'm doing a project where I read info from a socket and then intend to display it on a gui using tkinter. The thing is, my read info from socket is a loop and for the gui I need another loop.
I'm pretty inexperienced with both Python and Tkinter, which probably explains my mistake here.
The fd_dict is a dictionary with the properties and respective values of a car ex: gear, power, speed, etc (theme of my project).
The main problem is either I get the values from the socket or I display the gui, never both obviously, since it stays on the earlier loop.
while True:
# UDP server part of the connection
message, address = server_socket.recvfrom(1024)
del address
fdp = ForzaDataPacket(message)
fdp.wall_clock = dt.datetime.now()
# Get all properties
properties = fdp.get_props()
# Get parameters
data = fdp.to_list(params)
assert len(data) == len(properties)
# Zip into a dictionary
fd_dict = dict(zip(properties, data))
# Add timestamp
fd_dict['timestamp'] = str(fdp.wall_clock)
# Print of various testing values
print('GEAR: ', fd_dict['gear'])
print('SPEED(in KMH): ', fd_dict['speed'] * 3.6) #speed in kph
print('POWER(in HP): ', fd_dict['power'] * 0.0013596216173) #power in hp
#print('PERFORMANCE INDEX: ', fd_dict['car_performance_index'])
print('\n')
The tkinter code:
window = Tk()
window.title('Forza Horizon 5 Telemetry')
window.geometry("1500x800")
window.configure(bg="#1a1a1a")
frame = Frame(window)
frame.pack()
label_gear = Label(text = '0')
label_gear.configure(bg="darkgrey")
label_gear.pack()
I read about using after() and using classes, but I've never used them, and can't figure out how to apply them here.
Thanks in advance.
I have designed and created a program using Python 3 that reads unread messages in my Gmail inbox under two labels.
By using tkinter I have two lovely boxes that display the total messages in each label. One for sales of one particular product and the other for another.
They use the update loop to recheck each label every few seconds.
Then after the business day, I use a cleanup script in Gmail that flushes the inboxes two labels.
The program is for use on my team's sales floor. They can see daily, the number of sales, and get a real-time readout to the success of certain marketing campaigns. It has done wonders for morale.
Now I would like to implement some sounds when sales go up. A cliche bell ring, a "chhaaaching" perhaps, you get the drift.
So, I am currently tackling with my limited knowledge and have searched all throughout StackOverflow and other sites for an answer. My guess is that I need something like the following...
"if an integer value changes on the next loop from it's previous value, by an increment of 1 play soundA, or, play soundB if that value decreases by 1."
I can't for the life of me figure out what would be the term for 'increases by 1', I am also clueless on how to attach a sound to any changes made to the integer on the proceeding loop. Help!!
If I wasn't clear enough, I am more than happy to explain and go into this further.
Thank you so much guys.
Here is my code as it stands so far...
#! /usr/bin/python3
import imaplib
import email
import tkinter as tk
WIDTH = 1000
HEIGHT = 100
def update():
mail=imaplib.IMAP4_SSL('imap.gmail.com',993)
mail.login('email#gmail.com','MyPassword')
mail.select("Submissions")
typ, messageIDs = mail.search(None, "UNSEEN")
FirstDataSetSUBS = str(messageIDs[0], encoding='utf8')
if FirstDataSetSUBS == '':
info1['text'] = 'no submissions'
else:
SecondDataSetSUBS = FirstDataSetSUBS.split(" ")
nosubs = len(SecondDataSetSUBS)
nosubs = int(nosubs)
info1['text'] = '{} submission[s]'.format(nosubs)
subs.after(1000, update)
def update_2():
mail=imaplib.IMAP4_SSL('imap.gmail.com',993)
mail.login('email#gmail.com','MyPassword')
mail.select("Memberships")
typ, messageIDs = mail.search(None, "UNSEEN")
FirstDataSetMSGS = str(messageIDs[0], encoding='utf8')
if FirstDataSetMSGS == '':
info2['text'] = 'no memberships'
else:
SecondDataSetMSGS = FirstDataSetMSGS.split(" ")
memberships = len(SecondDataSetMSGS)
memberships = int(memberships)
info2['text'] = '{} membership[s]'.format(memberships)
membs.after(1000, update_2)
membs = tk.Tk()
subs = tk.Tk()
membs.title('memberships counter')
membs.configure(background="black")
subs.title('submissions counter')
subs.configure(background="black")
x = (subs.winfo_screenwidth()//5) - (WIDTH//5)
y = (subs.winfo_screenheight()//5) - (HEIGHT//5)
subs.geometry('{}x{}+{}+{}'.format(WIDTH, HEIGHT, x, y))
info1 = tk.Label(subs, text='nothing to display', bg="black", fg="green", font="Lucida_Console 40")
info1.pack()
x = (membs.winfo_screenwidth()//2) - (WIDTH//2)
y = (membs.winfo_screenheight()//2) - (HEIGHT//2)
membs.geometry('{}x{}+{}+{}'.format(WIDTH, HEIGHT, x, y))
info2 = tk.Label(membs, text='nothing to display', bg="black", fg="red", font="Lucida_Console 40")
info2.pack()
update()
update_2()
membs.mainloop
subs.mainloop()
for playing audio you can use pyaudio module
in ubuntu, install by pip3 install pyaudio include sudo if required
import pygame
pygame.init()
increased=0
inc_music=pygame.mixer.music
dec_music=pygame.mixer.music
inc_music.load("/home/pratik/Documents/pos.wav")
dec_music.load("/home/pratik/Documents/neg.wav")
def get_inc():
global increased
a=increased
return a
def pl():
global inc_music
global dec_music
while True:
increased=get_inc()
if increased == 1:
inc_music.play()
increased=increased-1
elif increased == -1:
dec_music.play()
increased=increased+1
here increased is a global variable. Make sure whenever your sales is increased it is set to +1 and whenever it is decreased it is set to -1 and call pl function in a separate thread by using threading library in the background. Since, it is a forever loop it will continuosly run in background and ring the bells.
from threading import Thread
th=Thread(target=pl)
th.setDaemon(True)
th.start()
While writing the above I assumed at a moment there is either an increase or a decrease in sales, both dont occour simultaneously. If they do, use another global variable decreased which is also initialised with 0 and decreased by -1 each time decrease in sales. And return both of them from get_inc() .
Below code produces a label that gets updated randomly, and plays the sound files located in "C:\Windows\Media\" based on the change:
import tkinter as tk
import random
from playsound import playsound
def sound(is_positive=True):
if is_positive:
playsound(r"C:\Windows\Media\chimes.wav", False)
else:
playsound(r"C:\Windows\Media\chord.wav", False)
def random_update():
_old = label['text']
_new = random.randint(1, 100)
if _new > _old: # use (_new - _old) == 1 for checking
sound() # increment of 1 exclusively
elif _new < _old: # use (_new - _old) == -1 for checking
sound(False) # decrement of 1 exclusively
label['text'] = _new
label.after(2000, random_update)
if __name__ == '__main__':
root = tk.Tk()
label = tk.Label(root, text=1)
label.after(2000, random_update)
label.pack()
root.mainloop()
playsound is not a built-in package so it needs to be installed separately. You can install using:
pip install playsound
or:
python -m pip install playsound
in the command prompt on windows.
I've got a program that will eventually receive data from an external source over serial, but I'm trying to develop the display-side first.
I've got this "main" module that has the simulated data send and receive. It updates a global that is used by a Matplotlib stripchart. All of this works.
#-------------------------------------------------------------------------------
# Name: BBQData
# Purpose: Gets the data from the Arduino, and runs the threads.
#-------------------------------------------------------------------------------
import time
import math
import random
from threading import Thread
import my_globals as bbq
import sys
import BBQStripChart as sc
import serial
import BBQControl as control
ser = serial.serial_for_url('loop://', timeout=10)
def simData():
newTime = time.time()
if not hasattr(simData, "lastUpdate"):
simData.lastUpdate = newTime # it doesn't exist yet, so initialize it
simData.firstTime = newTime # it doesn't exist yet, so initialize it
if newTime > simData.lastUpdate:
simData.lastUpdate = newTime
return (140 + 0.05*(simData.lastUpdate - simData.firstTime), \
145 + 0.022*(simData.lastUpdate - simData.firstTime), \
210 + random.randrange(-10, 10))
else:
return None
def serialDataPump():
testCtr = 0;
while not bbq.closing and testCtr<100:
newData = simData()
if newData != None:
reportStr = "D " + "".join(['{:3.0f} ' for x in newData]) + '\n'
reportStr = reportStr.format(*newData)
ser.write(bytes(reportStr, 'ascii'))
testCtr+=1
time.sleep(1)
bbq.closing = True
def serialDataRcv():
while not bbq.closing:
line = ser.readline()
rcvdTime = time.time()
temps = str(line, 'ascii').split(" ")
temps = temps[1:-1]
for j, x in enumerate(temps):
bbq.temps[j].append(float(x))
bbq.plotTimes.append(rcvdTime)
def main():
sendThread = Thread(target = serialDataPump)
receiveThread = Thread(target = serialDataRcv)
sendThread.start()
receiveThread.start()
# sc.runUI()
control.runControl() #blocks until user closes window
bbq.closing = True
time.sleep(2)
exit()
if __name__ == '__main__':
main()
## testSerMain()
However, I'd like to add a SEPARATE tkinter window that just has the most recent data on it, a close button, etc. I can get that window to come up, and show data initially, but none of the other threads run. (and nothing works when I try to run the window and the plot at the same time.)
#-------------------------------------------------------------------------------
# Name: BBQ Display/Control
# Purpose: displays current temp data, and control options
#-------------------------------------------------------------------------------
import tkinter as tk
import tkinter.font
import my_globals as bbq
import threading
fontSize = 78
class BBQControl(tk.Tk):
def __init__(self,parent):
tk.Tk.__init__(self,parent)
self.parent = parent
self.labelFont = tkinter.font.Font(family='Helvetica', size=int(fontSize*0.8))
self.dataFont = tkinter.font.Font(family='Helvetica', size=fontSize, weight = 'bold')
self.makeWindow()
def makeWindow(self):
self.grid()
btnClose = tk.Button(self,text=u"Close")
btnClose.grid(column=1,row=5)
lblFood = tk.Label(self,anchor=tk.CENTER, text="Food Temps", \
font = self.labelFont)
lblFood.grid(column=0,row=0)
lblPit = tk.Label(self,anchor=tk.CENTER, text="Pit Temps", \
font = self.labelFont)
lblPit.grid(column=1,row=0)
self.food1Temp = tk.StringVar()
lblFoodTemp1 = tk.Label(self,anchor=tk.E, \
textvariable=self.food1Temp, font = self.dataFont)
lblFoodTemp1.grid(column=0,row=1)
#spawn thread to update temps
updateThread = threading.Thread(target = self.updateLoop)
updateThread.start()
def updateLoop(self):
self.food1Temp.set(str(bbq.temps[1][-1]))
def runControl():
app = BBQControl(None)
app.title('BBQ Display')
app.after(0, app.updateLoop)
app.mainloop()
bbq.closing = True
if __name__ == '__main__':
runControl()
Your title sums up the problem nicely: Tkinter doesn't play well with threads. That's not a question, that's the answer.
You can only access tkinter widgets from the same thread that created the widgets. If you want to use threads, you'll need your non-gui threads to put data on a queue and have the gui thread poll the queue periodically.
One way of getting tkinter to play well with threads is to modify the library so all method calls run on a single thread. Two other questions deal with this same problem: Updating a TKinter GUI from a multiprocessing calculation and Python GUI is not responding while thread is executing. In turn, the given answers point to several modules that help to solve the problem you are facing. Whenever I work with tkinter, I always use the safetkinter module in case threads appear to be helpful in the program.
I would like to know in any way that i can run a function as a background process.
I have gone through reading threading but i am still unclear on how to implement it.
In my system , the scapy detection script run when i click on the Button.But then the system will be hang as the scapy function is running.
What i want to achieve is that when i click on the button that initiate the scapy detection script , i would like to be able to do other function in the same system.(general idea was to prevent the system from hang)
def startMonitor(self,event):
selectedInterface = self.interfaces_cblist.GetValue()
#selectInterfaceStr = str(selectedInterface)
if len(selectedInterface) == 0:
noInterfaceSelected = wx.MessageDialog(None,"Please select an interface","",wx.ICON_ERROR)
noInterfaceSelected.ShowModal()
else:
#confirmMonitor = wx.MessageDialog(None,"Monitoring Start on %s interface"%selectedInterface,"",wx.OK)
#confirmMonitor.ShowModal()
x = selectedInterface
thread.start_new_thread(self.camtableDetection(x))
def camtableDetection(self,a):
global interface
interface = str(a)
THRESH=(254/4)
START = 5
def monitorPackets(p):
if p.haslayer(IP):
hwSrc = p.getlayer(Ether).src
if hwSrc not in hwList:
hwList.append(hwSrc)
delta = datetime.datetime.now() - start
if((delta.seconds > START) and ((len(hwList)/delta.seconds) > THRESH)):
camAttackDetected = wx.MessageDialog(None,"Cam Attack Detected","",wx.ICON_ERROR)
camAttackDetected.ShowModal()
hwList = []
start = datetime.datetime.now()
sniff(iface=interface,prn=monitorPackets)