1. 程式人生 > >vnpy原始碼閱讀學習(3):學習vnpy的介面的實現

vnpy原始碼閱讀學習(3):學習vnpy的介面的實現

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