1.登录界面
import tkinter as tk
from PIL import Image, ImageTk
from tkinter import messagebox
from tkinter import ttk
from Mainpage import *
from DB import *
class Login:
'''
登录界面
'''
def __init__(self, master=tk.Tk):
'''
初始化登录界面
:param master: 父窗口
'''
self.master = master
self.master.title("登录")
self.master.geometry("500x500+650+200") # 设置窗口大小
self.create_widgets()
def create_widgets(self):
'''
创建登录界面的控件
'''
self.label_username = ttk.Label(self.master, text="用户名:")
self.label_username.pack()
self.entry_username = ttk.Entry(self.master)
self.entry_username.pack()
self.label_password = ttk.Label(self.master, text="密码:")
self.label_password.pack()
self.entry_password = ttk.Entry(self.master, show="*")
self.entry_password.pack()
self.button_login = ttk.Button(self.master, text="登录", command=self.login)
self.button_login.pack()
def login(self):
'''
登录按钮的回调函数
'''
username = self.entry_username.get()
password = self.entry_password.get()
exist_flag,tip = DB().searchPersonByname(username)
# 登录验证逻辑
if exist_flag and password == "xu":
messagebox.showinfo("登录成功", f"欢迎,{tip[0]['name']}!")
# 在这里添加登录成功后的操作
self.master.destroy()
self.open_main_Page(2)
elif exist_flag == False and password == 'xu':
messagebox.showerror("登录失败", "用户不存在!")
self.master.destroy()
self.open_main_Page(0)
elif username == "admin" and password == "admin":
messagebox.showinfo("登录成功", f"欢迎,{username}!")
self.master.destroy()
self.open_main_Page(1)
else:
messagebox.showerror("登录失败", "用户名或密码错误!")
def open_main_Page(self, user_flag):
'''
打开主界面
:param user_flag: 用户类型,1为管理员,2为普通用户'''
if user_flag:
root = tk.Tk()
MainPage(root)
root.mainloop()
if __name__ == "__main__":
'''
程序入口
'''
root = tk.Tk()
imgpath = "login_title.gif"
img = Image.open(imgpath)
photo = ImageTk.PhotoImage(img)
canvas = tk.Canvas(root,width=img.width, height=img.height,bd=0, highlightthickness=0)
canvas.create_image(150,150,image=photo)
canvas.pack()
app = Login(root)
root.mainloop()
其中login_title.gif为登陆界面的头像
2.主界面MainPage
import tkinter as tk
from Implement import *
from Login import *
class MainPage:
'''
主界面
'''
def __init__(self,master: tk.Tk):
'''
:param master: 父容器
设定主界面的各项参数,初始化各个子界面
'''
self.root = master
self.root.title("家谱管理系统")
# self.root.configure(bg="white")
self.root.geometry('900x600+400+150')
self.root.iconbitmap('标头.ico')
self.createPage()
def createPage(self):
'''
创建各个子界面
AboutPage:关于
AddPage:添加
SearchPage:查询
DeletePage:删除
ShowPage:显示绘制图表
UpdatePage:修改
ImportPage:导入
'''
self.aboutpage = AboutPage(self.root)
self.addpage = AddPage(self.root)
self.searchpage = SearchPage(self.root)
self.deletepage = DeletePage(self.root)
self.showpage = ShowPage(self.root)
self.updatepage = UpdatePage(self.root)
self.importpage = ImportPage(self.root)
topmenu = tk.Menu(self.root)
topmenu.add_command(label="添加",command=self.showadd,font=("宋体", 15))
topmenu.add_command(label="查询",command=self.showsearch,font=("宋体", 15))
topmenu.add_command(label="删除",command=self.showdelete,font=("宋体", 15))
topmenu.add_command(label="显示",command=self.showshow,font=("宋体", 15))
topmenu.add_command(label="修改",command=self.showupdate,font=("宋体", 15))
topmenu.add_command(label="导入", command=self.showimport,font=("宋体", 15))
topmenu.add_command(label="关于", command=self.showabout,font=("宋体", 15))
self.root["menu"] = topmenu
self.aboutpage.pack()
def showabout(self):
'''
显示关于界面
,并隐藏其他界面'''
self.aboutpage.pack()
self.addpage.pack_forget()
self.searchpage.pack_forget()
self.deletepage.pack_forget()
self.showpage.pack_forget()
self.updatepage.pack_forget()
self.importpage.pack_forget()
def showadd(self):
'''
显示添加界面
,并隐藏其他界面'''
self.aboutpage.pack_forget()
self.addpage.pack()
self.searchpage.pack_forget()
self.deletepage.pack_forget()
self.showpage.pack_forget()
self.updatepage.pack_forget()
self.importpage.pack_forget()
def showsearch(self):
'''
显示查询界面
,并隐藏其他界面'''
self.aboutpage.pack_forget()
self.addpage.pack_forget()
self.searchpage.pack()
self.deletepage.pack_forget()
self.showpage.pack_forget()
self.updatepage.pack_forget()
self.importpage.pack_forget()
def showdelete(self):
'''
显示删除界面
,并隐藏其他界面'''
self.aboutpage.pack_forget()
self.addpage.pack_forget()
self.searchpage.pack_forget()
self.deletepage.pack()
self.showpage.pack_forget()
self.updatepage.pack_forget()
self.importpage.pack_forget()
def showshow(self):
'''
显示显示界面
,并隐藏其他界面'''
self.aboutpage.pack_forget()
self.addpage.pack_forget()
self.searchpage.pack_forget()
self.deletepage.pack_forget()
self.showpage.pack()
self.updatepage.pack_forget()
self.importpage.pack_forget()
def showupdate(self):
'''
显示修改界面
,并隐藏其他界面'''
self.aboutpage.pack_forget()
self.addpage.pack_forget()
self.searchpage.pack_forget()
self.deletepage.pack_forget()
self.showpage.pack_forget()
self.updatepage.pack()
self.importpage.pack_forget()
def showimport(self):
'''
显示导入界面
,并隐藏其他界面'''
self.aboutpage.pack_forget()
self.addpage.pack_forget()
self.searchpage.pack_forget()
self.deletepage.pack_forget()
self.showpage.pack_forget()
self.updatepage.pack_forget()
self.importpage.pack()
if __name__ == '__main__':
root = tk.Tk()
MainPage(root)
root.mainloop()
标头代表左上角那个小图标
3.实现方法Implement
主要包含模块各个函数的实现方法,每一个类代表一个模块
有关于,查找,导出,添加,展示,删除,修改等功能
from tkinter import ttk
import tkinter as tk
import DB as DB
from datetime import datetime
from tkinter import messagebox
import time
import Family_Tree as FT
#创建输入界面模板
def createPage(self, elseflag,elsename,function):#elseflag代表可拓展计数,最多三个,function代表拓展功能,elsename代表拓展功能名称
# 创建页面
#创建界面样式
ttk.Style().configure('TLabel', font=('宋体', 12))
ttk.Style().configure('TButton', font=('宋体', 12))
ttk.Style().configure('TEntry', font=('宋体', 12))
ttk.Style().configure('TRadiobutton', font=('宋体', 12))
ttk.Label(self, text='姓名').grid(row=0, column=0, padx=(0, 10), pady=(10, 0))
ttk.Entry(self, textvariable=self.name).grid(row=0, column=1, padx=(0, 10), pady=(10, 0))
ttk.Label(self, text='性别').grid(row=1, column=0, padx=(0, 10), pady=(10, 0))
ttk.Radiobutton(self, text='男', variable=self.sex, value=0).grid(row=1, column=1, padx=(0, 10), pady=(10, 0))
ttk.Radiobutton(self, text='女', variable=self.sex, value=1).grid(row=1, column=2, padx=(0, 10), pady=(10, 0))
ttk.Label(self, text='出生日期').grid(row=2, column=0, padx=(0, 10), pady=(10, 0))
ttk.Entry(self, textvariable=self.birthday).grid(row=2, column=1, padx=(0, 10), pady=(10, 0))
ttk.Label(self,textvariable=self.date_status).grid(row=2, column=2, padx=(0, 10), pady=(10, 0))
ttk.Label(self, text='地址').grid(row=3, column=0, padx=(0, 10), pady=(10, 0))
ttk.Entry(self, textvariable=self.address).grid(row=3, column=1, padx=(0, 10), pady=(10, 0))
ttk.Label(self, text='学历').grid(row=4, column=0, padx=(0, 10), pady=(10, 0))
ttk.Entry(self, textvariable=self.degree).grid(row=4, column=1, padx=(0, 10), pady=(10, 0))
ttk.Label(self, text='电话').grid(row=5, column=0, padx=(0, 10), pady=(10, 0))
ttk.Entry(self, textvariable=self.phone).grid(row=5, column=1, padx=(0, 10), pady=(10, 0))
ttk.Label(self,text='子嗣').grid(row=6, column=0, padx=(0, 10), pady=(10, 0))
ttk.Entry(self, textvariable=self.child).grid(row=6, column=1, padx=(0, 10), pady=(10, 0))
#ttk.Button(self, text='添加', command=self.add_Record).grid(row=6, column=0, padx=(0, 10), pady=(10, 0))
ttk.Button(self, text='清空', command=self.clear_All).grid(row=7, column=1, padx=(0, 10), pady=(10, 0))
tk.Label(self, textvariable=self.status).grid(row=8, column=1, padx=(0, 10), pady=(10, 0))
if elseflag>0:
ttk.Button(self, text=elsename[0], command=function[0]).grid(row=7, column=0, padx=(0, 10), pady=(10, 0))
if elseflag-1>0:
ttk.Button(self, text=elsename[1], command=function[1]).grid(row=7, column=2, padx=(0, 10), pady=(10, 0))
if elseflag-2>0:
ttk.Button(self, text=elsename[2], command=function[2]).grid(row=7, column=3, padx=(0, 10), pady=(10, 0))
#创建treeview控件模板
def createTreeview(self,treedata_function,extend_num,extend_name,extend_function): #treedata_function为插入表格数据,extend_num为扩展按钮数量,最多支持四个,extend_name为扩展按钮名称,extend_function为扩展按钮功能
# 创建表格
flag,columns = DB.DB().getCsvHead()
print(columns)
if flag :
self.tree = ttk.Treeview(self, columns=columns,show='headings',displaycolumns='#all')
self.tree.heading('name', text='姓名',anchor='center') # 显示表头
self.tree.heading('sex', text='性别',anchor='center') # 显示表头
self.tree.heading('age', text='年龄',anchor='center') # 显示表头
self.tree.heading('birthday', text='出生日期',anchor='center') # 显示表头
self.tree.heading('address', text='地址',anchor='center') # 显示表头
self.tree.heading('degree', text='学历',anchor='center') # 显示表头
self.tree.heading('phone', text='电话',anchor='center') # 显示表头
self.tree.heading('child', text='子嗣',anchor='center') # 显示表头
self.tree.column('name', width=80, anchor='center') # 设置列宽
self.tree.column('sex', width=40, anchor='center') # 设置列宽
self.tree.column('age', width=40, anchor='center') # 设置列宽
self.tree.column('birthday', width=100, anchor='center') # 设置列宽
self.tree.column('address', width=100, anchor='center') # 设置列宽
self.tree.column('degree', width=50, anchor='center') # 设置列宽
self.tree.column('phone', width=100, anchor='center') # 设置列宽
self.tree.column('child', width=100, anchor='center') # 设置列宽
self.tree.pack( fill=tk.BOTH, expand=True)
treedata_function()
# 拓展功能按钮
for i in range(extend_num):
ttk.Button(self,text=extend_name[i],command=extend_function[i]).pack()
return self.tree
else:
print(columns)
class AboutPage(tk.Frame):
def __init__(self,root):
super().__init__(root)
tk.Label(self, text='关于作者:由 厘不哩布 制作').pack()
class AddPage(tk.Frame):
def __init__(self,root):
super().__init__(root)
self.name = tk.StringVar(value='')
self.sex = tk.IntVar()
self.birthday = tk.StringVar(value='')
self.address = tk.StringVar(value='')
self.degree = tk.StringVar(value='')
self.phone = tk.StringVar(value='')
self.child = tk.StringVar(value='') #子嗣
# 插入状态
self.status = tk.StringVar()
#日期状态
self.date_status = tk.StringVar(value='如2000-1-1')
elseflag = 1
elsename = ['添加']
function = [self.add_Record]
createPage(self,elseflag,elsename,function)
print('创建添加页面')
def add_Record(self):
print('添加记录')
age = 0
birthday_one = self.birthday.get()
true_or_false = True # 日期格式是否正确
# 设置样式
style = ttk.Style()
style.configure('TLabel', font=('宋体', 12))
style.configure('TButton', font=('宋体', 12))
style.configure('TEntry', font=('宋体', 12))
style.configure('TRadiobutton', font=('宋体', 12))
try:
# 尝试计算年龄
age = datetime.now().year - int(birthday_one.split('-')[0])
# 如果日期格式正确,跳出循环
true_or_false = True
except:
true_or_false = False
messagebox.askretrycancel('错误', '日期格式错误,请重新输入')
if self.sex.get() == 0:
person_info = {'name': str(self.name.get()),
'sex': '男',
'age': str(age),
'birthday': str(self.birthday.get()),
'address': str(self.address.get()),
'degree': str(self.degree.get()),
'phone': str(self.phone.get()),
'child': str(self.child.get())
}
else:
person_info = {'name': str(self.name.get()),
'sex': '女',
'age': str(age),
'birthday': str(self.birthday.get()),
'address': str(self.address.get()),
'degree': str(self.degree.get()),
'phone': str(self.phone.get()),
'child': str(self.child.get())
}
print(person_info)
# 添加记录#########################################
if all(value.strip() != "" for value in person_info.values()): # 判断是否为空,如果为空则提示
if true_or_false: # 日期格式正确
flag,tip = DB.DB().addPerson(person_info)
#flag代表true/false,tip代表提示信息
else:
flag,tip = False,'日期格式错误,请重新输入'
if flag:
DB.Export().exportCsv()
else:
flag,tip = False,'请输入完整信息'
messagebox.askretrycancel('错误', '请输入完整信息')
# #############################################
self.status.set(tip)
def clear_All(self):
# 清空所有输入框
self.name.set('')
self.sex.set('')
self.birthday.set('')
self.address.set('')
self.degree.set('')
self.phone.set('')
self.status.set('清空成功,请重新输入')
class SearchPage(tk.Frame):
def __init__(self,root):
super().__init__(root)
style = ttk.Style()
style.configure('TLabel', font=('宋体', 12))
style.configure('TButton', font=('宋体', 12))
self.status = tk.StringVar()
self.extend_num = 4
self.extend_name = ['刷新', '按姓名查找', '按性别查找', '按学位查找']
self.extend_function = [self.refresh, self.search_by_name, self.search_by_sex, self.search_by_degree]
ttk.Label(self, text='总览', style='TLabel').pack(side=tk.TOP, pady=10)
self.tree = createTreeview(self,self.show_treedata,self.extend_num,self.extend_name,self.extend_function)
ttk.Button(self,text='按年龄升序排序',command=self.sort_by_age_up).pack(side=tk.TOP,pady=10)
ttk.Button(self,text='按年龄降序排序',command=self.sort_by_age_down).pack(side=tk.TOP,pady=10)
ttk.Label(self, textvariable=self.status, style='TLabel').pack(side=tk.TOP, pady=10)
def sort_by_age_up(self):# 按年龄排序
flag,person_dic = DB.DB().searchPersonAll()
if flag:
DB.DB().sortPerson_Age(False, person_dic)
self.refresh()
self.status.set('按年龄升序排序成功')
def sort_by_age_down(self):# 按年龄排序
flag,person_dic = DB.DB().searchPersonAll()
if flag:
DB.DB().sortPerson_Age(True, person_dic)
self.refresh()
self.status.set('按年龄降序排序成功')
def show_treedata(self):
# 查询所有记录,转化为元组
flag,person_dic = DB.DB().searchPersonAll()
if flag:
for i in person_dic:
person_tuple = tuple(i.values())
self.tree.insert('', 'end', values=person_tuple)
def refresh(self):
# 删除所有记录,保证记录都是最新的
for _ in map(self.tree.delete, self.tree.get_children()):
pass
# 查询所有记录,转化为元组
flag,person_dic = DB.DB().searchPersonAll()
if flag:
for i in person_dic:
person_tuple = tuple(i.values())
self.tree.insert('', 'end', values=person_tuple)
def search_name(page_name,name):
name_f = name.get()
# 查询所有记录,转化为元组
for _ in map(page_name.tree.delete, page_name.tree.get_children()):
pass
flag,person_dic = DB.DB().searchPersonByname(name_f)
if flag:
for i in person_dic:
person_tuple = tuple(i.values())
page_name.tree.insert('', 'end', values=person_tuple)
else:
page_name.tree.insert('', 'end', values='无此记录')
def search_sex(page_name,sex):
sex_f = sex.get()
# 查询所有记录,转化为元组
for _ in map(page_name.tree.delete, page_name.tree.get_children()):
pass
flag,person_dic = DB.DB().searchPersonBysex(sex_f)
if flag:
for i in person_dic:
person_tuple = tuple(i.values())
page_name.tree.insert('', 'end', values=person_tuple)
else:
page_name.tree.insert('', 'end', values='无此记录')
def search_degree(page_name,degree):
degree_f = degree.get()
# 查询所有记录,转化为元组
for _ in map(page_name.tree.delete, page_name.tree.get_children()):
pass
flag,person_dic = DB.DB().searchPersonBydegree(degree_f)
if flag:
for i in person_dic:
person_tuple = tuple(i.values())
page_name.tree.insert('', 'end', values=person_tuple)
else:
page_name.tree.insert('', 'end', values='无此记录')
def search_by_name(self):
style = ttk.Style()
style.configure('TLabel', font=('宋体', 12))
style.configure('TButton', font=('宋体', 12))
extend_num = 0
extend_name = ['查找']
extend_function = [lambda:0]
#page_name = tk.Tk() # 创建一个新窗口
page_name = tk.Toplevel() # 创建一个新窗口
page_name.title('按姓名查找')
page_name.geometry('500x500+400+150') # 设置窗口大小
name = tk.StringVar()
#ttk.Label(page_name.root, text=' 请输入姓名 ', style='TLabel').grid(row=0, column=2, padx=5, pady=5)
ttk.Label(page_name, text='姓名:', style='TLabel').pack( padx=5, pady=5)
ttk.Entry(page_name, textvariable=name, width=30).pack( padx=5, pady=5)
page_name.tree = createTreeview(page_name,lambda:0,extend_num,extend_name,extend_function)
ttk.Button(page_name, text='查询', command=lambda:SearchPage.search_name(page_name,name), style='TButton').pack(padx=5, pady=5)
page_name.mainloop()
def search_by_sex(self):
style = ttk.Style()
style.configure('TLabel', font=('宋体', 12))
style.configure('TButton', font=('宋体', 12))
extend_num = 0
extend_name = ['查找']
extend_function = [lambda:0]
#page_name = tk.Tk() # 创建一个新窗口
page_name = tk.Toplevel() # 创建一个新窗口
page_name.title('按性别查找')
page_name.geometry('500x500+400+150') # 设置窗口大小
sex = tk.StringVar()
#ttk.Label(page_name.root, text=' 请输入姓名 ', style='TLabel').grid(row=0, column=2, padx=5, pady=5)
ttk.Label(page_name, text='性别:', style='TLabel').pack( padx=5, pady=5)
ttk.Entry(page_name, textvariable=sex, width=30).pack( padx=5, pady=5)
page_name.tree = createTreeview(page_name,lambda:0,extend_num,extend_name,extend_function)
ttk.Button(page_name, text='查询', command=lambda:SearchPage.search_sex(page_name,sex), style='TButton').pack(padx=5, pady=5)
page_name.mainloop()
def search_by_degree(self):
style = ttk.Style()
style.configure('TLabel', font=('宋体', 12))
style.configure('TButton', font=('宋体', 12))
extend_num = 0
extend_name = ['查找']
extend_function = [lambda:0]
#page_name = tk.Tk() # 创建一个新窗口
page_name = tk.Toplevel() # 创建一个新窗口
page_name.title('按学位查找')
page_name.geometry('500x500+400+150') # 设置窗口大小
degree = tk.StringVar()
#ttk.Label(page_name.root, text=' 请输入姓名 ', style='TLabel').grid(row=0, column=2, padx=5, pady=5)
ttk.Label(page_name, text='学位:', style='TLabel').pack( padx=5, pady=5)
ttk.Entry(page_name, textvariable=degree, width=30).pack( padx=5, pady=5)
page_name.tree = createTreeview(page_name,lambda:0,extend_num,extend_name,extend_function)
ttk.Button(page_name, text='查询', command=lambda:SearchPage.search_degree(page_name,degree), style='TButton').pack(padx=5, pady=5)
page_name.mainloop()
class DeletePage(tk.Frame):
def __init__(self, root):
super().__init__(root)
self.name = tk.StringVar()
# 删除状态
self.status = tk.StringVar()
self.createPage()
self.click_times = 0 #点击次数,用于判断是否是第一次点击
def createPage(self):
# 创建一个容器
frame = ttk.Frame(self, padding="50")
frame.grid(row=0, column=0, sticky=(tk.W, tk.E, tk.N, tk.S))
# 设置样式
style = ttk.Style()
style.configure('TLabel', font=('宋体', 12))
style.configure('TButton', font=('宋体', 12))
# 布局控件
ttk.Label(frame, text='姓名', style='TLabel').grid(row=0, column=0, padx=5, pady=5)
ttk.Entry(frame, textvariable=self.name, width=30).grid(row=0, column=1, padx=5, pady=5)
ttk.Button(frame, text='查询', command=self.search_Record, style='TButton').grid(row=1, column=0, padx=5, pady=5)
ttk.Button(frame, text='删除', command=self.delete_Record, style='TButton').grid(row=1, column=1, padx=5, pady=5)
ttk.Button(frame, text='清空', command=self.clear_All, style='TButton').grid(row=1, column=2, padx=5, pady=5)
#提示信息
ttk.Label(frame, textvariable=self.status, style='TLabel').grid(row=2, column=0, columnspan=3, padx=5, pady=5)
def search_Record(self):
# 查询记录#########################################
flag,tip = DB.DB().searchPersonByname(self.name.get()) #flag代表true/false,tip代表提示信息或数据人物信息
if flag:
# if len(tip) == 1:
# self.status.set('查询成功\n\n姓名:'+tip['name']+'\n\n性别:'+tip['sex']+'\n\n年龄:'+tip['age']+'\n\n生日:'+tip['birthday']+'\n\n地址:'+tip['address']+'\n\n学历:'+tip['degree']+'\n\n电话:'+tip['phone'])
# else:
self.status.set('查询成功\n\n姓名:'+tip[self.click_times%len(tip)]['name']+'\n\n性别:'+tip[self.click_times%len(tip)]['sex']+'\n\n年龄:'+tip[self.click_times%len(tip)]['age']+'\n\n生日:'+tip[self.click_times%len(tip)]['birthday']+'\n\n地址:'+tip[self.click_times%len(tip)]['address']+'\n\n学历:'+tip[self.click_times%len(tip)]['degree']+'\n\n电话:'+tip[self.click_times%len(tip)]['phone']+tip[self.click_times%len(tip)]['child'])
return tip[self.click_times%len(tip)] #返回查询到的数据,供后续功能引用
else:
self.status.set(tip)
self.click_times += 1 #点击次数加一
# #############################################
def delete_Record(self):
# 删除记录#########################################
flag,tip = DB.DB().deletePerson(self.name.get()) #flag代表true/false,tip代表提示信息或被删除的人物信息
if flag:
self.status.set('删除成功\n\n姓名:'+tip['name']+'\n\n性别:'+tip['sex']+'\n\n年龄:'+tip['age']+'\n\n生日:'+tip['birthday']+'\n\n地址:'+tip['address']+'\n\n学历:'+tip['degree']+'\n\n电话:'+tip['phone']+tip['child'])
else:
self.status.set(tip)
# ##############################################
def clear_All(self):
# 清空所有输入框
self.name.set('')
self.status.set('清空成功,请重新输入')
class ShowPage(tk.Frame):
def __init__(self,root):
super().__init__(root)
style = ttk.Style()
style.configure('TLabel', font=('宋体', 12))
style.configure('TButton', font=('宋体', 12))
self.status = tk.StringVar(value='')
self.createPage()
def createPage(self):
ttk.Button(self, text='家谱图', command=self.show_Family_Graph,style='TButton',width=30,padding=50).pack(pady=20)
ttk.Button(self, text='家谱树', command=self.show_Family_Tree,style='TButton',width=30,padding=50).pack(pady=20)
ttk.Label(self, textvariable=self.status,style='TLabel').pack(pady=20)
def show_Family_Tree(self):
####################################
'''
树状图算法
'''
self.status.set('暂未开放,敬请期待')
######################################
def show_Family_Graph(self):
####################################
'''
关系图算法
'''
self.status.set('')
FT.Family_Tree()
######################################
class UpdatePage(tk.Frame):
def __init__(self,root):
super().__init__(root)
self.name = tk.StringVar(value='')
self.sex = tk.IntVar()
self.birthday = tk.StringVar(value='')
self.address = tk.StringVar(value='')
self.degree = tk.StringVar(value='')
self.phone = tk.StringVar(value='')
self.child = tk.StringVar(value='')
# 插入状态
self.status = tk.StringVar()
#日期状态
self.date_status = tk.StringVar(value='如2000-1-1')
elseflag = 2 #添加两个功能,1为查询,2为修改
elsename = ['查询','修改']
function = [self.search_Record,self.update_Record]
createPage(self,elseflag,elsename,function)
self.click_times = 0 #点击次数,用于判断是否是第一次点击
def search_Record(self):
flag,tip = DB.DB().searchPersonByname(self.name.get()) #flag代表true/false,tip代表提示信息或数据人物信息
if flag:
# self.name = tk.StringVar(value=tip[self.click_times%len(tip)]['name'])
# self.sex = tk.IntVar(value=tip[self.click_times%len(tip)]['sex'])
# self.birthday = tk.StringVar(value=tip[self.click_times%len(tip)]['birthday'])
# self.address = tk.StringVar(value=tip[self.click_times%len(tip)]['address'])
# self.degree = tk.StringVar(value=tip[self.click_times%len(tip)]['degree'])
# self.phone = tk.StringVar(value=tip[self.click_times%len(tip)]['phone'])
self.status.set('查询成功')
if tip[self.click_times%len(tip)]['sex'] == '男':
self.sex.set(0)
# tip[self.click_times%len(tip)]['sex'] = 0
if tip[self.click_times%len(tip)]['sex'] == '女':
self.sex.set(1)
# tip[self.click_times%len(tip)]['sex'] = 1
self.birthday.set(tip[self.click_times%len(tip)]['birthday'])
self.address.set(tip[self.click_times%len(tip)]['address'])
self.degree.set(tip[self.click_times%len(tip)]['degree'])
self.phone.set(tip[self.click_times%len(tip)]['phone'])
self.child.set(tip[self.click_times%len(tip)]['child'])
self.click_times += 1 #点击次数加一
return tip[self.click_times%len(tip)] #返回查询到的数据,供后续功能引用
else:
self.status.set(tip)
self.click_times += 1 #点击次数加一
def update_Record(self): #修改记录
# uperson = self.search_Record()
age = 0
birthday_one = self.birthday.get()
print('获取到的birthday_one'+birthday_one)
true_or_false = True # 日期格式是否正确
try:
# 尝试计算年龄
age = datetime.now().year - int(birthday_one.split('-')[0])
true_or_false = True
except:
true_or_false = False
messagebox.askretrycancel('错误', '日期格式错误,请重新输入')
if self.sex.get() == 0:
uperson = {'name': str(self.name.get()),
'sex': '男',
'age': str(age),
'birthday': str(self.birthday.get()),
'address': str(self.address.get()),
'degree': str(self.degree.get()),
'phone': str(self.phone.get()),
'child': str(self.child.get())
}
else:
uperson = {'name': str(self.name.get()),
'sex': '女',
'age': str(age),
'birthday': str(self.birthday.get()),
'address': str(self.address.get()),
'degree': str(self.degree.get()),
'phone': str(self.phone.get()),
'child': str(self.child.get())
}
print('获取到的uperson'+str(uperson))
# 更新记录#########################################
if all(value.strip() != "" for value in uperson.values()): # 判断是否为空,如果为空则提示
if true_or_false: # 日期格式正确
flag,tip = DB.DB().updatePerson(uperson)
#flag代表true/false,tip代表提示信息
else:
flag,tip = False,'日期格式错误,请重新输入'
else:
flag,tip = False,'请输入完整信息'
messagebox.askretrycancel('错误', '请输入完整信息')
self.status.set(tip)
def clear_All(self):
AddPage.clear_All(self)
class ImportPage(tk.Frame):
def __init__(self,root):
super().__init__(root)
tk.Label(self, text='关于作者:由厘 不哩布 制作').pack()
self.create_Page()
def create_Page(self):
self.import_Button = ttk.Button(self, text='导入数据', command=self.import_Data_append,width=30,padding=50)
self.import_Button.pack()
self.exchange_Button = ttk.Button(self, text='替换数据', command=self.import_Data_unappend,width=30,padding=50)
self.exchange_Button.pack()
self.export_Button = ttk.Button(self, text='导出数据', command=self.export_Data_csv,width=30,padding=50)
self.export_Button.pack()
self.clear_csv_Button = ttk.Button(self, text='清空数据', command=self.clear_All_csv,width=30,padding=50)
self.clear_csv_Button.pack()
def import_Data_append(self):
flag,tip =DB.Import().read_Foreign_info___append()
tk.messagebox.showinfo('提示', tip)
def import_Data_unappend(self):
flag,tip =DB.Import().read_Foreign_info___unappend()
tk.messagebox.showinfo('提示', tip)
def export_Data_csv(self):
flag,tip = DB.Export().export_csvfile()
tk.messagebox.showinfo('提示', tip)
def clear_All_csv(self):
cflag = tk.messagebox.askokcancel('提示', '确定要清空所有数据吗?')
if cflag:
cflag,tip = DB.Import().clear_All_csv()
tk.messagebox.showinfo('提示', tip)
4.数据文件交互DB
主要包含与人员文件csv的交互函数接口以及csv的导入导出接口
import csv
import os
from tkinter import filedialog
personlist=[] #已有人物列表
addpersonlist=[] #要添加的人物列表
class DB:
'''
数据库,人员处理类,用于处理人员记录,包含添加、删除、查找、更新、排序、导入、导出等操作,用于与personlist列表交互,将数据存入personlist列表中
'''
def __init__(self):
# self.addPerson() #添加记录
# self.searchPersonAll() #查找所有记录
# self.deletePerson() #通过姓名删除记录
# self.updatePerson() #通过姓名更新记录
# self.searchPersonByname() #通过姓名查找记录
# self.sortPerson_Age() #通过年龄排序记录
# self.getCsvHead() #获取csv文件表头,用于知道有哪些信息
# self.Person_toDict() #将输入的记录转化为字典
self.persondict={} #人物记录字典
if Import().counts_Csv() > len(personlist):
Import().importCsv() #导入csv文件中的记录到personlist列表中
#Import().importCsv()
def addPerson(self,Person):
'''
添加人员的接口
:param Person: 人员记录
:return: True/False,提示信息'''
self.persondict = Person
addpersonlist.append(self.persondict)
personlist.append(self.persondict)
print(personlist)
self.persondict = {} #清空字典
return True, f'姓名:{Person.get("name")}\n年龄:{Person.get("age")}\n性别:{Person.get("sex")}\n地址:{Person.get("address")}\n电话:{Person.get("phone")}\n添加成功'
def searchPersonAll(self):
'''
查询所有人员记录的接口
:return: True/False,提示信息'''
if len(personlist) == 0:
return False,'暂无信息'
return True,personlist
def searchPersonByname(self,name):
'''
查询同名的接口
:param name: 姓名
:return: True/False,提示信息'''
#####################################
#查询同名人的接口
spersons = [] #定义一个空列表,用于存放查询到的记录
# print(personlist)
######################################
if len(personlist) == 0:
return False,'暂无信息'
for i in personlist:
if i['name'] == name:
spersons.append(i)
if len(spersons) != 0:
print(spersons)
return True,spersons
return False,'暂无此人信息'
def searchPersonBydegree(self,degree):
'''
查询同学历的接口
:param degree: 学历
:return: True/False,提示信息'''
#####################################
#查询同学历的接口
spersons = [] #定义一个空列表,用于存放查询到的记录
# print(personlist)
######################################
if len(personlist) == 0:
return False,'暂无信息'
for i in personlist:
if i['degree'] == degree:
spersons.append(i)
if len(spersons) != 0:
print(spersons)
return True,spersons
return False,'暂无此人信息'
def searchPersonBysex(self,sex):
'''
查询同性别的接口
:param sex: 性别
:return: True/False,提示信息'''
#####################################
#查询同性别的接口
spersons = [] #定义一个空列表,用于存放查询到的记录
# print(personlist)
######################################
if len(personlist) == 0:
return False,'暂无信息'
for i in personlist:
if i['sex'] == sex:
spersons.append(i)
if len(spersons) != 0:
print(spersons)
return True,spersons
return False,'暂无此人信息'
def deletePerson(self,name):
'''
删除人员信息
:param name: 人员姓名
:return: True/False,提示信息'''
if len(personlist) == 0:
return False,'删除失败,暂无数据' #返回False和提示信息
for i in personlist:
if i['name'] == name:
dperson = i #记录被删除的人
Export().csv_Person_delete(i) #删除csv文件中的记录
return True ,dperson #返回True和被删除的人
return False,'删除失败,暂无此人' #返回False和提示信息
def updatePerson(self,person):
'''
更新人员信息
:param person: 人员信息
:return: True/False,提示信息'''
if len(personlist) == 0:
return '更新失败,暂无数据',False
for i in personlist:
if i['name'] == person['name']:
i['age'] = person['age']
i['sex'] = person['sex']
i['address'] = person['address']
i['phone'] = person['phone']
i['degree'] = person['degree']
i['birthday'] = person['birthday']
i['child'] = person['child']
Export().csv_Person_update() #更新csv文件中的记录
return True,'姓名:{}\n年龄:{}\n性别:{}\n地址:{}\n电话:{}\n学历:{}\n更新成功'.format(i['name'], i['age'], i['sex'], i['address'], i['phone'],i['degree'])
return False,'更新失败,暂无此人'
def sortPerson_Age(self,reverse=True,in_personlist=personlist):
'''
按年龄排序
:param reverse: True为降序,False为升序
:return: 排序后的列表'''
in_personlist.sort(key=lambda x: x['age'], reverse=reverse)
return in_personlist,True
def getCsvHead(self):
"""
获取csv文件表头
:return: 表头列表
"""
try:
with open('person.csv', 'r',encoding='utf-8') as csvfile:
reader = csv.reader(csvfile)
head = next(reader)
return True,head
except FileNotFoundError :
return False,'文件不存在'
class Export:
'''
用于将personlist导出数据到csv文件
'''
def __init__(self):
'''
初始化,将csv文件中的数据导入到personlist列表中
'''
if Import().counts_Csv() > len(personlist):
Import().importCsv() #导入csv文件中的记录到personlist列表中
# self.exportCsv()
def exportCsv(self):
"""
导出数据到csv文件
:return: True
"""
print(personlist)
with open('person.csv', 'a', newline='', encoding='utf-8') as csvfile:
fieldnames = ['name', 'sex', 'age', 'birthday','address','degree','phone','child']
#fieldheaders = ['姓名', '性别', '年龄', '生日','地址','学历','电话']
writer = csv.DictWriter(csvfile, fieldnames=fieldnames)
if csvfile.tell() == 0: # 如果文件为空
writer.writeheader()
for i,person in enumerate(addpersonlist): # 使用 enumerate() 函数,i是索引,person是元素!!!!!!!!!!!!i不能删,否则会报错,enumerate返回的元组
writer.writerow(person)
addpersonlist.clear() # 清空添加人员列表,否则会重复插入
print('数据导出成功!')
return True,'数据导出成功!'
def csv_Person_delete(self,dperson):
'''
删除csv文件中的记录
:param dperson: 要删除的记录
:return: True
'''
# 打开CSV文件并清空内容
with open('person.csv', mode='w', encoding='utf-8', newline='') as file:
csv.writer(file) # 写入空行
personlist.remove(dperson)
addpersonlist = personlist.copy()
with open('person.csv', 'a', newline='', encoding='utf-8') as csvfile:
fieldnames = ['name', 'sex', 'age', 'birthday','address','degree','phone','child']
#fieldheaders = ['姓名', '性别', '年龄', '生日','地址','学历','电话']
writer = csv.DictWriter(csvfile, fieldnames=fieldnames)
if csvfile.tell() == 0: # 如果文件为空
writer.writeheader()
for i, person in enumerate(addpersonlist): # 使用 enumerate() 函数
writer.writerow(person)
addpersonlist.clear() # 清空添加人员列表,否则会重复插入
return True,'删除成功'
def csv_Person_update(self):
'''
修改csv文件中的记录
:return: True'''
# 打开CSV文件并清空内容
with open('person.csv', mode='w', encoding='utf-8', newline='') as file:
csv.writer(file) # 写入空行
addpersonlist = personlist.copy()
with open('person.csv', 'a', newline='', encoding='utf-8') as csvfile:
fieldnames = ['name', 'sex', 'age', 'birthday','address','degree','phone','child']
#fieldheaders = ['姓名', '性别', '年龄', '生日','地址','学历','电话']
writer = csv.DictWriter(csvfile, fieldnames=fieldnames)
if csvfile.tell() == 0: # 如果文件为空
writer.writeheader()
for i, person in enumerate(addpersonlist): # 使用 enumerate() 函数
writer.writerow(person)
addpersonlist.clear() # 清空添加人员列表,否则会重复插入
return True,'修改成功'
def export_csvfile(self):
'''
导出csv文件
:return: True'''
# 打开文件选择对话框,选择文件,返回路径
file_path = filedialog.asksaveasfilename(title="选择保存文件", defaultextension=".csv", filetypes=[("CSV files", "*.csv"), ("All files", "*.*")])
file_path = file_path.encode('utf-8').decode('utf-8') # 确保路径字符串正确编码
file_path = os.path.abspath(file_path) # 使用os.path模块处理路径
if file_path:
addpersonlist = personlist.copy()
with open(file_path, 'w', newline='', encoding='utf-8') as csvfile:
fieldnames = ['name', 'sex', 'age', 'birthday','address','degree','phone','child']
#fieldheaders = ['姓名', '性别', '年龄', '生日','地址','学历','电话']
writer = csv.DictWriter(csvfile, fieldnames=fieldnames)
if csvfile.tell() == 0: # 如果文件为空
writer.writeheader()
for i,person in enumerate(addpersonlist): # 使用 enumerate() 函数,i是索引,person是元素!!!!!!!!!!!!i不能删,否则会报错,enumerate返回的元组
writer.writerow(person)
addpersonlist.clear() # 清空添加人员列表,否则会重复插入
print('数据导出成功!')
return True,'数据导出成功!'
class Import:
'''
导入csv文件
:return: True'''
def __init__(self):
self.counts_Csv()
def importCsv(self): # 导入生成的csv文件,获取数据
'''
读取csv文件,获取数据
:return: 全局personlist列表,其他函数可直接访问'''
try:
with open('person.csv', 'r',encoding='utf-8') as csvfile:
reader = csv.DictReader(csvfile)
for row in reader:
personlist.append(row)
print('数据导入成功!')
except FileNotFoundError:
print('文件不存在')
# 读取csv文件,返回总行数
def counts_Csv(self):
'''
读取csv文件,返回总行数
:return: 总行数'''
try:
with open('person.csv', 'r',encoding='utf-8') as csvfile:
reader = csv.DictReader(csvfile)
return len(list(reader))
except FileNotFoundError:
return 0
#追加模式将外部csv文件数据导入
def read_Foreign_info___append(self):
'''
打开文件选择对话框,选择文件,返回路径,将文件内容追加到personlist列表中
:return: 选择了,就True
发生错误,未选择中途取消,就False'''
#打开文件选择对话框,选择文件,返回路径
file_path = filedialog.askopenfilename(title="选择人员文件")
file_path = file_path.encode('utf-8').decode('utf-8') # 确保路径字符串正确编码
file_path = os.path.abspath(file_path) # 使用os.path模块处理路径
if file_path:
# 打开文件,读取内容
try:
with open(file_path, 'r',encoding='utf-8') as csvfile:
reader = csv.reader(csvfile)
head = next(reader)
print(head)
except FileNotFoundError:
print(f'文件未找到:{file_path}')
return False,'文件未找到'
except Exception as e:
print(f'打开文件时发生错误:{e}')
return False,'打开文件时发生错误'
except:
return False,'文件格式不正确,请选择csv文件'
if head == ['name', 'sex', 'age', 'birthday', 'address', 'degree', 'phone','child'] or head == ['姓名', '性别', '年龄', '生日', '地址', '学历', '电话','子女']:
with open(file_path, 'r',encoding='utf-8') as file:
reader = csv.DictReader(file)
for row in reader:
addpersonlist.append(row)
#addpersonlist = personlist.copy()
print('数据导入::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::;')
print(addpersonlist)
Export().exportCsv()
print(addpersonlist)
addpersonlist.clear() # 清空添加人员列表,否则会重复插入
print('数据导入成功!')
return True,'导入成功'
else:
print('文件格式不正确')
return False,'文件格式不正确\n列名分别需为:姓名,性别,年龄,生日,地址,学历,电话'
else:
print('未选择文件')
return False,'未选择文件'
def read_Foreign_info___unappend(self): # 将原来文件内容清空,再导入
'''
打开文件选择对话框,选择文件,返回路径,将原来文件内容清空,再导入
:return: 选择了,就True
发生错误,未选择中途取消,就False'''
#打开文件选择对话框,选择文件,返回路径
file_path = filedialog.askopenfilename(title="选择人员文件")
file_path = file_path.encode('utf-8').decode('utf-8') # 确保路径字符串正确编码
file_path = os.path.abspath(file_path) # 使用os.path模块处理路径
if file_path:
# 打开文件,读取内容
try:
with open(file_path, 'r',encoding='utf-8') as csvfile:
reader = csv.reader(csvfile)
head = next(reader)
except FileNotFoundError:
print(f'文件未找到:{file_path}')
return False,'文件未找到'
except Exception as e:
print(f'打开文件时发生错误:{e}')
return False,'打开文件时发生错误'
except:
return False,'文件格式不正确,请选择csv文件'
if head == ['name', 'sex', 'age', 'birthday', 'address', 'degree', 'phone','child'] or head == ['姓名', '性别', '年龄', '生日', '地址', '学历', '电话','子女']:
with open('person.csv', mode='w', encoding='utf-8', newline='') as file:
csv.writer(file) # 写入空行,清空文件
with open(file_path, 'r',encoding='utf-8') as file:
reader = csv.DictReader(file)
personlist.clear()
for row in reader:
personlist.append(row)
addpersonlist.append(row)
Export().exportCsv()
addpersonlist.clear() # 清空添加人员列表,否则会重复插入
print('数据导入成功!')
return True,'导入成功'
else:
print('文件格式不正确')
return False,'文件格式不正确\n列名分别需为:姓名,性别,年龄,生日,地址,学历,电话, 子嗣'
else:
print('未选择文件')
return False,'未选择文件'
def clear_All_csv(self):
''' 清空文件内容
:return: True'''
header = ['name', 'sex', 'age', 'birthday', 'address', 'degree', 'phone','child']
with open('person.csv', mode='w', encoding='utf-8', newline='') as file:
csv.writer(file) # 写入空行,清空文件
csv.DictWriter(file, fieldnames=header).writeheader() # 写入表头
return True,'清空成功'
5.Family_Tree 绘制人员关系图
将从DB获取到的数据加工,用plt和networkx绘制为图
import DB as DB
import networkx as nx
import matplotlib.pyplot as plt
class Family_Tree:
def __init__(self):
'''
初始化人员节点和边
'''
flag,self.personlist_head = DB.DB().getCsvHead()
flag,self.personlist = DB.DB().searchPersonAll()
# 将边转换为元组
self.person_edges_tuple = [(item['name'],item['child']) for item in self.personlist]
self.person_nodes = [f"{item['name']}" for item in self.personlist]
print(self.person_edges_tuple)
self.drawGraph()
def drawGraph(self):
'''
绘制家谱图
'''
G = nx.DiGraph()
# for node in self.person_nodes:
# if not G.has_node(node):
# G.add_node(node)
G.add_edges_from(self.person_edges_tuple)
plt.figure("家谱图") # 创建一个新的图形窗口,并设置窗口的标题
# plt.title("家谱图") # 设置窗口的标题
# 设置默认字体
plt.rcParams['font.sans-serif'] = ['SimHei'] # 用来正常显示中文标签
plt.rcParams['axes.unicode_minus'] = False # 用来正常显示负号
nx.draw(G, with_labels=True, node_size=1000, node_color='lightblue', font_size=10, font_weight='bold', arrows=True, arrowsize=20, arrowstyle='->')
plt.show()
if __name__ == '__main__':
Family_Tree()
6.打包
用pyinstaller将以上打包,其中pyinstaller相关操作可以参考
使用 PyInstaller | PyInstaller中文文档 (gitbook.io)
结语
目前还在学习技术,这是本人第一次打包成功一个pythonGUI的简单程序,还有特别多不完善的地方,还会继续努力^_^