上一篇:Python tkinter(GUI编程)模块全解(上)_Python zzy的博客-CSDN博客

总目录:tkinter/README.md · Python-ZZY/CSDN-articles - Gitee.com

2 tkinter主模块

2.10 Checkbutton

Checkbutton即多选框,用户可以勾选或取消勾选。Checkbutton可以绑定一个variable,一般是BooleanVar。Checkbutton拥有Button的参数之外,还可以有一些别的参数。

参考资料:Python ---(三)Tkinter窗口组件:Checkbutton_近视的脚踏实地的博客-CSDN博客

Checkbutton(master=None, **kw)

参数作用
variable与Checkbutton选择相关的Variable
onvalue多选框选中时variable的值,默认为1
offvalue多选框未选中时variable的值,默认为0
selectcolor选择方框的颜色
selectimage选中时的图片(须指定image参数)
indicatoron是否显示为勾选框样式,默认为True

常用方法: 

方法作用
select()选中多选框
deselect()取消选中选框
toggle()切换选框的选中状态(反选选框)
invoke()调用Checkbutton的command(disabled无效)
flash()使Checkbutton闪烁几次(在normal和active几次切换)

创建Checkbutton

from tkinter import *

root = Tk()
root.geometry("200x200")

def tg():
    print("选中/取消选中")

cb = Checkbutton(root, text="toggle", command=tg)
cb.pack()

mainloop()

 

可以尝试点击Checkbutton,左边的方框就会打上勾或取消打勾。每次选择后都会打印"选中/取消选中"。

variable参数

from tkinter import *

root = Tk()
root.geometry("200x200")

def tg():
    print("当前状态为:", cb_var.get())

cb_var = BooleanVar()
cb = Checkbutton(root, text="toggle", command=tg, variable=cb_var)
cb.pack()

mainloop()

把多选按钮绑定到一个Var上,这样就可以获取Checkbutton的值。

indicatoron参数

Checkbutton有两种样式,一种是上面的勾选框样式,还可以设置为一个按钮盒的样式。默认是显示为勾选框的样式,如果把indicatoron设置为False也可以设置为按钮的样式。

from tkinter import *

root = Tk()
root.geometry("200x200")

cb = Checkbutton(root, text="toggle", indicatoron=False)
cb.pack()

mainloop()

 ​​​​​​​选中状态>>>

2.11 Radiobutton

与多选框相对应,Radiobutton是单选框。多个单选框可以绑定一个variable,这个variable的值是选中的单选框的值。

参考资料:Python ---(四)Tkinter窗口组件:Radiobutton_近视的脚踏实地的博客-CSDN博客

Radiobutton(master=None, cnf={}, **kw)

Radiobutton的参数和Checkbutton几乎完全一样,不同的是Radiobutton没有onvalue和offvalue这两个参数,而是由value这个参数代替。value参数的作用是:指定单选框选中时绑定的Var的值。

常用方法和Checkbutton介绍的几个基本一样,但是没有toggle方法。

创建Radiobutton

一般情况下Radiobutton会出现多个,而Checkbutton只出现一个。

from tkinter import *

root = Tk()
root.geometry("200x200")

var = StringVar()
var.set("A")

Radiobutton(root, text="A. Python", variable=var, value="A").pack()
Radiobutton(root, text="B. C", variable=var, value="B").pack()
Radiobutton(root, text="C. Java", variable=var, value="C").pack()

mainloop()

上面的三个单选框,都绑定了一个variable,还指定了一个value参数。当variable的值被设为单选框的value,那么这个选框将是选中的状态。当点击单选按钮,variable的值就被设为这个单选按钮的值。因为variable的值只能有一个,所以你只能在绑定该variable的单选按钮里面选中一个单选按钮。

2.12 Menu

Menu也就是菜单,菜单一般有两种,一种是窗口上的菜单,一种是弹出式菜单。

参考资料:Python ---(十三)Tkinter窗口组件:Menu_近视的脚踏实地的博客-CSDN博客 

Menu(master=None, cnf={}, **kw)

参数作用
tearoff是否允许用户分离菜单,默认为True
title分离菜单的标题
tearoffcommand用户分离菜单时执行的事件
postcommand菜单被打开时执行的事件

常用方法:

方法作用
add(itemType, **kw)添加一个菜单项,itemType是command, cascade, checkbutton, radiobutton, separator之一
insert(index, itemType, **kw)在index位置插入菜单项
add_command(**kw)添加命令菜单项
add_cascade(**kw)添加分层菜单项
add_checkbutton(**kw)添加多选框菜单项
add_radiobutton(**kw)添加单选框菜单项
add_separator(**kw)添加菜单分割线
delete(index1, index2=None)删除位于index1(到index2之间)的菜单项
entrycget(index, option)获取位于index菜单项的option值
entryconfig(index, **kw)更改位于index菜单项的参数的值
post(x, y)在(x, y)位置弹出菜单
unpost()取消弹出菜单
invoke(index)执行位于index菜单项的command,如果菜单项是单选框或多选框,则选中它们。
type(index)返回位于index菜单项的类型,是command, cascade, checkbutton, radiobutton, separator之一

创建窗口菜单

from tkinter import *

root = Tk()

menubar = Menu(root)
root.config(menu=menubar) #把菜单绑定root

mainloop()

这样,我们就成功创建出一个菜单,并且绑定了显示的窗口。不过这样的菜单什么都没有,接下来我们要在菜单中添加菜单项。

add方法

add方法可以添加菜单项。第一个参数itemType指定菜单项的类型,马上会介绍到。add(itemType)也可以被add_itemType()所替换。你还可以提供如下参数**kw(activebackground, activeforeground, bg, fg由于之前的组件介绍过,这里不介绍了):

参数作用
accelerator显示菜单的补充说明标签
label菜单项显示的标签
font标签的字体
bitmap菜单项显示的位图
image菜单项显示的图片
compound图片显示于标签的方位
state菜单项的状态
underline在标签的第几个索引的字符处画下划线,用来绑定Alt快捷键
columnbreak从此菜单项开始另起一列显示
hidemargin菜单项长度适应label长度,默认为True
command点击菜单项时执行的回调函数
menu绑定的分层子菜单(add_cascade)
selectcolor单选或多选按钮菜单的selectcolor
selectimage单选或多选按钮菜单的selectimage
value单选按钮菜单的value
variable单选或多选按钮菜单绑定的variable
onvalue多选按钮菜单的onvalue
offvalue多选按钮菜单的offvalue

add_command方法

menu.add_command(**kw)相当于menu.add("command", **kw)

add_command方法在菜单中添加一个命令菜单项。它有如下参数:

下面, 让我们给菜单创建一个命令,点击时打印一段文字。

from tkinter import *

root = Tk()

menubar = Menu(root)
root.config(menu=menubar)

menubar.add_command(label="Click", command=lambda:print("Hello"))

mainloop()

 点击菜单的Click按钮,就会执行print("Hello")

add_cascade方法

菜单可以有层级之分。最上层的菜单就是绑定了root的菜单,下面可以有一些子菜单,继承绑定窗口的父菜单,实现分层效果。

from tkinter import *

root = Tk()

menubar = Menu(root)
root.config(menu=menubar)

filemenu = Menu(menubar)
filemenu.add_command(label="Save", command=lambda:print("OK!"))

menubar.add_cascade(label="File", menu=filemenu)

mainloop()

首先需要创建一个菜单,继承绑定窗口的菜单menubar,然后添加这个子菜单的项目。接着,需要把这个子菜单添加到父菜单中,需要提供一个menu参数指定添加的子菜单。

子菜单也可以有子菜单。如下示例:

from tkinter import *

root = Tk()

menubar = Menu(root)
root.config(menu=menubar)

filemenu = Menu(menubar)
savemenu = Menu(filemenu)

menubar.add_cascade(label="File", menu=filemenu)
filemenu.add_cascade(label="Save", menu=savemenu)

savemenu.add_command(label="save1")
savemenu.add_command(label="save2")

mainloop()

分离菜单

当我们点击菜单上的虚线,可以跳出一个工具窗口,这就是分离菜单。

分离菜单功能有很多问题,并且大多数应用都没有这个功能,建议大家把它禁用。如果想要禁用分离菜单功能,可以在定义Menu的时候把tearoff参数设置为False。

Menu(menubar, tearoff=False)

accelerator参数

accelerator可以对菜单项进行补充。一般这个参数指定的是一个加速键(快捷键)名称,比如Ctrl+N这种。

不过,即使指定了accelerator参数也没有真正的绑定快捷键,需要使用bind来进行绑定。

from tkinter import *

root = Tk()

menu = Menu(root)
root.config(menu=menu)

def new(event=None):
    print("New file")

fm = Menu(menu)
menu.add_cascade(label="File", menu=fm)    
fm.add_command(label="New", accelerator="Ctrl+N", command=new)

root.bind("<Control-n>", new)

mainloop()

按下Ctrl-N或点击菜单,执行new函数。

underline参数

underline参数指定一个索引,绑定一个Alt快捷键。指定了underline的菜单项可以快捷触发。触发方式是:先按下Alt,然后下划线会显示出来,再按下菜单项标签下划线上的字符即可执行这个菜单项。

from tkinter import *

root = Tk()

menu = Menu(root)
root.config(menu=menu)

def new():
    print("New file")

menu.add_command(label="New", underline=0, command=new)

mainloop()

激活并按下Alt>>>按下n键>>>

add_separator方法

add_separator方法可以给菜单添加一条分割线。

from tkinter import *

root = Tk()

menu = Menu(root)
root.config(menu=menu)

fm = Menu(menu, tearoff=False)
menu.add_cascade(label="File", menu=fm)

fm.add_command(label="New File")
fm.add_command(label="Save")
fm.add_separator() #添加分割线
fm.add_command(label="Exit")

mainloop()

分割线常用于分隔不同类的菜单项,比如编辑器中,打开文件、保存文件、退出几个文件操作类别都用分隔线隔开。

弹出菜单

如果要弹出菜单,那么不需要绑定到窗口(root.config(menu=menu)),当然绑定也没关系。弹出窗口需要使用post方法,需要提供x, y两个参数。但这两个参数必须是屏幕上的位置,x_root和y_root。

下面的示例:当右击鼠标时在鼠标处弹出菜单。

from tkinter import *

root = Tk()

menu = Menu(root)

def show(event):
    menu.post(event.x_root, event.y_root)
    
def cp():
    print("Copy")

menu.add_command(label="Copy", underline=0, command=cp)

root.bind("<Button-3>", show)

mainloop()

2.13 Menubutton

Menubutton是一个Button的样式,可以绑定一个菜单,点击后弹出菜单。这个组件比较老,现在可以用Menu实现Menubutton的功能了,不过Menubutton在某些情况下要更简单好用一些。

参考资料:Python ---(十四)Tkinter窗口组件:Menubutton_近视的脚踏实地的博客-CSDN博客

Menubutton(master=None, cnf={}, **kw)

参数和Button基本一样,但是没有command,还有一些其他的参数。

参数作用
menu绑定一个菜单
direction位于按钮的菜单弹出方位,有"left"(左),"right"(右),"above"(上),"below"(下),默认是below

创建Menubutton

from tkinter import *

root = Tk()

mb = Menubutton(root, text="弹出")
mb.pack()

menu = Menu(mb)
def cp():
    print("Copy")
menu.add_command(label="Copy", command=cp)

mb.config(menu=menu)

mainloop()

 点击按钮>>>

需要注意的是,继承关系不要弄错。绑定到Menubutton的菜单的master必须是该Menubutton,否则菜单跳不出来。 

2.14 组件基类

现在,你已经学习了一部分组件。在学习更多组件之前,先要详细介绍一下组件的基类。在tkinter(Python)中,大多数组件都有共用的方法,这是因为它们都继承共同的类。如果你想自己了解一下继承关系,可以看Lib/tkinter/__init__.py。

在这些类中,有一些方法的参数默认为None,这意味着如果为None可以返回当前参数设定的值。

内容过多,整理到此篇文章中:Python tkinter Misc类+Wm类详解

2.15 Listbox

Listbox是列表框,里面可以添加一些文本项目,在列表框中竖向显示。可以让用户选择它们。

参考资料:Python ---(七)Tkinter窗口组件:Listbox_近视的脚踏实地的博客-CSDN博客

Listbox(master=None, cnf={}, **kw)

参数作用
width组件的宽,单位是字母的平均宽度
height组件的高,单位是项目的数量
listvariable设置与Listbox关联的variable,是一个StringVar,不同项目用空格隔开(不推荐)。
selectmode组件的选择模式,可以是"browse"(单选,可用鼠标拖动或方向键改变选择,默认模式), "single"(单选,只能鼠标点击改变选择), "extended"(多选,需拖动鼠标或结合Shift或Ctrl键才能多选), "multiple"(多选,点击多个选项就能多次选择)
xscrollcommandx方向滚动条(下一节介绍)
yscrollcommandy方向滚动条(下一节介绍)

常用方法: 

方法作用
activate(index)将位于index位置的项目激活(在下方画下划线)
bbox(index)返回位于index位置的项目的边框尺寸信息,返回值是(xoffset, yoffset, width, height),表示左上角的偏移和宽高。
curselection()返回被选中选项的索引的元组
delete(first, last=None)删除first到last的选项
insert(index, *elements)在列表框中添加单或多个选项
get(first, last=None)如果不指定last,返回first位置的选项;如果指定last,返回两个位置之间的选项
index(index)返回index位置的数字索引,如index("end")返回最后一个选项的数字索引
itemconfig(index, **option)设置index位置的选项的参数,可以设置的有:bg(background), fg(foreground), selectbackground, selectforeground
itemcget(index, option)返回index位置的选项的option参数选项值
see(index)

滚动列表框,使位于index位置的项目可见

selection_set(first, last=None)选中first到last的项目
selection_clear(first, last=None)取消选中first到last的项目
selection_includes(index)返回位于index位置的项目的选中状态,1表示选中,0表示未选中
selection_anchor(index)在index位置的项目设置锚点(类似于一个标记,可通过特殊索引"anchor"访问锚点)
size()返回列表框中选项数量

创建Listbox

from tkinter import *

root = Tk()

lb = Listbox(root)
lb.pack()

mainloop()

 

这样就创建了一个Listbox,不过里面什么项目也没有。

虚拟事件<<ListboxSelect>>

Listbox中有项目选中时会产生一个虚拟事件<<ListboxSelect>>,可以被bind捕捉到。

from tkinter import *

root = Tk()

lb = Listbox(root)
lb.pack()

for n in range(20):
    lb.insert("end", n)

def select(event):
    print(lb.curselection()) #打印选中项的索引
    
lb.bind("<<ListboxSelect>>", select)

mainloop()

 

如图示,选中项目时会打印选中的项目索引。

see方法

see方法可以滚动列表框,使位于index位置的项目可见。

from tkinter import *

root = Tk()

lb = Listbox(root)
lb.pack()

for n in range(20):
    lb.insert("end", n)

Button(root, text="see end", command=lambda:lb.see("end")).pack()

mainloop()

 点击按钮>>>

点击按钮后列表框滚动到了end位置。

实例:列表编辑器

下面是一个实例,用户可以添加或删除列表框中的内容。

from tkinter import *

root = Tk()

def add():
    lb.insert("end", entry.get())

def remove():
    try:
        lb.delete(lb.curselection()[0])
    except: #如果没有选中列表框内容会报错
        pass
    
lb = Listbox(root)
lb.pack()

entry = Entry(root)
entry.pack()

Button(root, text="Add", command=add).pack()
Button(root, text="Remove", command=remove).pack()

root.mainloop()

2.16 Scrollbar

Scrollbar也就是滚动条,可以起到滚动组件,使用户能够完整看到的效果。但也有部分组件不支持滚动条。

参考资料:Python ---(八)Tkinter窗口组件:Scrollbar_近视的脚踏实地的博客-CSDN博客

Scrollbar(master=None, cnf={}, **kw)

滚动条没有常规的参数height,只有width。

参数作用
activerelief滚动条的滑块被激活时的relief样式
command滚动条被滚动更新时执行的回调函数,会传递给函数几个参数,可以直接传递给需滚动组件的xview和yview方法。
elementborderwidth滚动条和箭头的边框宽度
jump是否当鼠标松开长按滚动条才调用command,默认为False(此参数似乎无效)。
orient滚动条的方向,可以是"horizontal"(水平,横向), "vertical"(垂直,竖向),默认是vertical
repeatdelay鼠标长按在滚动条上时,持续触发滚动条的准备时长,默认为300(ms)
repeatinterval持续触发滚动条的间隔,默认为100(ms)

常用方法:

方法作用
get()返回滑块的位置,是一个元组,包含滑块左边或上边的位置,和滑块右边或下边的位置,都是0.0 ~ 1.0之间的浮点数,代表占整个滚动条的比例
set(*args)设置滑块的位置,需提供两个参数,分别是滑块左边或上边的位置,和滑块右边或下边的位置,都是0.0 ~ 1.0之间的浮点数,代表占整个滚动条的比例

创建Scrollbar

from tkinter import *

root = Tk()

sb = Scrollbar(root)
sb.pack(side="right", fill="y")

mainloop()

这段代码在屏幕右侧绘制了一个滚动条。这个滚动条没有绑定任何可滚动的组件,所以没有任何用处。在映射滚动条时,通常会加入fill="y"这个参数,让滚动条完全伸展开,方便拖拽(在水平滚动条中是fill="x",grid布局则要指定sticky)。

XView和YView类

在tkinter中有XView类和YView类,所有支持滚动条的组件继承它们。支持x方向滚动条则继承XView,支持y方向滚动条则继承YView。比如Listbox组件同时支持x,y方向滚动条,而Entry组件只支持x方向的滚动条。

下面介绍一下XView的方法(YView的方法和XView相比,只是方法名字中的xview改为了yview)。

方法作用
xview(*args)在x方向滚动组件。需提供几个参数,第一个是滚动类型,可以是"moveto"(滚动到), "scroll"(向右或向下滚动)。如果滚动类型是moveto,则还需要提供一个参数表示滚动到的位置,是0.0 ~ 1.0之间的浮点数。如果滚动类型是scroll,则还需要提供滚动的数量,和滚动单位。滚动单位可以是"units"(按滚动单位滚动), "pages"(按页面滚动)。
xview_moveto(fraction)和xview(“moveto”, fraction) 一样
xview_scroll(number, what)和xview("scroll", number, what)一样

支持滚动条的组件,本身有xscrollcommand和yscrollcommand两个参数,可以设置为滚动条的set方法,滚动条拖拽的时候会让滚动条的位置改变。如果不设置这两个参数,那么滚动条位置拖拽后会返回原位。

同时还要设置滚动条的command参数为需滚动组件的xview或yview方法,因为command在滚动条拖拽时调用,会将滚动信息传递给command,使组件滚动。 

orient参数

orient参数指定滚动条的朝向。部分组件也有这个方法,都有两个可选值:"horizontal"(水平,横向), "vertical"(垂直,竖向)。

下面是这两个orient形态的滚动条:

horizontal
vertical

滚动条绑定组件

Listbox继承XView和YView,支持滚动条,下面就以Listbox为例示范一下滚动条的绑定。

from tkinter import *

root = Tk()

sb = Scrollbar(root)
sb.pack(side="right", fill="y")

lb = Listbox(root, yscrollcommand=sb.set)
lb.pack(side="left", fill="both")
for i in range(100):
    lb.insert("end", i)

sb.config(command=lb.yview)

mainloop()

运行效果:

2.17 Message

Message是一个比较少用的组件,功能基本可以被Label替代。Message组件可以自动换行,指定Label组件的wraplength也可以达到这个功能,不过Message可能更加简便一些。

参考资料:Python ---(十六)Tkinter窗口组件:Message_近视的脚踏实地的博客-CSDN博客

Message(master=None, cnf={}, **kw)

参数和Label基本一样,但有一个参数aspect,表示组件达到多少像素换行。

2.18 Scale

Scale也就是标尺组件。用户可以通过拖拽滑块,设定一个数值。

参考资料:Tkinter 组件详解(十):Scale_来自江南的你的博客-CSDN博客

Scale(master=None, cnf={}, **kw)

参数作用
length设置Scale的长度
width设置Scale的宽度
label在Scale上显示一个文字标签
commandScale数值改变时执行的回调函数,会传递给该函数当前Scale的值
from_设置Scale的最小值,默认是0
to设置Scale的最大值,默认是100
digits刻度数值最多显示的数字位数
orient设置Scale的朝向,可以是"horizontal"(水平)或"vertical"(垂直)
resolution设置滑块拖动的步长(也就是拖动一次滑块,滑块移动的数值长度),默认为1
showvalue设置是否在滑块旁边显示当前的数值
sliderlength设置滑块的长度
sliderrelief设置滑块的样式,默认是"raised"
tickinterval设置显示的刻度,默认不显示
troughcolor设置凹槽的颜色
variable设置与Scale值相关联的variable

常用方法:

方法作用
get()返回Scale的值
set(value)设置Scale的值

创建Scale

from tkinter import *

root = Tk()

scale = Scale(root)
scale.pack()

mainloop()

用户可以拖动滑块,设定滑块的值。

from_和to参数

设置这两个参数可以设定拖拽的最小和最大值。

from tkinter import *

root = Tk()

scale = Scale(root, from_=10, to=30)
scale.pack()

mainloop()

 

滑块只能从10拖动到30。

tickinterval参数

tickinterval参数使Scale可以添加刻度数值。如from_=0, to=100, tickinterval=10的Scale:

垂直的Scale的刻度显示在左边,水平的Scale则显示在下面。

label参数

label参数可以显示在Scale旁边显示一个标签。例如:label="拖动滑块":

垂直的Scale的标签在右边,水平的Scale则在显示上面。 

resolution参数

resolution参数设置Scale的步长,默认为1。如resolution=0.1, from_=0, to=100,那么你可以在0到100之间拖动滑块,每次可拖动0.1个数值。每次拖动的数值都是0.1,不能小于这个数。

实例:颜色调节器

下面是一个简单的小程序,用户可以拖拽Scale滑块控制背景的颜色。

from tkinter import *

root = Tk()

def change(x):
    color = "#%02x%02x%02x"%(r.get(),
                             g.get(),
                             b.get()) #将普通的RGB色彩元组转换成16进制的形式
    for w in root.winfo_children(): #设置Scale的背景色
        w.config(bg=color)
    
r = Scale(root, from_=0, to=255, fg="red", command=change)
r.pack(side="left")

g = Scale(root, from_=0, to=255, fg="green", command=change)
g.pack(side="left")

b = Scale(root, from_=0, to=255, fg="blue", command=change)
b.pack(side="left")

mainloop()

2.19 Spinbox

Spinbox和Entry组件很类似,可以算是Entry组件的一个变形。Spinbox拥有Entry组件的所有参数。不同的是,Spinbox的侧面多了一个上箭头和一个下箭头,可以调节Spinbox的值。当然用户也可以在里面输入

参考资料:Python ---(十七)Tkinter窗口组件:Spinbox_近视的脚踏实地的博客-CSDN博客

Spinbox(master=None, cnf={}, **kw) 

下面是Spinbox比Entry多的参数选项。

参数作用
buttonbackgroundSpinbox调节箭头的背景颜色
buttoncursor鼠标在调节箭头上方的样式
buttondownrelief下调节箭头的relief样式
buttonuprelief上调节箭头的relief样式
command点击调节箭头时执行的回调函数
from_设置调节箭头调节的最小数值
to设置调节箭头调节的最大数值
format设置调节箭头调整数值的数值格式,使用%格式化,如:"%4.4f"
increment调节箭头调节数值的步长
values设置调节箭头可调节值的元组,和from_, to只能指定一边
wrap可调节值是否可以循环调节,比如wrap=True, values=("0", "1", "2"),然后一直点击下箭头,那么Spinbox的值依次变成"0", "1", "2", "0", "1", "2"的循环。如果wrap=False,那么无法循环调节,调节到"2"点下箭头就无效了。默认wrap=False

常用方法同Entry。 

创建Spinbox

from tkinter import *

root = Tk()

spinbox = Spinbox(root)
spinbox.pack()

mainloop()

由于没有指定from_, to或values,所以箭头按了也无效。

from_, to和increment参数

如设置from_=0, to=10,那么点击上下箭头的时候,文本框里面的内容会在0到10之间调节。但用户仍然可以在Spinbox中输入任何内容。

increment参数指定点击箭头的步长,类似于Scale组件的resolution参数选项。

values参数

values参数和上面的from_, to, increment只能指定一边。values参数传递一个元组之类的序列。比如values=("Python", "C", "Java", "Tcl"),那么点击上下箭头的时候,文本框中的内容会在元组里面切换。

readonly状态

Spinbox可以设置为readonly状态,和Entry一样。设置为readonly的时候,文本框中不能输入,但是仍可以通过按上下箭头调节Spinbox的值。

2.20 OptionMenu

OptionMenu是选项菜单,用户可以下拉一个选择菜单,指定OptionMenu的值。

参考资料:Python ---(十五)Tkinter窗口组件:OptionMenu_近视的脚踏实地的博客-CSDN博客

OptionMenu(master, variable, value, *values, **kwargs)

这个组件比较特殊,它继承Menubutton类,有一些必选参数。比如master参数在其他的组件是可选参数,如果不指定master将会自动继承主Tk窗口。而OptionMenu必选master, variable参数。

参数作用
master指定父容器
variable指定绑定的variable,设为选项菜单的值
value选项菜单的初始选择值,没什么用,仍需要设置variable的值
*values选项菜单的可选项(不需要再加上value,初始选择值会自动添加到可选项中)
**kwargsOptionMenu继承Menubutton类,在此处指定Menubutton的**kw选项值

创建OptionMenu

from tkinter import *

root = Tk()

var = StringVar()
var.set("Python") #设置OptionMenu的值

m = OptionMenu(root, var, "Python", "C", "Java")
m.pack()

mainloop()

此处创建了一个选项菜单,可以有3个可选项。获取OptionMenu的值,只需要执行绑定的variable的get方法。

2.21 PanedWindow

PanedWindow组件是Tk8.4新增的组件,类似于Frame。不同的是,PanedWindow组件允许用户调节子组件的布局。每个添加到PanedWindow的子组件作为一个窗格管理。

参考资料:Python ---(十八)Tkinter窗口组件:PanedWindow_近视的脚踏实地的博客-CSDN博客

PanedWindow(master=None, cnf={}, **kw)

参数作用
showhandle是否显示调整窗格布局的手柄,是一个正方形
orient窗格的分布朝向,可以是"horizontal"(水平), "vertical"(垂直)
sashpad分割线和组件的间距
sashrelief分割线的relief样式,默认是"flat"
sashwidth分割线的宽度
handlepad手柄在分割线上的位置,默认是8(像素)
handlesize手柄的边长,默认是8(像素)
opaqueresize窗格尺寸是否随鼠标拖拽而改变,默认为True,如果设为False,只有释放鼠标窗格尺寸才会改变

 常用方法:

方法作用
add(child, **options)将child组件添加到PanedWindow中,option选项稍后介绍
forget(child)从PanedWindow中移除child组件
panecget(child, option)返回child组件option选项的值
paneconfig(child, **options)设置child组件option选项的值
panes()返回子组件的列表

创建PanedWindow

from tkinter import *

root = Tk()

w = PanedWindow(root, orient="horizontal")
w.pack(fill="both", expand=True)

mainloop()

这段代码创建了一个空的水平方向PanedWindow,需要在里面添加组件才能看出来。 

add方法

add方法用于在PanedWindow中添加组件。如果是水平的PanedWindow,添加的组件都按水平方向排放。如果是垂直PanedWindow,添加的组件按竖直方向排放。 

add(child, **options)

options可以是这些参数选项:

参数作用
after将child组件排放到after组件的后面
before将child组件排放到before组件的前面
width排放时child的宽度
height排放时child的高度
minsize窗格长度的最小值(在水平PanedWindow中是窗格的宽,垂直则是窗格的高)
padx组件的x方向间距
pady组件的y方向间距
sticky组件位于窗格的位置,和grid布局的sticky参数作用一样
from tkinter import *

root = Tk()

w = PanedWindow(root, orient="horizontal")
w.pack(fill="both", expand=True)

b = Button(w, text="1b")
w.add(b)

b = Button(w, text="2b")
w.add(b)

mainloop()

代码生成了两个按钮,中间有一个细小的间隔,是窗格之间的分割线。因为分割线默认是"flat",所以是看不见的, 如可见需设置sashrelief参数。可以拖拽这个分割线,来调整窗格之间的大小。

showhandle参数

showhandle参数如果设置为True,将会在分割线的位置添加一个手柄。用户可以拖拽这个手柄,这样可以方便地调整窗格的大小。

手柄是一个正方形。

2.22 Text

Text是多行文本输入框,和Entry不同的是,Entry只有单行。并且Text里面不仅能够插入文本,还可以插入图片、组件等,还可以有标记功能,对于特殊的内容改变颜色。

参考资料:Python ---(十)Tkinter窗口组件:Text_近视的脚踏实地的博客-CSDN博客

Text(master=None, cnf={}, **kw)

Text的参数有一大部分和Entry的参数相同,如光标、激活、颜色一类的参数。

参数作用
widthText的宽,以字符数量为单位
heightText的高,以行数为单位
undo设置是否开启撤销功能,默认为False;开启撤销功能时,用户可以按下Ctrl+Z进行撤销操作
autoseparators设置是否自动插入撤销分隔,默认为True
maxundo允许撤销的数量,设置为-1代表无限次
padx设置内容与边框的x方向间距
pady设置内容与边框的y方向间距
wrap设置Text的换行方式,可以是"none"(不自动换行), "char"(按字符换行), "word"(按单词换行)
xscrollcommandx方向滚动
yscrollcommandy方向滚动

常用方法:

方法作用
bbox(index)返回位于index字符的边界框位置(x, y, width, height),需先调用update方法
delete(start, end=None)删除从start到end的内容
insert(index, text, *tag)在index位置插入text,并标记为*tag
dump(index1, index2=None, command=None, **kw)以列表形式返回Text的插入内容
get(index1, index2=None)返回index到index2的文本内容(注:不是插入内容,Text里面不仅可以插入文本)
see(index)滚动Text,使位于index的文本可见
edit_redo()重做,需设置undo=True
edit_undo()撤销,需设置undo=True
edit_reset()清空文本操作记录
edit_separator()插入撤销分隔
index(index)将index的位置以line.column返回
replace(index1, index2, chars, *args)将index1到index2的内容替换为chars字符串,*args设定tag
search(pattern, index, stopindex=None, forwards=None, backwards=None, exact=None, regexp=None, nocase=None, count=None)在index到stopindex的文本之间搜索pattern字符串

除了这些方法外,还有一些tag, mark, image之类的方法,放在后面讲。

wrap参数

wrap参数设置Text的换行方式,可以是"none"(不自动换行), "char"(按字符换行), "word"(按单词换行)。

设置为"none"时,如果字符数量超过Text的宽,不会进行换行,Text会进行滚动,以适应光标位置。

设置为"char"时,字符数量超过Text的宽,会换到下一行。

设置为"word"时,字符同样会换行,但是换到下一行的是一个完整的单词。

创建Text

from tkinter import *

root = Tk()

text = Text(root)
text.pack()

mainloop()

上面的代码创建一个Text,可以在Text里面输入内容。 

撤销和重做

用户可以在Text中进行撤销和重做操作。开启撤销和重做操作,首先要设置undo=True,然后用户可以按Ctrl+Z来撤销。

text = Text(root, undo=True)

edit_undo和edit_redo方法用于撤销和重做,edit_undo方法执行一次撤销操作,edit_redo方法执行一次重做操作。如果没有内容可撤销或重做会报错。如下示例:

from tkinter import *

root = Tk()

text = Text(root, undo=1)
text.pack()

Button(root, text="Undo", command=text.edit_undo).pack()
Button(root, text="Redo", command=text.edit_redo).pack()

mainloop()

 

但是测试的时候,会发现:每当换行或粘贴文本的时候,才会认为这是进行了一次文本操作。撤销的时候,没能按照输入字符进行撤销。这是因为Text设置了自动加入撤销分隔,自动插入的方式和我们的期望不同。要实现按字符撤销,需要手动加入撤销分隔。

首先需要设置自动加入撤销分隔为False,也就是autoseparators=False。然后绑定<Key>,每当输入一个字符调用edit_separator()方法进行手动撤销分隔。如下示例:

from tkinter import *

root = Tk()

text = Text(root, undo=True, autoseparators=False)
text.pack()
text.bind("<Key>", lambda e: text.edit_separator())

Button(root, text="Undo", command=text.edit_undo).pack()
Button(root, text="Redo", command=text.edit_redo).pack()

mainloop()

运行后,发现成功按照字符撤销。 

索引

Text指定字符位置的索引比较特殊,下面介绍一下Text的索引。

  • 表示为line.column的格式:line是行数,column是列数,行数和列数之间用一个"."隔开,可以是一个浮点数或字符串。比如:第一行,第一个字符表示为"1.0"或1.0;第三行,第四个字符表示为"3.3"或3.3。行数的索引以1为开始,列数的索引以0为开始。
  • current:表示离鼠标上一次点击位置最近的一个字符。
  • insert:表示光标位置。
  • end:表示结尾的位置。
  • 表示为line.end的格式:line是行数,end代表结尾,是一个字符串。表示第line行的最后一个字符。比如:第二行最后一个字符表示为"2.end"。
  • 表示为mark的名称:mark下文介绍,如果写作mark的名称可以表示mark的位置。上面的current和insert是两个预定义的mark。
  • 表示为tagName.first/last:tag下文介绍,如果写作tag的名称加上.first表示tag的第一个字符之前,"tagName.last"表示tagName的最后一个字符之后。
  • sel.first/last:sel是一个特殊的tag,"sel.first"代表选中内容的开头字符之前,"sel.last"代表选中内容的结尾字符之后。
  • 表示为window或image的对象字符串:如果插入了某个组件或图像,可以表示为它们的字符串形式(执行str)来表示它们的位置。
  • 表示为@x, y:表示离(x, y)窗口位置最接近的字符。比如"@100, 100"表示最接近窗口位置(100, 100)的字符。
  • 位置+表达式字符串:可以在上述的索引后面加上一些特殊的表达式来指定位置,表达式的规范如下。
    表达式作用示例
    "+ %d chars"%count索引往后移动count个字符"1.0+ 2 chars"为"1.2"位置
    "- %d chars"%count索引往前移动count个字符"1.2- 2 chars"为"1.0"位置
    "+ %d lines"%count索引向后移动count个字符"1.0+ 2lines"为"3.0"位置
    "- %d lines"%count索引向前移动count个字符"3.0- 2lines"为"1.0"位置
    " linestart"索引移动到这一行的起始处,表达式前需用空格隔开"1.5 linestart"为"1.0"位置
    " lineend"索引移动到这一行的末尾处,表达式前需用空格隔开"1.5 lineend"为"1.end"位置
    " wordstart"索引移动到这个单词(非空白字符组成的整体)的起始处,表达式前需用空格隔开如Text内容为"python tkinter",那么"1.4 wordstart"为"1.0"位置
    " wordend"索引移动到这个单词(非空白字符组成的整体)的末尾处,表达式前需用空格隔开如Text内容为"python tkinter",那么"1.4 wordend"为"1.6"位置
    此外,如果不会弄混,表达式还可以进行简写。比如:"insert + 3 chars"写成"insert+3c"是没有问题的。 

如想要根据一个特殊的索引,比如"insert", "@1, 2 + 2l"来获取准确的line.column式索引,可以使用index方法。

Text.index(index)

此方法返回index的line.column形式索引。

插入文字

insert方法可以在Text中插入文字。

insert(index, text, *tag) 

在index位置插入text内容。*tag是文本的tag标记,把这一串文字标记为tag, 后面会介绍tag。

需要注意的是,insert方法只能插入文字,而delete方法不仅可以删除文字,还可以删除其他的组件或图片。

插入图片

Text可以使用image_create方法插入图片。

Text.image_create(index, cnf={}, **kw)

index是插入的索引位置,**kw参数可以是:

参数作用
align设置图像的对齐方式,可以是"top", "center", "bottom"或"baseline"
image设置插入的图像,是一个PhotoImage或BitmapImage
name图像的名称,一般不需要指定
padx图像的x方向间距
pady图像的y方向间距

 示例如下:

from tkinter import *

root = Tk()

image = PhotoImage(file="monster.gif")

text = Text(root)
text.pack()
text.insert("end", "插入图片")
text.image_create("end", image=image)

mainloop()

 

与图片相关的方法还有如下:

image_cget(index, option)

返回index位置的图像的option选项值

image_configure(index, cnf={}, **kw)

设置index位置的图像的选项值。

image_names()

返回嵌入Text的所有图像名称。

插入组件

window_create方法可以在Text中插入组件。

window_create(index, cnf={}, **kw)

**kw参数可以是:

参数作用
align设置组件的对齐方式,可以是"top"(上), "center"(中), "bottom"(下)或"baseline"(基线)
create传递一个回调函数,函数须返回一个Text的子组件用于插入Text
window设置插入的组件,必须是Text的子组件,window和create参数指定一个即可
stretch当行高大于组件高度时,是否延伸组件的高,默认为False
padx组件的x方向间距
pady组件的y方向间距

下面两个示例,效果都是一样的。 

from tkinter import *

root = Tk()

text = Text(root)
text.pack()

b = Button(text, text="MyButton")
text.window_create("end", window=b)

mainloop()
from tkinter import *

root = Tk()

text = Text(root)
text.pack()

def create_button():
    b = Button(text, text="MyButton")
    return b

text.window_create("end", create=create_button)

mainloop()

  

Mark(标记)

在Text中,可以记录一个索引的位置,在此处做一个标记,也就是mark。mark标记的位置可以在索引中使用。有两个事先定义好的mark,即"current"和"insert",参见上文。需要注意的是:"end"并不是一个mark,而是一个比较特殊的索引。

添加一个mark,需要使用mark_set方法,这个方法不仅可以添加mark,也可以更改定义过的mark。

Text.mark_set(markName, index)

在index的位置定义一个mark,名字为markName。定义了mark后,可以在索引中使用。

from tkinter import *

root = Tk()

text = Text(root)
text.pack()

text.insert("end", "123456789")
text.mark_set("myMark", "1.1") #定义一个mark,并设置位置
text.insert("myMark", "M") #在自定义的mark处插入"M"

mainloop()

如图,"M"插入到了1.1的位置。

如果想要删除mark,可以使用mark_unset方法。

Text.mark_unset(*markNames)

你可以给定一系列的markName,删除它们。但是不能删除"current"和"insert"这两个特殊mark。

如果mark附近的文本内容改变,mark也会跟着移动。如果你不理解mark的移动方式,不妨在光标旁边的文本试一下改变它们的内容,看insert标记是如何移动的。

在mark位置插入文本的时候,mark默认会往右边移动一个字符,如果想要让mark在插入时改变移动方向,可以使用mark_gravity方法。

Text.mark_gravity(markName, direction=None)

markName指定mark的名称,direction指定mark的移动方向。默认是"right",也可以设为"left"(左),设置为None则返回direction。

还有一些关于mark的用法:

mark_names()

返回Text中所有mark的名称,包括"current"和"insert"。

mark_next(index)

返回在index位置后面的一个mark的名字,不存在返回空字符串。

mark_previous(index)

返回在index位置前面的一个mark的名字,不存在返回空字符串。

Tag(标签)

Mark是对于一个索引进行记录,而Tag是对于一段文本进行记录。并且记录的这一段文字还可以进行特殊的操作,比如更改字体,更改颜色,甚至还可以进行事件绑定。

Text有一个特殊的tag叫做sel,代表选中的内容。Text对于选中内容会进行高亮显示,就是利用了sel这个tag。上文介绍过,insert插入的内容可以直接指定tag的名称。

如要自定义一个tag,须使用tag_add方法。

Text.tag_add(tagName, index1, *args)

作用:添加一个名为tagName的tag,位置是index1+*args。*args允许你提供多个位置。比如:添加一个tag,位置是1.0-1.3, 2.0-2.3,那么可以写作tag_add("tagName", 1.0, 1.3, 2.0, 2.3);如果位置是1.0-1.3, 2.0,那么以写作tag_add("tagName", 1.0, 1.3, 2.0)。

添加完了tag,可以用tag_config方法对tag的文本进行样式更改。这些参数的值也可以用tag_cget来获取

Text.tag_config = Text.tag_configure(tagName, cnf=None, **kw)

Text.tag_cget(tagName, option)

**kw给定的参数如下,用来设置tag范围内的文本。

参数作用
borderwidth文本的边框的宽度
relief文本的relief
background文本的背景色

bgstipple

bg

使用一个位图作为背景,并使用background指定的颜色填充
foreground文本的前景色

fgstipple

fg

使用一个位图作为前景,并使用foreground指定的颜色填充
font文本的字体
justify文本的对齐方式,可以是"left", "right", "center",需先指定tag为该文本行的第一个字符
offset文本相对于基线的偏移距离,升高文本则为正数(基线:一般在文本底部的一条基准线,类似于英文四线三格的第三条线)
underline是否添加下划线,默认为False
overstrike是否添加删除线,默认为False
wrap换行方式

如下示例:

from tkinter import *

root = Tk()

text = Text(root)
text.pack()

text.insert("end", "123456789")
text.tag_add("tag", "1.0", "1.4")
text.tag_config("tag", background="yellow", overstrike=True)

mainloop()

 

tag可以进行事件绑定,需通过tag_bind方法。相对地,也有用于解除绑定的tag_unbind方法。

tag_bind(tagName, sequence, func, add=None)

tag_unbind(tagName, sequence, funcid=None)

tag_bind在tagName标记的文本处绑定sequence事件,回调func。比如text.tag_bind("mytag", "<Button-1>", callback),点击一下"mytag"的tag文本即可执行callback函数。如下示例,点击tag内容会自动链接到网站。

from tkinter import *
from webbrowser import open as webopen #webbrowser.open用来启动浏览器

root = Tk()

text = Text(root)
text.pack()

text.insert("end", "进入CSDN", "link") #插入内容并设置tag为"link"
text.tag_config("link", foreground="blue", underline=1)
text.tag_bind("link", "<Button-1>", lambda x:webopen("https://www.csdn.net"))

mainloop()

如果tag的位置之间发生重合,一段文本就有可能有多个tag。tag之间有优先级,多个tag的文本选项以较优先的tag为准。但如果较优先的tag中有一部分选项没有设置,那么就设置为较低tag的选项。比如一段文本有两个tag,tag1较优先,tag2优先级较低,此时设置tag1中(foreground="red", background=None),而tag2中(foreground="red", background="yellow"),那么整个段落的颜色则为(foreground="red", background="yellow")。

tag_raise和tag_lower方法分别可以提高tag的优先级和降低tag的优先级。

Text.tag_raise(tagName, aboveThis=None)

如果不指定aboveThis,则将tagName优先级提升至最高。如果指定aboveThis,则将tagName优先级提升至名为aboveThis的tag之上。

Text.tag_lower(tagName, belowThis=None)

如果不指定belowThis,则将tagName优先级降至最低。如果指定belowThis,则将tagName优先级降至名为belowThis的tag之下。

文本删除tag可以使用tag_remove方法。

Text.tag_remove(tagName, index1, index2=None)

删除tag需要指定tag的名称,还需要指定删除的范围,从index1到index2之间,将tag删除。

此外,还有一些用于tag操作的方法:

tag_names(index=None)

返回所有tag的名称,index指定位置。

tag_ranges(tagName)

返回所有tagName标记的文本以及位置信息。

tag_nextrange(tagName, index1, index2=None)

返回index1至index2之间第一个有tagName的位置。

tag_prevrange(tagName, index1, index2=None)

tag_nextrange反过来查找,返回最后一个有tagName的位置。

get和dump方法

get方法用于返回Text的文本内容的字符串,而不能返回其他插入的组件或图片。

Text.get(index1, index2=None)

返回index1到index2两个位置之间的文本。

如果还需要返回其他插入的组件或图片,就需要使用dump方法。

Text.dump(index1, index2=None, command=None, **kw)

index1和index2是获取的范围,这个方法会返回一个列表,列表的每个项目都是一个类型的插入内容,是一个三项元组。

from tkinter import *

root = Tk()

text = Text(root)
text.pack()
text.tag_config("MyTag", background="blue")

image = PhotoImage(file="monster.gif")
window = Button(text, text="这是一个按钮组件")

text.insert("end", "这是一段普通的文字")
text.insert("end", "这是一段tag文字", "MyTag")
text.image_create("end", image=image)
text.window_create("end", window=window)

mainloop()

上面的代码运行效果如图,如果再打印出text.dump("1.0", "end")获取text的内容,得到这样的输出:

 

下面详解dump返回列表中的每个元组的项的含义。第一项表示插入内容的类型,第二项表示插入内容的信息,第三项表示的是插入内容的位置。

插入内容类型(第一项)第一项解释第二项解释
text文本表示插入的文本
tagon标签的起始表示tag名称
tagoff标签的结束表示tag名称
markmark标记mark的名称
image图片表示插入的图片对象的名称,name参数可设定
window组件表示插入的组件对象的名称,name参数可设定

注:由于dump返回的插入组件是组件的名称,是一个字符串,不方便直接对组件对象进行操作。如果你还记得Misc类中讲解的nametowidget方法,可以使用这个方法将组件名称转换成组件对象。

search方法

search方法用于在Text中搜索某段文本。

Text.search(pattern, index, stopindex=None, forwards=None, backwards=None, exact=None, regexp=None, nocase=None, count=None)

这个方法返回搜索到的第一段文本的第一个字符的位置。参数及作用如下:

参数作用
pattern需要在Text内容中搜索的文本
index起始搜索位置
stopindex结束搜索位置(默认为最后)
forwards由前往后查找,默认为True
backwards由后往前查找,和forwards可指定一个
exact搜索与pattern完全匹配的文本,和regexp, nocase指定一个
regexp将pattern解释为正则表达式匹配,和exact, nocase指定一个
nocase搜索忽略大小写,和exact, regexp指定一个
count是一个IntVar,存储搜索结果的文本字符数量

2.23 tkinter图片对象

tkinter有两种图片对象,一种是PhotoImage,用于导入*.gif, *.ppm, *.pgm格式的文件,新版tk还支持*.png;还有一种是BitmapImage,用于导入*.xbm的位图格式文件。这些图片对象都可以被传递给组件的image参数。这些图片对象都支持以下方法:

方法作用
config(**kw)更改图片参数设置
height()返回图片的高
width()返回图片的宽
type()返回图片的类型,"photo"或"bitmap"

PhotoImage

PhotoImage(name=None, cnf={}, master=None, **kw)

name用来设定图片的名称。此外,PhotoImage支持以下关键字参数。

参数作用
data将图像的数据内容以字符串传递;字符串可以是二进制数据或base64编码数据(支持PNG和GIF);如同时指定了data和file参数,优先使用file指定的图片
file指定图片文件名称
format指定文件格式名
gamma图像的伽马值,可以理解为亮度;默认值为1,指定的值必须大于0
height图像的高度
width图像的宽度
palette指定要分配用于显示此图像的颜色立方体的分辨率,因此指定从显示它的窗口的颜色图中使用的颜色数。调色板规范字符串可以是一个十进制数,指定要使用的灰色阴影数,也可以是由斜线 (/) 分隔的三个十进制数,分别指定要使用的红色、绿色和蓝色阴影数。 如果使用第一种形式(单个数字),图像将以单色(即灰度)显示

除上描述的方法外,还支持以下方法:

方法作用
blank()将图片设为完全透明
cget(option)获取option参数的值
copy()复制图片,返回新的图片对象
zoom(x, y="")返回原图像在x方向缩放至原来x倍,y方向缩放至原来y倍的图像;如不指定y,则y同x
subsample(x, y="")将图片在x方向缩放至原来x分之一的大小,y方向缩放至原来y分之一的大小;如果x为负数,则图像垂直翻转;如果y为负数,则图像水平翻转;如不指定y,则y同x
get(x, y)返回在x, y像素位置上的颜色元组(red, green, blue)
put(data, to=None)从to位置的像素点开始向设置右方像素的颜色;data需提供[(color, color, ...), (color, color, ...), ...]这样的形式,列表中每一个项都表示一行,每一个项中间设定了这一行需改变的像素点颜色
write(filename, format=None, from_coords=None)将图像写入filename文件,图片格式为format;from_coords指定写入的图像范围,可以是(x1, y1, x2, y2)表示一个矩形范围,或是(x1, y1)表示从一个点的位置到图片右下角,也可以不指定表示整个图像
transparency_get(x, y)返回一个布尔值,根据位于x, y的像素点是否透明
transparency_set(x, y, boolean)将x, y的像素点是否透明的状态设为boolean

BitmapImage

BitmapImage(name=None, cnf={}, master=None, **kw)

name用于设定图片的名称,此外还可以设置如下关键字参数:

参数作用
data将图像的数据内容以字符串形式传递;字符串必须遵循X11位图格式;如同时指定了data和file参数,优先使用data指定的图片
file指定图片文件名称
background设置位图的背景色
foreground设置位图的前景色
maskdata将位图掩码的数据内容以字符串形式传递
maskfile指定位图掩码文件名称

BitmapImage不支持除上面描述的公共方法外的其他方法。 

下一篇:Python tkinter(GUI编程)模块全解(下)_Python zzy的博客-CSDN博客

如果你在开发Python tkinter程序时遇到了问题,或是有文章内容的建议,可以私信联系我,感谢支持!

Logo

开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!

更多推荐