学习 vnpy 的界面的实现
通过简单的学习了 PyQt5 的一些代码以后, 我们基本上可以理解 PyQt 的一些用法, 下面让我们来先研究下 vnpy 的 UI 部分的代码.
首先回到上一节看到的 run.py(/vnpy/example/trade/run.py) 的关于 UI 部分的代码.
生成 QApplication 部分
qapp = create_qapp()
我们跟踪得到 create_qapp() 方法是写在 "/vnpy/trader/ui/init.py" 上面的.
init.py 主要是把一个文件夹变成一个包, 方便包的引入和管理, 方法写在__init__.py 中可以会在引入的时候被直接调用, 也就是说不需要在调用的时候通过 xxx.method() 的形式来调用. init.py 详细解释
我们来看看这部分的代码
- def excepthook(exctype, value, tb):
- ## 全局的一个异常处理的钩子, 所有的异常都会被处理到这里来
- def create_qapp(app_name: str = "VN Trader"):
- sys.excepthook = excepthook
- # 让窗体可以适应高分辨率屏幕
- QtWidgets.QApplication.setAttribute(QtCore.Qt.AA_EnableHighDpiScaling)
- qapp = QtWidgets.QApplication([])
- qapp.setStyleSheet(qdarkstyle.load_stylesheet_pyqt5())
- font = QtGui.QFont(SETTINGS["font.family"], SETTINGS["font.size"])
- qapp.setFont(font)
- icon = QtGui.QIcon(get_icon_path(__file__, "vnpy.ico"))
- qapp.setWindowIcon(icon)
- if "Windows" in platform.uname():
- ctypes.windll.shell32.SetCurrentProcessExplicitAppUserModelID(
- app_name
- )
- return qapp
- class ExceptionDialog(QtWidgets.QDialog):
- """"""
- #这里是异常窗口的代码
上面的这部分代码就是简单的生成一个 QApplication 代码, 并且指定了全局的异常发生以后弹出异常窗体. 需要注意以下代码:
- if "Windows" in platform.uname():
- ctypes.windll.shell32.SetCurrentProcessExplicitAppUserModelID(
- app_name
- )
在 Windows 7 中, 任务栏本身不适用于 "应用程序 Windows", 它适用于 "应用程序用户模型". 例如, 如果您运行了多个不同的应用程序实例, 并且每个实例都有自己的图标, 那么它们将全部分组到一个任务栏图标下. Windows 使用各种启发式方法来决定是否应该对不同的实例进行分组, 在这种情况下, 它决定将 Pythonw.exe 托管的所有内容分组到 Pythonw.exe 的图标下. 正确的解决方案是让 Pythonw.exe 告诉 Windows 它只是托管其他应用程序. 也许未来的 Python 版本会这样做. 或者, 您可以添加一个注册表项来告诉 Windows,Pythonw.exe 本身只是一个主机而不是一个应用程序. 有关 AppUserModelIDs 的信息, 请参阅 MSDN 文档. 或者, 您可以使用 Python 的 Windows 调用, 明确告诉 Windows 此进程的正确 AppUserModelID:
主窗体生成部分
让我们接着看看 UI 的主窗体生成部分的代码
- main_window = MainWindow(main_engine, event_engine)
- main_window.showMaximized()
MainWindows 的代码位置在 /vnpy/vnpy/trader/ui/mainwindow.py
在__init__() 方法中就是对常见的 self 的属性赋值, 没什么稀奇的. 我们直接看 initUI() 部分的代码.
- def init_ui(self):
- self.setWindowTitle(self.window_title) #设置标题
- self.init_dock()
- self.init_toolbar()
- self.init_menu()
- self.load_window_setting("custom")
我们一个一个看看这部分的函数和功能.
- init_dock
- def init_dock(self):
- """""" self.trading_widget, trading_dock = self.create_dock(TradingWidget," 交易 ", QtCore.Qt.LeftDockWidgetArea)
- tick_widget, tick_dock = self.create_dock(TickMonitor, "行情", QtCore.Qt.RightDockWidgetArea)
- #中间省略掉 N 多调用 create_dock 的方法
- self.tabifyDockWidget(active_dock, order_dock)
- self.save_window_setting("default")
在 init_dock 的方法中首先调用了 create_dock
咱们来研究下 create_dock 的方法.
- def create_dock(
- self, widget_class: QtWidgets.QWidget, name: str, area: int
- ):
- widget = widget_class(self.main_engine, self.event_engine)
- dock = QtWidgets.QDockWidget(name)
- dock.setWidget(widget)
- dock.setObjectName(name)
- dock.setFeatures(dock.DockWidgetFloatable | dock.DockWidgetMovable)
- self.addDockWidget(area, dock)
- return widget, dock
我们基本上可以这样理解, 就是实例化了一个自定义的 Widget, 然后放入 docker 中.
docker 大概是这样的一个概念 [浮动窗口] .
我搜索到了一篇详细的教程: PyQt5 高级界面控件之 QDockWidget(八)
为了联系 docker 我简单的写了一段代码:
- from PyQt5.QtCore import *
- from PyQt5.QtWidgets import *
- class DockDemo(QMainWindow):
- def __init__(self):
- super().__init__()
- tradeWidget = TradeWidget()
- self.trade_docker = QDockWidget('交易窗口', self)
- self.trade_docker.setWidget(tradeWidget)
- self.trade_docker.setFeatures(self.trade_docker.DockWidgetFloatable | self.trade_docker.DockWidgetMovable)
- self.trade_docker.setObjectName("交易窗口")
- self.trade_docker.setFloating(False)
- self.addDockWidget(Qt.RightDockWidgetArea,self.trade_docker)
- tickWidget = TickMonitorWidget()
- self.tick_docker = QDockWidget('行情窗口', self)
- self.tick_docker.setWidget(tickWidget)
- self.tick_docker.setFloating(False)
- self.addDockWidget(Qt.LeftDockWidgetArea, self.tick_docker)
- self.show()
- class TradeWidget(QWidget):
- def __init__(self):
- super().__init__()
- self.initUI()
- def initUI(self):
- self.setWindowTitle("这里是交易窗口")
- button = QPushButton('交易按钮', self)
- button.move(10, 20)
- self.show()
- class TickMonitorWidget(QWidget):
- def __init__(self):
- super().__init__()
- self.initUI()
- def initUI(self):
- self.setWindowTitle("这里是行情窗口")
- button = QPushButton('行情', self)
- button.move(10, 20)
- self.show()
- App = QApplication([])
- dd = DockDemo()
- exit(App.exec_())
- init_toolbar
这个没什么好说的, 就是初始化工具栏
init_menu()
这个方法是生成菜单, 里面有一个有趣的方法把菜单和插槽连接起来
- self.add_menu_action(
- help_menu,
- "查询合约",
- "contract.ico",
- partial(self.open_widget, ContractManager, "contract"),
- )
- def add_menu_action(
- self,
- menu: QtWidgets.QMenu,
- action_name: str,
- icon_name: str,
- func: Callable,
- ):
- """"""
- icon = QtGui.QIcon(get_icon_path(__file__, icon_name))
- action = QtWidgets.QAction(action_name, self)
- action.triggered.connect(func)
- action.setIcon(icon)
- menu.addAction(action)
保存 Windows 布局和恢复布局
- def save_window_setting(self, name: str):
- """
- Save current window size and state by trader path and setting name.
- """
- settings = QtCore.QSettings(self.window_title, name)
- settings.setValue("state", self.saveState())
- settings.setValue("geometry", self.saveGeometry())
- def load_window_setting(self, name: str):
- """
- Load previous window size and state by trader path and setting name.
- """
- settings = QtCore.QSettings(self.window_title, name)
- state = settings.value("state")
- geometry = settings.value("geometry")
- if isinstance(state, QtCore.QByteArray):
- self.restoreState(state)
- self.restoreGeometry(geometry)
- def restore_window_setting(self):
- """
- Restore window to default setting.
- """ self.load_window_setting("default")
- self.showMaximized()
打开一个定义的 Wiget
- def open_widget(self, widget_class: QtWidgets.QWidget, name: str):
- """
- Open contract manager.
- """
- widget = self.widgets.get(name, None)
- if not widget:
- widget = widget_class(self.main_engine, self.event_engine)
- self.widgets[name] = widget
- if isinstance(widget, QtWidgets.QDialog):
- widget.exec_()
- else:
- widget.show()
这个简单, 就是当菜单和工具栏调用打开一个功能窗口的时候, 首先查找这个窗口是否在 wigets 方法里面. 如果不的话, 实例化, 添加进去. 然后打开.
需要注意的是, 如果是 widget 直接调用 show, 如果是 dialog 需要调用的是 exec_() 方法
来源: https://www.cnblogs.com/bbird/p/12197433.html