• Python 的Tkinter包系列之七:好例子补充


    Python 的Tkinter包系列之七:好例子补充

    简单记事本

    使用python之tkinter 创建简单记事本,采用多种实现代码。

    Python的文件操作可参考https://blog.csdn.net/cnds123/article/details/108308817

    特别提示,用下面语句打开由记事本创建的.txt文件:

    open(filename, 'r')

    时,可能报错“UnicodeDecodeError: 'gbk' codec can't decode byte……illegal multibyte sequence”

    原因是,Python 的 open 方法默认编码取决于平台,如果是 Windows 中文平台,默认编码是 gbk,如果文件是 utf-8 编码,就会报这个错误。可以 encoding='utf-8'更改

    改为:

    open(filename, 'r', encoding='utf-8')

    也就是添加参数encoding='utf-8'

    代码参考或借鉴自网络。

    第一种实现代码

    源码如下:

    1. import tkinter as tk #导入tkinter库,并重命名为tk
    2. from tkinter import filedialog #从tkinter库中导入filedialog模块
    3. from tkinter import messagebox #从tkinter库中导入messagebox模块
    4. import os #导入os标准库
    5. mywindow = tk.Tk() #创建一个窗体
    6. mywindow.title("简单记事本1") #设置窗体的标题
    7. mywindow.geometry("400x360") #设置窗体的大小
    8. filename="" #文件名,全局变量
    9. def about():
    10. messagebox.showinfo('提示','这是一个消息框!很简单不需要帮助吧')
    11. def myopen():
    12. global filename
    13. # 下面两句用其一即可,在此用第二句:你可以试试,留意区别
    14. #filename=filedialog.askopenfilename(defaultextension='.txt')
    15. filename=filedialog.askopenfilename(filetypes=[('txt格式', 'txt')])
    16. if filename=="":
    17. filename=None
    18. else:
    19. mywindow.title("记事本"+os.path.basename(filename))
    20. mytext.delete(1.0,tk.END)
    21. f=open(filename,'r',encoding='utf-8') # encoding='utf-8'
    22. mytext.insert(tk.INSERT,f.read())
    23. f.close()
    24. def mysave():
    25. global filename
    26. # 下面两句用其一即可,在此用第二句:你可以试试,留意区别
    27. #f=filedialog.asksaveasfilename(initialfile="未命名.txt",defaultextension='.txt' )
    28. f=filedialog.asksaveasfilename(initialfile="未命名.txt",filetypes=[('txt格式', 'txt')])
    29. filename=f
    30. if filename:
    31. fh=open(f,'w',encoding='utf-8') # encoding='utf-8'
    32. msg=mytext.get(1.0,tk.END)
    33. fh.write(msg)
    34. fh.close()
    35. mywindow.title("记事本"+os.path.basename(f))
    36. #多行文本框及布局
    37. mytext = tk.Text(mywindow,height = 24,width =60)
    38. mytext.pack(side='bottom')
    39. #按钮控件及布局
    40. myb1 = tk.Button(mywindow,text="打开",command = myopen)
    41. myb1.pack(side = 'left')
    42. myb2 = tk.Button(mywindow,text="保存", command = mysave )
    43. myb2.pack(side = 'left')
    44. myb3 = tk.Button(mywindow,text="帮助", command = about)
    45. myb3.pack(side = 'left')
    46. mywindow.mainloop()

    运行效果:

    第二种实现代码(使用菜单)

    源码如下:

    1. from tkinter import *
    2. from tkinter import messagebox
    3. from tkinter import filedialog
    4. import os
    5. filename='' #文件名,全局变量
    6. def about():
    7. messagebox.showinfo('提示','这是一个消息框!很简单不需要帮助吧')
    8. def openFile():
    9. global filename #使用全局变量
    10. # 将下句 filetypes=[('txt格式', 'txt')] 改为 defaultextension='.txt' 试试
    11. filename=filedialog.askopenfilename( filetypes=[('txt格式', 'txt')])
    12. if filename=='': #如果没有选择
    13. filename=None
    14. else:
    15. root.title('FileName:'+os.path.basename(filename)) #将文件名显示在标题栏中
    16. textPad.delete(1.0,END) #删除文本框中原有内容
    17. f=open(filename,'r',encoding='UTF-8') #获取文件中内容
    18. textPad.insert(1.0,f.read()) #显示到文本框
    19. f.close()
    20. def saveOtherFile():
    21. global filename
    22. # 将下句 filetypes=[('txt格式', 'txt')] 改为 defaultextension='.txt' 试试
    23. f=filedialog.asksaveasfilename(initialfile='未命名.txt', filetypes=[('txt格式', 'txt')])
    24. filename=f
    25. root.title('FileName:' + os.path.basename(filename))
    26. fh=open(f,'w',encoding='UTF-8') #只写方式打开文件
    27. msg=textPad.get(1.0,END) #从文本框中获取内容
    28. fh.write(msg)
    29. fh.close()
    30. root=Tk()
    31. root.title('简单记事本1b')
    32. root.geometry('500x500+100+100') #widthxheight+x+y
    33. menubar=Menu(root)#设置菜单栏
    34. root.config(menu=menubar)
    35. filemenu=Menu(menubar,tearoff=0) #第一个菜单栏
    36. filemenu.add_command(label='打开',accelerator='Ctrl+N',command=openFile)
    37. filemenu.add_command(label='保存',accelerator='Ctrl+T',command=saveOtherFile)
    38. menubar.add_cascade(label='文件',menu=filemenu)
    39. aboutmenu=Menu(menubar,tearoff=0)
    40. aboutmenu.add_command(label='关于',accelerator='Ctrl+Y',command=about)
    41. menubar.add_cascade(label='帮助',menu=aboutmenu)
    42. status=Label(root,text='',bd=1,relief=SUNKEN,anchor=W) #底部边界栏
    43. status.pack(side=BOTTOM,fill=X)
    44. lnlabel=Label(root,width=2,bg='antique white') #左边边界栏
    45. lnlabel.pack(side=LEFT,fill=Y)
    46. textPad=Text(root,undo=True) #设置文本框
    47. textPad.pack(expand=YES,fill=BOTH)
    48. scroll=Scrollbar(textPad) #右边进度条栏
    49. textPad.config(yscrollcommand=scroll.set)
    50. scroll.config(command=textPad.yview)
    51. scroll.pack(side=RIGHT,fill=Y)
    52. root.mainloop()

    运行效果:

    第三种实现代码(使用类)

    源码如下: 

    1. import tkinter
    2. import os
    3. from tkinter import *
    4. from tkinter.messagebox import *
    5. from tkinter.filedialog import *
    6. class Notepad:
    7. root = Tk()
    8. Width = 300
    9. Height = 300
    10. TextArea = Text(root)
    11. MenuBar = Menu(root)
    12. FileMenu = Menu(MenuBar, tearoff=0)
    13. EditMenu = Menu(MenuBar, tearoff=0)
    14. HelpMenu = Menu(MenuBar, tearoff=0)
    15. ScrollBar = Scrollbar(TextArea)
    16. file = None
    17. def __init__(self, **kwargs):
    18. # 设置文本框的大小
    19. try:
    20. self.Width = kwargs['width']
    21. except KeyError:
    22. pass
    23. try:
    24. self.Height = kwargs['height']
    25. except KeyError:
    26. pass
    27. # 设置窗口标题
    28. self.root.title("Python记事本")
    29. # 将窗口居中显示
    30. screenWidth = self.root.winfo_screenwidth()
    31. screenHeight = self.root.winfo_screenheight()
    32. left = (screenWidth / 2) - (self.Width / 2)
    33. top = (screenHeight / 2) - (self.Height / 2)
    34. self.root.geometry('%dx%d+%d+%d' %
    35. (self.Width, self.Height, left, top))
    36. # 文本区 域大小调整
    37. self.root.grid_rowconfigure(0, weight=1)
    38. self.root.grid_columnconfigure(0, weight=1)
    39. # Add controls (widget)
    40. self.TextArea.grid(sticky=N + E + S + W)
    41. # 增加新建配置
    42. self.FileMenu.add_command(label="新建", command=self.__newFile)
    43. # 增加打开配置
    44. self.FileMenu.add_command(label="打开", command=self.__openFile)
    45. # 增加保存配置
    46. self.FileMenu.add_command(label="保存", command=self.__saveFile)
    47. # 增加退出配置
    48. self.FileMenu.add_separator()
    49. self.FileMenu.add_command(label="退出", command=self.__quitApplication)
    50. # 菜单中设置文件按钮
    51. self.MenuBar.add_cascade(label="文件", menu=self.FileMenu)
    52. # 增加剪切功能
    53. self.EditMenu.add_command(label="剪切", command=self.__cut)
    54. # 增加复制功能
    55. self.EditMenu.add_command(label="复制", command=self.__copy)
    56. # 增加粘贴功能
    57. self.EditMenu.add_command(label="粘贴", command=self.__paste)
    58. # 菜单中设置编辑按钮
    59. self.MenuBar.add_cascade(label="编辑", menu=self.EditMenu)
    60. # 增加关于记事本选项
    61. self.HelpMenu.add_command(label="关于记事本", command=self.__showAbout)
    62. # 菜单中射者帮助按钮
    63. self.MenuBar.add_cascade(label="帮助", menu=self.HelpMenu)
    64. self.root.config(menu=self.MenuBar)
    65. self.ScrollBar.pack(side=RIGHT, fill=Y)
    66. # 滚动条根据内容进行调整
    67. self.ScrollBar.config(command=self.TextArea.yview)
    68. self.TextArea.config(yscrollcommand=self.ScrollBar.set)
    69. def __quitApplication(self):
    70. '''
    71. 用于退出程序(关了就消失)
    72. '''
    73. self.root.destroy()
    74. def __showAbout(self):
    75. '''
    76. 添加帮助菜单中的信息
    77. '''
    78. showinfo("关于记事本", "很简单不需要帮助吧")
    79. def __openFile(self):
    80. '''
    81. 打开文件
    82. '''
    83. self.file = askopenfilename(defaultextension=".txt",
    84. filetypes=[("All Files", "*.*"),
    85. ("Text Documents", "*.txt")])
    86. if self.file == "":
    87. self.file = None
    88. else:
    89. self.root.title(os.path.basename(self.file))
    90. self.TextArea.delete(1.0, END)
    91. file = open(self.file, "r",encoding='utf-8')
    92. self.TextArea.insert(1.0, file.read())
    93. file.close()
    94. def __newFile(self):
    95. '''
    96. 新文件:默认是一个未命名文件
    97. '''
    98. self.root.title("未命名文件")
    99. self.file = None
    100. self.TextArea.delete(1.0, END)
    101. def __saveFile(self):
    102. '''
    103. 用于保存文件,不存在的文件进行新建,存在的文件在原文件基础上覆盖保存
    104. '''
    105. if self.file == None:
    106. self.file = asksaveasfilename(initialfile='Untitled.txt',
    107. defaultextension=".txt",
    108. filetypes=[("All Files", "*.*"),
    109. ("Text Documents",
    110. "*.txt")])
    111. if self.file == "":
    112. self.file = None
    113. else:
    114. file = open(self.file, "w",encoding='utf-8')
    115. file.write(self.TextArea.get(1.0, END))
    116. file.close()
    117. # 更改title名字为文件名
    118. self.root.title(os.path.basename(self.file))
    119. else:
    120. file = open(self.file, "w",encoding='utf-8')
    121. file.write(self.TextArea.get(1.0, END))
    122. file.close()
    123. # 添加功能项
    124. def __cut(self):
    125. self.TextArea.event_generate("<>")
    126. def __copy(self):
    127. self.TextArea.event_generate("<>")
    128. def __paste(self):
    129. self.TextArea.event_generate("<>")
    130. def run(self):
    131. # 使用mainloop()使得窗口一直存在
    132. self.root.mainloop()
    133. notepad = Notepad(width=600, height=400)
    134. notepad.run()

    运行效果:

    点名系统

    第一种实现代码

    1. #简单点名系统
    2. from tkinter import *
    3. import random
    4. data = ['杨晓军', '赵 杰', '张大海', '张飞龙', '田志刚', '程 峰',
    5. '段雅倩', '崔彬彬', '袁飞龙', '王志强', '王翔宇', '夏 文',
    6. '苏欣悦', '苏欣悦', '顾文博' ]
    7. going = True
    8. is_run = False
    9. def lottery_roll(var1, var2):
    10. global going
    11. show_member = random.choice(data)
    12. var1.set(show_member)
    13. if going:
    14. window.after(50, lottery_roll, var1, var2)
    15. else:
    16. var2.set('恭喜 {} !!!'.format(show_member))
    17. going = True
    18. return
    19. def lottery_start(var1, var2):
    20. global is_run
    21. if is_run:
    22. return
    23. is_run = True
    24. var2.set('幸运儿是你吗。。。')
    25. lottery_roll(var1, var2)
    26. def lottery_end():
    27. global going, is_run
    28. if is_run:
    29. going = False
    30. is_run = False
    31. if __name__ == '__main__':
    32. window = Tk()
    33. window.geometry('405x320+250+150')
    34. window.title('点名')
    35. bg_label = Label(window, width=70, height=24, bg='#ECf5FF')
    36. bg_label.place(anchor=NW, x=0, y=0)
    37. var1 = StringVar(value='即 将 开 始')
    38. show_label1 = Label(window, textvariable=var1, justify='left', anchor=CENTER, width=17, height=3, bg='#BFEFFF',
    39. font='楷体 -40 bold', foreground='black')
    40. show_label1.place(anchor=NW, x=21, y=20)
    41. var2 = StringVar(value='幸运儿是你吗。。。')
    42. show_label2 = Label(window, textvariable=var2, justify='left', anchor=CENTER, width=38, height=3, bg='#ECf5FF',
    43. font='楷体 -18 bold', foreground='red')
    44. show_label2.place(anchor=NW, x=21, y=240)
    45. button1 = Button(window, text='开始', command=lambda: lottery_start(var1, var2), width=14, height=2, bg='#A8A8A8',
    46. font='宋体 -18 bold')
    47. button1.place(anchor=NW, x=20, y=175)
    48. button2 = Button(window, text='结束', command=lambda: lottery_end(), width=14, height=2, bg='#A8A8A8',
    49. font='宋体 -18 bold')
    50. button2.place(anchor=NW, x=232, y=175)
    51. window.mainloop()

    运行效果:

    第二种实现代码

    1. #点名程序
    2. import random
    3. import re
    4. import time
    5. import threading
    6. from tkinter import *
    7. from tkinter import messagebox
    8. from tkinter.filedialog import askopenfilename
    9. class APP:
    10. def __init__(self):
    11. self.root = Tk()
    12. self.running_flag=False #开始标志
    13. self.time_span=0.05 #名字显示间隔
    14. self.root.title('Point_name-V1.0')
    15. width = 680
    16. height = 350
    17. left = (self.root.winfo_screenwidth() - width) / 2
    18. top = (self.root.winfo_screenheight() - height) / 2
    19. self.root.geometry("%dx%d+%d+%d" % (width, height, left, top))
    20. self.root.resizable(0,0)
    21. self.create_widget()
    22. self.set_widget()
    23. self.place_widget()
    24. self.root.mainloop()
    25. def create_widget(self):
    26. self.label_show_name_var=StringVar()
    27. self.label_show_name=Label(self.root,textvariable=self.label_show_name_var,font=('Arial', 100,"bold"),foreground = '#1E90FF')
    28. self.btn_start=Button(self.root,text="开始",)
    29. self.btn_load_names=Button(self.root,text="加载名单",)
    30. self.lf1=LabelFrame(self.root,text="方式")
    31. self.radioBtn_var=IntVar()
    32. self.radioBtn_var.set(1)
    33. self.radioBtn_sequence=Radiobutton(self.lf1,text="顺序",variable=self.radioBtn_var, value=1)
    34. self.radioBtn_random=Radiobutton(self.lf1,text="随机",variable=self.radioBtn_var, value=2)
    35. self.label_show_name_num=Label(self.root,font=('Arial', 20),foreground = '#FF7F50')
    36. paned = PanedWindow(self.root)
    37. def set_widget(self):
    38. default_name_="是?"
    39. self.label_show_name_var.set(default_name_)
    40. self.label_show_name_adjust(default_name_)
    41. self.btn_start.config(command=lambda :self.thread_it(self.start_point_name))
    42. self.btn_load_names.config(command=self.load_names)
    43. init_names=self.load_names_txt("./names.txt")
    44. self.root.protocol('WM_DELETE_WINDOW',self.quit_window)
    45. self.root.bind('',self.quit_window)
    46. if init_names:
    47. self.default_names=init_names #1.文件存在但是无内容。2.文件不存在
    48. self.label_show_name_num.config(text=f"一共加载了{len(self.default_names)}个")
    49. else:
    50. self.btn_start.config(state=DISABLED)
    51. self.label_show_name_num.config(text=f"请选定名单!")
    52. def place_widget(self):
    53. self.lf1.place(x=300,y=160,width=250,height=50)
    54. self.radioBtn_sequence.place(x=20,y=0)
    55. self.radioBtn_random.place(x=150,y=0)
    56. self.btn_start.place(x=300,y=220,width=100,height=30)
    57. self.btn_load_names.place(x=450,y=220,width=100,height=30)
    58. #self._img.place(x=90, y=165, height=120, width=180)
    59. self.label_show_name_num.place(x=300,y=260)
    60. def label_show_name_adjust(self,the_name):
    61. if len (the_name)==1:
    62. self.label_show_name.place(x=280, y=10)
    63. elif len(the_name) == 2:
    64. self.label_show_name.place(x=180, y=10)
    65. elif len(the_name) == 3:
    66. self.label_show_name.place(x=120, y=10)
    67. elif len(the_name) == 4:
    68. self.label_show_name.place(x=80, y=10)
    69. else:
    70. self.label_show_name.place(x=0, y=10)
    71. def start_point_name(self):
    72. """
    73. 启动之前进行判断,获取点名模式
    74. :return:
    75. """
    76. if len(self.default_names)==1:
    77. messagebox.showinfo("提示",'名单就一个,不用选了!')
    78. self.label_show_name_var.set(self.default_names[0])
    79. self.label_show_name_adjust(self.default_names[0])
    80. return
    81. if self.btn_start["text"]=="开始":
    82. self.btn_load_names.config(state=DISABLED)
    83. self.running_flag=True
    84. if isinstance(self.default_names,list):
    85. self.btn_start.config(text="就你了")
    86. if self.radioBtn_var.get()==1:
    87. mode="sequence"
    88. elif self.radioBtn_var.get()==2:
    89. mode="random"
    90. else:
    91. pass
    92. self.thread_it(self.point_name_begin(mode))
    93. else:
    94. messagebox.showwarning("警告","请先选定名单!")
    95. else:
    96. self.running_flag=False
    97. self.btn_load_names.config(state=NORMAL)
    98. self.btn_start.config(text="开始")
    99. def point_name_begin(self,mode):
    100. """
    101. 开始点名主函数
    102. :param mode:
    103. :return:
    104. """
    105. if mode == "sequence":
    106. if self.running_flag:
    107. self.always_ergodic()
    108. elif mode=="random":
    109. while True:
    110. if self.running_flag:
    111. random_choice_name=random.choice(self.default_names)
    112. self.label_show_name_var.set(random_choice_name)
    113. self.label_show_name_adjust(random_choice_name)
    114. time.sleep(self.time_span)
    115. else:
    116. break
    117. def always_ergodic(self):
    118. """
    119. 一直遍历此列表,使用死循环会造成线程阻塞
    120. :return:
    121. """
    122. for i in self.default_names:
    123. if self.running_flag:
    124. self.label_show_name_var.set(i)
    125. self.label_show_name_adjust(i)
    126. time.sleep(self.time_span)
    127. if i==self.default_names[-1]:
    128. self.always_ergodic()
    129. else:
    130. break
    131. def load_names(self):
    132. """
    133. 手动加载txt格式人名单
    134. :return:
    135. """
    136. filename = askopenfilename(
    137. filetypes = [('文本文件', '.TXT'), ],
    138. title = "选择一个文本文件",
    139. initialdir="./"
    140. )
    141. if filename:
    142. names=self.load_names_txt(filename)
    143. if names:
    144. self.default_names=names
    145. no_Chinese_name_num=len([n for n in names if not self.load_name_check(n)])
    146. if no_Chinese_name_num==0:
    147. pass
    148. else:
    149. messagebox.showwarning("请注意",f'导入名单有{no_Chinese_name_num}个不是中文名字')
    150. self.label_show_name_num.config(text=f"一共加载了{len(self.default_names)}个")
    151. self.btn_start.config(state=NORMAL)
    152. else:
    153. messagebox.showwarning("警告","导入失败,请检查!")
    154. def load_names_txt(self,txt_file):
    155. """
    156. 读取txt格式的人名单
    157. :param txt_file:
    158. :return:
    159. """
    160. try:
    161. with open(txt_file,'r',encoding="utf-8")as f:
    162. names=[name.strip() for name in f.readlines()]
    163. if len(names)==0:
    164. return False
    165. else:
    166. return names
    167. except:
    168. return False
    169. def load_name_check(self,name):
    170. """
    171. 对txt文本中的人名进行校验
    172. 中文汉字->True
    173. 非中文汉字->False
    174. :param name:
    175. :return:
    176. """
    177. regex = r'[\u4e00-\u9fa5]+'
    178. if re.match(regex,name):
    179. return True
    180. else:
    181. return False
    182. def thread_it(self,func,*args):
    183. t=threading.Thread(target=func,args=args)
    184. #t.setDaemon(True)
    185. t.start()
    186. def quit_window(self,*args):
    187. """
    188. 程序退出触发此函数
    189. :param args:
    190. :return:
    191. """
    192. ret=messagebox.askyesno('退出','确定要退出?')
    193. if ret:
    194. self.root.destroy()
    195. if __name__ == '__main__':
    196. a=APP()

    人名文本文件如

     运行效果:

    tkinter 的Canvas示例程序

    关于Canvas(画布)可参见https://blog.csdn.net/cnds123/article/details/127344534

    画五角星源码如下:

    1. #使用Tkinter Canvas画五角星
    2. import tkinter as tk
    3. import math
    4. root = tk.Tk()
    5. w = tk.Canvas(root, width = 200, height = 100, background = "red")
    6. w.pack()
    7. center_x = 100
    8. center_y = 50
    9. r = 50
    10. points = [
    11. #左上点(A)
    12. center_x - int(r * math.sin(2 * math.pi / 5)),
    13. center_y - int(r * math.cos(2 * math.pi / 5)),
    14. #右上点(C)
    15. center_x + int(r * math.sin(2 * math.pi / 5)),
    16. center_y - int(r * math.cos(2 * math.pi / 5)),
    17. #左下点(E)
    18. center_x - int(r * math.sin(math.pi / 5)),
    19. center_y + int(r * math.cos(math.pi / 5)),
    20. #顶点(B)
    21. center_x,
    22. center_y - r,
    23. #右下点(D)
    24. center_x + int(r * math.sin(math.pi / 5)),
    25. center_y + int(r * math.cos(math.pi / 5))
    26. ]
    27. w.create_polygon(points, outline = "green", fill = "yellow") #fill 默认是 black
    28. root.mainloop()

    运行效果:

    说明:

    "2 * PI / 5" 是用弧度的方式表示的。

    1度的对应的弧长是等于圆周长的360分之一,而1弧度的是等于半径。参见下图:

    它们的关系可用下式表示和计算:

    角(弧度)=弧长/半径

    圆的周长是半径的 2π倍,所以一个周角(360度)是 2π弧度。

    一个平角是 π 弧度。

    即 180度=π弧度

    由此可知:

    1度=π/180 弧度 ( ≈0.017453弧度 )

    1弧度=180°/π (≈57.3°)

    画树源码如下:

    1. # 使用Tkinter Canvas画树,每次画的不一样
    2. import tkinter
    3. import sys, random, math
    4. class Point(object):
    5. def __init__(self, x, y):
    6. self.x = x
    7. self.y = y
    8. def __str__(self):
    9. return ": (%f, %f)" % (self.x, self.y)
    10. class Branch(object):
    11. def __init__(self, bottom, top, branches, level = 0):
    12. self.bottom = bottom
    13. self.top = top
    14. self.level = level
    15. self.branches = branches
    16. self.children = []
    17. def __str__(self):
    18. s = "Top: %s, Bottom: %s, Children Count: %d" % \
    19. (self.top, self.bottom, len(self.children))
    20. return s
    21. def nextGen(self, n = -1, rnd = 1):
    22. if n <= 0: n = self.branches
    23. if rnd == 1:
    24. n = random.randint(n / 2, n * 2)
    25. if n <= 0: n = 1
    26. dx = self.top.x - self.bottom.x
    27. dy = self.top.y - self.bottom.y
    28. r = 0.20 + random.random() * 0.2
    29. if self.top.x == self.bottom.x:
    30. # 如果是一条竖线
    31. x = self.top.x
    32. y = dy * r + self.bottom.y
    33. elif self.top.y == self.bottom.y:
    34. # 如果是一条横线
    35. x = dx * r + self.bottom.x
    36. y = self.top.y
    37. else:
    38. x = dx * r
    39. y = x * dy / dx
    40. x += self.bottom.x
    41. y += self.bottom.y
    42. oldTop = self.top
    43. self.top = Point(x, y)
    44. a = math.pi / (2 * n)
    45. for i in range(n):
    46. a2 = -a * (n - 1) / 2 + a * i - math.pi
    47. a2 *= 0.9 + random.random() * 0.2
    48. self.children.append(self.mkNewBranch(self.top, oldTop, a2))
    49. def mkNewBranch(self, bottom, top, a):
    50. dx1 = top.x - bottom.x
    51. dy1 = top.y - bottom.y
    52. r = 0.9 + random.random() * 0.2
    53. c = math.sqrt(dx1 ** 2 + dy1 ** 2) * r
    54. if dx1 == 0:
    55. a2 = math.pi / 2
    56. else:
    57. a2 = math.atan(dy1 / dx1)
    58. if (a2 < 0 and bottom.y > top.y) \
    59. or (a2 > 0 and bottom.y < top.y) \
    60. :
    61. a2 += math.pi
    62. b = a2 - a
    63. dx2 = c * math.cos(b)
    64. dy2 = c * math.sin(b)
    65. newTop = Point(dx2 + bottom.x, dy2 + bottom.y)
    66. return Branch(bottom, newTop, self.branches, self.level + 1)
    67. class Tree(object):
    68. def __init__(self, root, canvas, bottom, top, branches = 3, depth = 3):
    69. self.root = root
    70. self.canvas = canvas
    71. self.bottom = bottom
    72. self.top = top
    73. self.branches = branches
    74. self.depth = depth
    75. self.new()
    76. def gen(self, n = 1):
    77. for i in range(n):
    78. self.getLeaves()
    79. for node in self.leaves:
    80. node.nextGen()
    81. self.show()
    82. def new(self):
    83. self.leavesCount = 0
    84. self.branch = Branch(self.bottom, self.top, self.branches)
    85. self.gen(self.depth)
    86. print("leaves count: %d" % self.leavesCount)
    87. def chgDepth(self, d):
    88. self.depth += d
    89. if self.depth < 0: self.depth = 0
    90. if self.depth > 10: self.depth = 10
    91. self.new()
    92. def chgBranch(self, d):
    93. self.branches += d
    94. if self.branches < 1: self.branches = 1
    95. if self.branches > 10: self.branches = 10
    96. self.new()
    97. def getLeaves(self):
    98. self.leaves = []
    99. self.map(self.findLeaf)
    100. def findLeaf(self, node):
    101. if len(node.children) == 0:
    102. self.leaves.append(node)
    103. def show(self):
    104. for i in self.canvas.find_all():
    105. self.canvas.delete(i)
    106. self.map(self.drawNode)
    107. self.canvas.tag_raise("leaf")
    108. def exit(self, evt):
    109. sys.exit(0)
    110. def map(self, func = lambda node: node):
    111. # 遍历树
    112. children = [self.branch]
    113. while len(children) != 0:
    114. newChildren = []
    115. for node in children:
    116. func(node)
    117. newChildren.extend(node.children)
    118. children = newChildren
    119. def drawNode(self, node):
    120. self.line2(
    121. # self.canvas.create_line(
    122. node.bottom.x,
    123. node.bottom.y,
    124. node.top.x,
    125. node.top.y,
    126. fill = "#100",
    127. width = 1.5 ** (self.depth - node.level),
    128. tags = "branch level_%d" % node.level,
    129. )
    130. if len(node.children) == 0:
    131. # 画叶子
    132. self.leavesCount += 1
    133. self.canvas.create_oval(
    134. node.top.x - 3,
    135. node.top.y - 3,
    136. node.top.x + 3,
    137. node.top.y + 3,
    138. fill = "#090",
    139. tag = "leaf",
    140. )
    141. self.canvas.update()
    142. def line2(self, x0, y0, x1, y1, width = 1, fill = "#000", minDist = 10, tags = ""):
    143. dots = midDots(x0, y0, x1, y1, minDist)
    144. dots2 = []
    145. for i in range(len(dots) - 1):
    146. dots2.extend([dots[i].x,
    147. dots[i].y,
    148. dots[i + 1].x,
    149. dots[i + 1].y])
    150. self.canvas.create_line(
    151. dots2,
    152. fill = fill,
    153. width = width,
    154. smooth = True,
    155. tags = tags,
    156. )
    157. def midDots(x0, y0, x1, y1, d):
    158. dots = []
    159. dx, dy, r = x1 - x0, y1 - y0, 0
    160. if dx != 0:
    161. r = float(dy) / dx
    162. c = math.sqrt(dx ** 2 + dy ** 2)
    163. n = int(c / d) + 1
    164. for i in range(n):
    165. if dx != 0:
    166. x = dx * i / n
    167. y = x * r
    168. else:
    169. x = dx
    170. y = dy * i / n
    171. if i > 0:
    172. x += d * (0.5 - random.random()) * 0.25
    173. y += d * (0.5 - random.random()) * 0.25
    174. x += x0
    175. y += y0
    176. dots.append(Point(x, y))
    177. dots.append(Point(x1, y1))
    178. return dots
    179. if __name__ == "__main__":
    180. root = tkinter.Tk()
    181. root.title("Tree")
    182. gw, gh = 800, 600
    183. canvas = tkinter.Canvas(root,
    184. width = gw,
    185. height = gh,
    186. )
    187. canvas.pack()
    188. tree = Tree(root, canvas, Point(gw / 2, gh - 20), Point(gw / 2, gh * 0.2), \
    189. branches = 2, depth = 8)
    190. root.bind("n", lambda evt: tree.new())
    191. root.bind("=", lambda evt: tree.chgDepth(1))
    192. root.bind("+", lambda evt: tree.chgDepth(1))
    193. root.bind("-", lambda evt: tree.chgDepth(-1))
    194. root.bind("b", lambda evt: tree.chgBranch(1))
    195. root.bind("c", lambda evt: tree.chgBranch(-1))
    196. root.bind("q", tree.exit)
    197. root.mainloop()

    运行效果:

     OK!

  • 相关阅读:
    elementui中table进行表单验证
    23软考备考已开始,网络工程师知识点速记~(3)
    DBeaver下载安装与使用
    13-ES5和ES6基础
    07-mysql-SQL优化
    DockerFile系列:构建Tomcat9
    2022-08-23 学习笔记 day46-sql语言-TCL
    设计模式之装饰者模式
    【JPCS 独立出版】2022年机器人与人工智能国际会议(JCRAI 2022)
    十进制转其他进制算法 位运算
  • 原文地址:https://blog.csdn.net/cnds123/article/details/128091966