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'

代码参考或借鉴自网络。

第一种实现代码

源码如下:

import tkinter as tk                        #导入tkinter库,并重命名为tk
from tkinter import  filedialog             #从tkinter库中导入filedialog模块
from tkinter import messagebox              #从tkinter库中导入messagebox模块
import os                                   #导入os标准库

mywindow = tk.Tk()                          #创建一个窗体
mywindow.title("简单记事本1")      #设置窗体的标题               
mywindow.geometry("400x360")                #设置窗体的大小
filename=""  #文件名,全局变量
def about():
    messagebox.showinfo('提示','这是一个消息框!很简单不需要帮助吧')
def myopen():
    global filename
    # 下面两句用其一即可,在此用第二句:你可以试试,留意区别
    #filename=filedialog.askopenfilename(defaultextension='.txt')
    filename=filedialog.askopenfilename(filetypes=[('txt格式', 'txt')]) 
    if filename=="":
        filename=None
    else:
        mywindow.title("记事本"+os.path.basename(filename))
        mytext.delete(1.0,tk.END)
        f=open(filename,'r',encoding='utf-8') # encoding='utf-8'
        mytext.insert(tk.INSERT,f.read())
        f.close()

def mysave():
    global filename
    # 下面两句用其一即可,在此用第二句:你可以试试,留意区别
    #f=filedialog.asksaveasfilename(initialfile="未命名.txt",defaultextension='.txt' )
    f=filedialog.asksaveasfilename(initialfile="未命名.txt",filetypes=[('txt格式', 'txt')]) 
    filename=f
    if filename:
        fh=open(f,'w',encoding='utf-8') # encoding='utf-8'
        msg=mytext.get(1.0,tk.END)
        fh.write(msg)
        fh.close()
        mywindow.title("记事本"+os.path.basename(f))


#多行文本框及布局
mytext = tk.Text(mywindow,height = 24,width =60)
mytext.pack(side='bottom')
#按钮控件及布局
myb1 = tk.Button(mywindow,text="打开",command = myopen)
myb1.pack(side = 'left')
myb2 = tk.Button(mywindow,text="保存", command = mysave )
myb2.pack(side = 'left')
myb3 = tk.Button(mywindow,text="帮助", command = about)
myb3.pack(side = 'left')

mywindow.mainloop()

运行效果:

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

源码如下:

from tkinter import *
from tkinter import messagebox
from tkinter import filedialog
import os
 
filename=''   #文件名,全局变量
def about():
    messagebox.showinfo('提示','这是一个消息框!很简单不需要帮助吧')
def openFile():
    global filename   #使用全局变量
    # 将下句 filetypes=[('txt格式', 'txt')] 改为 defaultextension='.txt' 试试
    filename=filedialog.askopenfilename( filetypes=[('txt格式', 'txt')])   
    if filename=='':   #如果没有选择
        filename=None
    else:
        root.title('FileName:'+os.path.basename(filename))   #将文件名显示在标题栏中
        textPad.delete(1.0,END)   #删除文本框中原有内容
        f=open(filename,'r',encoding='UTF-8')   #获取文件中内容
        textPad.insert(1.0,f.read())   #显示到文本框
        f.close()
def saveOtherFile():
    global filename
    # 将下句 filetypes=[('txt格式', 'txt')] 改为 defaultextension='.txt' 试试
    f=filedialog.asksaveasfilename(initialfile='未命名.txt', filetypes=[('txt格式', 'txt')])   
    filename=f
    root.title('FileName:' + os.path.basename(filename))
    fh=open(f,'w',encoding='UTF-8')   #只写方式打开文件
    msg=textPad.get(1.0,END)   #从文本框中获取内容
    fh.write(msg)
    fh.close()
 
root=Tk()   
root.title('简单记事本1b')
root.geometry('500x500+100+100')   #widthxheight+x+y
menubar=Menu(root)#设置菜单栏
root.config(menu=menubar)
filemenu=Menu(menubar,tearoff=0)   #第一个菜单栏
filemenu.add_command(label='打开',accelerator='Ctrl+N',command=openFile)
filemenu.add_command(label='保存',accelerator='Ctrl+T',command=saveOtherFile)
menubar.add_cascade(label='文件',menu=filemenu)
 
aboutmenu=Menu(menubar,tearoff=0)
aboutmenu.add_command(label='关于',accelerator='Ctrl+Y',command=about)
menubar.add_cascade(label='帮助',menu=aboutmenu)
 
status=Label(root,text='',bd=1,relief=SUNKEN,anchor=W)   #底部边界栏
status.pack(side=BOTTOM,fill=X)
 
lnlabel=Label(root,width=2,bg='antique white')   #左边边界栏
lnlabel.pack(side=LEFT,fill=Y)
 
textPad=Text(root,undo=True)   #设置文本框
textPad.pack(expand=YES,fill=BOTH)

scroll=Scrollbar(textPad)   #右边进度条栏
textPad.config(yscrollcommand=scroll.set)
scroll.config(command=textPad.yview)
scroll.pack(side=RIGHT,fill=Y)
 
root.mainloop()

运行效果:

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

源码如下: 

import tkinter
import os
from tkinter import *
from tkinter.messagebox import *
from tkinter.filedialog import *


class Notepad:

    root = Tk()

    Width = 300
    Height = 300
    TextArea = Text(root)
    MenuBar = Menu(root)
    FileMenu = Menu(MenuBar, tearoff=0)
    EditMenu = Menu(MenuBar, tearoff=0)
    HelpMenu = Menu(MenuBar, tearoff=0)
    ScrollBar = Scrollbar(TextArea)
    file = None

    def __init__(self, **kwargs):

        # 设置文本框的大小

        try:
            self.Width = kwargs['width']
        except KeyError:
            pass

        try:
            self.Height = kwargs['height']
        except KeyError:
            pass

        # 设置窗口标题
        self.root.title("Python记事本")

        # 将窗口居中显示
        screenWidth = self.root.winfo_screenwidth()
        screenHeight = self.root.winfo_screenheight()
        left = (screenWidth / 2) - (self.Width / 2)
        top = (screenHeight / 2) - (self.Height / 2)
        self.root.geometry('%dx%d+%d+%d' %
                           (self.Width, self.Height, left, top))

        # 文本区      域大小调整
        self.root.grid_rowconfigure(0, weight=1)
        self.root.grid_columnconfigure(0, weight=1)

        # Add controls (widget)
        self.TextArea.grid(sticky=N + E + S + W)

        # 增加新建配置
        self.FileMenu.add_command(label="新建", command=self.__newFile)

        # 增加打开配置
        self.FileMenu.add_command(label="打开", command=self.__openFile)

        # 增加保存配置
        self.FileMenu.add_command(label="保存", command=self.__saveFile)

        # 增加退出配置
        self.FileMenu.add_separator()
        self.FileMenu.add_command(label="退出", command=self.__quitApplication)
        # 菜单中设置文件按钮
        self.MenuBar.add_cascade(label="文件", menu=self.FileMenu)

        # 增加剪切功能
        self.EditMenu.add_command(label="剪切", command=self.__cut)

        # 增加复制功能
        self.EditMenu.add_command(label="复制", command=self.__copy)

        # 增加粘贴功能
        self.EditMenu.add_command(label="粘贴", command=self.__paste)

        # 菜单中设置编辑按钮
        self.MenuBar.add_cascade(label="编辑", menu=self.EditMenu)

        # 增加关于记事本选项
        self.HelpMenu.add_command(label="关于记事本", command=self.__showAbout)
        # 菜单中射者帮助按钮
        self.MenuBar.add_cascade(label="帮助", menu=self.HelpMenu)

        self.root.config(menu=self.MenuBar)

        self.ScrollBar.pack(side=RIGHT, fill=Y)

        # 滚动条根据内容进行调整
        self.ScrollBar.config(command=self.TextArea.yview)
        self.TextArea.config(yscrollcommand=self.ScrollBar.set)

    def __quitApplication(self):
        '''
        用于退出程序(关了就消失)
        '''
        self.root.destroy()

    def __showAbout(self):
        '''
        添加帮助菜单中的信息
        '''

        showinfo("关于记事本", "很简单不需要帮助吧")

    def __openFile(self):
        '''
        打开文件
        '''
        self.file = askopenfilename(defaultextension=".txt",
                                    filetypes=[("All Files", "*.*"),
                                               ("Text Documents", "*.txt")])

        if self.file == "":
            self.file = None
        else:

            self.root.title(os.path.basename(self.file))
            self.TextArea.delete(1.0, END)
            file = open(self.file, "r",encoding='utf-8')
            self.TextArea.insert(1.0, file.read())
            file.close()

    def __newFile(self):
        '''
        新文件:默认是一个未命名文件
        '''

        self.root.title("未命名文件")
        self.file = None
        self.TextArea.delete(1.0, END)

    def __saveFile(self):
        '''
        用于保存文件,不存在的文件进行新建,存在的文件在原文件基础上覆盖保存
        '''
        if self.file == None:
            self.file = asksaveasfilename(initialfile='Untitled.txt',
                                          defaultextension=".txt",
                                          filetypes=[("All Files", "*.*"),
                                                     ("Text Documents",
                                                      "*.txt")])

            if self.file == "":
                self.file = None
            else:

                file = open(self.file, "w",encoding='utf-8')
                file.write(self.TextArea.get(1.0, END))
                file.close()

                # 更改title名字为文件名
                self.root.title(os.path.basename(self.file))

        else:
            file = open(self.file, "w",encoding='utf-8')
            file.write(self.TextArea.get(1.0, END))
            file.close()

    # 添加功能项
    def __cut(self):
        self.TextArea.event_generate("<<Cut>>")

    def __copy(self):
        self.TextArea.event_generate("<<Copy>>")

    def __paste(self):
        self.TextArea.event_generate("<<Paste>>")

    def run(self):
        # 使用mainloop()使得窗口一直存在
        self.root.mainloop()


notepad = Notepad(width=600, height=400)
notepad.run()

运行效果:

点名系统

第一种实现代码

#简单点名系统
from tkinter import *
import random
data = ['杨晓军', '赵 杰', '张大海', '张飞龙', '田志刚', '程 峰',
       '段雅倩', '崔彬彬', '袁飞龙', '王志强', '王翔宇', '夏 文',
       '苏欣悦', '苏欣悦', '顾文博' ]
going = True
is_run = False
def lottery_roll(var1, var2):
    global going
    show_member = random.choice(data)
    var1.set(show_member)
    if going:
        window.after(50, lottery_roll, var1, var2)
    else:
        var2.set('恭喜 {} !!!'.format(show_member))
        going = True
    return

def lottery_start(var1, var2):
    global is_run
    if is_run:
        return
    is_run = True
    var2.set('幸运儿是你吗。。。')
    lottery_roll(var1, var2)
    
def lottery_end():
    global going, is_run
    if is_run:
        going = False
        is_run = False
        
if __name__ == '__main__':
    window = Tk()
    window.geometry('405x320+250+150')
    window.title('点名')
    bg_label = Label(window, width=70, height=24, bg='#ECf5FF')
    bg_label.place(anchor=NW, x=0, y=0)
    var1 = StringVar(value='即 将 开 始')
    show_label1 = Label(window, textvariable=var1, justify='left', anchor=CENTER, width=17, height=3, bg='#BFEFFF',
            font='楷体 -40 bold', foreground='black')
    show_label1.place(anchor=NW, x=21, y=20)
    var2 = StringVar(value='幸运儿是你吗。。。')
    show_label2 = Label(window, textvariable=var2, justify='left', anchor=CENTER, width=38, height=3, bg='#ECf5FF',
            font='楷体 -18 bold', foreground='red')
    show_label2.place(anchor=NW, x=21, y=240)
    button1 = Button(window, text='开始', command=lambda: lottery_start(var1, var2), width=14, height=2, bg='#A8A8A8',
           font='宋体 -18 bold')
    button1.place(anchor=NW, x=20, y=175)
    button2 = Button(window, text='结束', command=lambda: lottery_end(), width=14, height=2, bg='#A8A8A8',
           font='宋体 -18 bold')
    button2.place(anchor=NW, x=232, y=175)
    
    window.mainloop()

运行效果:

第二种实现代码

#点名程序
import random
import re
import time
import threading
from tkinter import *
from tkinter import messagebox
from tkinter.filedialog import askopenfilename


class APP:
    def __init__(self):
        self.root = Tk()
        self.running_flag=False #开始标志
        self.time_span=0.05 #名字显示间隔
        self.root.title('Point_name-V1.0')
        width = 680
        height = 350
        left = (self.root.winfo_screenwidth() - width) / 2
        top = (self.root.winfo_screenheight() - height) / 2
        self.root.geometry("%dx%d+%d+%d" % (width, height, left, top))
        self.root.resizable(0,0)
        self.create_widget()
        self.set_widget()
        self.place_widget()
        self.root.mainloop()


    def create_widget(self):
        self.label_show_name_var=StringVar()
        self.label_show_name=Label(self.root,textvariable=self.label_show_name_var,font=('Arial', 100,"bold"),foreground = '#1E90FF')
        self.btn_start=Button(self.root,text="开始",)
        self.btn_load_names=Button(self.root,text="加载名单",)
        self.lf1=LabelFrame(self.root,text="方式")
        self.radioBtn_var=IntVar()
        self.radioBtn_var.set(1)
        self.radioBtn_sequence=Radiobutton(self.lf1,text="顺序",variable=self.radioBtn_var, value=1)
        self.radioBtn_random=Radiobutton(self.lf1,text="随机",variable=self.radioBtn_var, value=2)
        self.label_show_name_num=Label(self.root,font=('Arial', 20),foreground = '#FF7F50')
        paned = PanedWindow(self.root)


    def set_widget(self):
        default_name_="是?"
        self.label_show_name_var.set(default_name_)
        self.label_show_name_adjust(default_name_)
        self.btn_start.config(command=lambda :self.thread_it(self.start_point_name))
        self.btn_load_names.config(command=self.load_names)
        init_names=self.load_names_txt("./names.txt")
        self.root.protocol('WM_DELETE_WINDOW',self.quit_window)
        self.root.bind('<Escape>',self.quit_window)
        if init_names:
            self.default_names=init_names   #1.文件存在但是无内容。2.文件不存在
            self.label_show_name_num.config(text=f"一共加载了{len(self.default_names)}个")
        else:
            self.btn_start.config(state=DISABLED)
            self.label_show_name_num.config(text=f"请选定名单!")


    def place_widget(self):
        self.lf1.place(x=300,y=160,width=250,height=50)
        self.radioBtn_sequence.place(x=20,y=0)
        self.radioBtn_random.place(x=150,y=0)
        self.btn_start.place(x=300,y=220,width=100,height=30)
        self.btn_load_names.place(x=450,y=220,width=100,height=30)
        #self._img.place(x=90, y=165, height=120, width=180)
        self.label_show_name_num.place(x=300,y=260)


    def label_show_name_adjust(self,the_name):
        if len (the_name)==1:
            self.label_show_name.place(x=280, y=10)
        elif len(the_name) == 2:
            self.label_show_name.place(x=180, y=10)
        elif len(the_name) == 3:
            self.label_show_name.place(x=120, y=10)
        elif len(the_name) == 4:
            self.label_show_name.place(x=80, y=10)
        else:
            self.label_show_name.place(x=0, y=10)


    def start_point_name(self):
        """
        启动之前进行判断,获取点名模式
        :return:
        """
        if len(self.default_names)==1:
            messagebox.showinfo("提示",'名单就一个,不用选了!')
            self.label_show_name_var.set(self.default_names[0])
            self.label_show_name_adjust(self.default_names[0])
            return
        if self.btn_start["text"]=="开始":
            self.btn_load_names.config(state=DISABLED)
            self.running_flag=True
            if isinstance(self.default_names,list):
                self.btn_start.config(text="就你了")
                if self.radioBtn_var.get()==1:
                    mode="sequence"
                elif self.radioBtn_var.get()==2:
                    mode="random"
                else:
                    pass
                self.thread_it(self.point_name_begin(mode))


            else:
                messagebox.showwarning("警告","请先选定名单!")
        else:
            self.running_flag=False
            self.btn_load_names.config(state=NORMAL)
            self.btn_start.config(text="开始")


    def point_name_begin(self,mode):
        """
        开始点名主函数
        :param mode:
        :return:
        """
        if mode == "sequence":
            if self.running_flag:
                self.always_ergodic()
        elif mode=="random":
            while True:
                    if self.running_flag:
                        random_choice_name=random.choice(self.default_names)
                        self.label_show_name_var.set(random_choice_name)
                        self.label_show_name_adjust(random_choice_name)
                        time.sleep(self.time_span)
                    else:
                        break


    def always_ergodic(self):
        """
        一直遍历此列表,使用死循环会造成线程阻塞
        :return:
        """
        for i in self.default_names:
            if self.running_flag:
                self.label_show_name_var.set(i)
                self.label_show_name_adjust(i)
                time.sleep(self.time_span)
                if i==self.default_names[-1]:
                    self.always_ergodic()
            else:
                break
            
    def load_names(self):
        """
        手动加载txt格式人名单
        :return:
        """
        filename = askopenfilename(
                filetypes = [('文本文件', '.TXT'), ],
                title = "选择一个文本文件",
            initialdir="./"
                )
        if filename:
            names=self.load_names_txt(filename)
            if names:
                self.default_names=names
                no_Chinese_name_num=len([n for n in names if not self.load_name_check(n)])
                if no_Chinese_name_num==0:
                    pass
                else:
                    messagebox.showwarning("请注意",f'导入名单有{no_Chinese_name_num}个不是中文名字')
                self.label_show_name_num.config(text=f"一共加载了{len(self.default_names)}个")
                self.btn_start.config(state=NORMAL)
            else:
                messagebox.showwarning("警告","导入失败,请检查!")


    def load_names_txt(self,txt_file):
        """
        读取txt格式的人名单
        :param txt_file:
        :return:
        """
        try:
            with open(txt_file,'r',encoding="utf-8")as f:
                names=[name.strip() for name in f.readlines()]
                if len(names)==0:
                    return False
                else:
                    return names
        except:
            return False


    def load_name_check(self,name):
        """
        对txt文本中的人名进行校验
        中文汉字->True
        非中文汉字->False
        :param name:
        :return:
        """
        regex = r'[\u4e00-\u9fa5]+'
        if re.match(regex,name):
            return True
        else:
            return False


    def thread_it(self,func,*args):
        t=threading.Thread(target=func,args=args)
        #t.setDaemon(True)
        t.start()


    def quit_window(self,*args):
        """
        程序退出触发此函数
        :param args:
        :return:
        """
        ret=messagebox.askyesno('退出','确定要退出?')
        if ret:
            self.root.destroy()


if __name__ == '__main__':
    a=APP()

人名文本文件如

 运行效果:

tkinter 的Canvas示例程序

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

画五角星源码如下:

#使用Tkinter Canvas画五角星
import tkinter as tk
import math

root = tk.Tk()

w = tk.Canvas(root, width = 200, height = 100, background = "red")
w.pack()

center_x = 100
center_y = 50
r = 50

points = [
#左上点(A)
center_x - int(r * math.sin(2 * math.pi / 5)),
center_y - int(r * math.cos(2 * math.pi / 5)),
#右上点(C)
center_x + int(r * math.sin(2 * math.pi / 5)),
center_y - int(r * math.cos(2 * math.pi / 5)),
#左下点(E)
center_x - int(r * math.sin(math.pi / 5)),
center_y + int(r * math.cos(math.pi / 5)),
#顶点(B)
center_x,
center_y - r,
#右下点(D)
center_x + int(r * math.sin(math.pi / 5)),
center_y + int(r * math.cos(math.pi / 5))
]

w.create_polygon(points, outline = "green", fill = "yellow") #fill 默认是 black

root.mainloop()

运行效果:

说明:

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

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

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

角(弧度)=弧长/半径

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

一个平角是 π 弧度。

即 180度=π弧度

由此可知:

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

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

画树源码如下:

# 使用Tkinter Canvas画树,每次画的不一样
import tkinter
import sys, random, math
 
class Point(object):
	def __init__(self, x, y):
		self.x = x
		self.y = y
 
	def __str__(self):
		return "<Point>: (%f, %f)" % (self.x, self.y)
 
class Branch(object):
	def __init__(self, bottom, top, branches, level = 0):
		self.bottom = bottom
		self.top = top
		self.level = level
		self.branches = branches
		self.children = []
 
	def __str__(self):
		s = "Top: %s, Bottom: %s, Children Count: %d" % \
			(self.top, self.bottom, len(self.children))
		return s
 
	def nextGen(self, n = -1, rnd = 1):
		if n <= 0: n = self.branches
		if rnd == 1:
			n = random.randint(n / 2, n * 2)
			if n <= 0: n = 1
		dx = self.top.x - self.bottom.x
		dy = self.top.y - self.bottom.y
		r = 0.20 + random.random() * 0.2
		if self.top.x == self.bottom.x:
			# 如果是一条竖线
			x = self.top.x
			y = dy * r + self.bottom.y
		elif self.top.y == self.bottom.y:
			# 如果是一条横线
			x = dx * r + self.bottom.x
			y = self.top.y
		else:
			x = dx * r
			y = x * dy / dx
			x += self.bottom.x
			y += self.bottom.y
		oldTop = self.top
		self.top = Point(x, y)
		a = math.pi / (2 * n)
		for i in range(n):
			a2 = -a * (n - 1) / 2 + a * i - math.pi
			a2 *= 0.9 + random.random() * 0.2
			self.children.append(self.mkNewBranch(self.top, oldTop, a2))
 
	def mkNewBranch(self, bottom, top, a):
		dx1 = top.x - bottom.x
		dy1 = top.y - bottom.y
		r = 0.9 + random.random() * 0.2
		c = math.sqrt(dx1 ** 2 + dy1 ** 2) * r
		if dx1 == 0:
			a2 = math.pi / 2
		else:
			a2 = math.atan(dy1 / dx1)
			if (a2 < 0 and bottom.y > top.y) \
				or (a2 > 0 and bottom.y < top.y) \
				:
				a2 += math.pi
		b = a2 - a
		dx2 = c * math.cos(b)
		dy2 = c * math.sin(b)
		newTop = Point(dx2 + bottom.x, dy2 + bottom.y)
		return Branch(bottom, newTop, self.branches, self.level + 1)
 
class Tree(object):
	def __init__(self, root, canvas, bottom, top, branches = 3, depth = 3):
		self.root = root
		self.canvas = canvas
		self.bottom = bottom
		self.top = top
		self.branches = branches
		self.depth = depth
		self.new()
 
	def gen(self, n = 1):
		for i in range(n):
			self.getLeaves()
			for node in self.leaves:
				node.nextGen()
		self.show()
 
	def new(self):
		self.leavesCount = 0
		self.branch = Branch(self.bottom, self.top, self.branches)
		self.gen(self.depth)
		print("leaves count: %d" % self.leavesCount)
 
	def chgDepth(self, d):
		self.depth += d
		if self.depth < 0: self.depth = 0
		if self.depth > 10: self.depth = 10
		self.new()
 
	def chgBranch(self, d):
		self.branches += d
		if self.branches < 1: self.branches = 1
		if self.branches > 10: self.branches = 10
		self.new()
 
	def getLeaves(self):
		self.leaves = []
		self.map(self.findLeaf)
 
	def findLeaf(self, node):
		if len(node.children) == 0:
			self.leaves.append(node)
 
	def show(self):
		for i in self.canvas.find_all():
			self.canvas.delete(i)
		self.map(self.drawNode)
		self.canvas.tag_raise("leaf")
 
	def exit(self, evt):
		sys.exit(0)
 
	def map(self, func = lambda node: node):
		# 遍历树
		children = [self.branch]
		while len(children) != 0:
			newChildren = []
			for node in children:
				func(node)
				newChildren.extend(node.children)
			children = newChildren
 
	def drawNode(self, node):
		self.line2(
#		self.canvas.create_line(
				node.bottom.x,
				node.bottom.y,
				node.top.x,
				node.top.y,
				fill = "#100",
				width = 1.5 ** (self.depth - node.level),
				tags = "branch level_%d" % node.level,
			)
 
		if len(node.children) == 0:
			# 画叶子
			self.leavesCount += 1
			self.canvas.create_oval(
					node.top.x - 3,
					node.top.y - 3,
					node.top.x + 3,
					node.top.y + 3,
					fill = "#090",
					tag = "leaf",
				)
 
		self.canvas.update()
 
	def line2(self, x0, y0, x1, y1, width = 1, fill = "#000", minDist = 10, tags = ""):
		dots = midDots(x0, y0, x1, y1, minDist)
		dots2 = []
		for i in range(len(dots) - 1):
			dots2.extend([dots[i].x,
				dots[i].y,
				dots[i + 1].x,
				dots[i + 1].y])
		self.canvas.create_line(
				dots2,
				fill = fill,
				width = width,
				smooth = True,
				tags = tags,
			)
 
def midDots(x0, y0, x1, y1, d):
	dots = []
	dx, dy, r = x1 - x0, y1 - y0, 0
	if dx != 0:
		r = float(dy) / dx
	c = math.sqrt(dx ** 2 + dy ** 2)
	n = int(c / d) + 1
	for i in range(n):
		if dx != 0:
			x = dx * i / n
			y = x * r
		else:
			x = dx
			y = dy * i / n
		if i > 0:
			x += d * (0.5 - random.random()) * 0.25
			y += d * (0.5 - random.random()) * 0.25
		x += x0
		y += y0
		dots.append(Point(x, y))
	dots.append(Point(x1, y1))
	return dots
 
if __name__ == "__main__":
	root = tkinter.Tk()
	root.title("Tree")
	gw, gh = 800, 600
	canvas = tkinter.Canvas(root,
			width = gw,
			height = gh,
		)
	canvas.pack()
	tree = Tree(root, canvas, Point(gw / 2, gh - 20), Point(gw / 2, gh * 0.2), \
		branches = 2, depth = 8)
	root.bind("n", lambda evt: tree.new())
	root.bind("=", lambda evt: tree.chgDepth(1))
	root.bind("+", lambda evt: tree.chgDepth(1))
	root.bind("-", lambda evt: tree.chgDepth(-1))
	root.bind("b", lambda evt: tree.chgBranch(1))
	root.bind("c", lambda evt: tree.chgBranch(-1))
	root.bind("q", tree.exit)
	root.mainloop()

运行效果:

 OK!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

学习&实践爱好者

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值