我编写了更长的代码,以读出温度天平的重量并将其打印在excel文件中。现在,我想添加一个“绘图”函数,该函数绘制每个余额的十个最后一个值。我遇到的问题是,该程序仅在测量运行后才会绘图,这可能需要更长的时间。现在我发现,多线程可以避免此问题。但是我不知道如何在现有代码中实现它。可能有人可以告诉我如何在右边添加类。所以我可以在主循环将其他值写入excel文件的同时绘制其他值。
以下是我的基本代码,对于冗长的代码,我们感到抱歉,但是如果不丢失整个上下文,就无法显示较小的版本。我剪掉了平衡2到10的测量命令,并且只留下了一个绘图命令。因为它们所有人都在工作,并且与其他人非常相似。
测量函数“ def Measurement():”将花费最长的时间,并且此刻是正在前台运行的部分。
测量功能“ def Measurement():”将花费最长的时间,此刻是正在前台运行的零件。这将阻塞GUI中的所有其他命令,并导致其他命令的排队。例如:我按下“绘图”或“帮助”按钮,它们将在运行一次测量循环后打开。
# -*- coding: utf-8 -*-
Created on Thu Aug 8 15:22:25 2019
The following program, is planed and designed to collect the weight information form 10 OHAUS SCOUT SKX2202 balances.
The program generates an GUI for the user to input all needed information and dates for the measurements.
After collecting, the program writes all needed and important information to an Excel file (`.xlsx`). Also the program should give the possibility to plot some weight values while the measurement is running.
# import all needed basic libraries
import time
import matplotlib.pyplot as plt
import os
import os.path
import string
import serial
import sys
import xlsxwriter
from openpyxl import Workbook
from datetime import datetime
from os import path
#import all needed libraries for the graphic user interface
from PyQt5.QtWidgets import QApplication
from PyQt5.QtWidgets import QWidget
from PyQt5.QtWidgets import QLabel
from PyQt5.QtWidgets import QPushButton
from PyQt5.QtWidgets import QMessageBox
from PyQt5.QtWidgets import QLineEdit
from PyQt5.QtWidgets import QGridLayout
from PyQt5.QtWidgets import QHBoxLayout
from PyQt5.QtWidgets import QProgressBar
from PyQt5.QtWidgets import QVBoxLayout,QRadioButton, QPlainTextEdit
#creating the main window, show the code to be an application
app = QApplication([])
app.setStyle('Fusio')
app.setStyleSheet("QPushButton {margin: 10ex;}")
window = QWidget()
window.setWindowTitle('Balance Messurements')
#creating the basic window elements
#all buttons for the main window (text added to the button will show their use)
buttonOkName = QPushButton('Confirm User Name')
buttonOkAnzahl = QPushButton('Confirm input ')
buttonOkZeit = QPushButton('Confirm time ')
buttonStarter = QPushButton("Start")
buttonhelp = QPushButton("Help")
buttonClose = QPushButton("close")
buttonOpenFile = QPushButton("Open File")
buttonPlot = QPushButton("Plot Graph")
chooseTime = QLabel("Choose if your Input Time are Minutes or Seconds")
minWahl = QRadioButton("Minutes")
sekWahl = QRadioButton("Seconds")
#user input mask for the user name, only accept Letters, no numbers, no space
user_name = QLineEdit()
user_name.setPlaceholderText("User Name")
user_name.setInputMask("AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAaaaa")
#user input mask for number of measurements, only accept numbers , no Letters, no spaces
user_anzahl = QLineEdit()
user_anzahl.setPlaceholderText("Number")
user_anzahl.setInputMask('99999')
#user input mask for timeinterval, only accept numbers , no Letters, no spaces
user_zeit = QLineEdit()
user_zeit.setPlaceholderText("Timeintervall")
user_zeit.setInputMask('9999999999999')
#user input for the project name (written to the Excel file)
user_project=QLineEdit()
#the labels to name the single inputfields
user_name_label = QLabel("User Name:")
user_anzahl_label= QLabel("Number of measurements:")
user_zeit_label = QLabel("Break interval:")
Savepath =QLabel("Saving Path: ")
nameoffile = QLabel("Filename: ")
proname = QLabel("Project Name: ")
#progressbar, get values at the lower code
progcounter = QProgressBar()
#implememting the single parts to a full layout
firstlay = QHBoxLayout()
firstlay.addWidget(user_name_label)
firstlay.addWidget(user_name)
firstlay.addWidget (buttonOkName)
firstlay.addWidget(buttonhelp)
secondlay = QHBoxLayout()
secondlay.addWidget(user_anzahl_label)
secondlay.addWidget(user_anzahl)
secondlay.addWidget(buttonOkAnzahl)
layer10 = QHBoxLayout()
warning = QLabel("A breakinterval under 12 seconds can lead to irregular measurements.")
layer10.addWidget(warning)
layer10.addWidget(chooseTime)
layer11 = QHBoxLayout()
layer11.addWidget(minWahl)
layer11.addWidget(sekWahl)
layer11.addWidget(buttonOkZeit)
thirdlay = QHBoxLayout()
thirdlay.addWidget(user_zeit_label)
thirdlay.addWidget(user_zeit)
layer4 = QHBoxLayout()
notes = QLabel("Remarks/notes")
text_notes = QLineEdit()
layer4.addWidget(notes)
layer4.addWidget(text_notes)
layer5 =QHBoxLayout()
progres = QLabel("Progres: ")
layer5.addWidget(progres)
layer5.addWidget(progcounter)
layer5.addWidget(buttonStarter)
layer6= QHBoxLayout()
layer6.addWidget(proname)
layer6.addWidget(user_project)
layer7 = QHBoxLayout()
layer7.addWidget(Savepath)
layer8 = QHBoxLayout()
layer8.addWidget(nameoffile)
layer9 = QHBoxLayout()
layer9.addWidget(buttonPlot)
layer12 = QVBoxLayout()
layer12.addWidget(buttonClose)
#implementierung aller elemente in das zu zeugende Fenster
mainLayout = QGridLayout()
mainLayout.addLayout(firstlay, 1, 1, 1, 1)
mainLayout.addLayout(layer7, 2, 1, 1, 1)
mainLayout.addLayout(secondlay, 3 ,1, 1, 1)
mainLayout.addLayout(layer10, 5, 1, 1,1)
mainLayout.addLayout(layer11, 6, 1, 1, 1)
mainLayout.addLayout(thirdlay, 4,1, 1, 1)
mainLayout.addLayout(layer6, 7, 1, 1, 1)
mainLayout.addLayout(layer8, 9, 1, 1, 1)
mainLayout.addLayout(layer4, 8, 1, 1, 1)
mainLayout.addLayout(layer5, 10, 1, 1, 1)
mainLayout.addLayout(layer9, 11, 1, 1, 1)
mainLayout.addLayout(layer12,12,1,1,1)
#befehl das Fenster zu zeigen
window.setLayout(mainLayout)
window.show()
# funktion um Eingabe auf "NUr Letter" zu überprüfen
allowed_alpha = string.ascii_letters + string.whitespace
#following all functions will be defined
def on_minWahl_clicked():
global zeit
zeit_vorab = int(user_zeit.text())
zeit = zeit_vorab * 60
global Pause
Pause_vorab = int(user_zeit.text())
Pause = Pause_vorab * 60
global Zeitangabe
Zeitangabe = " minutes"
global intervall
intervall = 'Time [min]'
def on_sekWahl_clicked():
global zeit
zeit_vorab = int(user_zeit.text())
zeit = zeit_vorab
global Pause
Pause = int(user_zeit.text())
global Zeitangabe
Zeitangabe = " seconds"
global intervall
intervall = 'Time [sec]'
#function to confirm the user name
def on_OkName_clicked(self):
#convert the input to a global string for all other functions
name = str(user_name.text())
user = str(name)
# function to confirm the number of measurements
def on_OkAnzahl_clicked():
while True:
try:
#convert the input to a global integer for all other functions
global Anzahl
Anzahl = int(user_anzahl.text())
break
except Exception as inst:
print (type(inst) ) # the exception instance
print (inst.args) # arguments stored in .args
print (inst) # __str__ allows args to be printed directly
x, y = inst.args
print ('x =', x)
print ('y =', y)
#function to confirm the time for the break interval
def on_OkZeit_clicked():
while True:
try:
break
except Exception as inst:
print (type(inst) ) # the exception instance
print (inst.args) # arguments stored in .args
print (inst) # __str__ allows args to be printed directly
x, y = inst.args
print ('x =', x)
print ('y =', y)
intzeit = user_zeit.text()
if intzeit < 12:
alert = QMessageBox()
alert.setText('Intervall needs to be 12 seconds or longer!')
alert.exec_()
# start the main function/measurement function of this program by click on start button
def on_start_clicked():
while True: #function to run the measurements
try:
Measurement() #start the measurements
break
except IOError: # return information if an error appears
alert = QMessageBox()
alert.setText('Failed start the measurements!')
alert.exec_()
break
# function to check the saving path
def check_path():
dt = datetime.now()
global save_path
global user
user = user_name.text()
global filename
filename = filename = 'data_'+ dt.strftime("%Y%m%d_%I%M%S") + '.xlsx' # generates the file name
save_path = ('C:/Users/satzh/.spyder-py3/CPU Temp Measurements/' +user+'/') #generates the full saving path
a = os.path.exists(save_path) # function to found out if path exist
if a == True: # path exist = use the existing path
saveText = QLabel("Savepath: "+ save_path) #show saving path in the main window
layer7.addWidget(saveText)
#shows the saving path in a message box
alert = QMessageBox()
alert.setText("Userpath exist, Data will be saved into: %s"%save_path)
alert.exec_()
else: #path does not excist,
os.makedirs(save_path) # creats a new folder for the user, as save path
saveText = QLabel("Savepath: "+ save_path) # shows the new savepath in main window
layer7.addWidget(saveText)
#shows the saving path in a message box
alert = QMessageBox()
alert.setText("Path does not exist, file will be generated at:\n%s" %save_path)
alert.exec_()
name_end = QLabel(filename)
layer8.addWidget(name_end)
#check if their is a file withe the same name (not really probably, cause it's always named for the current time )
while True:
try:
path.exists(save_path,filename)
global wb # open an new excel workbook/file
wb = Workbook()
global sheet # creates in the workbook a new sheet
sheet = wb.active
sheet.title = "Book1"
wb.save(save_path+filename) # saves the workbook/file at the given path
#if the file exist, adding a 1 to the name and produce a new file
new_filename = 'filename'+'1'
global workbook
workbook = xlsxwriter.Workbook(save_path+ new_filename)
global worksheet
worksheet = workbook.add_worksheet()
break #if an error appears, it is creating a provisionally file
except TypeError:
workbook = xlsxwriter.Workbook(save_path + filename)
worksheet = workbook.add_worksheet()
break
else:
os.makedirs(save_path)
saveText = QLabel("Savepath: "+ save_path)
layer7.addWidget(saveText)
workbook = xlsxwriter.Workbook(save_path+filename)
worksheet = workbook.add_worksheet()
# measurement main function, collect values form the balances and write this in an Excel file
def Measurement():
c = 0
row = 2
col = 0
n = 0
# generates the title line and basic information for the Excel file
worksheet.write(0,0, 'Project Name:')
worksheet.write(0,1, user_project.text())
worksheet.write(0,2, "Remarks/Notes:")
worksheet.write(0,3, text_notes.text())
worksheet.write(0,4,"Number of measurements: %d"%Anzahl)
worksheet.write(0,5, "Breakinterval: %d"%Pause)
worksheet.write(1,0, 'Date' )
worksheet.write(1,1, 'Balance 1')
worksheet.write(1,2, 'Balance 2')
worksheet.write(1,3, 'Balance 3')
worksheet.write(1,4, 'Balance 4')
worksheet.write(1,5, 'Balance 5')
worksheet.write(1,6, 'Balance 6')
worksheet.write(1,7, 'Balance 7')
worksheet.write(1,8, 'Balance 8')
worksheet.write(1,9, 'Balance 9')
worksheet.write(1,10, 'Balance 10')
worksheet.set_column('A:B', 30)
worksheet.set_column('B:L',15)
#generates and show a message box that the measurements are started
alert = QMessageBox()
alert.setText("Measurements in progress")
alert.exec_()
# generating the value list for each balance for the print command
global lb1,lb2,lb3,lb4,lb5,lb6,lb7,lb8,lb9,lb10
lb1 =[]
lb2=[]
lb3=[]
lb4=[]
lb5=[]
lb6=[]
lb7=[]
lb8=[]
lb9=[]
lb10=[]
# measurments loop,
while c <= Anzahl:
dt = datetime.now() #create a shortcut for the date and time value
date_format = workbook.add_format({'num_format': 'd.mm.yyyy hh:mm:ss'}) #create the format which is written to the Excel file
worksheet.write_datetime(row,col, dt, date_format)
# the following notes are for all 10 measurements loops the same:
#Measurement loop for balance 1
while True:
try:
bal1 = serial.Serial('COM7' ,9600, bytesize=8, stopbits=1, timeout =0.1) #opens the serial/com-port for balance 1
#and set the bal1 variable for the port name/class
break #ends the loop when balance was opened
except OSError: #except Error for failing to open the serial port
alert = QMessageBox() #generates a messagebox to say that an error appears
alert.setText("Failed to open Balance 1")
alert.exec_()
break
while True:
try:
bal1.write(b'Z\r\n') #sends the first few characters to the balance, to activate her and make her responding to the Computer
s=bal1.read(17) #reads out the weight information
bal1.write(b'\x50') #sends the "print" command to the balance
t=list(str(s)) # creating a list of strings of the measurement values
liste = t
satz = "".join(liste [3:13] ) # reads the numbers out of the list and generate a new combined string
zwischen_string = satz # generates the values for the plotting lists
lb1.append(zwischen_string) # connect the value to the print list
gewicht = zwischen_string.replace('.', ',') #change the dot to a comma for the exported Value to excel
worksheet.write(row, 1 , gewicht) # writes the value to the Excel file
bal1.close() # close the serial port
break
except OSError:
alert = QMessageBox()
alert.setText("Failed to open collect and write the information form balance 1")
alert.exec_()
break
row += 1 #say the program, to use a new row for the next measuremnt in the Excel file
c +=1 # counter for measurements +1, next measurements for the while loop
time.sleep(Pause) # set the break, which is given by the user
#loop to set the maximum number of list elements to 10
#for the direct printing of the values
def check_plotlists():
if len(lb1)>=10:
del lb1[0]
if len(lb2)>=10:
del lb2[0]
if len(lb3)>=10:
del lb3[0]
if len(lb4)>=10:
del lb4[0]
if len(lb5)>=10:
del lb5[0]
if len(lb6)>=10:
del lb6[0]
if len(lb7)>=10:
del lb7[0]
if len(lb8)>=10:
del lb8[0]
if len(lb9)>=10:
del lb9[0]
if len(lb10)>=10:
del lb10[0]
app.processEvents() #allows to send back the percent value vor the processbar
prozent =(n/Anzahl)*100 #creating the percent value
progcounter.setValue(prozent) # gives back the percent value to the processbar
n +=1 #counter to generate the percent value
workbook.close() # close the excel file
layer9.addWidget(buttonOpenFile) # add the "open file" button
# öffnet am ende die angelegt datei
def on_OpenFile_clicked():
os.chdir
os.system('start excel.exe "%s%s"' % (save_path,filename ))
#helpFuntion, reads text from Help Instructions.txt file
#generating the help window, by clicking on the Help button
def open_help():
f = open('C:\\Users\\satzh\\.spyder-py3\\Help Instructions.txt',"r") #source for the help text
x = f.read() #reads the text from the txt. file
global windowhelp
windowhelp = QWidget()
windowhelp.setWindowTitle('Help')
buttonClose = QPushButton("Close")
buttonClose.clicked.connect(on_close_clicked)
helplayer1 = QVBoxLayout()
help_text= QPlainTextEdit()
help_text.appendPlainText(x)
help_text.zoomIn(4)
helplayer1.addWidget(help_text)
helplayer1.addWidget(buttonClose)
HelpLayout = QGridLayout()
HelpLayout.addLayout(helplayer1,1,1,1,1)
windowhelp.setLayout(HelpLayout)
windowhelp.setGeometry(300,300 ,800,480 )
windowhelp.show()
# close function for help window
def on_close_clicked():
windowhelp.close()
#generating plot menu
def on_plot_clicked():
#window generating
global printwindow
printwindow = QWidget()
printwindow.setWindowTitle('Plott')
#button generating
Balance1=QPushButton("Print Balance 1")
Balance2=QPushButton("Print Balance 2")
Balance3=QPushButton("Print Balance 3")
Balance4=QPushButton("Print Balance 4")
Balance5=QPushButton("Print Balance 5")
Balance6=QPushButton("Print Balance 6")
Balance7=QPushButton("Print Balance 7")
Balance8=QPushButton("Print Balance 8")
Balance9=QPushButton("Print Balance 9")
Balance10=QPushButton("Print Balance 10")
Balance11=QPushButton("Print All together")
Pclose = QPushButton("Close")
#generate all buttons for the print menu
printLayout1 = QHBoxLayout()
printLayout1.addWidget(Balance1)
printLayout1.addWidget(Balance2)
printLayout1.addWidget(Balance3)
printLayout2 = QHBoxLayout()
printLayout2.addWidget(Balance4)
printLayout2.addWidget(Balance5)
printLayout2.addWidget(Balance6)
printLayout3 = QHBoxLayout()
printLayout3.addWidget(Balance7)
printLayout3.addWidget(Balance8)
printLayout3.addWidget(Balance9)
printLayout4 = QHBoxLayout()
printLayout4.addWidget(Balance10)
printLayout4.addWidget(Balance11)
printLayout5 = QHBoxLayout()
printLayout5.addWidget(Pclose)
#generate the printlayout
printFinal = QVBoxLayout()
printFinal.addLayout(printLayout1)
printFinal.addLayout(printLayout2)
printFinal.addLayout(printLayout3)
printFinal.addLayout(printLayout4)
printFinal.addLayout(printLayout5)
#connect the print buttons to print orders
printwindow.setLayout(printFinal)
printwindow.show()
Balance1.clicked.connect(print_1)
Balance2.clicked.connect(print_2)
Balance3.clicked.connect(print_3)
Balance1.clicked.connect(print_4)
Balance5.clicked.connect(print_5)
Balance6.clicked.connect(print_6)
Balance7.clicked.connect(print_7)
Balance8.clicked.connect(print_8)
Balance9.clicked.connect(print_9)
Balance10.clicked.connect(print_10)
Balance11.clicked.connect(print_11)
Pclose.clicked.connect(on_pclose_clicked)
#closing the print menue
def on_pclose_clicked():
printwindow.close()
#printing for balancce 1
#following notes are for all print funtions the same
def print_1():
fig = plt.figure() #generates the basic figure for the plot, like the new window
plt.plot(lb1) #give the dates which should be printed
plt.title('Hydrogen Evolution') #generates the head-title
plt.ylabel('Weight [g]') # name fo the y cooridnates
plt.xlabel(intervall) # name for the x intervall (is defined by the choose between seconds and minutes functions above)
ax = plt.subplot(111)
ax.legend()
plt.ylim(-1, 425) # defined the intervall on the y coordinates
xmax= Anzahl*Pause #defined the intervall on the x coordinates
plt.xlim(0, xmax )
plt.show()
timestamp = time.strftime("%d%m%Y-%H%M%S") #generates part of the saving name
command = "fig.savefig('"+timestamp+"_h2evo_multiplot.png', dpi=200)" # saves the figure
exec(command)
fig.savefig('multiplot.png' , dpi=200)
#closing the Mainwindow and main Process
def close_all():
window.close()
sys.exit(app.exec_())
exit()
#connect the Buttons with their functions
buttonOkName.clicked.connect(on_OkName_clicked) #confirm name with checking user name
buttonOkName.clicked.connect( check_path) #confrim name with function to check saving path
buttonOkAnzahl.clicked.connect(on_OkAnzahl_clicked) #confirm number of messurements
buttonOkZeit.clicked.connect(on_OkZeit_clicked) #confirm time input
buttonStarter.clicked.connect(on_start_clicked)
buttonOpenFile.clicked.connect(on_OpenFile_clicked)
minWahl.toggled.connect(on_minWahl_clicked)
sekWahl.toggled.connect(on_sekWahl_clicked)
buttonhelp.clicked.connect(open_help)
buttonPlot.clicked.connect(on_plot_clicked)
buttonClose.clicked.connect(close_all)
app.exec_()
在另一个线程中,我发现以下内容:QThread可以运行事件循环,QRunnable没有事件循环,因此请勿将其用于设计有事件循环的任务。发件人:C++/Qt - QThread vs QRunnable
答案 0 :(得分:0)
假设您要使用线程运行名为my_func的函数。
import threading
my_thread = threading.Thread(target=my_func, args=())
my_thread.start()
如果您希望线程在整个程序退出后退出,请在启动前键入
my_thread.setDaemon(True)
如果要传递函数需要的变量(参数)。假设您有一个函数add(num1,num2)返回num1 + num2,则在列表的args中传递num1和num2,如下所示:
add_thread = threading.Thread(target=add, args=([4, 6]))
答案 1 :(得分:0)
库来防止Qt UI线程中断与进程相关的线程执行。 自定义QEventLoop实现可能是执行类似操作的替代方法,并且非常耗时。 如果不管理python线程和Qt UI线程,代码将开始生成意外的错误/行为
import sys
import asyncio
import time
from PyQt5.QtWidgets import QApplication, QProgressBar
from quamash import QEventLoop, QThreadExecutor
app = QApplication(sys.argv)
loop = QEventLoop(app)
asyncio.set_event_loop(loop) # NEW must set the event loop
progress = QProgressBar()
progress.setRange(0, 99)
progress.show()
async def master():
await first_50()
with QThreadExecutor(1) as exec:
await loop.run_in_executor(exec, last_50)
# TODO announce completion?
async def first_50():
for i in range(50):
progress.setValue(i)
await asyncio.sleep(.1)
def last_50():
for i in range(50,100):
loop.call_soon_threadsafe(progress.setValue, i)
time.sleep(.1)
with loop: ## context manager calls .close() when loop completes, and releases all resources
loop.run_until_complete(master())