Python与tkinter


tkinter是一个Python包,它封装了tk图形库,最近用tkinter写了一些测试工具,还是挺简洁好用的

tkinter包在Python2版本中名为Tkinter,Python3中更名为tkinter,另外,有个tkinter中有个ttk,这个组件库对原始的组件进行了优化,使界面元素更加好看了,需要注意的是,ttk组件库中相较于tk组件库中的组件,一些属性是不被支持的,使用时需要注意

另外这个tkinter的英文文档:tkinter,还不错

(一)关于grid布局

tkinter中布局有三种方式(Pack,Place,Grid),官方建议使用Grid进行布局,刚开始使用grid进行布局的时候有些棘手的地方就是界面布局时,这行元素长了,那行显示的短了,而且整个页面也会根据内容进行伸缩,虽然说灵活自动调整了,但是有时挺难看的...

翻看了文档及SF的问题,算是了解了grid布局,下面是一个demo,窗口大小固定的800x400,并且label与label2根据内容的不同进行了扩充空白区域,它们的大小是相等的,其中参数ipadx是为指定组件两边各增加给定数值

如label中的按钮ipadx值为30,其相对默认按钮增加了60px,labelFrame指定了ipadx为70,增加了140px,所以label2中ipadx指定了100,刚好可以跟label框的大小相同,另外padx可以指定左右间距,和css中padding是同理,相应还有pady,ipady,效果同理

代码如下:

#!/bin/env python
# -*- coding: utf-8 -*-

'''
Demo about tkinter's .grid() method <ipadx, padx>
'''

import tkinter as tk
import tkinter.ttk as ttk


class Application(tk.Frame):
    def __init__(self, master=None):
        super(Application, self).__init__(master, width=800, height=400)
        self.grid(sticky=tk.N+tk.S+tk.E+tk.W)
        self.grid_propagate(0)
        self.label()
        self.label2()
        self.label3()
        self.grid_info()

    def label(self):
        lf = ttk.Labelframe(self, text='label')
        lf.grid(padx=(30, 30), pady=(20, 0), ipadx=70)

        quitButton = ttk.Button(lf, text='Quit',
            command=self.quit)
        quitButton.grid(ipadx=(30), padx=(30, 30), pady=(20, 20))

        print('---1---')
        quitButton.update()
        print(quitButton.winfo_width(), quitButton.winfo_height(), lf.winfo_width())

    def label2(self):
        lf = ttk.Labelframe(self, text='label2')
        lf.grid(padx=(30, 30), pady=(20, 0), ipadx=100)

        quitButton = ttk.Button(lf, text='Quit',
            command=self.quit)
        quitButton.grid(padx=(30, 30), pady=(20, 20))
        print('---2---')
        quitButton.update()
        print(quitButton.winfo_width(), quitButton.winfo_height(), lf.winfo_width())


    def label3(self):
        lf = ttk.Labelframe(self, text='label3')
        lf.grid(padx=(30, 30), pady=(20, 0), sticky=tk.W)

        quitButton = ttk.Button(lf, text='Quit',
            command=self.quit)
        quitButton.grid(padx=(30, 30), pady=(20, 20))
        print('---3---')
        quitButton.update()
        print(quitButton.winfo_width(), quitButton.winfo_height(), lf.winfo_width())


    def grid_info(self):

        print(self.grid_size())


app = Application()
app.master.title('Sample Application')
app.mainloop()

(二)组件交互

下面这个小demo,点击hello按钮,按钮文字会变成world,再次点击变成hello,旁边的数字按钮,点击一下数字加1,并且会在程序右侧text面板输出一行记录,并且每隔1s钟,数字会自动加1,右侧也会输出一行记录,这个是用thread实现的,后面有更优雅的补充,clear按钮可以清除右上侧按钮的数字和右侧text面板的数据

代码如下:

#!/bin/env python
# -*- coding: utf-8 -*-

'''
Demo about tkinter's Button and Text
'''

import tkinter as tk
import tkinter.ttk as ttk
from time import strftime
from threading import Thread
import time

def datetime_str():

    '''
    return strftime
    '''

    return strftime('[%Y-%m-%d %H:%M:%S] ')


class Application(tk.Frame):
    def __init__(self, master=None):
        super(Application, self).__init__(master, width=800, height=400)
        self.grid(sticky=tk.N+tk.S+tk.E+tk.W)
        self.grid_propagate(0)
        self.label()
        self.textBox()
        self.start_plusone_with_thread()

    def change_text(self):

        self.button_msg.set('hello' if self.button_msg.get() == 'world' else 'world')

    def plusone(self):

        self.count_msg.set(self.count_msg.get() + 1)
        self.outputText.insert(tk.INSERT, datetime_str() + '+1s\n')
        self.outputText.see("end")

    def clear_msg(self):

        self.count_msg.set(1)
        self.outputText.delete('1.0', tk.END)

    def label(self):

        lf = ttk.Labelframe(self, text='label')
        lf.grid(sticky=tk.N, padx=(30, 30), pady=(20, 0))

        self.button_msg = tk.StringVar()
        self.count_msg = tk.IntVar()

        self.button_msg.set('hello')
        self.count_msg.set(1)

        self.b1 = ttk.Button(lf, textvariable=self.button_msg, command=self.change_text)
        self.b1.grid(row=0, column=0, padx=(30, 30), pady=(20, 0))

        self.b2 = ttk.Button(lf, textvariable=self.count_msg, command=self.plusone)
        self.b2.grid(row=0, column=1, padx=(30, 30), pady=(20, 0))

        self.b3 = ttk.Button(lf, text = 'clear', command=self.clear_msg)
        self.b3.grid(row=1, column=0, padx=(30, 30), pady=(20, 20))

        lf.update()
        print(lf.grid_size())
        print(lf.winfo_width(), lf.winfo_height())

    def textBox(self):

        self.outputText = tk.Text(self, width=50, height=26)
        self.outputText.grid(row=0, column=1, padx=(10, 30), pady=(30, 20))
        self.outputText.update()
        print(self.outputText.winfo_width(), self.outputText.winfo_height())

    def plusone_1s(self):

        while True:
            self.count_msg.set(self.count_msg.get() + 1)
            self.outputText.insert(tk.INSERT, datetime_str() + '+1s\n')
            self.outputText.see("end")
            time.sleep(1)

    def start_plusone_with_thread(self):

        thread = Thread(target=self.plusone_1s)
        thread.daemon = True
        thread.start()


app = Application()
app.master.title('Sample Application')
app.mainloop()

(三)文本框拖拽条

最开始向Text组件输出数据时,超出下边框的数据默认不会自动滚动,不过这个挺好解决的,使用 self.outputText.see("end") 就可以总是显示最后一行

在网上看到一个例子,他是使用pack布局的,不过里面有个定时输出的用法,还是挺不错的,可以替换(二)中的线程代码,更加简单优雅

代码如下:

import time
try:
    # python 2.x
    import Tkinter as tk
except ImportError:
    # python 3.x
    import tkinter as tk

class Example(tk.Frame):
    def __init__(self, *args, **kwargs):
        tk.Frame.__init__(self, *args, **kwargs)

        self.text = tk.Text(self, height=6, width=40)
        self.vsb = tk.Scrollbar(self, orient="vertical", command=self.text.yview)
        self.text.configure(yscrollcommand=self.vsb.set)
        self.vsb.pack(side="right", fill="y")
        self.text.pack(side="left", fill="both", expand=True)

        self.add_timestamp()

    def add_timestamp(self):
        self.text.insert("end", time.ctime() + "\n")
        self.text.see("end")
        self.after(1000, self.add_timestamp)

if __name__ == "__main__":
    root =tk.Tk()
    frame = Example(root)
    frame.pack(fill="both", expand=True)
    root.mainloop()

不过使用grid应该怎样实现呢,本来也打算新建个Text,还有Scrollbar,然后进行关联,尝试了几次发现都不成功,搜索发现原来是有ScrolledText的,自带滚动条的Text,那就方便多了

self.outputText = ScrolledText(self, width=70, height=35)  
self.outputText.grid(sticky=tk.N, row=0, column=1, rowspan=3, padx=(10, 30), pady=(30, 50))

(四)弹出新窗口与窗口居中

弹出新的窗口,使用Toplevel组件可以完成,新的窗口也像根窗口一样,可以用来布局,添加组件

代码如下:

#!/bin/env python
# -*- coding: utf-8 -*-

'''
Demo about tkinter's Toplevel and center it
'''

import tkinter as tk
import tkinter.ttk as ttk


class Application(tk.Frame):
    def __init__(self, master=None):
        super(Application, self).__init__(master, width=800, height=400)
        self.grid(sticky=tk.N+tk.S+tk.E+tk.W)
        self.grid_propagate(0)
        self.label()
        self.grid_info()
        self.center(self.master)
        self.counter = 0

    def center(self, win):
        win.update_idletasks()
        width = win.winfo_width()
        height = win.winfo_height()
        x = (win.winfo_screenwidth() // 2) - (width // 2)
        y = (win.winfo_screenheight() // 2) - (height // 2)
        win.geometry('{}x{}+{}+{}'.format(width, height, x, y))

    def create_window(self):

        self.counter += 1
        t = tk.Toplevel(self, width=400, height=200)
        t.grid_propagate(0)
        t.wm_title("Window #%s" % self.counter)   # t.title("title")
        l = tk.Label(t, text="This is window #%s" % self.counter)
        l.grid(padx=(30, 0), pady=(20, 0))
        self.center(t)


    def label(self):
        lf = ttk.Labelframe(self, text='label')
        lf.grid(padx=(30, 30), pady=(20, 0), ipadx=70)

        quitButton = ttk.Button(lf, text='New Window',
            command=self.create_window)
        quitButton.grid(ipadx=(30), padx=(30, 30), pady=(20, 20))

        print('---1---')
        quitButton.update()
        print(quitButton.winfo_width(), quitButton.winfo_height(), lf.winfo_width())

    def grid_info(self):

        print(self.grid_size())


app = Application()
app.master.title('Sample Application')
app.mainloop()

先这样,一些简单的记录,包裹些简单的脚本基本够用,有时间或者需要再进一步学习菜单什么的吧