(二)主界面
目前就到了我们整个项目的核心部分,主界面部分。因为这里可能会涉及到来回调试,所以前后端不分的那么明确了。
1.主界面
进到wxfb,在项目树上新建一个Frame(注意跟刚才那个窗口不一样了,刚才那个是Dialog会话窗),并将其命名为MainFrame:
在主界面中,我们需要实现以下功能:
(1)数据可视化(把某个测点数据根据不同的时间绘制成折线)
(2)相关度分析(对不同类型的测点数据进行分析,取出每个类型相关度最高的测点,并计算出相关系数,绘制出热力图)
(3)参数再计算(计算出某个测点所有数据的最大值、最小值、均值、方差)
到这里,你傻了,不是,啥数据啊,你搁着扯啥呢。
别急嘛。这不数据就来了嘛:
链接:https://pan.baidu.com/s/1o0T_NRuNuen0Fe7oESvQsg
提取码:zv3z
数据样式截图:
简单来说就是第一列是时间,一共有31个测点,n打头是应变,s打头是扰度,t打头是温度,9个应变测点,9个扰度测点,13个温度测点。
而在程序主界面中,我们通常不做按钮,而是用菜单栏展示(最初我用的Dialog+按钮做的主界面,后来知道要重做时人都懵了。还好只是重做界面,后端代码不用改)
如何添加一个菜单栏:在MainFrame下,中央组件Menu/Toolbar-wxMenuBar,可以看到框架中突然多了一横,这就是菜单栏容器的位置,只是现在什么都还没加,所以是空的:
点击组件中wxMenuBar后面那个wxMenu,就可以在这个容器中添加具体菜单,我这里添加了四个:
依次在属性栏中把他们标签改成功能-关于-帮助-点此退出(注意是修改属性中的label,不是name,name是调用时的名字,label是展示出来的标签)
然后要我们要把几个功能都丢尽菜单栏的功能里,就在功能这个菜单的下面再新建wxMenuItem(和刚才新建菜单栏在一个位置,鼠标在上面停留他就出来名字了):
添加成功后,我们点击功能两个字,他就会出现下属菜单。重复性的新建三个并改名,因为我们需要三个功能嘛:
因为wxMenu只能实现展开,不能实现跳转,其下属的wxMenuItem才能实现跳转(也可能是我自己没挖掘到这个功能,轻骂),所以我们要实现关于、帮助、点此退出的事件的话还得在他们下面添加同名的wxMenuItem:
既然都做到这里了,我们先把这个主界面展示出来看看吧,按F8生成代码。进到pycharm中,在myframe模块中对该窗口做继承并重写方法:
class MainFrame(CSDNtext01.MainFrame):
def __init__(self):
CSDNtext01.MainFrame.__init__(self,parent=None)
在myapp模块(多次强调这是程序主流程)的OnInit方法中进行修改,还是一样的实例化和Show():
def OnInit(self):
# 对刚才写的继承的实例化
login = myframe.Login()
# 把该窗口展示出来
login.Show()
# 以下是新加的代码
mainframe= myframe.MainFrame()
mainframe.Show()
# 以上是新加的代码
return True
此时我们去跑一下试试,会发现出现了一个这情况,主界面和登陆界面同步展示出来了:
这种情况就是程序在登陆界面没有卡住,直接把OnInit执行完了,所以我们要给他一个信号,在打开登陆界面后把OnInit方法卡住,不让他继续往下执行了,而我们刚好可以用验证密码来做这个开关(其实验证密码按理来说是卡住程序了的,但是没卡住myapp方法),给登录模块一个参数status,默认为False,在密码验证成功后把这个参数变成True,就可以使用if来验证这个值,把程序卡住,具体实现方法是:
1.在Login类的__init__方法中添加一个self.status=False:
class Login(CSDNtext01.LoginFrame):
def __init__(self):
CSDNtext01.LoginFrame.__init__(self, parent=None)
self.status=False
2.在Login类的evt_login方法中,验证密码成功时赋值self.status=True:
def evt_login(self, event ):
'''
获取用户在输入框输入的内容,self.m_textCtrl1是指向某一个输入框
GetValue()是获取到输入框的值
strip()是删除掉首尾可能会产生的空格,如果没有就不删除
'''
username = self.m_textCtrl1.GetValue().strip()
password = self.m_textCtrl2.GetValue().strip()
if self.validate_credentials(username, password):
'''
使用wx.MessageBox方法可以展示一个简易弹窗
第一个参数是内容,第二个参数是标题
'''
self.status=True
wx.MessageBox("账号密码正确","提示信息")
else:
wx.MessageBox("账号密码错误","提示信息")
'''
如果密码错误就会执行这个return一直返回让他输入
密码正确就会正常去执行关闭登录窗口
'''
return
# 关闭登陆窗口
self.Close()
event.Skip()
3.在MyApp类的中OnInit方法中添加if判断status,在if条件里面写一个return false,在进入if条件后可以执行return,让用户重新输入账号密码,别忘了在if条件之后执行一个Destroy(),确保释放掉模态对话框的资源。
def OnInit(self):
# 对刚才写的继承的实例化
login = myframe.Login()
# 把该窗口展示出来
login.ShowModal()
# 如果密码错误,status仍然是False,就可以进入if条件,执行再次输入
# 如果密码正确,status返回True,直接执行后续程序
if not login.status:
return False
login.Destroy()
mainframe= myframe.MainFrame()
mainframe.Show()
return True
操作完这些后,再回到main中执行代码,可以发现目前流程正常,主界面刚才设计的功能正常展示:
有没有感觉空白那部分很丑,那咱们就加一幅图片看看吧:
回到wxfb中,在MainFrame下添加一个wxBoxSizer(之前那个添加行的组件),在在这行添加一个wxStaticBitmap,在右侧的file_path处选择路径(最后面的三个点点),选择一张大小合适的图片,如果差一点点就用鼠标把窗口稍微拉一点点:
一定不要选择成那个wxBitmapButton,那个也能插入图片但是会导致图片不停闪烁(困惑了我好久,把老师都看懵了)!!!
在功能下面,我们给三个子菜单的事件(参考登录界面的登录事件添加)中的OnMenuSelection分别写上evt_visual,evt_relate,evt_count。实现点击这三个之后进行跳转,在myframe模块中的MainFrame类下先做一个方法的简单重写进行占位:
class MainFrame(CSDNtext01.MainFrame):
def __init__(self):
CSDNtext01.MainFrame.__init__(self,parent=None)
def evt_visual( self, event ):
pass
def evt_relate( self, event ):
pass
def evt_count( self, event ):
pass
2.用代码实现从excel中读取数据及写入数据库
后面三个功能都是基于已有数据进行计算及绘图,所以我们先完成数据的准备工作:
首先我默认你安装好了mysql和Navicat,我使用的是Navicat15,若版本不同则界面UI有所不同,但原理一样。
打开Navicat,新建一个连接(如果没有默认连接的话,有默认连接就直接跳过这一步,意思就是看左边项目树最开始有没有东西):
连接名任取,只要后续自己能对上就行,用户名和密码就是你安装mysql的时候那个用户名和密码,自行更改,如果密码不正确就新建不成功,输入完成后可以点一下左下角测试连接,提示成功后点击右边确定。
在该连接下新建一个数据库:
字符集选择utf8mb4,排序规则选择utf8mb4_general_ci,应该没拼错哈,照着打的,Navicat这边的操作就这些了,剩下对数据库的操作都通过代码实现:
新建一个db.py模块,我们将在此模块中完成excel表格的处理及数据库相关操作(此处excel表格路径直接写到代码中,选择路径功能作为拓展功能实现)。
下来就要堆代码了。可能不太友好,但都是一些固定的搭配。
在db.py定义一个MyConnection类,在其中重写__init__方法,在这里直接把文件地址写死,,在后续拓展功能会更改为前端选择路径,其中“你的路径”自行更改。
class MyConnection():
def __init__(self):
# 直接把路径写到了代码里,后续拓展功能会更改
self.filepath="你的路径"
我们再在该类中写入一个方法用来与表格做连接并把表格中数据读入程序中,变成三个独立的列表
def excel_connect(self,colx=None):
try:
# 打开该路径的表格
xlsx=xlrd.open_workbook(self.filepath)
#选择该工作簿下第一张工作表
table=xlsx.sheet_by_index(0)
except Exception as e:
# 如果打开失败的话
wx.MessageBox("请检查文件格式是否正确!", '错误信息', wx.OK)
return False
# 获取列数
ncols = table.ncols
# 行数
nrows = table.nrows
# 抓取第一列date格式的数据
# usecols=['time']指只读取名为time的列
# 结果将得到一个包含只有 'time' 列数据的Pandas DataFrame(数据帧)
data_time = pd.read_excel(self.filepath, usecols=['time'])
# 为了确保数据的格式一致性,将格式再次转换成Pandas DataFrame
data_time = pd.DataFrame(data_time)
# 将 'time' 列的数据转换为Pandas中的日期时间格式
data_time['time'] = pd.to_datetime(data_time['time']) # 获取到date格式的第一列数据
# 以下操作实现了行列转置并将表格中的数据去掉第一列后存储为列表。
whole_list = []
for j in range(1, ncols):
list = []
for i in range(1, nrows):
values = table.cell_value(i, j)
list.append(values)
whole_list.append(list)
# print(whole_list)
# 以下操作实现了把表格分为三个独立的列表,每个列表的第一列都是时间序列
yb_list = []
rd_list = []
wd_list = []
# 用列数进行循环
for i in range(0, nrows - 1):
temp_yblist = []
temp_rdlist = []
temp_wdlist = []
#三种类型都是固定数据数,再加上该项目有固定表格格式限制,所以直接把代码写死了,如果更换表格格式的话,这里要更改
for j in range(0, 9):
temp_yblist.append(whole_list[j][i])
temp_rdlist.append(whole_list[j + 9][i])
for t in range(0, 13):
temp_wdlist.append(whole_list[t + 19][i])
temp_timelist = [data_time['time'][i]]
temp_yblist2 = temp_timelist + temp_yblist
yb_list.append(temp_yblist2)
temp_rdlist2 = temp_timelist + temp_rdlist
rd_list.append(temp_rdlist2)
temp_wdlist2 = temp_timelist + temp_wdlist
wd_list.append(temp_wdlist2)
# print(yb_list)
return True
这里是先把整个表格逐列读取成两个列表,一个事件列表一个数据列表,然后再把数据列表逐列读取,分为三个独立的表(把应变、扰度、温度分开了),再把时间拼接到这三个表上面去,是行列转置了两次。实际操作步骤有点绕,自己可以思考一下,能不能不经过转置,直接用原表就提取出三个类型的数据,自己发挥哈。
此时我们拿到了三个不同类型数据的列表,要写入数据库,也需要一个方法,这里给他定义一个方法write_todb。进行数据库写入。
def write_todb(self,yb_list,rd_list,wd_list):
# 这个Connection是pymysql包里面的,别导错包
conn = Connection(
host='localhost', # 主机名或Ip
port=3306, # 端口,默认3306
user='root', # 账户名
password='123456', # 密码
db='Csdn_text01' # 数据库名
)
# 创建cursor流处理对象
cursor = conn.cursor()
# 对应变表格的处理:
# 这里写的是如果不存在的话新建,如果存在该表格就直接跳过这步,省略了我们自己去建表的过程
cursor.execute("create table if not exists yb_db("
"timestamp_column TIMESTAMP,"
"n1 FLOAT,"
"n2 FLOAT,"
"n3 FLOAT,"
"n4 FLOAT,"
"n5 FLOAT,"
"n6 FLOAT,"
"n7 FLOAT,"
"n8 FLOAT,"
"n9 FLOAT"
")")
# mysql插入语句
insert_query = ("INSERT INTO yb_db (timestamp_column,n1,n2,n3,n4,n5,n6,n7,n8,n9)"
"VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s)")
cursor.executemany(insert_query, yb_list)
# 对扰度表格的处理:
cursor.execute("create table if not exists rd_db("
"timestamp_column TIMESTAMP,"
"s1 FLOAT,"
"s2 FLOAT,"
"s3 FLOAT,"
"s4 FLOAT,"
"s5 FLOAT,"
"s6 FLOAT,"
"s7 FLOAT,"
"s8 FLOAT,"
"s9 FLOAT"
")")
insert_query = ("INSERT INTO rd_db (timestamp_column,s1,s2,s3,s4,s5,s6,s7,s8,s9)"
"VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s)")
cursor.executemany(insert_query, rd_list)
# 对温度表格的处理:
cursor.execute("create table if not exists wd_db("
"timestamp_column TIMESTAMP,"
"t20 FLOAT,"
"t12 FLOAT,"
"t13 FLOAT,"
"t21 FLOAT,"
"t31 FLOAT,"
"t43 FLOAT,"
"t50 FLOAT,"
"t53 FLOAT,"
"t62 FLOAT,"
"t63 FLOAT,"
"t91 FLOAT,"
"t95 FLOAT,"
"t42 FLOAT"
")")
insert_query = ("INSERT INTO wd_db (timestamp_column,t20,t12,t13,t21,t31,t43,t50,t53,t62,t63,t91,t95,t42)"
"VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s)")
cursor.executemany(insert_query, wd_list)
# 每次对数据库进行修改时都要执行commit
conn.commit()
cursor.close()
conn.close()
pass
对三个表格的处理方法都是一样的,原理一样,就是重复工作。
在excel_connect方法最后添加一句
self.write_todb(yb_list, rd_list, wd_list)
就可以在这个方法里调用write_todb,直接写入数据库。
这时候呢,db.py和我们的主程序还是独立开的,我们需要在主程序中调用它才能执行刚才写的这一大堆。在登陆成功之后,也就是给他提示账号密码正确之后,调用这个方法(在Login类的evt_login方法中,账号密码正确的弹窗之后,else之前,if块儿内):
myconnection=db.MyConnection()
myconnection.excel_connect()
准备好了数据,可以来从数据库调用数据了,不用每次都用excel读取。
现在先设计三个窗口,不然事件里面也调用不了。