下面是我的应用程序员工经理的完整代码。该应用程序确实允许基本的员工管理,比如添加、删除或更改员工的详细信息。因为这是我的第一个GUI应用程序之一,我是自学的(我已经学习了大约6个月,每天工作几个小时),我想看看你们对我的代码的看法。想听听别人的意见。任何关于改进(我的工作和代码)的建议都是欢迎的。如果我受到批评就不会生气。在这里学习和提高。
from json.decoder import JSONDecodeError
import tkinter as tk
from tkinter import StringVar, Toplevel, ttk
import os, json
import copy
class Staff_Manager(tk.Tk):
def __init__(self) -> None:
super().__init__()
self.check_save_file()
self.geometry("1520x600")
self.title("Staff Manager")
self.iconbitmap("icon.ico")
self.buttons()
self.data_view()
self.update
self.delete
self.is_retired
# check if data save file exists and if not create one.
def check_save_file(self):
current_path = os.path.dirname(__file__)
json_file = f'{current_path}\\emp_data.json'
if os.path.isfile(json_file) == True and os.stat(json_file).st_size != 0:
pass
else:
with open(json_file, 'w+') as f:
data = {"people": []}
json.dump(data, f)
def buttons(self):
# managing buttons, main window
add = ttk.Button(self, text= 'Add', command= Add_New_Emp)
add.place(x= 10, y= 10, width= 75)
amend = ttk.Button(self, text= 'Amend', command= self.amend)
amend.place(x= 10, y= 45, width= 75)
refresh = ttk.Button(self, text= 'Refresh', command= self.update)
refresh.place(x= 10, y= 80, width= 75)
retire = ttk.Button(self, text= 'Retire', command= self.is_retired)
retire.place(x= 10, y= 135, width= 75, height= 40)
remove = ttk.Button(self, text= 'Remove', command= self.delete)
remove.place(x= 10, y= 230, width= 75, height= 40)
exit_button = ttk.Button(self, text= 'EXIT', command= self.destroy)
exit_button.place(x= 10, y= 538, width= 75, height= 40)
def data_view(self):
#create columns for tree view
columns = ('#1', '#2', '#3', '#4', '#5', '#6', '#7')
self.view_panel = ttk.Treeview(self, columns= columns, show= 'headings', height= 27)
self.view_panel.place(x= 90, y= 10)
# headings
self.view_panel.heading('#1', text= 'Name')
self.view_panel.heading('#2', text= 'Surname')
self.view_panel.heading('#3', text= 'Position')
self.view_panel.heading('#4', text= 'DoB')
self.view_panel.heading('#5', text= 'Start Date')
self.view_panel.heading('#6', text= 'End Date')
self.view_panel.heading('#7', text= 'Retired?')
# set up the scrollbar
scrollbar = ttk.Scrollbar(self, orient= tk.VERTICAL, command= self.view_panel.yview)
self.view_panel.configure(yscrollcommand= scrollbar.set)
scrollbar.place(x= 1495, y= 10, height= 575)
# insert data
with open('emp_data.json', 'r') as f:
empty_file = False
try:
data = json.load(f)
except JSONDecodeError:
empty_file = True
pass
employees = []
if empty_file == False:
for item in data["people"]:
name = item["name"]
surname = item["surname"]
position = item["position"]
dob = item["dob"]
start = item["start"]
end = item["end"]
retired = item["retired"]
employees.append((name, surname, position, dob, start, end, retired))
# insert all the data to the view panel
for emp in employees:
self.view_panel.insert('', tk.END, values= emp)
# update information on the view panel
def update(self):
self.data_view()
# amend emp data
def amend(self):
amend_win = tk.Tk()
amend_win.geometry('350x250')
amend_win.title('Amend Employee Details')
amend_win.iconbitmap('icon.ico')
# cancel button
def is_cancel():
amend_win.destroy()
# change button
def is_amended(to_change, new_personal_data):
current_item = self.view_panel.focus()
info = self.view_panel.item(current_item)
details = info["values"]
to_remove = {
"name": str(details[0]),
"surname": str(details[1]),
"position": str(details[2]),
"dob": str(details[3]),
"start": str(details[4]),
"end": str(details[5]),
"retired": str(details[6])
}
to_remove = copy.deepcopy(to_remove)
details_dict = {
"name": str(details[0]),
"surname": str(details[1]),
"position": str(details[2]),
"dob": str(details[3]),
"start": str(details[4]),
"end": str(details[5]),
"retired": str(details[6])
}
for key, value in details_dict.items():
if key == to_change:
details_dict[f"{to_change}"] = new_personal_data
with open('emp_data.json', 'r') as f:
data = json.load(f)
new_data = {
"people": []
}
for emp_dict in data["people"]:
if to_remove != emp_dict:
new_data["people"].append(emp_dict)
new_data["people"].append(details_dict)
with open('emp_data.json', 'w') as f:
json.dump(new_data, f, indent= 4)
amend_win.destroy()
# check if data is not empty, if is: n/a
def is_empty(data):
if len(data) == 0:
return 'n/a'
else:
return data
main_choice_lbl = ttk.Label(amend_win, text= 'What would you like to change?', font= ('Arial', 12))
main_choice_lbl.place(x= 10, y= 10)
self.data_options = ('name', 'surname', 'position', 'dob', 'start', 'end')
self.choice = StringVar()
options = ttk.OptionMenu(amend_win,
self.choice,
self.data_options[0],
*self.data_options)
options.place(x= 10, y= 47)
options.config(width= 15)
new_emp_data = ttk.Label(amend_win, text= 'New data:', font= ('Arial', 12))
new_emp_data.place(x= 10, y= 90)
main_entry = ttk.Entry(amend_win, justify= 'right')
main_entry.place(x= 10, y= 130)
ok_button = ttk.Button(amend_win, text= 'CHANGE', command= lambda: is_amended(self.choice.get(), is_empty(main_entry.get())))
ok_button.place(x= 60, y= 200)
cancel_button = ttk.Button(amend_win, text= 'CANCEL', command= is_cancel)
cancel_button.place(x= 150, y= 200)
def delete(self):
# get the "clicked" emp
current_item = self.view_panel.focus()
# get the info of "clicked" emp : dict
info = self.view_panel.item(current_item)
# get details of "clicked" emp, "values" are stored emp data : list
details = info["values"]
# set the data to json format (need to convert some int to str as program is creating all data as strings)
details_dict = {
"name": str(details[0]),
"surname": str(details[1]),
"position": str(details[2]),
"dob": str(details[3]),
"start": str(details[4]),
"end": str(details[5]),
"retired": str(details[6])
}
with open('emp_data.json', 'r') as f:
data = json.load(f)
new_data = {
"people": []
}
for emp_dict in data["people"]:
if details_dict != emp_dict:
new_data['people'].append(emp_dict)
with open('emp_data.json', 'w') as f:
json.dump(new_data, f, indent= 4)
def is_retired(self):
retired_win = tk.Tk()
retired_win.geometry('280x130')
# cancel button
def is_cancel():
retired_win.destroy()
def is_ok():
current_item = self.view_panel.focus()
info = self.view_panel.item(current_item)
details = info["values"]
to_remove = {
"name": str(details[0]),
"surname": str(details[1]),
"position": str(details[2]),
"dob": str(details[3]),
"start": str(details[4]),
"end": str(details[5]),
"retired": str(details[6])
}
to_remove = copy.deepcopy(to_remove)
details_dict = {
"name": str(details[0]),
"surname": str(details[1]),
"position": str(details[2]),
"dob": str(details[3]),
"start": str(details[4]),
"end": str(details[5]),
"retired": str(details[6])
}
# for key, value in details_dict.items():
details_dict["retired"] = "Yes"
with open('emp_data.json', 'r') as f:
data = json.load(f)
new_data = {
"people": []
}
for emp_dict in data["people"]:
if to_remove != emp_dict:
new_data["people"].append(emp_dict)
new_data["people"].append(details_dict)
with open('emp_data.json', 'w') as f:
json.dump(new_data, f, indent= 4)
retired_win.destroy()
ret_lbl = ttk.Label(retired_win, text= "Are you sure you want to RETIRE your employee?")
ret_lbl.place(x= 10, y= 20)
ok_button = ttk.Button(retired_win, text= 'RETIRE', command= is_ok)
ok_button.place(x= 40, y= 60)
cancel_button = ttk.Button(retired_win, text= 'CANCEL', command= is_cancel)
cancel_button.place(x= 150, y= 60)
class Add_New_Emp(Toplevel):
def __init__(self) -> None:
super().__init__()
self.geometry('380x400')
self.title('Employee Data')
self.iconbitmap('icon.ico')
self.labels()
self.data_entry()
# set labels
def labels(self):
name_lbl = ttk.Label(self, text= 'Name:', font= ('Arial', 12))
name_lbl.place(x= 10, y= 10)
surname_lbl = ttk.Label(self, text= 'Surname:', font= ('Arial', 12))
surname_lbl.place(x= 10, y= 50)
position_lbl = ttk.Label(self, text= 'Position:', font= ('Arial', 12))
position_lbl.place(x= 10, y= 90)
dob_lbl = ttk.Label(self, text= 'Date of Birth:', font= ('Arial', 12))
dob_lbl.place(x= 10, y= 130)
start_lbl = ttk.Label(self, text= 'Start Date:', font= ('Arial', 12))
start_lbl.place(x= 10, y= 170)
end_lbl = ttk.Label(self, text= 'End Date:', font= ('Arial', 12))
end_lbl.place(x= 10, y= 210)
retired_lbl = ttk.Label(self, text= 'Retired?', font= ('Arial', 12))
retired_lbl.place(x= 10, y= 250)
# set data entry points
def data_entry(self):
def is_empty(data):
if len(data) == 0:
return 'n/a'
else:
return data
# accepting details, pressing ok button
def is_ok():
# get all the data
name = is_empty(name_ent.get())
surname = is_empty(surname_ent.get())
position = is_empty(position_ent.get())
dob = is_empty(dob_ent.get())
start = is_empty(start_ent.get())
end = is_empty(end_ent.get())
retired = retired_var.get()
# append row to the employee data file
with open('emp_data.json', 'r+') as f:
data = json.load(f)
new_emp = {
"name": name,
"surname": surname,
"position": position,
"dob": dob,
"start": start,
"end": end,
"retired": retired
}
data["people"].append(new_emp)
with open('emp_data.json', 'w') as f:
json.dump(data, f, indent= 4)
self.destroy()
# cancelation of adding new emp
def is_cancel():
self.destroy()
name_ent = ttk.Entry(self, justify= 'right')
name_ent.place(x= 215, y= 10)
surname_ent = ttk.Entry(self, justify= 'right')
surname_ent.place(x= 215, y= 50)
position_ent = ttk.Entry(self, justify= 'right')
position_ent.place(x= 215, y= 90)
dob_ent = ttk.Entry(self, justify= 'right')
dob_ent.place(x= 215, y= 130)
start_ent = ttk.Entry(self, justify= 'right')
start_ent.place(x= 215, y= 170)
end_ent = ttk.Entry(self, justify= 'right')
end_ent.place(x= 215, y= 210)
retired_var = StringVar(value= 'No')
retired_chk = ttk.Checkbutton(
self,
text= 'Yes',
onvalue= 'Yes',
offvalue= 'No',
variable= retired_var)
retired_chk.place(x= 215, y= 250)
ok_button = ttk.Button(self, text= 'ADD', command= is_ok)
ok_button.place(x= 100, y= 350)
cancel_button = ttk.Button(self, text= 'CANCEL', command= is_cancel)
cancel_button.place(x= 200, y= 350)
if __name__ == "__main__":
Staff_Manager().mainloop()发布于 2021-09-29 19:25:58
在以下几个方面,您偏离了Python代码样式指南:
CapitalizedWords,而不是Capitalized_Words_With_Underscores。所以Staff_Manager和Add_New_Emp应该是StaffManager和AddNewEmp。我所看到的唯一使用类型提示的地方是def __init__(self) -> None:。显然,您刚刚开始使用它们,但是您需要更多地使用它们,并通过检查器(mypy,pylint,.)运行您的and。
self.buttons()
self.data_view()
self.update
self.delete
self.is_retired最后三个“声明”什么也不做,应该删除。
名为is_xxx()的函数类似于不修改任何状态并返回True或False结果的函数。您的函数更改程序的状态,而不返回任何内容。找到更好的名字,比如do_cancel()和perform_amend。
每次调用.data_view()时,都会创建一个新的ttk.Treeview,并将其放在前面的位置上。
创建一次ttk.Treeview,并在信息更改时刷新其内容。
os.path.isfile(file)和os.stat(file)是老式的函数.您应该开始使用更新的pathlib。( Eg)
from pathlib import Path
...
json_file = Path(__file__).parent / 'emp_data.json'
if json_file.is_file() and json_file.stat().st_size != 0:
...有时您使用f'{current_path}\\emp_data.json'来指定数据库文件,而另一些时候则使用with open('emp_data.json', 'w') as f:,这可能会写入一个完全不同的位置!
我看到了大量重复的代码,用于读取和写入emp_data.json数据库。您应该创建并使用数据访问对象来管理数据库。
例如:
class EmployeeDB:
def __init__(self, json_file: Path) -> None:
self._file = json_file
# Create an empty database, if file doesn't exist or is empty
if not json_file.is_file() or json_file.stat().st_size == 0:
self.save([])
def load(self) -> list:
employees = []
with open(self._file, 'r') as f:
data = json.load(f)
for item in data['people']:
...
return employees
def save(self, employees: list) -> None:
with open(self._file, 'w') as f:
data = {"people": employees}
json.dump(data, f, indent=4)
class StaffManager(tk.Tk):
def __init__(self, database: EmployeeDB) -> None:
self._db = database
...
...
...
if __name__ == '__main__':
db = EmployeeDB(Path('emp_data.json'))
StaffManager(db).mainloop()每个员工都是一本字典。
您应该创建一个实际的Employee 数据集来保存信息。由此,您可以添加其他方法,比如.age(),它可以返回基于.dob的计算值。
from dataclasses import dataclass
@dataclass
class Employee:
name: str
surname: str
position: str
dob: str
start: str
end: str
retired: strEmployeeDB现在可以使用list[Employee]作为类型提示,而不是普通的list。
我还会做更多的改变,但这是一个好的开始。我期待着审查下一次修订。
发布于 2021-10-04 20:58:05
不确定我是否应该在这篇文章中创建一个新的帖子或答案。这是我的员工经理应用程序的修订版。
改进:
尚未发生:
如有任何建议和意见,将不胜感激。发生了一些小的变化,多亏了AJNeufeld,290个代码行减少到225行,而程序做的事情完全一样,所有的事情都像它想象的那样工作。该数据文件仍然以人类可读的形式存在(尽管将数据存储为列表并在应用程序中正确显示它们会更容易)。
import json
from os import path
import tkinter as tk
from tkinter import StringVar, ttk
import os
from tkinter.constants import CENTER, NO
from ttkbootstrap import Style
class Window(tk.Tk):
def __init__(self, database: object) -> None:
super().__init__()
self._db = database
self.geometry('1000x500')
self.title('Staff Manager')
self.iconbitmap('icon.ico')
self.buttons()
self.data_view()
# set theme for the app (from ttkbootstrap)
style = Style(theme='darkly')
self = style.master
def buttons(self) -> None:
# managing buttons, main window
add = ttk.Button(self, text='Add', command=self.add_new_emp)
add.place(x=10, y=10, width=75, height=40)
amend = ttk.Button(self, text='Amend', command=self.amend_emp)
amend.place(x=10, y=55, width=75, height=40)
retire = ttk.Button(self, text='Retire', command=self.retire_emp)
retire.place(x=10, y=100, width=75, height=40)
remove = ttk.Button(self, text='Remove', command=self.delete_emp)
remove.place(x=10, y=180, width=75, height=40)
exit_button = ttk.Button(self, text= 'EXIT', command=self.destroy)
exit_button.place(x=10, y=450, width=75, height=40)
def data_view(self) -> None:
# create columns
columns = ('#1', '#2', '#3', '#4', '#5', '#6', '#7')
self.view_panel = ttk.Treeview(self, columns=columns, show='headings', height=27, selectmode='browse')
self.view_panel.place(x=90, y=10, width=890, height=480)
# headings
self.view_panel.heading('#1', text='Name')
self.view_panel.heading('#2', text='Surname')
self.view_panel.heading('#3', text='Position')
self.view_panel.heading('#4', text='DoB')
self.view_panel.heading('#5', text='Start Date')
self.view_panel.heading('#6', text='End Date')
self.view_panel.heading('#7', text='Retired?')
# set colums properties
self.view_panel.column('#1', anchor=CENTER, stretch=NO, width=135)
self.view_panel.column('#2', anchor=CENTER, stretch=NO, width=135)
self.view_panel.column('#3', anchor=CENTER, stretch=NO, width=135)
self.view_panel.column('#4', anchor=CENTER, stretch=NO, width=135)
self.view_panel.column('#5', anchor=CENTER, stretch=NO, width=135)
self.view_panel.column('#6', anchor=CENTER, stretch=NO, width=135)
self.view_panel.column('#7', anchor=CENTER, stretch=NO, width=80)
# set scrollbal for view panel
scrollbar = ttk.Scrollbar(self, orient= tk.VERTICAL, command= self.view_panel.yview)
self.view_panel.configure(yscrollcommand=scrollbar.set)
scrollbar.place(x=980, y=10, width=20, height=480)
for emp in EmployeeDatabase.load(self._db):
self.view_panel.insert('', tk.END, values=emp)
def refresh_view_panel(self) -> None:
self.view_panel.destroy()
self.data_view()
def add_new_emp(self) -> None:
new_emp_win = tk.Toplevel(self)
new_emp_win.geometry('380x400')
new_emp_win.title('Add New Employee')
new_emp_win.iconbitmap('icon.ico')
def check_empty_entry(data: str) -> str:
if len(data) == 0:
return 'n/a'
else:
return data
def accept_new_emp() -> None:
# get all the data
name = check_empty_entry(name_ent.get())
surname = check_empty_entry(surname_ent.get())
position = check_empty_entry(position_ent.get())
dob = check_empty_entry(dob_ent.get())
start = check_empty_entry(start_ent.get())
end = check_empty_entry(end_ent.get())
retired = retired_var.get()
new_emp = [name, surname, position, dob, start, end, retired]
EmployeeDatabase.save(self._db, employees=new_emp)
new_emp_win.destroy()
self.refresh_view_panel()
# set labels for Add New Emp window
name_lbl = ttk.Label(new_emp_win, text='Name:', font=('Arial', 12))
name_lbl.place(x=10, y=10)
surname_lbl = ttk.Label(new_emp_win, text='Surname:', font=('Arial', 12))
surname_lbl.place(x=10, y=50)
position_lbl = ttk.Label(new_emp_win, text='Position:', font=('Arial', 12))
position_lbl.place(x=10, y=90)
dob_lbl = ttk.Label(new_emp_win, text='Date of Birth:', font=('Arial', 12))
dob_lbl.place(x=10, y=130)
start_lbl = ttk.Label(new_emp_win, text='Start Date:', font=('Arial', 12))
start_lbl.place(x=10, y=170)
end_lbl = ttk.Label(new_emp_win, text='End Date:', font=('Arial', 12))
end_lbl.place(x=10, y=210)
retired_lbl = ttk.Label(new_emp_win, text='Retired?', font=('Arial', 12))
retired_lbl.place(x=10, y=250)
# set entry points for data
name_ent = ttk.Entry(new_emp_win, justify='right')
name_ent.place(x=215, y=10)
surname_ent = ttk.Entry(new_emp_win, justify='right')
surname_ent.place(x=215, y=50)
position_ent = ttk.Entry(new_emp_win, justify='right')
position_ent.place(x=215, y=90)
dob_ent = ttk.Entry(new_emp_win, justify='right')
dob_ent.place(x=215, y=130)
start_ent = ttk.Entry(new_emp_win, justify='right')
start_ent.place(x=215, y=170)
end_ent = ttk.Entry(new_emp_win, justify='right')
end_ent.place(x=215, y=210)
retired_var = StringVar(value='No')
retired_chk = ttk.Checkbutton(
new_emp_win,
text='Yes',
onvalue='Yes',
offvalue='No',
variable=retired_var)
retired_chk.place(x=215, y=250)
ok_button = ttk.Button(new_emp_win, text='ADD', command=accept_new_emp)
ok_button.place(x=100, y=350)
cancel_button = ttk.Button(new_emp_win, text='CANCEL', command=new_emp_win.destroy)
cancel_button.place(x=200, y=350)
def delete_emp(self) -> None:
# get "clicked" emp
current_emp = self.view_panel.focus()
emp_info = self.view_panel.item(current_emp)
emp_details = emp_info["values"]
# need it as tuple for 'for loop' comparison
emp_details = tuple(emp_details)
# delete emp
new_data = []
for employee in EmployeeDatabase.load(self._db):
if employee != emp_details:
new_data.append(employee)
EmployeeDatabase.create_new(self._db, [])
for emp in new_data:
EmployeeDatabase.save(self._db, emp)
# update view panel
self.refresh_view_panel()
def change_details(self, change_detail_index: int, new_detail: str) -> None:
# get 'clicked' emp
current_emp = self.view_panel.focus()
emp_info = self.view_panel.item(current_emp)
details = emp_info["values"]
# need it as tuple for 'for loop' comparison
details_tup = tuple(details)
emp_to_change = details
# copy rest of emps
new_data = []
for employee in EmployeeDatabase.load(self._db):
if employee != details_tup:
new_data.append(employee)
# append emp with changed detail
details[change_detail_index] = new_detail
new_data.append(details)
EmployeeDatabase.create_new(self._db, [])
# write to database
for emp in new_data:
EmployeeDatabase.save(self._db, emp)
self.refresh_view_panel()
def amend_emp(self) -> None:
amend_win = tk.Toplevel()
amend_win.geometry('250x250')
amend_win.title('Amend Employee Details')
amend_win.iconbitmap('icon.ico')
# check if data is not empty, if is: n/a
def is_empty(data: str) -> str:
if len(data) == 0:
return 'n/a'
else:
return data
main_choice_lbl = ttk.Label(amend_win, text='What would you like to change?', font=('Arial', 12))
main_choice_lbl.place(x=10, y=10)
self.data_options = ('Name', 'Surname', 'Position', 'DoB', 'Start', 'End')
self.choice = StringVar()
options = ttk.OptionMenu(amend_win,
self.choice,
self.data_options[0],
*self.data_options)
options.place(x=10, y=47)
options.config(width=15)
new_emp_data = ttk.Label(amend_win, text='New data:', font=('Arial', 12))
new_emp_data.place(x=10, y=90)
main_entry = ttk.Entry(amend_win, justify='right')
main_entry.place(x=10, y=130)
ok_button = ttk.Button(amend_win, text='CHANGE', command=lambda: [self.change_details(self.data_options.index(self.choice.get()), is_empty(main_entry.get())), amend_win.destroy()])
ok_button.place(x=60, y=200)
cancel_button = ttk.Button(amend_win, text='CANCEL', command=amend_win.destroy)
cancel_button.place(x=150, y=200)
def retire_emp(self) -> None:
retired_win = tk.Toplevel()
retired_win.geometry('280x130')
ret_lbl = ttk.Label(retired_win, text="Are you sure you want to RETIRE your employee?")
ret_lbl.place(x=10, y=20)
ok_button = ttk.Button(retired_win, text='RETIRE', command=lambda: [self.change_details(6, "Yes"), retired_win.destroy()])
ok_button.place(x=40, y=60)
cancel_button = ttk.Button(retired_win, text='CANCEL', command=retired_win.destroy)
cancel_button.place(x=150, y=60)
class EmployeeDatabase:
def __init__(self, json_file: str) -> None:
self._file = json_file
if os.path.isfile(json_file) == True and os.stat(json_file).st_size != 0:
pass
else:
self.create_new([])
def load(self) -> list[str]:
employees: list[str] = []
with open(self._file, 'r') as f:
data = json.load(f)
for item in data["people"]:
name: str = item["name"]
surname: str = item["surname"]
position: str = item["position"]
dob: str = item["dob"]
start: str = item["start"]
end: str = item["end"]
retired: str = item["retired"]
employees.append((name, surname, position, dob, start, end, retired))
return employees
def create_new(self, employees: list) -> None:
with open(self._file, 'w') as f:
data = {"people": employees}
json.dump(data, f, indent=4)
def save(self, employees: list[str]) -> None:
new_emp = {
"name": employees[0],
"surname": employees[1],
"position": employees[2],
"dob": employees[3],
"start": employees[4],
"end": employees[5],
"retired": employees[6]
}
with open(self._file, 'r') as f:
data = json.load(f)
data["people"].append(new_emp)
with open(self._file, 'w') as f:
json.dump(data, f, indent=4)
if __name__ == '__main__':
path = f'{os.path.dirname(__file__)}\\emp_db.json'
db = EmployeeDatabase(path)
Window(database= db).mainloop()https://codereview.stackexchange.com/questions/268508
复制相似问题