1,需求:
做一个UI,实现对载入的数据列表进行增删改和导出,支持选中列表选项的时候可以将所选项显示在输入框,方便修改
2,代码实现:
这里借鉴了pyqt的界面和逻辑分离的思想,用了类的继承
1)UI类:主要实现setup_ui(frame和控件以及事件绑定)
import tkinter as tk
class ListboxUiDialog:
def setup_ui(self, root):
self.frame_for_listbox = tk.Frame(root, relief=tk.RAISED, padx=20, pady=10)
self.frame_for_listbox.pack(side=tk.LEFT, fill=tk.BOTH, expand=tk.YES)
self.frame_for_control_widget = tk.Frame(root, relief=tk.GROOVE, padx=20, pady=10)
self.frame_for_control_widget.pack(side=tk.LEFT, fill=tk.BOTH, expand=tk.YES)
self.listbox_pack()
self.control_widget_pack()
def listbox_pack(self):
self.listbox_name_deque = tk.Listbox(self.frame_for_listbox, selectmode="extended")
self.listbox_name_deque.pack(side=tk.TOP, fill=tk.BOTH, expand=tk.YES)
# 列表框绑定函数
self.listbox_name_deque.bind('<ButtonRelease-1>', self.listbox_click)
# self.listbox_name_deque.bind('<Up>', self.listbox_click) # 这个不行,总是慢一拍,类似<Button-1>;要用KeyRelease-
# self.listbox_name_deque.bind('<Down>', self.listbox_click) # 这个不行,总是慢一拍,类似<Button-1>
self.listbox_name_deque.bind('<KeyRelease-Up>', self.listbox_click)
self.listbox_name_deque.bind('<KeyRelease-Down>', self.listbox_click)
vbar = tk.Scrollbar(self.listbox_name_deque, orient=tk.VERTICAL) # 竖直滚动条
vbar.pack(side=tk.RIGHT, fill=tk.Y, expand=tk.NO)
vbar.configure(command=self.listbox_name_deque.yview)
# hbar = tk.Scrollbar(self.listbox_name_deque, orient=tk.HORIZONTAL) # 水平滚动条【会挡住最后一行,所有直接不加了】
# hbar.pack(side=tk.BOTTOM, fill=tk.X, expand=tk.NO)
# hbar.configure(command=self.listbox_name_deque.xview)
self.listbox_name_deque.config(yscrollcommand=vbar.set) # 设置
# self.listbox_name_deque.config(xscrollcommand=hbar.set, yscrollcommand=vbar.set) # 设置
def control_widget_pack(self):
self.var_new_name = tk.StringVar()
self.entry_new_name = tk.Entry(self.frame_for_control_widget, textvariable=self.var_new_name)
self.entry_new_name.pack(side=tk.TOP, fill=tk.X, expand=tk.YES)
self.btn_ini = tk.Button(self.frame_for_control_widget, text='初始化(导入文件)', command=self.ini)
self.btn_ini.pack(side=tk.TOP, fill=tk.X, expand=tk.YES)
self.btn_clear = tk.Button(self.frame_for_control_widget, text='清空', command=self.clear)
self.btn_clear.pack(side=tk.TOP, fill=tk.X, expand=tk.YES)
# self.btn2 = tk.Button(self.frame_for_control_widget, text='添加', command=self.ins)
# self.btn2.pack(side=tk.TOP, fill=tk.X, expand=tk.YES)
self.btn_ins = tk.Button(self.frame_for_control_widget, text='插入', command=self.ins) # 添加和插入功能实质上是一样的
self.btn_ins.pack(side=tk.TOP, fill=tk.X, expand=tk.YES) # 添加和插入功能实质上是一样的
self.btn_delt = tk.Button(self.frame_for_control_widget, text='删除', command=self.delt)
self.btn_delt.pack(side=tk.TOP, fill=tk.X, expand=tk.YES)
self.btn_updt = tk.Button(self.frame_for_control_widget, text='修改', command=self.updt)
self.btn_updt.pack(side=tk.TOP, fill=tk.X, expand=tk.YES)
self.btn_save = tk.Button(self.frame_for_control_widget, text='保存', command=self.save)
self.btn_save.pack(side=tk.TOP, fill=tk.X, expand=tk.YES)
self.btn_save_as = tk.Button(self.frame_for_control_widget, text='另存为', command=self.save_as)
self.btn_save_as.pack(side=tk.TOP, fill=tk.X, expand=tk.YES)
2)主类及其逻辑实现
import tkinter as tk
from tkinter.filedialog import askopenfilename
from tkinter.filedialog import asksaveasfilename
from collections import deque
import os
from pickle_process.listbox_ui import ListboxUiDialog
from pickle_process.pickle_file import PickleFile
class ListboxMainDialog(ListboxUiDialog, tk.Toplevel):
def __init__(self):
super(ListboxMainDialog, self).__init__()
self.title('列表框实验')
self.grab_set() # 设置为模态对话框
screen_width = self.winfo_screenwidth()
screen_height = self.winfo_screenheight()
# 设置窗口大小
win_width = 660
win_height = 400
x = int(screen_width / 3)
y = int(screen_height / 4)
self.geometry("%sx%s+%s+%s" % (win_width, win_height, x, y))
# self.geometry('660x400')
self.setup_ui(self)
self.pickle_file_obj = PickleFile()
self.pickle_filename = None
self.mainloop()
def listbox_click(self, event):
"""初始化 listbox"""
cur_sels = self.listbox_name_deque.curselection()
if cur_sels != ():
cur = self.listbox_name_deque.selection_get()
self.var_new_name.set(cur)
# def ini(self):
# """初始化 listbox"""
#
# self.clear()
#
# self.pickle_filename = askopenfilename(filetypes=(("pickle files", "*.pickle"),),
# defaultextension='.pickle', title="加载 pickle 文件", initialdir=os.getcwd())
# print(f"pickle_filename:{self.pickle_filename}")
#
# if os.path.exists(self.pickle_filename):
# self.bt_names = self.pickle_file_obj.read_pickle_file(self.pickle_filename) # 本来存进去的就是一个deque
# self.bt_names = list(self.bt_names)
# self.bt_names.reverse()
# for bt_name in self.bt_names:
# self.listbox_name_deque.insert(tk.END, bt_name)
def ini(self):
"""初始化 listbox"""
for bt_name in ["数学", "物理", "化学", "语文", "外语"]:
self.listbox_name_deque.insert(tk.END, bt_name)
def clear(self):
"""清空 listbox"""
self.listbox_name_deque.delete(0, tk.END)
def ins(self):
"""插入 listbox"""
if self.var_new_name.get() != '':
if self.listbox_name_deque.curselection() == ():
self.listbox_name_deque.insert(self.listbox_name_deque.size(), self.var_new_name.get())
else:
self.listbox_name_deque.insert(self.listbox_name_deque.curselection(), self.var_new_name.get())
def delt(self):
"""
删除 listbox 元素
目前不支持删除多个 # _tkinter.TclError: bad listbox index "1 2": must be active, anchor, end, @x,y, or a number
:return:
"""
if self.listbox_name_deque.curselection() != ():
cur_sels = self.listbox_name_deque.curselection()
print(f"cur_sels: {cur_sels}")
for cur_sel in sorted(list(cur_sels), reverse=True): # 要从后面往前面删除,否则删除过程中会因为次序乱掉而删了一些不是当初所选的
self.listbox_name_deque.delete(cur_sel)
def updt(self):
"""更新 listbox 元素"""
if self.var_new_name.get() != '' and self.listbox_name_deque.curselection() != ():
selected = self.listbox_name_deque.curselection()
print(f"selected: {selected}")
self.listbox_name_deque.delete(selected[0]) # 只删除最后一个
self.listbox_name_deque.insert(selected, self.var_new_name.get())
else:
print(f"修改失败:没有选择项目或者修改内容为空")
def remove_duplication(self, list_old):
"""去重,按原来顺序"""
list_new = list()
for x in list_old:
if x not in list_new:
list_new.append(x)
return list_new
def save(self):
"""保存"""
all_content = self.listbox_name_deque.get(first=0, last=tk.END)
print(f"all_content: {all_content}")
all_content_list = list(all_content)
all_content_list.reverse()
all_content_list = self.remove_duplication(all_content_list)
bt_name_deque = deque()
bt_name_deque.extend(all_content_list)
self.pickle_file_obj.write_pickle_file(bt_name_deque, pickle_filename=self.pickle_filename)
# self.pickle_filename = None
def save_as(self):
"""另存为"""
all_content = self.listbox_name_deque.get(first=0, last=tk.END)
print(f"all_content: {all_content}")
all_content_list = list(all_content)
all_content_list.reverse()
all_content_list = self.remove_duplication(all_content_list)
saveasfilename = asksaveasfilename(filetypes=(("pickle files", "*.pickle"),),
initialfile=f"last_n_earphone_set_name_deque_1.pickle",
defaultextension='.pickle', title="保存 pickle 文件", initialdir=os.getcwd())
print(f"saveasfilename:{saveasfilename}")
bt_name_deque = deque()
bt_name_deque.extend(all_content_list)
self.pickle_file_obj.write_pickle_file(bt_name_deque, pickle_filename=saveasfilename)
if __name__ == '__main__':
lb_obj = ListboxMainDialog()
3)附上一个读写文件的类
from collections import deque
import pickle
class PickleFile:
def __init__(self):
pass
@staticmethod
def read_pickle_file(pickle_filename):
try:
with open(pickle_filename, 'rb') as fr:
data = pickle.load(fr)
return data
except Exception as e:
return None
@staticmethod
def write_pickle_file(data, pickle_filename):
"""保存pickle_filename"""
try:
with open(pickle_filename, 'wb') as fw:
pickle.dump(data, fw)
return True
except Exception as e:
return False
3,结果:
4,总结要点:
1)# 列表框绑定函数
self.listbox_name_deque.bind(‘’, self.listbox_click)
self.listbox_name_deque.bind(‘’, self.listbox_click)
self.listbox_name_deque.bind(‘’, self.listbox_click)
2)tk.Toplevel解决输入框的set()不起作用的情况
3)使用self.grab_set()将主界面设置为模态对话框,这样不至于导入一个文件操作的时候,本界面“不见了”
5,参考
(八)Python 图形化界面设计