def ctrlCHandler(sig, frame):
print("CTRL-C Handler:")
print("Trackback:")
faulthandler.dump_traceback()
print("")
print("Aborting now")
assert(False)
if (__name__ == "__main__"):
# Use both so that we can pass sys.argv to QApplication.
import argparse
import sys
# Get command line arguments..
parser = argparse.ArgumentParser(description = 'STORM microscope control software')
parser.add_argument('config', type = str, help = "The name of the configuration file to use.")
parser.add_argument('--xml', dest = 'default_xml', type = str, required = False, default = None,
help = "The name of a settings xml file to use as the default.")
args = parser.parse_args()
# Start..
app = QtWidgets.QApplication(sys.argv)
# This keeps Qt from closing everything if a message box is displayed
# before HAL's main window is shown.
app.setQuitOnLastWindowClosed(False)
# Splash Screen.
pixmap = QtGui.QPixmap("splash.png")
splash = QtWidgets.QSplashScreen(pixmap)
splash.show()
app.processEvents()
# Load configuration.
config = params.config(args.config)
# Start logger.
hdebug.startLogging(config.get("directory") + "logs/", "hal4000")
# Setup HAL and all of the modules.
hal = HalCore(config = config,
parameters_file_name = args.default_xml)
#主程序,配置初始文件,也能传递数据和结束进程
# Hide splash screen and start.
splash.hide()
# Configure ctrl-c handling.
signal.signal(signal.SIGINT, ctrlCHandler)
#SIGINT抓住crtl+c命令,故命令行窗口 crtl+c ,退出应用
app.exec_()
HalCore(config = config, parameters_file_name = args.default_xml) 函数:
The core of it all. It sets everything else up, handles the message passing and tears everything down.
config是从xml文件中解析出的,
class HalCore(QtCore.QObject):
"""
The core of it all. It sets everything else up, handles
the message passing and tears everything down.
"""
def __init__(self, config = None,
parameters_file_name = None,
testing_mode = False,
show_gui = True,
**kwds):
super().__init__(**kwds)
self.modules = []
self.module_name = "core"
self.qt_settings = QtCore.QSettings("storm-control", "hal4000" + config.get("setup_name").lower())
self.queued_messages = deque() #双端队列
self.queued_messages_timer = QtCore.QTimer(self)
self.running = True # This is solely for the benefit of unit tests.
self.sent_messages = []
self.strict = config.get("strict", False)
self.queued_messages_timer.setInterval(0)
self.queued_messages_timer.timeout.connect(self.handleSendMessage)
self.queued_messages_timer.setSingleShot(True)
# Initialize messages.
halMessage.initializeMessages()
# In strict mode we all workers must finish in 60 seconds.
if self.strict:
halModule.max_job_time = 60000
# Load all the modules.
print("Loading modules")
#
# For HAL it is easier to just use a list of modules, but at initialization
# we also send a dictionary with the module names as keys to all of the
# modules
#
# In testing mode the testing.testing module may use the other modules to
# spoof the message sources.
#
# During normal operation most inter-module communication is done using
# messages. Modules may also request functionalities from other modules
# that they can use to do specific tasks, such as daq output or displaying
# the images from a camera.
#
all_modules = {}
if testing_mode:
all_modules["core"] = self
else:
all_modules["core"] = True
#
# Need to load HAL's main window first so that other GUI windows will
# have the correct Qt parent.
#
module_names = sorted(config.get("modules").getAttrs())
module_names.insert(0, module_names.pop(module_names.index("hal")))
for module_name in module_names:
print(" " + module_name)
# Get module specific parameters.
module_params = config.get("modules").get(module_name)
# Add the 'root' parameters to this module parameters
# so that they are visible to the module.
for root_param in config.getAttrs():
if (root_param != "modules"):
module_params.add(root_param, config.getp(root_param))
# Load the module.
a_module = importlib.import_module(module_params.get("module_name"))
a_class = getattr(a_module, module_params.get("class_name"))
a_object = a_class(module_name = module_name,
module_params = module_params,
qt_settings = self.qt_settings)
# If this is HAL's main window set the HalDialog qt_parent class
# attribute so that any GUI QDialogs will have the correct Qt parent.
if (module_name == "hal"):
halDialog.HalDialog.qt_parent = a_object.view
self.modules.append(a_object)
if testing_mode:
all_modules[module_name] = a_object
else:
all_modules[module_name] = True
print("")
# Connect signals.
for module in self.modules:
module.newMessage.connect(self.handleMessage)
# Create messages.
#
# We do it this way with finalizers because otherwise all of these messages
# would get queued first and the modules would not have a chance to insert
# messages in between these messages.
#
# The actual sequence of sent messages is:
#
# 1. "configure1", tell modules to finish configuration.
# The message includes a dictionary of the names of
# all modules that were loaded.
#
# 2. "configure2", gives the modules a chance to 'react'
# based on what happened during configure1.
#
# 3. "configure3", gives the modules a chance to 'react'
# based on what happened during configure2.
#
# 4. "new parameters file", initial parameters (if any).
#
# 5. "start", tell the modules to start.
# This is the point where any GUI modules that are
# visible should call show().
#
message_chain = []
# configure1.
message_chain.append(halMessage.HalMessage(source = self,
m_type = "configure1",
data = {"all_modules" : all_modules}))
# configure2.
message_chain.append(halMessage.HalMessage(source = self,
m_type = "configure2"))
# configure3.
message_chain.append(halMessage.HalMessage(source = self,
m_type = "configure3"))
# update default parameters.
if parameters_file_name is not None:
message_chain.append(halMessage.HalMessage(source = self,
m_type = "new parameters file",
data = {"parameters filename" : parameters_file_name,
"is_default" : True}))
# start.
#
# It is safe to stop blocking Qt's last window closed behavior after
# this message as HAL's main window will be open.
#
# If we run HAL from another module, in testing for example, app might
# be none.
#
if app is not None:
message_chain.append(halMessage.HalMessage(source = self,
m_type = "start",
data = {"show_gui" : show_gui},
sync = True,
finalizer = lambda : app.setQuitOnLastWindowClosed(True)))
else:
message_chain.append(halMessage.HalMessage(source = self,
m_type = "start",
data = {"show_gui" : show_gui},
sync = True))
message_chain.append(halMessage.SyncMessage(source = self))
self.handleMessage(halMessage.chainMessages(self.handleMessage,
message_chain))
threadpool = QtCore.QThreadPool.globalInstance()
QThreadPool: 线程池
线程池的作用是管理、复用、回收一组线程,控制线程的数量,避免频繁的创建和销毁线程而浪费资源。
频繁创建、耗时短的任务使用线程池来执行更合适;
Qt中的线程池类为QThreadPool,全局的线程池可以用QThreadPool::globalInstance()得到,它默认最多创建8个线程,参考链接[1]展示了多个线程被创建和销毁的过程。
假设我们要创建一个任务task,继承QRunnable,task的对象作为threadpool.start(ct_task),线程池会自动的在线程池中调用task的run函数,异步执行,提交给线程池的 QRunnable 对象在它的 run() 函数执行完后会被自动 delete 掉,如果不想线程池删除它,在调用线程池的 start() 前调用 setAutoDelete(false) 即可。
定义新的信号,新的信号作为类的属性;
新的信号应该定义在QtCore.QObject的子类中,
最开始理解pyqtSignal()时非常不好理解,等明白了发现是因为我之前没有信号发射的概念。
比如:
点击一个按钮与槽函数链接,通过clicked.connect我们能链接对应的槽函数并执行;
但是我们自己新建的信号怎么知道是什么时候触发自建的槽函数呢?当我们连接了自建信号与自建槽函数,接下来就需要emit()函数去执行发射信号这个过程,但什么时候去发射信号呢?这就要看具体情况了,甚至我们有时候需要再去点击某个按钮发射信号,那么又会多一步将按钮和对应的槽函数链接,而这个槽函数就包含了信号的触发函数(emit).
下面有个例子,来源于他人的博客:
#创建新的信号
printSignal = pyqtSignal(list)
#槽函数·
def printPaper(self,list):
self.resultLabel.setText("Print: "+"份数:"+ str(list[0]) +" 纸张:"+str(list[1]))#绑定信号与槽函数
#创建的信号与槽函数链接
self.printSignal.connect(self.printPaper)
#通过点击按钮发射自建的信号
self.printButton.clicked.connect(self.emitPrintSignal)
#触发信号的函数
def emitPrintSignal(self):
pList = []
pList.append(self.numberSpinBox.value ())
pList.append(self.styleCombo.currentText())
self.printSignal.emit(pList) # 发射信号
从而完成了信号传输的一个闭环。
有些消息需要立即处理的,就得用runWorkerTask来处理,runWorkerTask开辟了一个新的单线程,并保留其他信息在队列中。
app.exec_()
一直对app为什么能在桌面上运行感到疑惑,之前是以为是show(),但storm_control的主文件中没有show()过,且程序运行完最后一句后才出现的程序界面,所以使程序运行的实际上是最后一句的app.exec_() ,其作用是保证进入程序的主循环直到exit()被调用
那么所谓的主循环 是什么呢?
主循环应该是指之前的程序中设定好的界面上,程序一直在等信号触发,除非遇到关闭信号。