學習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_()
方法