文章目录

  • 项目介绍
  • 先从界面开始吧
  • 让我们的窗口动起来,还能隐藏哦
  • 从哪爬点股票数据呢
  • 添加一支你的自选股吧
  • 获取些股票信息来填充我们的窗体吧
  • 让我们的股票价格滚动起来吧
  • 删除、置顶、排序,你还能想到什么使用操作呢
  • 一些展望

项目介绍

你还在为了不能及时看到股价发愁吗?你还在为了上班偷看股票APP而担心吗?现在我们隆重推出新的一款Windows实时股票显示应用,你看它简洁的界面,丰富的功能,还支持贴边自动隐藏,现在开始不要998,不要998,统统免费,只需要看本教程,你就可以自己做出这个应用,有兴趣还可以扩展一下功能。我敢保证,你只要把这应用往桌面一放,那你的股票一定得连涨10天!!!

哈哈哈,上面略有夸张成分,接下来有兴趣的小伙伴们一起来和我完成这款应用吧,所需要的软件:

  • python3.7
  • PyQt5==5.15.4
  • pyqt5-plugins==5.15.4.2.2
  • pyqt5-tools==5.15.4.3.2

代码可以参考我的github。

先从界面开始吧

我们先建立自己的工程目录,工程名就命名为RTStock吧,翻译过来就是实时股票(Real Time Stock)。然后我们在工程目录中添加文件Window.py,它就作为我们的界面文件,同时也是应用的主入口。

Window.py中添加类Window,该类继承了QWidget,我们为该类先定义两个方法,第一个方法是构造方法__init__(类实例化时第一个调用的函数),该方法有5个参数:

  • self,指向自己的引用
  • screen_width,屏幕的宽
  • screen_hight,屏幕的长
  • window_width,窗口的宽
  • window_height,窗口的长

我们用这些参数将窗口设置到屏幕中央,并调用我们的提供的第二个函数initUI,该函数负责将界面丰富化,并且显示出界面。同时,我们还要提供主函数,在主函数中实例化一个Window类,同时要实例化一个QApplication类,该类提供了exec_函数使得界面可以等待进行交互式操作。下面给出了实现代码及需要导入的包:

import sys
from PyQt5.QtCore import Qt, QRect
from PyQt5.QtWidgets import QWidget, QApplicationclass Window(QWidget):"""docstring for Window"""def __init__(self, screen_width, screen_height, window_width, window_height):super().__init__()self.setGeometry(QRect(   screen_width / 2 - window_width / 2,screen_height / 2 - window_height / 2,window_width,window_height))self.screen_width = screen_widthself.screen_height = screen_heightself.window_width = self.frameGeometry().width()self.window_height = self.frameGeometry().height()self.initUI()def initUI(self):self.show()if __name__ == '__main__':app = QApplication(sys.argv)screen_width = app.primaryScreen().geometry().width()screen_height = app.primaryScreen().geometry().height()window = Window(screen_width, screen_height, 800, 300)sys.exit(app.exec_())

运行代码可以得到下面的界面:

接着我们对窗口做一些美化,例如修改透明度,去边框,光标样式等等,在initUI中添加下面的代码:

def initUI(self):self.setWindowTitle('RTStock') # set titleself.move(100, 100) # modify the position of windowself.setWindowFlags(Qt.FramelessWindowHint | Qt.WindowStaysOnTopHint) # remove frame and keep it always at the topself.setWindowOpacity(0.8) # set the transparency of the windowself.setCursor(Qt.PointingHandCursor) # set the style of the cursorself.show()

再次运行一下可以得到更漂亮的窗口(其实就是一张半透明的白纸,哈哈哈~):

然后,我们对窗口填充一些组件用于展示我们想要的信息,在本文中,我将该界面分为三块:

  • 头部放一个logo
  • 中间放一张表,去展示信息
  • 尾部是一个时间戳,展示的数据最后一次更新的时间

代码如下:

# some acquired packages
from PyQt5.QtWidgets import QTableWidgetItem, QAbstractItemView, QHeaderView
from PyQt5.QtWidgets import QLabel, QTableWidget, QHBoxLayout
from PyQt5.QtGui import QFont, QBrush, QColor# add code in the function initUI
def initUI(self):...# the header of window (a label)self.HeadView = QLabel('RT Stock',self)self.HeadView.setAlignment(Qt.AlignCenter)self.HeadView.setGeometry(QRect(0, 0, self.window_width, 25))self.HeadView.setFont(QFont("Roman times",12,QFont.Bold))# the body of window (a table)      self.stockTableView = QTableWidget(self)self.stockTableView.setGeometry(QRect(0, 40, self.window_width, 220))self.stockTableView.verticalHeader().setVisible(False)  # remove the vertical header of the tableself.stockTableView.setEditTriggers(QAbstractItemView.NoEditTriggers) # disable editingself.stockTableView.horizontalHeader().setSectionResizeMode(QHeaderView.Stretch) # let the size of horizontal header of the table stretch automatically self.stockTableView.setShowGrid(False) # remove the grid of the tableself.stockTableView.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff) # remove the scroll bar of the tableself.stockTableView.setColumnCount(5) # the table has 5 column(name, price, change rate, change, operation)self.stockTableView.setHorizontalHeaderLabels(['名称↑↓','现价↑↓','涨跌幅↑↓','涨跌↑↓','操作'])# the tail of window (a label)self.timeView = QLabel('last update: ',self)self.timeView.setGeometry(QRect(0, 270, self.window_width, 20))self.show()

运行效果如下图所示:

可以看到,我们的显示区(表格)有5列,每一行显示一只股票的现价情况,而操作一栏用于提供一些功能,例如将该只股票置顶或者删除。现在的界面已经大致成型,接下来,我们将开始设计窗口的拖拽和隐藏功能。

让我们的窗口动起来,还能隐藏哦

我们先来设计移动功能,也就是拖拽窗口。有同学可能会说,这个功能还用设计?不是一开始窗口就能被拖拽吗?哈哈,那你就单纯了,我们已经去掉了窗口边框,而靠窗体是没有办法移动的,不信的话你试试。

说到底,拖拽窗口就三个步骤:

  • 在窗体中按下鼠标
  • 移动鼠标到目标位置
  • 释放鼠标按键

因此我们只要将这三个事件对应的事件函数实现就好,即QWidgetmousePressEvent,mouseMoveEvent,mouseReleaseEvent事件函数,它们在鼠标做出相应动作后会被自动调用,下面是相关的实现代码:

# add these functions in the class Window
def mousePressEvent(self, event):if event.button() == Qt.LeftButton: # press the left key of mouseself._startPos = event.pos() # record the start position of the cursor before movingdef mouseMoveEvent(self, event):self._wmGap = event.pos() - self._startPos # move distance = current position - start positionfinal_pos = self.pos() + self._wmGap # the position where the window is currently supposed to be# The window should not be moved out of the left side of the screen.if self.frameGeometry().topLeft().x() + self._wmGap.x() <= 0:final_pos.setX(0)# The window should not be moved out of the top side of the screen.if self.frameGeometry().topLeft().y() + self._wmGap.y() <= 0:final_pos.setY(0)# The window should not be moved out of the right side of the screen.if self.frameGeometry().bottomRight().x() + self._wmGap.x() >= self.screen_width:final_pos.setX(self.screen_width - self.window_width)# The window should not be moved out of the below side of the screen.if self.frameGeometry().bottomRight().y() + self._wmGap.y() >= self.screen_height:final_pos.setY(self.screen_height - self.window_height)self.move(final_pos) # move windowdef mouseReleaseEvent(self, event):# clear _startPos and _wmGapif event.button() == Qt.LeftButton:self._startPos = Noneself._wmGap = Noneif event.button() == Qt.RightButton:self._startPos = Noneself._wmGap = None

这时候,你再试着拖拽一下窗体,此时它变得可以移动了,而且由于代码中做了边界检查,所以窗体是不会被移出屏幕的。接着我们再试试贴边隐藏功能,这个功能其实也简单,就是在窗体贴住屏幕边界的时候,将其隐藏,如果你把鼠标移到窗体隐藏的地方,它又会弹出。要实现这个功能要考虑两个问题:

  • 怎么检查贴边,在哪检查?
  • 怎么隐藏,怎么弹出?

对于怎么弹出或者隐藏,我给出的方案是将窗口形状改变为一个窄条,看起来就和藏起来一样,例如窗口大小为800 * 300,那么右侧贴边就变成了10 * 300,上面贴边就变成800 * 10(我们不考虑向下贴边,下面是任务栏就不贴边了吧,哈哈)。而这个变化过程可以使用QPropertyAnimation,我们先实现这个隐藏和弹出函数:

from PyQt5.QtCore import QPropertyAnimation# add the function in the class Window
# "direction" indicate the direction the window hides or pops
def startAnimation(self, x, y, mode='show', direction=None):animation = QPropertyAnimation(self, b"geometry", self)animation.setDuration(200) # the duration of the moving animationif mode == 'hide':if direction == 'right':animation.setEndValue(QRect(x, y, 10, self.window_height))elif direction == 'left':animation.setEndValue(QRect(0, y, 10, self.window_height))else:animation.setEndValue(QRect(x, 0, self.window_width, 10))else:animation.setEndValue(QRect(x, y, self.window_width, self.window_height))# start show the animation that the shape of the window becomes the shape of EndValueanimation.start()

然后,对于怎么检查贴边,我建议可以在鼠标移入和移除窗体后进行检查,一般情况是这样:

  • 窗口隐藏起来后,鼠标移入残留的窄条中,窗体弹出
  • 窗口被移动到屏幕边界,鼠标移除窗体后,窗口隐藏

我们用一个变量保存现在窗口的状态(隐藏或者在外面),然后配和边界检查决定窗口怎么隐藏:

# add a variable indicate the status of window
def __init__(self, screen_width, screen_height, window_width, window_height):...self.hidden = False # The window has not been hidden.self.initUI()# add these functions in the class Window
# invoke automatically after entering windows
def enterEvent(self, event):self.hide_or_show('show', event)# invoke automatically after leaving windows
def leaveEvent(self, event):self.hide_or_show('hide', event)def hide_or_show(self, mode, event):pos = self.frameGeometry().topLeft()if mode == 'show' and self.hidden: # try to pop up the window# The window is hidden on the right side of the screenif pos.x() + self.window_width >= self.screen_width:self.startAnimation(self.screen_width - self.window_width, pos.y())event.accept()self.hidden = False# The window is hidden on the left side of the screenelif pos.x() <= 0:self.startAnimation(0, pos.y())event.accept()self.hidden = False# The window is hidden on the top side of the screenelif pos.y() <= 0:self.startAnimation(pos.x(), 0)event.accept()self.hidden = Falseelif mode == 'hide' and (not self.hidden): # try to hide the window# The window is on the right side of the screenif pos.x() + self.window_width >= self.screen_width:self.startAnimation(self.screen_width - 10, pos.y(), mode, 'right')event.accept()self.hidden = True# The window is on the left side of the screen  elif pos.x() <= 0:self.startAnimation(10 - self.window_width, pos.y(), mode, 'left')event.accept()self.hidden = True# The window is on the top side of the screenelif pos.y() <= 0:self.startAnimation(pos.x(), 10 - self.window_height, mode, 'up')event.accept()self.hidden = True

现在,你试着把窗口拖拽到屏幕最右边,最上边或者最左边看看效果,你会发现窗口会自动”隐藏起来“。同样在窗口隐藏后,你把鼠标放到残留的窗体上,窗口会自动“弹出”。

到现在位置,我们本小节的任务就已经完成,接下来该接触点股票(数据)相关的东西啦。

从哪爬点股票数据呢

为了能够获得股票的数据,我们就需要提供股票的代码或者名称,然后通过一些股票分析网站(例如新浪财经,同花顺等等)提供的API去获取该股票当前的行情。因此我们需要设计一个类,该类需要完成如下几点功能:

  • 通过股票代码获取该只股票的当前价格以及开盘价
  • 通过股票名称获取该名称对应股票的代码

因此,我们先添加一个Fetcher.py文件到工程目录中,该文件提供一个类Fetcher。为了能够不被网站识别为非法爬虫,我们还需要在类的构造函数__init__中设置用户代理,顺便一提我们使用的爬取网页的库是request,代码如下:

class Fetcher:def __init__(self):     self.headers = {'user-agent' : 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.212 Safari/537.36',}

然后,这里我们需要先普及一点股票的小知识,首先是股票代码,它是由[market][code]格式组成。其次本文选择的API是新浪财经的API接口,其形式如下:

http://hq.sinajs.cn/list=[market][code]

以贵州茅台sh600519为例通过访问链接http://hq.sinajs.cn/list=sh600519,新浪服务器会返回如下格式的内容,含义请阅读相关API文档:

var hq_str_sh600519="贵州茅台,1989.000,1980.000,1969.000,2006.000,1962.010,1969.000,1969.500,2978013,5902656221.000,4205,1969.000,100,1968.670,100,1968.660,100,1968.600,400,1968.220,100,1969.500,100,1969.580,300,1969.660,200,1969.680,300,1969.690,2021-07-21,15:00:00,00,";

因此根据这个API,我们通过闯入marketcode,那么我们就能得到其当前的价格,代码如下:

import requests# add the function in the class
def get_quotation(self, market : str, code : str) -> dict:try:rsp = requests.get(url, headers=self.headers)rsp = rsp.text.split('=')[1].strip('";\n').split(',')rsp = { 'name' : rsp[0],'open' : rsp[1],'close' : rsp[2],'price' : rsp[3],'high' : rsp[4],'low' : rsp[5],'bidding_buy' : rsp[6],'bidding_sell' : rsp[7],'count' : rsp[8],'amount' : rsp[9],'quote_buy' : rsp[10:20],'quote_sell' : rsp[20:30],'date' : rsp[30],'time' : rsp[31]}return rspexcept Exception:print("check your network")return {}

我们先测试一下,在Fetcher.py文件最后添加下面的代码:

# for testing Fetcher
if __name__ == '__main__':fetcher = Fetcher()print( fetcher.get_quotation('sh', '600519') )

运行这个文件,你如果得到了贵州茅台的股票信息,那么说明你代码ok:

# result
{'name': '贵州茅台', 'open': '1989.000', 'close': '1980.000', 'price': '1969.000', 'high': '2006.000', 'low': '1962.010', 'bidding_buy': '1969.000', 'bidding_sell': '1969.500', 'count': '2978013', 'amount': '5902656221.000', 'quote_buy': ['4205', '1969.000', '100', '1968.670', '100', '1968.660', '100', '1968.600', '400', '1968.220'], 'quote_sell': ['100', '1969.500', '100', '1969.580', '300', '1969.660', '200', '1969.680', '300', '1969.690'], 'date': '2021-07-21', 'time': '15:00:00'}

对于获取名字,我们使用另一个链接https://suggest3.sinajs.cn/suggest/key=[name],以贵州茅台为例,访问https://suggest3.sinajs.cn/suggest/key=贵州茅台你可以得到下面的信息:

var suggestvalue="贵州茅台,11,600519,sh600519,贵州茅台,,贵州茅台,99,1";

可以看到,我们的股票代码就在网页信息当中,代码如下:

import re# add the function in the class
# input: name
# output: (market, code)
def get_market_and_code(self, name : str):url = 'https://suggest3.sinajs.cn/suggest/key=%s' % nametry:rsp = requests.get(url, headers=self.headers)rsp = re.search('[a-z]{2}[0-9]{6}', rsp.text)if rsp:        return (rsp.group()[:2], rsp.group()[2:])else:return Noneexcept Exception:print("check your network")return None

添加一支你的自选股吧

怎样添加一只自选股呢?我想的办法是右击窗口,然后弹出一个菜单栏,里面放些功能选项,例如添加一只自选股,或者退出程序。之后点击”添加一只自选股“按钮,弹出一个输入框,这时你把要添加股票的名字输进去就大功告成啦。接下来的部分我们介绍它的实现机制。

对于右击弹出菜单栏我们可以使用PyQTQWidget自带的相关机制。另外,我们需要为其添加两个活动(功能项),即添加股票和退出,因此需要提供这两个函数用于去绑定到相应的活动中。

退出函数可以什么都不用写,暂时留个空壳子就好,而添加股票函数就比较麻烦一些,需要依次完成如下内容:

  • 弹出输入框
  • 获取输入框中的股票名称
  • 将股票名称转化为股票代码
  • 把代码保存下来

可以发现我们需要保存股票代码,这些股票代码都是我们的自选股。为了能够将股票代码的操作分离出来,我们设计了一个新的类OptionalStock,该类可以完成股票代码的添加,删除等操作。

新建一个文件OptionalStock.py,添加下面的代码:

class OptionalStock(object):def __init__(self):self.stocks = []def add_stock(self, market : str, code : str) -> None:self.stocks.append({'market' : market,'code' : code,})

接下来,就可以设计我们的添加股票的函数了:

from PyQt5.QtWidgets import QMenu, QAction, QMessageBox, QInputDialog
from Fetcher import Fetcher
from OptionalStock import OptionalStockclass Window(QWidget):"""docstring for Window"""def __init__(self, screen_width, screen_height, window_width, window_height):...self.stocks = OptionalStock()self.initUI()def initUI(self):...# menuself.setContextMenuPolicy(Qt.ActionsContextMenu) # activate menuaddStockAction = QAction("Add a optional stock", self)addStockAction.triggered.connect(self.add_optional_stock_by_name)self.addAction(addStockAction)quitAction = QAction("Quit", self)quitAction.triggered.connect(self.close)self.addAction(quitAction)self.show()def add_optional_stock_by_name(self):name, ok = QInputDialog.getText(self, 'Stock Name', '输入股票名称')if ok:res = Fetcher().get_market_and_code(name)if res:self.stocks.add_stock(res[0], res[1])else:QMessageBox.information(self,'error','搜索不到改股票!',QMessageBox.Ok)def closeEvent(self, event):pass

到此为止,我觉得你应该设想一下我们虽然本次保存股票代码到self.stocks中了,但下次启动应用这些股票将不存在了,因此我们有必要将这些信息保存到磁盘中的一个文件中,每次启动应用的时候将原先的股票代码读出来,这些只需要修改一下OptionalStock类就好:

import osclass OptionalStock(object):def __init__(self, record : str):self.stocks = []# if record doesn't exist, generate it.if not os.path.exists(record):f = open(record, 'w')f.close()with open(record, mode = 'r') as f:self.recordFile = record # the file that stores all code of optional stocksline = f.readline()while line: # read a optional stock, format: market,codestock = list(map(lambda x : x.strip(), line.split(',')))if len(stock) == 2:self.stocks.append({'market' : stock[0],'code' : stock[1],'key' : None})line = f.readline()def add_stock(self, market : str, code : str) -> None:with open(self.recordFile, mode = 'a') as f:f.write('\n%s, %s' % (market, code)) # store this stock in the fileself.stocks.append({'market' : market,'code' : code,'key' : None})

上面代码中,可以看到self.stocks中是一个字典列表,每个字典有三个内容,包括市场代码,股票代码以及关键字(这个现在不说,之后会讲到,它是用于排序这些股票的规则)。相应地,我们这时候就要修改下这句代码:

self.stocks = OptionalStock('./record') # This file ’./record‘ stores all codes of stocks

现在你可以尝试一下设计的功能了,右击窗体,输入我们的自选股,回车就能在文件夹下看到一个文件record,里面记录了我们刚刚添加的自选股。

获取些股票信息来填充我们的窗体吧

对于填充窗体,我的想法是分两个步骤进行,第一步填充一些固定不变的数据和格式,例如初始值,一些操作按钮;第二步我们去获取一些动态数据填充股票的股价,涨跌幅等等,代码如下:

def stock_rate(price : float, close : float) -> str:""" return a percentage of rate """rate = (price - close) / closereturn "%.2f%%" % (rate * 100)# add a variable self.stockTable
class Window(QWidget):"""docstring for Window"""def __init__(self, screen_width, screen_height, window_width, window_height):...self.stockTable = self.stocks.stocks # point to stocks in self.stocksself.initUI()def initUI(self):...self.update_table_view()self.update_table_view_data()self.show()# flush static datadef update_table_view(self):self.stockTableView.clearContents() # clear tableself.stockTableView.setRowCount(len(self.stockTable))# set format and '--' for each unit in tablefor i in range(len(self.stockTable)):for j in range(self.stockTableView.columnCount() - 1):item = QTableWidgetItem()item.setText('--')item.setTextAlignment(Qt.AlignVCenter|Qt.AlignHCenter) # alignmentself.stockTableView.setItem(i, j, item)# flush dynamic datadef update_table_view_data(self):lastTime = ''for i in range(len(self.stockTable)): # for each every stockstock = self.stockTable[i]data = {}try:data = Fetcher().get_quotation(stock['market'], stock['code'])except:QMessageBox.information(self,'error','不能获取到股票信息!',QMessageBox.Ok)self.close()# fill dynamic data for items of a stockself.stockTableView.item(i, 0).setText(data['name'])self.stockTableView.item(i, 1).setText(data['price'])# stcok_rate return a string of a percentageself.stockTableView.item(i, 2).setText(stock_rate( float(data['price']) , float(data['close']) ))# set font color according to change of stocksif float(data['price']) > float(data['close']):self.stockTableView.item(i, 2).setForeground(QBrush(QColor(255,0,0)))else:self.stockTableView.item(i, 2).setForeground(QBrush(QColor(0,255,0)))self.stockTableView.item(i, 3).setText("%.2f" % (float(data['price']) - float(data['close'])))# set font color according to change of stocksif float(data['price']) > float(data['close']):self.stockTableView.item(i, 3).setForeground(QBrush(QColor(255,0,0)))else:self.stockTableView.item(i, 3).setForeground(QBrush(QColor(0,255,0)))# set timestamp stringlastTime = data['date'] + ' ' + data['time']self.timeView.setText('last update: ' + lastTime)

现在你再尝试运行一下你的代码,假设你的record文件中保存着贵州茅台的股票代码,那你看到的窗口应该是这个样子的(可能数据不大一样):

另外一点,不知道你注意到没,你现在使用添加股票的功能时,会发现界面并没有多出你新添加的股票,只有重启后才会出现。因此,我们需要在add_optional_stock_by_name添加代码成功后,再追加一个刷新函数,该函数可以更新表项:

class Window(QWidget):def add_optional_stock_by_name(self):...self.stocks.add_stock(res[0], res[1])self.__update_optional_stock()def __update_optional_stock(self):self.stockTable = self.stocks.stocksself.update_table_view()self.update_table_view_data()

这会儿,你再尝试一下,你就会发现我们的功能恢复正常了。

让我们的股票价格滚动起来吧

现在我们应用上显示的股票信息其实是静态的,为了能够不停的刷新最新的数据,我们必须设置一个定时器,隔一会去调用update_table_view_data函数,因此,这时就需要用到QTimer了。

我们只需要设置一个QTImer,为其定好超时相应函数,并在窗口关闭的时候停用该定时器就好。另外还需要注意的是,为了防止在添加股票的时候出问题,我们最好在更新表项静态数据时停掉定时器,具体代码如下:

from PyQt5.QtCore import QTimerclass Window(QWidget):"""docstring for Window"""def __init__(self, screen_width, screen_height, window_width, window_height):...self.initUI()self.timer = QTimer(self)self.timer.timeout.connect(self.update_table_view_data)self.tick = 3000 # time tick is 3 secondsself.timer.start(self.tick) # start timer# modify __update_optional_stockdef __update_optional_stock(self):self.timer.stop()self.stockTable = self.stocks.stocksself.update_table_view()self.update_table_view_data()self.timer.start(self.tick)# modify closeEvent   def closeEvent(self, event):self.timer.stop()

现在你再去运行一下程序,看看是不是你的股价已经动起来了,是不是很激动!!!哈哈,别急呀,接下来还有更好玩的,我们要为我们的应用加一些更实用的操作。

删除、置顶、排序,你还能想到什么使用操作呢

像删除,置顶,排序这些操作,虽然表面上看是更新表格中行的位置,但实质上,根据我们的实现逻辑,你只需要更新self.stocks中每个股票在列表中的位置就好,因此重点还是要回到OptionalStock身上。

对于删除和置顶,假定我们会得到被操作股票原先的索引号,那么我们可以通过操作列表来达到我们的目的:

def top_stock(self, row):self.stocks = [self.stocks[row]] + self.stocks[:row] + self.stocks[(row + 1):]# update the file recordwith open(self.recordFile, mode = 'w') as f:for stock in self.stocks:f.write('%s, %s\n' % (stock['market'], stock['code']))def del_stock(self, row):del self.stocks[row]# update the file recordwith open(self.recordFile, mode = 'w') as f:for stock in self.stocks:f.write('%s, %s\n' % (stock['market'], stock['code']))

对于排序,还记得我们列表中每一项的key关键字吗,我们假设调用者会为这些列表项设置该值,然后我们通过该值进行排序即可,至于逆序还是正序由调用者指定:

def sort_stock(self, reverse = False):def sort_method(elem):return elem['key']self.stocks.sort(key = sort_method, reverse=reverse)with open(self.recordFile, mode = 'w') as f:for stock in self.stocks:f.write('%s, %s\n' % (stock['market'], stock['code']))

好了,回到我们的Window中来,为了执行这些操作,我们必须要为用户提供调用接口,例如排序,我们可以设置表头的按钮触发事件来调用排序方法:

def initUI(self):# menu...self.addAction(quitAction)self.sort_flag = [False] * 4 # reverse or positive sequence  self.stockTableView.horizontalHeader().sectionClicked.connect(self.sort_optional_stock)# index: sort by the index'th column
def sort_optional_stock(self, index : int):if index == 0:for i in range(len(self.stocks.stocks)):self.stocks.stocks[i]['key'] = self.stockTableView.item(i, 0).text()elif index == 1 or index == 3:for i in range(len(self.stocks.stocks)):self.stocks.stocks[i]['key'] = float(self.stockTableView.item(i, index).text())elif index == 2:for i in range(len(self.stocks.stocks)):self.stocks.stocks[i]['key'] = float(self.stockTableView.item(i, index).text().strip('%'))else:raise ValueError('sort_optional_stock index error')self.stocks.sort_stock(self.sort_flag[i])self.sort_flag[i] = ~self.sort_flag[i] # inverse sort methodself.__update_optional_stock()

现在去试试点击你的表头,看看能不能去排序这些股票!!

接下来为了提供删除和置顶的操作接口,我们在操作一栏为每只股票添加其相应的删除和置顶按钮:

from PyQt5.QtWidgets import QPushButton
from functools import partialclass Window(QWidget):def update_table_view(self):...self.stockTableView.setItem(i, j, item)topBtn = QPushButton()topBtn.setText('												

花几个小时做一个看股票的摸鱼神器相关推荐

  1. 为了监控老板,我用Python做了个摸鱼神器

    本文转载自 Crossin的编程教室 作为打工人来说,特别是996.007的工作,除了干饭之外,最紧张刺激的莫过于上班的时候偶尔偷偷闲,去池塘里面摸摸鱼 一般人摸的哪些鱼呢?聊天.微博.微信朋友圈.小 ...

  2. 达摩院清华博士研发了一个AI摸鱼神器!有了它,老板都不好意思打扰你

    点击上方"视学算法",选择加"星标"或"置顶" 重磅干货,第一时间送达 来自:开源最前线 最近,清华大学又火了!开设了一门课程--<摸 ...

  3. 最强摸鱼神器:开着IDEA看股票,看小说...

    这周很多公司都开始复工了,不出意外下周应该大部分都要开始上班了吧.今天TJ冒着被各公司老板追杀的风险,给大家推荐一个上班摸鱼神器:Thief-Book. **项目名称:**Thief Book **项 ...

  4. 三步教你用Node做一个微信哄女友(基友)神器,小白可上手

    前言 不知道大家最近有没有被python版的<微信每日说>刷屏呢,他可是霸占了github的python热门快两周了.我们前端的小伙伴是不是也看着有点眼馋呢,因为毕竟是不那么熟悉的pyth ...

  5. 牛逼坏了,华为做了一个摸鱼神器!

    昨天写文章的时候我提到我最近刚买了一个可以听歌的眼镜,很多人小伙伴觉的很好奇:卧槽,还有这种眼镜,这么神奇,什么时候搞个抽奖送一送? 当初,我也是怀着这样的心态买的这个可以听歌的眼镜,我想着只要带上了 ...

  6. 揭秘:一个月不摸鱼能写多少代码?

    作者 | 老鱼皮 来源 | 程序员鱼皮(ID:coder_yupi) 猜猜写了多少行?都写了哪些语言呢? 时间过得真是太快了,又到月底了.对于程序员来说,总结还是挺重要的,我也一直保持着一个习惯,就是 ...

  7. 国企程序员是真舒服啊,每天上班5小时,2万一月摸鱼不要太快乐

    "没想到,来到了国企后悔没有早跳槽.因为国企的日子实在是太安逸了" 这位博主主人公感慨,早知道国企这么好,给我一个腾讯的总裁我都不换. 每天到班上泡壶茶,聊会天儿,打扫一下卫生,然 ...

  8. 【第二弹】没有一个程序员,可以拒绝这三个摸鱼神器

    1.GfEasy 一款开源.低代码开发框架,号称一行代码不用写,减少百分90%工作量,让你有更多的时间去摸鱼.后端使用GoFrame开发,后台前端使用 cool-admin-vue,后台使用自适应布局 ...

  9. 同事用Excel花了半小时做甘特图,我用一工具只用10分钟

    经常与数据打交道的人都知道,当我们有一个大计划需要按照进度执行的时候,或者需要统一管理所有工作的任务与资料的时候,就需要一个好用的项目管理工具,甘特图就是最高的工具! 一周君整理了一些关于Excel中 ...

最新文章

  1. spring beans源码解读之--Bean的定义及包装
  2. python和C语言互相调用的几种方式
  3. VS Resharper快捷键没了处理办法
  4. Maven依赖中的scope详解
  5. Tomcat优化之配置线程池
  6. 实现才是目的——《大道至简》第六章读后感
  7. HTML5 实现离线数据缓存
  8. 风控报表大全(全面触及)
  9. Django book 2.0
  10. html登陆滑动验证,JavaScript实现登录滑块验证
  11. HashMap,LinkedHashMap,TreeMap应用
  12. arcmap给tif添加地理坐标_如何将JPG格式的图片转化为带地理坐标的TIFF格式
  13. Linux中ifconfig command not found
  14. Android Display 之 HAL Gralloc
  15. ESP8266_STA模式
  16. 智能电视看凤凰卫视,不用直播源
  17. iOS应用之微信支付集成
  18. 大数据 就业 缺口_三年培养10万大数据人才,解决大数据人才缺口
  19. 解决 rsync IO input / output error , failed verification — update discarded
  20. PageAdmin文章采集-自动批量文章采集发布

热门文章

  1. FS4054锂电池充电ic
  2. 史上最强多线程面试47题(含答案)
  3. JavaScript高级语法之异步Promise(2:深入)
  4. 谁来切分1.8万亿元的社区服务蛋糕?
  5. [Realtek sdk-3.4.14b]RTL8197FH-VG增加IPv6功能支持
  6. 77页集团企业数字化转型方案PPT
  7. 高校大学生校园app网站源码,校园大学生求职招聘兼职app源码,校园服务app源码,校园php/java网站源码
  8. 《生物药剂与药物动力学》
  9. 优胜劣汰:苹果和诺基亚的不同命运为我们敲响警钟
  10. 模拟量使用计算机电缆,解读高新技术特种电缆-计算机电缆