Pyqt4 vnpy源码中关于GUI设计 2

来源:互联网 发布:cpa诱导源码 编辑:程序博客网 时间:2024/05/29 09:00

一、介绍

在上章中介绍了pyQt4整个运行的流程,该章摘取的是vnpy中关于MainWindow窗口的布局。源代码中有个父类uiBasicWidget.py内容是布局中需要的单元格和表格类。

二、代码

uiBasicWidget.py主要是布局组件的代码。待Cell的类为单元格类,后面Monitor是大的组件表格类。整个代码主要部分有qt的connect事件绑定及触发,界面右键菜单的实现,单元格双击事件的connect,以及qt中基本的组件。

# encoding: UTF-8import jsonimport csvimport osimport platformfrom collections import OrderedDictfrom vnpy.event import *from vnpy.trader.vtEvent import *from vnpy.trader.vtFunction import *from vnpy.trader.vtGateway import *from vnpy.trader import vtTextfrom vnpy.trader.uiQt import QtGui, QtWidgets, QtCore, BASIC_FONTCOLOR_RED = QtGui.QColor('red')COLOR_GREEN = QtGui.QColor('green')########################################################################class BasicCell(QtWidgets.QTableWidgetItem):    """基础的单元格"""    #----------------------------------------------------------------------    def __init__(self, text=None, mainEngine=None):        """Constructor"""        super(BasicCell, self).__init__()        self.data = None        if text:            self.setContent(text)    #----------------------------------------------------------------------    def setContent(self, text):        """设置内容"""        if text == '0' or text == '0.0':            self.setText('')        else:            self.setText(text)########################################################################class BasicMonitor(QtWidgets.QTableWidget):    """    基础监控    headerDict中的值对应的字典格式如下    {'chinese': u'中文名', 'cellType': BasicCell}    """    signal = QtCore.Signal(type(Event()))    #----------------------------------------------------------------------    def __init__(self, mainEngine=None, eventEngine=None, parent=None):        """Constructor"""        super(BasicMonitor, self).__init__(parent)        self.mainEngine = mainEngine        self.eventEngine = eventEngine        # 保存表头标签用        self.headerDict = OrderedDict()  # 有序字典,key是英文名,value是对应的配置字典        self.headerList = []             # 对应self.headerDict.keys()        # 保存相关数据用        self.dataDict = {}  # 字典,key是字段对应的数据,value是保存相关单元格的字典        self.dataKey = ''   # 字典键对应的数据字段        # 监控的事件类型        self.eventType = ''        # 列宽调整状态(只在第一次更新数据时调整一次列宽)        self.columnResized = False        # 字体        self.font = None        # 保存数据对象到单元格        self.saveData = False        # 默认不允许根据表头进行排序,需要的组件可以开启        self.sorting = False        # 初始化右键菜单        self.initMenu()    #----------------------------------------------------------------------    def setHeaderDict(self, headerDict):        """设置表头有序字典"""        self.headerDict = headerDict        self.headerList = headerDict.keys()    #----------------------------------------------------------------------    def setDataKey(self, dataKey):        """设置数据字典的键"""        self.dataKey = dataKey    #----------------------------------------------------------------------    def setEventType(self, eventType):        """设置监控的事件类型"""        self.eventType = eventType    #----------------------------------------------------------------------    def setFont(self, font):        """设置字体"""        self.font = font    #----------------------------------------------------------------------    def setSaveData(self, saveData):        """设置是否要保存数据到单元格"""        self.saveData = saveData    #----------------------------------------------------------------------    def initTable(self):        """初始化表格"""        # 设置表格的列数        col = len(self.headerDict)        self.setColumnCount(col)        # 设置列表头        labels = [d['chinese'] for d in self.headerDict.values()]        self.setHorizontalHeaderLabels(labels)        # 关闭左边的垂直表头        self.verticalHeader().setVisible(False)        # 设为不可编辑        self.setEditTriggers(self.NoEditTriggers)        # 设为行交替颜色        self.setAlternatingRowColors(True)        # 设置允许排序        self.setSortingEnabled(self.sorting)    #----------------------------------------------------------------------    def registerEvent(self):        """注册GUI更新相关的事件监听"""        #此处qtpy自身的事件引擎self.signal.connect绑定了事件函数,而self.eventEngine是vnpy中自定义的事件引擎。当该eventEngine事件引擎被触发时会调用self.signal.emit发出信号函数,再由qtpy自身的事件引擎处理调用self.updateEvent函数。        self.signal.connect(self.updateEvent)        self.eventEngine.register(self.eventType, self.signal.emit)    #----------------------------------------------------------------------    def updateEvent(self, event):        """收到事件更新"""        data = event.dict_['data']        self.updateData(data)    #----------------------------------------------------------------------    def updateData(self, data):        """将数据更新到表格中"""        # 如果允许了排序功能,则插入数据前必须关闭,否则插入新的数据会变乱        if self.sorting:            self.setSortingEnabled(False)        # 如果设置了dataKey,则采用存量更新模式        if self.dataKey:            key = data.__getattribute__(self.dataKey)            # 如果键在数据字典中不存在,则先插入新的一行,并创建对应单元格            if key not in self.dataDict:                self.insertRow(0)                     d = {}                for n, header in enumerate(self.headerList):                                      content = safeUnicode(data.__getattribute__(header))                    cellType = self.headerDict[header]['cellType']                    cell = cellType(content, self.mainEngine)                    if self.font:                        cell.setFont(self.font)  # 如果设置了特殊字体,则进行单元格设置                    if self.saveData:            # 如果设置了保存数据对象,则进行对象保存                        cell.data = data                    self.setItem(0, n, cell)                    d[header] = cell                self.dataDict[key] = d            # 否则如果已经存在,则直接更新相关单元格            else:                d = self.dataDict[key]                for header in self.headerList:                    content = safeUnicode(data.__getattribute__(header))                    cell = d[header]                    cell.setContent(content)                    if self.saveData:            # 如果设置了保存数据对象,则进行对象保存                        cell.data = data                            # 否则采用增量更新模式        else:            self.insertRow(0)              for n, header in enumerate(self.headerList):                content = safeUnicode(data.__getattribute__(header))                cellType = self.headerDict[header]['cellType']                cell = cellType(content, self.mainEngine)                if self.font:                    cell.setFont(self.font)                if self.saveData:                    cell.data = data                                self.setItem(0, n, cell)                                # 调整列宽        if not self.columnResized:            self.resizeColumns()            self.columnResized = True        # 重新打开排序        if self.sorting:            self.setSortingEnabled(True)    #----------------------------------------------------------------------    def resizeColumns(self):        """调整各列的大小"""        self.horizontalHeader().resizeSections(QtWidgets.QHeaderView.ResizeToContents)        #----------------------------------------------------------------------    def setSorting(self, sorting):        """设置是否允许根据表头排序"""        self.sorting = sorting    #----------------------------------------------------------------------    def saveToCsv(self):        """保存表格内容到CSV文件"""        # 先隐藏右键菜单        self.menu.close()        # 获取想要保存的文件名        path = QtWidgets.QFileDialog.getSaveFileName(self, vtText.SAVE_DATA, '', 'CSV(*.csv)')        try:            #if not path.isEmpty():            if path:                with open(unicode(path), 'wb') as f:                    writer = csv.writer(f)                    # 保存标签                    headers = [header.encode('gbk') for header in self.headerList]                    writer.writerow(headers)                    # 保存每行内容                    for row in range(self.rowCount()):                        rowdata = []                        for column in range(self.columnCount()):                            item = self.item(row, column)                            if item is not None:                                rowdata.append(                                    unicode(item.text()).encode('gbk'))                            else:                                rowdata.append('')                        writer.writerow(rowdata)             except IOError:            pass    #----------------------------------------------------------------------    def initMenu(self):        """初始化右键菜单"""        #qt邮件菜单,先实例化菜单和菜单内容项,将菜单内容项saveAction链接到相应事件,再将saveAction加入到菜单对象menu中。        self.menu = QtWidgets.QMenu(self)            saveAction = QtWidgets.QAction(vtText.SAVE_DATA, self)        saveAction.triggered.connect(self.saveToCsv)        self.menu.addAction(saveAction)    #----------------------------------------------------------------------    def contextMenuEvent(self, event):        """右键点击事件"""        self.menu.popup(QtGui.QCursor.pos())    ########################################################################class OrderMonitor(BasicMonitor):    """委托监控"""    #----------------------------------------------------------------------    def __init__(self, mainEngine, eventEngine, parent=None):        """Constructor"""        super(OrderMonitor, self).__init__(mainEngine, eventEngine, parent)        self.mainEngine = mainEngine        d = OrderedDict()        d['orderID'] = {'chinese':vtText.ORDER_ID, 'cellType':NumCell}        d['symbol'] = {'chinese':vtText.CONTRACT_SYMBOL, 'cellType':BasicCell}        d['vtSymbol'] = {'chinese':vtText.CONTRACT_NAME, 'cellType':NameCell}        d['direction'] = {'chinese':vtText.DIRECTION, 'cellType':DirectionCell}        d['offset'] = {'chinese':vtText.OFFSET, 'cellType':BasicCell}        d['price'] = {'chinese':vtText.PRICE, 'cellType':BasicCell}        d['totalVolume'] = {'chinese':vtText.ORDER_VOLUME, 'cellType':BasicCell}        d['tradedVolume'] = {'chinese':vtText.TRADED_VOLUME, 'cellType':BasicCell}        d['status'] = {'chinese':vtText.ORDER_STATUS, 'cellType':BasicCell}        d['orderTime'] = {'chinese':vtText.ORDER_TIME, 'cellType':BasicCell}        d['cancelTime'] = {'chinese':vtText.CANCEL_TIME, 'cellType':BasicCell}        #d['frontID'] = {'chinese':vtText.FRONT_ID, 'cellType':BasicCell}         # 考虑到在vn.trader中,ctpGateway的报单号应该是始终递增的,因此这里可以忽略        #d['sessionID'] = {'chinese':vtText.SESSION_ID, 'cellType':BasicCell}        d['gatewayName'] = {'chinese':vtText.GATEWAY, 'cellType':BasicCell}        self.setHeaderDict(d)        self.setDataKey('vtOrderID')        self.setEventType(EVENT_ORDER)        self.setFont(BASIC_FONT)        self.setSaveData(True)        self.setSorting(True)        self.initTable()        self.registerEvent()        self.connectSignal()    #----------------------------------------------------------------------    def connectSignal(self):        """连接信号"""        # 双击单元格撤单        self.itemDoubleClicked.connect(self.cancelOrder)     #----------------------------------------------------------------------    def cancelOrder(self, cell):        """根据单元格的数据撤单"""        order = cell.data        req = VtCancelOrderReq()        req.symbol = order.symbol        req.exchange = order.exchange        req.frontID = order.frontID        req.sessionID = order.sessionID        req.orderID = order.orderID        self.mainEngine.cancelOrder(req, order.gatewayName)########################################################################class PositionMonitor(BasicMonitor):    """持仓监控"""    #----------------------------------------------------------------------    def __init__(self, mainEngine, eventEngine, parent=None):        """Constructor"""        super(PositionMonitor, self).__init__(mainEngine, eventEngine, parent)        d = OrderedDict()        d['symbol'] = {'chinese':vtText.CONTRACT_SYMBOL, 'cellType':BasicCell}        d['vtSymbol'] = {'chinese':vtText.CONTRACT_NAME, 'cellType':NameCell}        d['direction'] = {'chinese':vtText.DIRECTION, 'cellType':DirectionCell}        d['position'] = {'chinese':vtText.POSITION, 'cellType':BasicCell}        d['ydPosition'] = {'chinese':vtText.YD_POSITION, 'cellType':BasicCell}        d['frozen'] = {'chinese':vtText.FROZEN, 'cellType':BasicCell}        d['price'] = {'chinese':vtText.PRICE, 'cellType':BasicCell}        d['positionProfit'] = {'chinese':vtText.POSITION_PROFIT, 'cellType':PnlCell}        d['gatewayName'] = {'chinese':vtText.GATEWAY, 'cellType':BasicCell}        self.setHeaderDict(d)        self.setDataKey('vtPositionName')        self.setEventType(EVENT_POSITION)        self.setFont(BASIC_FONT)        self.setSaveData(True)        self.initTable()        self.registerEvent()########################################################################class AccountMonitor(BasicMonitor):    """账户监控"""    #----------------------------------------------------------------------    def __init__(self, mainEngine, eventEngine, parent=None):        """Constructor"""        super(AccountMonitor, self).__init__(mainEngine, eventEngine, parent)        d = OrderedDict()        d['accountID'] = {'chinese':vtText.ACCOUNT_ID, 'cellType':BasicCell}        d['preBalance'] = {'chinese':vtText.PRE_BALANCE, 'cellType':BasicCell}        d['balance'] = {'chinese':vtText.BALANCE, 'cellType':BasicCell}        d['available'] = {'chinese':vtText.AVAILABLE, 'cellType':BasicCell}        d['commission'] = {'chinese':vtText.COMMISSION, 'cellType':BasicCell}        d['margin'] = {'chinese':vtText.MARGIN, 'cellType':BasicCell}        d['closeProfit'] = {'chinese':vtText.CLOSE_PROFIT, 'cellType':BasicCell}        d['positionProfit'] = {'chinese':vtText.POSITION_PROFIT, 'cellType':BasicCell}        d['gatewayName'] = {'chinese':vtText.GATEWAY, 'cellType':BasicCell}        self.setHeaderDict(d)        self.setDataKey('vtAccountID')        self.setEventType(EVENT_ACCOUNT)        self.setFont(BASIC_FONT)        self.initTable()        self.registerEvent()########################################################################class TradingWidget(QtWidgets.QFrame):    """简单交易组件"""    signal = QtCore.Signal(type(Event()))    directionList = [DIRECTION_LONG,                     DIRECTION_SHORT]    offsetList = [OFFSET_OPEN,                  OFFSET_CLOSE,                  OFFSET_CLOSEYESTERDAY,                  OFFSET_CLOSETODAY]    priceTypeList = [PRICETYPE_LIMITPRICE,                     PRICETYPE_MARKETPRICE,                     PRICETYPE_FAK,                     PRICETYPE_FOK]    exchangeList = [EXCHANGE_NONE,                    EXCHANGE_CFFEX,                    EXCHANGE_SHFE,                    EXCHANGE_DCE,                    EXCHANGE_CZCE,                    EXCHANGE_SSE,                    EXCHANGE_SZSE,                    EXCHANGE_SGE,                    EXCHANGE_HKEX,                    EXCHANGE_HKFE,                    EXCHANGE_SMART,                    EXCHANGE_ICE,                    EXCHANGE_CME,                    EXCHANGE_NYMEX,                    EXCHANGE_LME,                    EXCHANGE_GLOBEX,                    EXCHANGE_IDEALPRO]    currencyList = [CURRENCY_NONE,                    CURRENCY_CNY,                    CURRENCY_HKD,                    CURRENCY_USD]    productClassList = [PRODUCT_NONE,                        PRODUCT_EQUITY,                        PRODUCT_FUTURES,                        PRODUCT_OPTION,                        PRODUCT_FOREX]    gatewayList = ['']    #----------------------------------------------------------------------    def __init__(self, mainEngine, eventEngine, parent=None):        """Constructor"""        super(TradingWidget, self).__init__(parent)        self.mainEngine = mainEngine        self.eventEngine = eventEngine        self.symbol = ''        # 添加交易接口        l = mainEngine.getAllGatewayDetails()        gatewayNameList = [d['gatewayName'] for d in l]        self.gatewayList.extend(gatewayNameList)        self.initUi()        self.connectSignal()    #----------------------------------------------------------------------    def initUi(self):        """初始化界面"""        self.setWindowTitle(vtText.TRADING)        self.setMaximumWidth(400)        self.setFrameShape(self.Box)    # 设置边框        self.setLineWidth(1)                   # 左边部分        labelSymbol = QtWidgets.QLabel(vtText.CONTRACT_SYMBOL)        labelName =  QtWidgets.QLabel(vtText.CONTRACT_NAME)        labelDirection = QtWidgets.QLabel(vtText.DIRECTION)        labelOffset = QtWidgets.QLabel(vtText.OFFSET)        labelPrice = QtWidgets.QLabel(vtText.PRICE)        self.checkFixed = QtWidgets.QCheckBox(u'')  # 价格固定选择框        labelVolume = QtWidgets.QLabel(vtText.VOLUME)        labelPriceType = QtWidgets.QLabel(vtText.PRICE_TYPE)        labelExchange = QtWidgets.QLabel(vtText.EXCHANGE)         labelCurrency = QtWidgets.QLabel(vtText.CURRENCY)        labelProductClass = QtWidgets.QLabel(vtText.PRODUCT_CLASS)        labelGateway = QtWidgets.QLabel(vtText.GATEWAY)        self.lineSymbol = QtWidgets.QLineEdit()        self.lineName = QtWidgets.QLineEdit()        self.comboDirection = QtWidgets.QComboBox()        self.comboDirection.addItems(self.directionList)        self.comboOffset = QtWidgets.QComboBox()        self.comboOffset.addItems(self.offsetList)        self.spinPrice = QtWidgets.QDoubleSpinBox()        self.spinPrice.setDecimals(4)        self.spinPrice.setMinimum(0)        self.spinPrice.setMaximum(100000)        self.spinVolume = QtWidgets.QSpinBox()        self.spinVolume.setMinimum(0)        self.spinVolume.setMaximum(1000000)        self.comboPriceType = QtWidgets.QComboBox()        self.comboPriceType.addItems(self.priceTypeList)        self.comboExchange = QtWidgets.QComboBox()        self.comboExchange.addItems(self.exchangeList)              self.comboCurrency = QtWidgets.QComboBox()        self.comboCurrency.addItems(self.currencyList)        self.comboProductClass = QtWidgets.QComboBox()        self.comboProductClass.addItems(self.productClassList)             self.comboGateway = QtWidgets.QComboBox()        self.comboGateway.addItems(self.gatewayList)                  gridleft = QtWidgets.QGridLayout()        gridleft.addWidget(labelSymbol, 0, 0)        gridleft.addWidget(labelName, 1, 0)        gridleft.addWidget(labelDirection, 2, 0)        gridleft.addWidget(labelOffset, 3, 0)        gridleft.addWidget(labelPrice, 4, 0)        gridleft.addWidget(labelVolume, 5, 0)        gridleft.addWidget(labelPriceType, 6, 0)        gridleft.addWidget(labelExchange, 7, 0)        gridleft.addWidget(labelCurrency, 8, 0)        gridleft.addWidget(labelProductClass, 9, 0)           gridleft.addWidget(labelGateway, 10, 0)        gridleft.addWidget(self.lineSymbol, 0, 1, 1, -1)        gridleft.addWidget(self.lineName, 1, 1, 1, -1)        gridleft.addWidget(self.comboDirection, 2, 1, 1, -1)        gridleft.addWidget(self.comboOffset, 3, 1, 1, -1)        gridleft.addWidget(self.checkFixed, 4, 1)        gridleft.addWidget(self.spinPrice, 4, 2)        gridleft.addWidget(self.spinVolume, 5, 1, 1, -1)        gridleft.addWidget(self.comboPriceType, 6, 1, 1, -1)        gridleft.addWidget(self.comboExchange, 7, 1, 1, -1)        gridleft.addWidget(self.comboCurrency, 8, 1, 1, -1)        gridleft.addWidget(self.comboProductClass, 9, 1, 1, -1)        gridleft.addWidget(self.comboGateway, 10, 1, 1, -1)        # 右边部分        labelBid1 = QtWidgets.QLabel(vtText.BID_1)        labelBid2 = QtWidgets.QLabel(vtText.BID_2)        labelBid3 = QtWidgets.QLabel(vtText.BID_3)        labelBid4 = QtWidgets.QLabel(vtText.BID_4)        labelBid5 = QtWidgets.QLabel(vtText.BID_5)        labelAsk1 = QtWidgets.QLabel(vtText.ASK_1)        labelAsk2 = QtWidgets.QLabel(vtText.ASK_2)        labelAsk3 = QtWidgets.QLabel(vtText.ASK_3)        labelAsk4 = QtWidgets.QLabel(vtText.ASK_4)        labelAsk5 = QtWidgets.QLabel(vtText.ASK_5)        self.labelBidPrice1 = QtWidgets.QLabel()        self.labelBidPrice2 = QtWidgets.QLabel()        self.labelBidPrice3 = QtWidgets.QLabel()        self.labelBidPrice4 = QtWidgets.QLabel()        self.labelBidPrice5 = QtWidgets.QLabel()        self.labelBidVolume1 = QtWidgets.QLabel()        self.labelBidVolume2 = QtWidgets.QLabel()        self.labelBidVolume3 = QtWidgets.QLabel()        self.labelBidVolume4 = QtWidgets.QLabel()        self.labelBidVolume5 = QtWidgets.QLabel()           self.labelAskPrice1 = QtWidgets.QLabel()        self.labelAskPrice2 = QtWidgets.QLabel()        self.labelAskPrice3 = QtWidgets.QLabel()        self.labelAskPrice4 = QtWidgets.QLabel()        self.labelAskPrice5 = QtWidgets.QLabel()        self.labelAskVolume1 = QtWidgets.QLabel()        self.labelAskVolume2 = QtWidgets.QLabel()        self.labelAskVolume3 = QtWidgets.QLabel()        self.labelAskVolume4 = QtWidgets.QLabel()        self.labelAskVolume5 = QtWidgets.QLabel()           labelLast = QtWidgets.QLabel(vtText.LAST)        self.labelLastPrice = QtWidgets.QLabel()        self.labelReturn = QtWidgets.QLabel()        self.labelLastPrice.setMinimumWidth(60)        self.labelReturn.setMinimumWidth(60)        gridRight = QtWidgets.QGridLayout()        gridRight.addWidget(labelAsk5, 0, 0)        gridRight.addWidget(labelAsk4, 1, 0)        gridRight.addWidget(labelAsk3, 2, 0)        gridRight.addWidget(labelAsk2, 3, 0)        gridRight.addWidget(labelAsk1, 4, 0)        gridRight.addWidget(labelLast, 5, 0)        gridRight.addWidget(labelBid1, 6, 0)        gridRight.addWidget(labelBid2, 7, 0)        gridRight.addWidget(labelBid3, 8, 0)        gridRight.addWidget(labelBid4, 9, 0)        gridRight.addWidget(labelBid5, 10, 0)        gridRight.addWidget(self.labelAskPrice5, 0, 1)        gridRight.addWidget(self.labelAskPrice4, 1, 1)        gridRight.addWidget(self.labelAskPrice3, 2, 1)        gridRight.addWidget(self.labelAskPrice2, 3, 1)        gridRight.addWidget(self.labelAskPrice1, 4, 1)        gridRight.addWidget(self.labelLastPrice, 5, 1)        gridRight.addWidget(self.labelBidPrice1, 6, 1)        gridRight.addWidget(self.labelBidPrice2, 7, 1)        gridRight.addWidget(self.labelBidPrice3, 8, 1)        gridRight.addWidget(self.labelBidPrice4, 9, 1)        gridRight.addWidget(self.labelBidPrice5, 10, 1)         gridRight.addWidget(self.labelAskVolume5, 0, 2)        gridRight.addWidget(self.labelAskVolume4, 1, 2)        gridRight.addWidget(self.labelAskVolume3, 2, 2)        gridRight.addWidget(self.labelAskVolume2, 3, 2)        gridRight.addWidget(self.labelAskVolume1, 4, 2)        gridRight.addWidget(self.labelReturn, 5, 2)        gridRight.addWidget(self.labelBidVolume1, 6, 2)        gridRight.addWidget(self.labelBidVolume2, 7, 2)        gridRight.addWidget(self.labelBidVolume3, 8, 2)        gridRight.addWidget(self.labelBidVolume4, 9, 2)        gridRight.addWidget(self.labelBidVolume5, 10, 2)        # 发单按钮        buttonSendOrder = QtWidgets.QPushButton(vtText.SEND_ORDER)        buttonCancelAll = QtWidgets.QPushButton(vtText.CANCEL_ALL)        size = buttonSendOrder.sizeHint()        buttonSendOrder.setMinimumHeight(size.height()*2)   # 把按钮高度设为默认两倍        buttonCancelAll.setMinimumHeight(size.height()*2)        # 整合布局        hbox = QtWidgets.QHBoxLayout()        hbox.addLayout(gridleft)        hbox.addLayout(gridRight)        vbox = QtWidgets.QVBoxLayout()        vbox.addLayout(hbox)        vbox.addWidget(buttonSendOrder)        vbox.addWidget(buttonCancelAll)        vbox.addStretch()        self.setLayout(vbox)        # 关联更新        buttonSendOrder.clicked.connect(self.sendOrder)        buttonCancelAll.clicked.connect(self.cancelAll)        self.lineSymbol.returnPressed.connect(self.updateSymbol)    #----------------------------------------------------------------------    def updateSymbol(self):        """合约变化"""        # 读取组件数据        symbol = str(self.lineSymbol.text())        exchange = unicode(self.comboExchange.currentText())        currency = unicode(self.comboCurrency.currentText())        productClass = unicode(self.comboProductClass.currentText())                   gatewayName = unicode(self.comboGateway.currentText())        # 查询合约        if exchange:            vtSymbol = '.'.join([symbol, exchange])            contract = self.mainEngine.getContract(vtSymbol)        else:            vtSymbol = symbol            contract = self.mainEngine.getContract(symbol)           if contract:            vtSymbol = contract.vtSymbol            gatewayName = contract.gatewayName            self.lineName.setText(contract.name)            exchange = contract.exchange    # 保证有交易所代码        # 清空价格数量        self.spinPrice.setValue(0)        self.spinVolume.setValue(0)        # 清空行情显示        self.labelBidPrice1.setText('')        self.labelBidPrice2.setText('')        self.labelBidPrice3.setText('')        self.labelBidPrice4.setText('')        self.labelBidPrice5.setText('')        self.labelBidVolume1.setText('')        self.labelBidVolume2.setText('')        self.labelBidVolume3.setText('')        self.labelBidVolume4.setText('')        self.labelBidVolume5.setText('')            self.labelAskPrice1.setText('')        self.labelAskPrice2.setText('')        self.labelAskPrice3.setText('')        self.labelAskPrice4.setText('')        self.labelAskPrice5.setText('')        self.labelAskVolume1.setText('')        self.labelAskVolume2.setText('')        self.labelAskVolume3.setText('')        self.labelAskVolume4.setText('')        self.labelAskVolume5.setText('')        self.labelLastPrice.setText('')        self.labelReturn.setText('')        # 重新注册事件监听        self.eventEngine.unregister(EVENT_TICK + self.symbol, self.signal.emit)        self.eventEngine.register(EVENT_TICK + vtSymbol, self.signal.emit)        # 订阅合约        req = VtSubscribeReq()        req.symbol = symbol        req.exchange = exchange        req.currency = currency        req.productClass = productClass        # 默认跟随价        self.checkFixed.setChecked(False)        self.mainEngine.subscribe(req, gatewayName)        # 更新组件当前交易的合约        self.symbol = vtSymbol    #----------------------------------------------------------------------    def updateTick(self, event):        """更新行情"""        tick = event.dict_['data']        if tick.vtSymbol == self.symbol:            if not self.checkFixed.isChecked():                self.spinPrice.setValue(tick.lastPrice)            self.labelBidPrice1.setText(str(tick.bidPrice1))            self.labelAskPrice1.setText(str(tick.askPrice1))            self.labelBidVolume1.setText(str(tick.bidVolume1))            self.labelAskVolume1.setText(str(tick.askVolume1))            if tick.bidPrice2:                self.labelBidPrice2.setText(str(tick.bidPrice2))                self.labelBidPrice3.setText(str(tick.bidPrice3))                self.labelBidPrice4.setText(str(tick.bidPrice4))                self.labelBidPrice5.setText(str(tick.bidPrice5))                self.labelAskPrice2.setText(str(tick.askPrice2))                self.labelAskPrice3.setText(str(tick.askPrice3))                self.labelAskPrice4.setText(str(tick.askPrice4))                self.labelAskPrice5.setText(str(tick.askPrice5))                self.labelBidVolume2.setText(str(tick.bidVolume2))                self.labelBidVolume3.setText(str(tick.bidVolume3))                self.labelBidVolume4.setText(str(tick.bidVolume4))                self.labelBidVolume5.setText(str(tick.bidVolume5))                self.labelAskVolume2.setText(str(tick.askVolume2))                self.labelAskVolume3.setText(str(tick.askVolume3))                self.labelAskVolume4.setText(str(tick.askVolume4))                self.labelAskVolume5.setText(str(tick.askVolume5))              self.labelLastPrice.setText(str(tick.lastPrice))            if tick.preClosePrice:                rt = (tick.lastPrice/tick.preClosePrice)-1                self.labelReturn.setText(('%.2f' %(rt*100))+'%')            else:                self.labelReturn.setText('')    #----------------------------------------------------------------------    def connectSignal(self):        """连接Signal"""        self.signal.connect(self.updateTick)    #----------------------------------------------------------------------    def sendOrder(self):        """发单"""        symbol = str(self.lineSymbol.text())        exchange = unicode(self.comboExchange.currentText())        currency = unicode(self.comboCurrency.currentText())        productClass = unicode(self.comboProductClass.currentText())                   gatewayName = unicode(self.comboGateway.currentText())                # 查询合约        if exchange:            vtSymbol = '.'.join([symbol, exchange])            contract = self.mainEngine.getContract(vtSymbol)        else:            vtSymbol = symbol            contract = self.mainEngine.getContract(symbol)        if contract:            gatewayName = contract.gatewayName            exchange = contract.exchange    # 保证有交易所代码        req = VtOrderReq()        req.symbol = symbol        req.exchange = exchange        req.price = self.spinPrice.value()        req.volume = self.spinVolume.value()        req.direction = unicode(self.comboDirection.currentText())        req.priceType = unicode(self.comboPriceType.currentText())        req.offset = unicode(self.comboOffset.currentText())        req.currency = currency        req.productClass = productClass        self.mainEngine.sendOrder(req, gatewayName)    #----------------------------------------------------------------------    def cancelAll(self):        """一键撤销所有委托"""        l = self.mainEngine.getAllWorkingOrders()        for order in l:            req = VtCancelOrderReq()            req.symbol = order.symbol            req.exchange = order.exchange            req.frontID = order.frontID            req.sessionID = order.sessionID            req.orderID = order.orderID            self.mainEngine.cancelOrder(req, order.gatewayName)    #----------------------------------------------------------------------    def closePosition(self, cell):        """根据持仓信息自动填写交易组件"""        # 读取持仓数据,cell是一个表格中的单元格对象        pos = cell.data        symbol = pos.symbol        # 更新交易组件的显示合约        self.lineSymbol.setText(symbol)        self.updateSymbol()        # 自动填写信息        self.comboPriceType.setCurrentIndex(self.priceTypeList.index(PRICETYPE_LIMITPRICE))        self.comboOffset.setCurrentIndex(self.offsetList.index(OFFSET_CLOSE))        self.spinVolume.setValue(pos.position)        if pos.direction == DIRECTION_LONG or pos.direction == DIRECTION_NET:            self.comboDirection.setCurrentIndex(self.directionList.index(DIRECTION_SHORT))        else:            self.comboDirection.setCurrentIndex(self.directionList.index(DIRECTION_LONG))        # 价格留待更新后由用户输入,防止有误操作

MainWindow.py主要内容有主窗口菜单的创建,如何将Basic中组件加载到主窗口中,自定义Qtpy信号通道及触发,以及如何退出主窗口,和如何保存界面设置及其恢复。

# encoding: UTF-8# python跨平台库,用于获取系统进程,进运行信息(CPU,运存,磁盘,网络等)import psutilfrom vnpy.trader.vtFunction import loadIconPathfrom vnpy.trader.vtGlobal import globalSettingfrom vnpy.trader.uiBasicWidget import *########################################################################class MainWindow(QtWidgets.QMainWindow):    """主窗口"""      # 自定义信号变量,用于连接槽函数,并可主动触发.    signalStatusBar = QtCore.Signal(type(Event()))    #----------------------------------------------------------------------    def __init__(self, mainEngine, eventEngine):        """Constructor"""                  super(MainWindow, self).__init__()        self.mainEngine = mainEngine        self.eventEngine = eventEngine        l = self.mainEngine.getAllGatewayDetails()        self.gatewayNameList = [d['gatewayName'] for d in l]                self.widgetDict = {}    # 用来保存子窗口的字典        # 获取主引擎中的上层应用信息        self.appDetailList = self.mainEngine.getAllAppDetails()        self.initUi()        self.loadWindowSettings('custom')    #----------------------------------------------------------------------    def initUi(self):        """初始化界面"""        self.setWindowTitle('VnTrader')        self.initCentral()        self.initMenu()        self.initStatusBar()    #----------------------------------------------------------------------    def initCentral(self):        """初始化中心区域"""        widgetMarketM, dockMarketM = self.createDock(MarketMonitor, vtText.MARKET_DATA, QtCore.Qt.RightDockWidgetArea)        widgetLogM, dockLogM = self.createDock(LogMonitor, vtText.LOG, QtCore.Qt.BottomDockWidgetArea)        widgetErrorM, dockErrorM = self.createDock(ErrorMonitor, vtText.ERROR, QtCore.Qt.BottomDockWidgetArea)        widgetTradeM, dockTradeM = self.createDock(TradeMonitor, vtText.TRADE, QtCore.Qt.BottomDockWidgetArea)        widgetOrderM, dockOrderM = self.createDock(OrderMonitor, vtText.ORDER, QtCore.Qt.RightDockWidgetArea)        widgetPositionM, dockPositionM = self.createDock(PositionMonitor, vtText.POSITION, QtCore.Qt.BottomDockWidgetArea)        widgetAccountM, dockAccountM = self.createDock(AccountMonitor, vtText.ACCOUNT, QtCore.Qt.BottomDockWidgetArea)        widgetTradingW, dockTradingW = self.createDock(TradingWidget, vtText.TRADING, QtCore.Qt.LeftDockWidgetArea)        self.tabifyDockWidget(dockTradeM, dockErrorM)        self.tabifyDockWidget(dockTradeM, dockLogM)        self.tabifyDockWidget(dockPositionM, dockAccountM)        dockTradeM.raise_()        dockPositionM.raise_()        # 连接组件之间的信号        widgetPositionM.itemDoubleClicked.connect(widgetTradingW.closePosition)        # 保存默认设置        self.saveWindowSettings('default')    #----------------------------------------------------------------------    def initMenu(self):        """初始化菜单"""        # 创建菜单        menubar = self.menuBar()        # 设计为只显示存在的接口        gatewayDetails = self.mainEngine.getAllGatewayDetails()        sysMenu = menubar.addMenu(vtText.SYSTEM)        for d in gatewayDetails:            if d['gatewayType'] == GATEWAYTYPE_FUTURES:                self.addConnectAction(sysMenu, d['gatewayName'], d['gatewayDisplayName'])        sysMenu.addSeparator()        for d in gatewayDetails:            if d['gatewayType'] == GATEWAYTYPE_EQUITY:                self.addConnectAction(sysMenu, d['gatewayName'], d['gatewayDisplayName'])        sysMenu.addSeparator()        for d in gatewayDetails:            if d['gatewayType'] == GATEWAYTYPE_INTERNATIONAL:                self.addConnectAction(sysMenu, d['gatewayName'], d['gatewayDisplayName'])               sysMenu.addSeparator()        for d in gatewayDetails:            if d['gatewayType'] == GATEWAYTYPE_BTC:                self.addConnectAction(sysMenu, d['gatewayName'], d['gatewayDisplayName'])        sysMenu.addSeparator()        for d in gatewayDetails:            if d['gatewayType'] == GATEWAYTYPE_DATA:                self.addConnectAction(sysMenu, d['gatewayName'], d['gatewayDisplayName'])        sysMenu.addSeparator()        sysMenu.addAction(self.createAction(vtText.CONNECT_DATABASE, self.mainEngine.dbConnect, loadIconPath('database.ico')))        sysMenu.addSeparator()        sysMenu.addAction(self.createAction(vtText.EXIT, self.close, loadIconPath('exit.ico')))        # 功能应用        appMenu = menubar.addMenu(vtText.APPLICATION)        for appDetail in self.appDetailList:            function = self.createOpenAppFunction(appDetail)            action = self.createAction(appDetail['appDisplayName'], function, loadIconPath(appDetail['appIco']))            appMenu.addAction(action)        # 帮助        helpMenu = menubar.addMenu(vtText.HELP)        helpMenu.addAction(self.createAction(vtText.CONTRACT_SEARCH, self.openContract, loadIconPath('contract.ico')))        helpMenu.addSeparator()        helpMenu.addAction(self.createAction(vtText.RESTORE, self.restoreWindow, loadIconPath('restore.ico')))        helpMenu.addAction(self.createAction(vtText.ABOUT, self.openAbout, loadIconPath('about.ico')))        helpMenu.addSeparator()        helpMenu.addAction(self.createAction(vtText.TEST, self.test, loadIconPath('test.ico')))    #----------------------------------------------------------------------    def initStatusBar(self):        """初始化状态栏"""        self.statusLabel = QtWidgets.QLabel()        self.statusLabel.setAlignment(QtCore.Qt.AlignLeft)        self.statusBar().addPermanentWidget(self.statusLabel)        self.statusLabel.setText(self.getCpuMemory())        self.sbCount = 0        self.sbTrigger = 10     # 10秒刷新一次        self.signalStatusBar.connect(self.updateStatusBar)        self.eventEngine.register(EVENT_TIMER, self.signalStatusBar.emit)    #----------------------------------------------------------------------    def updateStatusBar(self, event):        """在状态栏更新CPU和内存信息"""        self.sbCount += 1        if self.sbCount == self.sbTrigger:            self.sbCount = 0            self.statusLabel.setText(self.getCpuMemory())    #----------------------------------------------------------------------    def getCpuMemory(self):        """获取CPU和内存状态信息"""        cpuPercent = psutil.cpu_percent()        memoryPercent = psutil.virtual_memory().percent        return vtText.CPU_MEMORY_INFO.format(cpu=cpuPercent, memory=memoryPercent)    #----------------------------------------------------------------------    def addConnectAction(self, menu, gatewayName, displayName=''):        """增加连接功能"""        if gatewayName not in self.gatewayNameList:            return        def connect():            self.mainEngine.connect(gatewayName)        if not displayName:            displayName = gatewayName        actionName = vtText.CONNECT + displayName        connectAction = self.createAction(actionName, connect,                                           loadIconPath('connect.ico'))        menu.addAction(connectAction)    #----------------------------------------------------------------------    def createAction(self, actionName, function, iconPath=''):        """创建操作功能"""        action = QtWidgets.QAction(actionName, self)        action.triggered.connect(function)        if iconPath:            icon = QtGui.QIcon(iconPath)            action.setIcon(icon)        return action    #----------------------------------------------------------------------    def createOpenAppFunction(self, appDetail):        """创建打开应用UI的函数"""        def openAppFunction():            appName = appDetail['appName']            try:                self.widgetDict[appName].show()            except KeyError:                appEngine = self.mainEngine.appDict[appName]                self.widgetDict[appName] = appDetail['appWidget'](appEngine, self.eventEngine)                self.widgetDict[appName].show()        return openAppFunction    #----------------------------------------------------------------------    def test(self):        """测试按钮用的函数"""        # 有需要使用手动触发的测试函数可以写在这里        pass    #----------------------------------------------------------------------    def openAbout(self):        """打开关于"""        try:            self.widgetDict['aboutW'].show()        except KeyError:            self.widgetDict['aboutW'] = AboutWidget(self)            self.widgetDict['aboutW'].show()    #----------------------------------------------------------------------    def openContract(self):        """打开合约查询"""        try:            self.widgetDict['contractM'].show()        except KeyError:            self.widgetDict['contractM'] = ContractManager(self.mainEngine)            self.widgetDict['contractM'].show()    #----------------------------------------------------------------------    def closeEvent(self, event):        """关闭事件"""        reply = QtWidgets.QMessageBox.question(self, vtText.EXIT,                                           vtText.CONFIRM_EXIT, QtWidgets.QMessageBox.Yes |                                            QtWidgets.QMessageBox.No, QtWidgets.QMessageBox.No)        if reply == QtWidgets.QMessageBox.Yes:             for widget in self.widgetDict.values():                widget.close()            self.saveWindowSettings('custom')            # 关闭主窗口            self.mainEngine.exit()            event.accept()        else:            event.ignore()    #----------------------------------------------------------------------    def createDock(self, widgetClass, widgetName, widgetArea):        """创建停靠组件"""        widget = widgetClass(self.mainEngine, self.eventEngine)        dock = QtWidgets.QDockWidget(widgetName)        dock.setWidget(widget)        dock.setObjectName(widgetName)        dock.setFeatures(dock.DockWidgetFloatable|dock.DockWidgetMovable)        self.addDockWidget(widgetArea, dock)        return widget, dock    #----------------------------------------------------------------------    def saveWindowSettings(self, settingName):        """保存窗口设置"""        settings = QtCore.QSettings('vn.trader', settingName)        settings.setValue('state', self.saveState())        settings.setValue('geometry', self.saveGeometry())    #----------------------------------------------------------------------    def loadWindowSettings(self, settingName):        """载入窗口设置"""        settings = QtCore.QSettings('vn.trader', settingName)        # 这里由于PyQt4的版本不同,settings.value('state')调用返回的结果可能是:        # 1. None(初次调用,注册表里无相应记录,因此为空)        # 2. QByteArray(比较新的PyQt4)        # 3. QVariant(以下代码正确执行所需的返回结果)        # 所以为了兼容考虑,这里加了一个try...except,如果是1、2的情况就pass        # 可能导致主界面的设置无法载入(每次退出时的保存其实是成功了)        try:            self.restoreState(settings.value('state').toByteArray())            self.restoreGeometry(settings.value('geometry').toByteArray())            except AttributeError:            pass    #----------------------------------------------------------------------    def restoreWindow(self):        """还原默认窗口设置(还原停靠组件位置)"""        self.loadWindowSettings('default')        self.showMaximized()########################################################################class AboutWidget(QtWidgets.QDialog):    """显示关于信息"""    #----------------------------------------------------------------------    def __init__(self, parent=None):        """Constructor"""        super(AboutWidget, self).__init__(parent)        self.initUi()    #----------------------------------------------------------------------    def initUi(self):        """"""        self.setWindowTitle(vtText.ABOUT + 'VnTrader')        text = u"""            Developed by Traders, for Traders.            License:MIT            Website:www.vnpy.org            Github:www.github.com/vnpy/vnpy            """        label = QtWidgets.QLabel()        label.setText(text)        label.setMinimumWidth(500)        vbox = QtWidgets.QVBoxLayout()        vbox.addWidget(label)        self.setLayout(vbox)