python3.6 +tkinter GUI编程 实现界面化的文本处理工具(推荐)

更新:

2017.07.17  补充滚动条、win批处理拉起py

2017.08.13  新增自定义图标

一、背景:

1.工作中自己及同事在查数据库、测试接口时需要对一些字符串或json串作预处理,目前这些问题网上均有在线转换的工具,但很繁杂,可能需要打开几个网页窗口;

2.之前做的文本处理工具是基于cmd命令行的,不太友好;

3.自己做的一些小工具也基本都是命令行执行的,也想接触下GUI;

基于以上,了解到python自带的tkinter库可以初步满足UI的需求,业余时间做了个图形化工具,涉及窗口、文本、按钮、标签、菜单、菜单树、滚动条、文件处理、界面布局等;

下文将梳理从零到完成的全过程,作为自己GUI学习的一个总结;

ps:后面版本间隙有时间的话想试试用pyQt来做。。tkinter做出的界面确实。。。23333

工具界面截图:     

二、实现

1.安装所需要的库 ps:环境是win7+python 3.6,tkinter是py的标准库,即:如不涉及EXCEL处理,可跳过此步骤;使用python处理Excel表格,后面有时间会单独整理

(1)et-xmlfile  openpyxl安装需要依赖,工具里有涉及处理EXCEL(xlsx),需要用到openpyxl库;

(2)jdcal    同上

(3)openpyxl  处理EXCEL(xlsx)

(4)xlrd     读取EXCEL(xls)

(5)xlwt     写入EXCEL(xls)

(6)xltils    配合上面两个库对已存在表格进行修改

2.新建父窗口(可理解为界面的最底层)

#################################################################
#author: 陈月白
#_blogs: http://www.cnblogs.com/chenyuebai/
#################################################################
from tkinter import *
def gui_start():
 init_window = Tk()  #实例化出一个父窗口
 init_window.mainloop() #父窗口进入事件循环,可以理解为保持窗口运行,否则界面不展示
gui_start()

运行结果:

- - - - - - - - - - - -- - - - - - - - - - - -- - - - - - - - - - - -- - - - - - - - - - - -

运行会弹出上图所示的空白窗口,这里我们还可以修改窗口默认大小、默认弹出位置、窗口名、父窗口颜色、虚化等等;

init_window.title("文本处理工具 by: 陈月白")
 init_window.geometry('290x160+10+10')  #290 160为窗口大小,+10 +10 定义窗口弹出时的默认展示位置
 init_window["bg"] = "pink"     #窗口背景色,其他背景色见:blog.csdn.net/chl0000/article/details/7657887
 init_window.attributes("-alpha",0.8)  #虚化 值越小虚化程度越高

注意要加在 init_window.mainloop() 之前;

运行结果:

整理下代码:

#################################################################
#author: 陈月白
#_blogs: http://www.cnblogs.com/chenyuebai/
#################################################################
from tkinter import *import hashlib
class MY_GUI():
 def __init__(self,init_window_name):
  self.init_window_name = init_window_name
 #设置窗口
 def set_init_window(self):
  self.init_window_name.title("文本处理工具_v1.2 by: 陈月白")  #窗口名
  self.init_window_name.geometry('1068x680+10+10')     #290 160为窗口大小,+10 +10 定义窗口弹出时的默认展示位置
  self.init_window_name["bg"] = "pink"       #窗口背景色,其他背景色见:blog.csdn.net/chl0000/article/details/7657887
  self.init_window_name.attributes("-alpha",0.9)     #虚化,值越小虚化程度越高
def gui_start():
 init_window = Tk()    #实例化出一个父窗口
 ZMJ_PORTAL = MY_GUI(init_window)
 # 设置根窗口默认属性
 ZMJ_PORTAL.set_init_window()
 init_window.mainloop()   #父窗口进入事件循环,可以理解为保持窗口运行,否则界面不展示
gui_start()

先到这,有时间再整理。。

--------------------------------------------------------------------------------

继续。

3.父窗口完成后,即可基于父窗口创建其他组件

下面以“字符串转MD5”为例,介绍数据读取、按钮、布局、事件触发、后台转换处理、界面展示结果;

(1)标签

标签可以使用tkinter里的Label()方法,生成标签,括号内可带入其他属性参数;
生成标签后需使用grid()或者pack()方法使其展示;  

#注:tkinter对于同一个父窗口,只可允许使用一种布局函数;另外还有个place()方法,不过目前我还没用过;

self.init_data_label = Label(self.init_window_name, text="待处理数据")
self.init_data_label.grid(row=0, column=0)
self.result_data_label = Label(self.init_window_name, text="输出结果")
self.result_data_label.grid(row=0, column=12)

这里正好提一下grid(),说下我的理解。grid()方法是tkinter库自带的布局函数,他可以将父窗口分割成类似一个Excel表格,对单个组件按照参数中传递的row、column放置在指定位置,并展示;

下图红框部分,代表整个父窗口界面:

代码中的:self.init_data_label.grid(row=0, column=0)   

代表将“init_data_label”这个标签放置在页面的第一行,第一列的位置(即左上角),并展示;

(2)文本框
tkinter库中的Text()方法,可以创建一个文本框,用于数据的录入和结果展示;Text()方法可以传入其他参数值,用于定义如背景颜色,前景色,字体,字号、字色等

self.init_data_Text = Text(self.init_window_name, width=67, height=35) #原始数据录入框
self.init_data_Text.grid(row=1, column=0, rowspan=10, columnspan=10)
self.result_data_Text = Text(self.init_window_name, width=70, height=49) #处理结果展示
self.result_data_Text.grid(row=1, column=12, rowspan=15, columnspan=10)

self.init_data_Text.grid(row=1, column=0, rowspan=10, columnspan=10) #"rowspan=10" 是grid()方法的一个参数,用于指定该组件(init_data_Text)占用的行数,同理columnspan用于指定占用列数;

(3)按钮

按钮的创建可以使用Button()方法实现,可自定义按钮大小,宽度,按钮文本,背景色,以及需要触发的事件;

#按钮
self.str_trans_to_md5_button = Button(self.init_window_name, text="字符串转MD5", bg="lightblue", width=10,command=self.str_trans_to_md5) # 调用内部方法 加()为直接调用
self.str_trans_to_md5_button.grid(row=1, column=11)

触发事件:在定义按钮时,使用command=functionname这个参数来指定;当按钮被点击,就会调用command后的方法,从而完成事件的触发;

这里有个小坑。。。就是command后跟函数名时,不可加括号,否则在代码执行时,就会触发该函数;

举个例子,上面贴的代码中“command=self.str_trans_to_md5”若写为“command=self.str_trans_to_md5()”,该gui_test.py在执行时便会调用类中的self.str_trans_to_md5()方法,而不是在点击按钮时才触发;

(4)事件代码:后台的处理

step3中在定义按钮时,会选择触发一个事件(或称为函数),函数的实现涉及:从界面文本框中读取待处理数据---逻辑处理---界面结果文本框展示

code:

def str_trans_to_md5(self):
  src = self.init_data_Text.get(1.0,END).strip().replace("\n","").encode()
  #print("src =",src)
  if src:
   try:
    myMd5 = hashlib.md5()
    myMd5.update(src)
    myMd5_Digest = myMd5.hexdigest()
    #print(myMd5_Digest)
    #输出到界面
    self.result_data_Text.delete(1.0,END)  
    self.result_data_Text.insert(1.0,myMd5_Digest)  #从第一行开始
   except:
    self.result_data_Text.delete(1.0,END)
    self.result_data_Text.insert(1.0,"字符串转MD5失败")
  else:
   print("ERROR") #界面左下角会有日志框,支持动态打印,下面会提到

读取待处理数据:对之前定义的self.init_data_Text(待处理数据文本框)可使用tkinter库中自带的get()方法,获取文本框中的内容,str类型;

逻辑处理:MD5加密,不展开了,从网上找的例子照着敲;

界面结果文本框展示:对获取的字符串处理后,即可使用Text.insert(index,strdata)方法插入结果文本框:self.result_data_Text;

插入前建议先清理下,Text.delete(1.0,END)代表将文本框从第一行删除至最后一行,即清空;

(5)日志打印

和上面没啥区别,直接贴代码了~

def get_current_time(self):
  current_time = time.strftime('%Y-%m-%d %H:%M:%S',time.localtime(time.time()))
  return current_time

 #日志动态打印
 def write_log_to_Text(self,logmsg):
  global LOG_LINE_NUM
  current_time = self.get_current_time()
  logmsg_in = str(current_time) +" " + str(logmsg) + "\n"  #换行
  if LOG_LINE_NUM <= 7:
   self.log_data_Text.insert(END, logmsg_in)
   LOG_LINE_NUM = LOG_LINE_NUM + 1
  else:
   self.log_data_Text.delete(1.0,2.0)
   self.log_data_Text.insert(END, logmsg_in)
  #移动光标
  self.log_data_Text.focus_force()

整体的例子代码如下:

#################################################################
#author: 陈月白
#_blogs: http://www.cnblogs.com/chenyuebai/
#################################################################
from tkinter import *
import hashlib
import time
LOG_LINE_NUM = 0
class MY_GUI():
 def __init__(self,init_window_name):
  self.init_window_name = init_window_name
 #设置窗口
 def set_init_window(self):
  self.init_window_name.title("文本处理工具_v1.2 by: 陈月白")   #窗口名
  #self.init_window_name.geometry('320x160+10+10')       #290 160为窗口大小,+10 +10 定义窗口弹出时的默认展示位置
  self.init_window_name.geometry('1068x681+10+10')
  #self.init_window_name["bg"] = "pink"         #窗口背景色,其他背景色见:blog.csdn.net/chl0000/article/details/7657887
  #self.init_window_name.attributes("-alpha",0.9)       #虚化,值越小虚化程度越高
  #标签
  self.init_data_label = Label(self.init_window_name, text="待处理数据")
  self.init_data_label.grid(row=0, column=0)
  self.result_data_label = Label(self.init_window_name, text="输出结果")
  self.result_data_label.grid(row=0, column=12)
  self.log_label = Label(self.init_window_name, text="日志")
  self.log_label.grid(row=12, column=0)
  #文本框
  self.init_data_Text = Text(self.init_window_name, width=67, height=35) #原始数据录入框
  self.init_data_Text.grid(row=1, column=0, rowspan=10, columnspan=10)
  self.result_data_Text = Text(self.init_window_name, width=70, height=49) #处理结果展示
  self.result_data_Text.grid(row=1, column=12, rowspan=15, columnspan=10)
  self.log_data_Text = Text(self.init_window_name, width=66, height=9) # 日志框
  self.log_data_Text.grid(row=13, column=0, columnspan=10)
  #按钮
  self.str_trans_to_md5_button = Button(self.init_window_name, text="字符串转MD5", bg="lightblue", width=10,command=self.str_trans_to_md5) # 调用内部方法 加()为直接调用
  self.str_trans_to_md5_button.grid(row=1, column=11)
 #功能函数
 def str_trans_to_md5(self):
  src = self.init_data_Text.get(1.0,END).strip().replace("\n","").encode()
  #print("src =",src)
  if src:
   try:
    myMd5 = hashlib.md5()
    myMd5.update(src)
    myMd5_Digest = myMd5.hexdigest()
    #print(myMd5_Digest)
    #输出到界面
    self.result_data_Text.delete(1.0,END)
    self.result_data_Text.insert(1.0,myMd5_Digest)
    self.write_log_to_Text("INFO:str_trans_to_md5 success")
   except:
    self.result_data_Text.delete(1.0,END)
    self.result_data_Text.insert(1.0,"字符串转MD5失败")
  else:
   self.write_log_to_Text("ERROR:str_trans_to_md5 failed")
 #获取当前时间
 def get_current_time(self):
  current_time = time.strftime('%Y-%m-%d %H:%M:%S',time.localtime(time.time()))
  return current_time
 #日志动态打印
 def write_log_to_Text(self,logmsg):
  global LOG_LINE_NUM
  current_time = self.get_current_time()
  logmsg_in = str(current_time) +" " + str(logmsg) + "\n"  #换行
  if LOG_LINE_NUM <= 7:
   self.log_data_Text.insert(END, logmsg_in)
   LOG_LINE_NUM = LOG_LINE_NUM + 1
  else:
   self.log_data_Text.delete(1.0,2.0)
   self.log_data_Text.insert(END, logmsg_in)
def gui_start():
 init_window = Tk()    #实例化出一个父窗口
 ZMJ_PORTAL = MY_GUI(init_window)
 # 设置根窗口默认属性
 ZMJ_PORTAL.set_init_window()
 init_window.mainloop()   #父窗口进入事件循环,可以理解为保持窗口运行,否则界面不展示
gui_start()

上面例子代码的执行结果:

大概就是这些,其他功能、按钮大同小异,就不赘述了;

有问题欢迎留言交流;

后面有时间会补充用windows批处理拉起python代码,这样就可以像普通软件一样使用这个小工具了;

2017.07.14

--------------------------------------------------------------------------------

继续。

(6)滚动条

对于展示长文本,需要在Text文本框侧翼(或下方)提供滚动条;tkinter库中提供Scrollbar()方法创建一个滚动条

# 滚动条
self.result_data_scrollbar_y = Scrollbar(self.init_window_name)    #创建纵向滚动条
self.result_data_scrollbar_y.config(command=self.result_data_Text.yview)  #将创建的滚动条通过command参数绑定到需要拖动的Text上
self.result_data_Text.config(yscrollcommand=self.result_data_scrollbar_y.set) #Text反向绑定滚动条
self.result_data_scrollbar_y.grid(row=1, column=23, rowspan=15, sticky='NS') 

ps1:滚动条与待绑定的组件(Text或其他)时,需要二者相互绑定,从而达到拖动任意一方,对应方同步移动;

ps2:grid()中rowspan的值最好取Text文本框的值,可以使滚动条长度完美贴合文本框

效果:

三、其他

1.windows批处理拉起python

py代码完成后,为方便使用,可以使用windows批处理脚本拉起python代码

common_tools_gui.py所在目录下新建start.bat,写入 @python common_tools_gui.py %* @pause

@pause可以保持代码一直处于运行状态,而不是控制台一闪而过;  #python需配置在环境变量中,或者使用绝对路径。。

但也会带来一个问题,就是在双击这个批处理时,软件界面出来的同时,会附带一个黑色的cmd控制台窗口;

百度了一下,可以使用windows的.vbs拉起批处理来规避:

common_tools_gui.py所在目录下新建start_my_gui.vbs,写入

import ctypes
ctypes.windll.shell32.SetCurrentProcessExplicitAppUserModelID("myappid") 

此时双击该vbs脚本,即可拉起软件界面,且隐藏cmd控制台界面;

2. 暂时木有了。。

后面项目间隙有可能会研究下robotFramework~

也有可能会试着用pyQt重写下这个界面

先到这了;

2017.07.17

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

3.自定义图标

python打开工具时,左上角默认为tkinter默认图标

,任务栏为python默认图标;

查了下,python的tkinter库支持自定义图标:py文件当前目录下制作ico图标文件(https://www.ico.la/),父窗口使用iconbitmap()方法,指定该文件即可;

self.init_window_name.iconbitmap('text_processing_tools.ico')
 #指定界面图标

中途有个坑,就是仅左上角生效,但windows下方的任务栏无效;

百度之,大概意思是文本工具的py文件运行时,windows认为弹出的工具窗口是python解释器的衍生程序(子程序?),即默认仍使用pythpn解释器的图标;

需要在代码中指定弹出窗口的AppUserModelID即可:

import ctypes
ctypes.windll.shell32.SetCurrentProcessExplicitAppUserModelID("myappid")

效果:

左上角: 底侧任务栏:

附目录结构:

2017.08.13

(0)

相关推荐

  • Python 数据可视化pyecharts的使用详解

    什么是pyecharts? pyecharts 是一个用于生成 Echarts 图表的类库. echarts是百度开源的一个数据可视化 JS 库,主要用于数据可视化.pyecharts 是一个用于生成 Echarts 图表的类库.实际上就是 Echarts 与 Python 的对接. 使用 pyecharts可以生成独立的网页,也可以在 flask , Django中集成使用. pyecharts包含的图表 Bar(柱状图/条形图) Bar3D(3D 柱状图) Boxplot(箱形图) Effe

  • 详解Python数据可视化编程 - 词云生成并保存(jieba+WordCloud)

    思维导图: 效果(语句版): 源码: # -*- coding: utf-8 -*- """ Created on Tue Mar 5 17:59:29 2019 @author: dell """ # ============================================================================= # 步骤: # 分割aaa = jieba.cut(str,cut_all=True/Fa

  • Python干货:分享Python绘制六种可视化图表

    可视化图表,有相当多种,但常见的也就下面几种,其他比较复杂一点,大都也是基于如下几种进行组合,变换出来的.对于初学者来说,很容易被这官网上众多的图表类型给吓着了,由于种类太多,几种图表的绘制方法很有可能会混淆起来. 因此,在这里,我特地总结了六种常见的基本图表类型,你可以通过对比学习,打下坚实的基础. 01. 折线图 绘制折线图,如果你数据不是很多的话,画出来的图将是曲折状态,但一旦你的数据集大起来,比如下面我们的示例,有100个点,所以我们用肉眼看到的将是一条平滑的曲线. 这里我绘制三条线,只

  • python可视化爬虫界面之天气查询

    执行效果如下: from tkinter import * import urllib.request import gzip import json from tkinter import messagebox root = Tk() def main(): # 输入窗口 root.title('Python学习交流群:973783996') # 窗口标题 Label(root, text='请输入城市').grid(row=0, column=0) # 设置标签并调整位置 enter = E

  • 详解python做UI界面的方法

    一直以来都是用python脚本,执行的时候就是在终端直接命令执行,或者直接输入代码执行,最近为了方便他人使用,想做个界面,可以通过里面的控件菜单直接点击执行程序功能. 在文件夹中创建一个py文件test. 首先引入tkinter. 通过tk与mainloop做一个窗口,标题为 界面. 通过geometry设置窗口的大小. 通过button直接创建一个按钮,宽度width为10,样式bd为1. 执行脚本,我们会得到一个窗口中有个一个按钮. 以上就是关于python做UI界面的步骤,感谢大家的阅读和

  • 利用Python进行数据可视化常见的9种方法!超实用!

    前言 如同艺术家们用绘画让人们更贴切的感知世界,数据可视化也能让人们更直观的传递数据所要表达的信息. 我们今天就分享一下如何用 Python 简单便捷的完成数据可视化. 其实利用 Python 可视化数据并不是很麻烦,因为 Python 中有两个专用于可视化的库 matplotlib 和 seaborn 能让我们很容易的完成任务. Matplotlib:基于Python的绘图库,提供完全的 2D 支持和部分 3D 图像支持.在跨平台和互动式环境中生成高质量数据时,matplotlib 会很有帮助

  • python编辑用户登入界面的实现代码

    1.需求分析 登入界面需要达到以下要求: 系统要有登入和注册两个选项可供选择 系统要能够实现登入出错提示,比如账户密码错误等,用户信息保存在user_info.txt文件夹中 系统要能够进行登入错误次数统计并锁定,当用户输入3次错误时账户密码时,改账户被锁定,并将锁定账户保存于login_lock.txt文件夹中 注册界面要能够进行用户名重复提示,当用户注册账户已存在时,进行相应信息的提示. 1.代码实现 getNum = int(input("1.登入\n2.注册\nPlease Input

  • python3.6 +tkinter GUI编程 实现界面化的文本处理工具(推荐)

    更新: 2017.07.17 补充滚动条.win批处理拉起py 2017.08.13 新增自定义图标 一.背景: 1.工作中自己及同事在查数据库.测试接口时需要对一些字符串或json串作预处理,目前这些问题网上均有在线转换的工具,但很繁杂,可能需要打开几个网页窗口: 2.之前做的文本处理工具是基于cmd命令行的,不太友好: 3.自己做的一些小工具也基本都是命令行执行的,也想接触下GUI: 基于以上,了解到python自带的tkinter库可以初步满足UI的需求,业余时间做了个图形化工具,涉及窗口

  • Python Tkinter GUI编程实现Frame切换

    目录 Frame切换 1.Frame的tkraise() 方法介绍 2.tkraise用法示例 Frame切换 在本文中,将介绍如何使用 Frame tkraise() 方法在 Tkinter 应用程序中的Frame之间切换. 1.Frame的tkraise() 方法介绍 通常,一个 Tkinter 应用程序由多个Frame组成. 而且您经常需要在Frame之间切换以显示与用户选择相关的Frame. Tkinter 允许将Frame堆叠在一起. 要显示特定Frame,只需按堆叠顺序将一个放在另一

  • Python Tkinter GUI编程入门介绍

    一.Tkinter介绍 Tkinter是一个python模块,是一个调用Tcl/Tk的接口,它是一个跨平台的脚本图形界面接口.Tkinter不是唯一的python图形编程接口,但是是其中比较流行的一个.最大的特点是跨平台,缺点是性能不太好,执行速度慢. 一般使用Tkinter的方法是: From Tkinter import * 或者: import Tkinter 两者的区别我们前面讲模块的时候已经说过了.   二.Tkinter的使用 先看一下GUI程序的开发,熟悉MFC的朋友应该不会陌生.

  • Python GUI编程学习笔记之tkinter界面布局显示详解

    本文实例讲述了Python GUI编程学习笔记之tkinter界面布局显示.分享给大家供大家参考,具体如下: 相关内容: pack 介绍 常用参数 使用情况 常用函数 grid 介绍 常用参数 使用情况 常用函数 place 介绍 常用参数 使用情况 常用函数 首发时间:2018-03-04 14:20 pack: 介绍: pack几何管理器按行或列打包小部件. 可以使用填充fill,展开expand和靠边side等选项来控制此几何体管理器. pack的排放控件的形式就像将一个个控件按大小从上到

  • python GUI编程(Tkinter) 创建子窗口及在窗口上用图片绘图实例

    注意主窗口一定要为tk.Tk(),在主窗口上通过button的点击相应子函数创建子窗口,注意此时创建出来的窗口必须是Toplevel,否则出错. 至于用图片在窗口上绘图,则按代码所示即可. # -*- coding: utf-8 -*- """ Created on Wed Oct 26 20:32:52 2016 @author: min """ import Tkinter as tk from PIL import Image, Image

  • Python3.7+tkinter实现查询界面功能

    Tkinter 是 Python 的标准 GUI 库.Python 使用 Tkinter 可以快速的创建 GUI 应用程序. 这篇文章使用tkinter实现一个简单的查询界面 #!/usr/bin/python # -*- coding: UTF-8 -*- from tkinter import * import sqlite3 # 导入消息对话框子模块 import tkinter.messagebox #import urllib #创建主窗口 root = Tk() root.title

  • 利用Python的tkinter模块实现界面化的批量修改文件名

    用Python编写过批量修改文件名的脚本程序,代码很简单,运行也比较快,唯一美中不足之处是每次批量修改文件名时都需要执行以下步骤: 1)复制文件夹路径: 2)打开脚本程序 3)替换脚本中的文件夹路径 4)保存脚本程序 5)执行脚本程序 为了便于操作,最好还是弄成GUI界面,手动选择文件夹,这样程序也更通用.Python中的GUI库很多,绝大部分都支持跨平台,其中安装python时自带的GUI库是tkinter,本文就学习并创建基于tkinte的批量修改文件名程序. 本文涉及的知识点包括以下几个:

  • Python GUI编程学习笔记之tkinter中messagebox、filedialog控件用法详解

    本文实例讲述了Python GUI编程学习笔记之tkinter中messagebox.filedialog控件用法.分享给大家供大家参考,具体如下: 相关内容: messagebox 介绍 使用 filedialog 介绍 使用 首发时间:2018-03-04 22:18 messagebox: 介绍:messagebox是tkinter中的消息框.对话框 使用: 导入模块:import tkinter.messagebox 选择消息框的模式: 提示消息框:[返回"ok"] tkint

  • Python GUI编程学习笔记之tkinter事件绑定操作详解

    本文实例讲述了Python GUI编程学习笔记之tkinter事件绑定操作.分享给大家供大家参考,具体如下: 相关内容: command bind protocol 首发时间:2018-03-04 19:26 command: command是控件中的一个参数,如果使得command=函数,那么点击控件的时候将会触发函数 能够定义command的常见控件有: Button.Menu- 调用函数时,默认是没有参数传入的,如果要强制传入参数,可以考虑使用lambda from tkinter imp

  • Python GUI编程学习笔记之tkinter控件的介绍及基本使用方法详解

    本文实例讲述了Python GUI编程学习笔记之tkinter控件的介绍及基本使用方法.分享给大家供大家参考,具体如下: 相关内容: tkinter的使用 1.模块的导入 2.使用 3.控件介绍 Tk Button Label Frame Toplevel Menu Menubutton Canvas Entry Message Text Listbox Checkbutton Radiobutton Scale Scrollbar 首发时间:2018-03-04 16:39 Python的GU

随机推荐