tkinter --- Tcl/Tk 的 Python 介面

原始碼:Lib/tkinter/__init__.py


tkinter 套件(「Tk 介面」)是 Tcl/Tk GUI 工具集的標準 Python 介面。Tk 和 tkinter 在大多數 Unix 平台(包含 macOS)以及 Windows 系統上都可以使用。

從命令列執行 python -m tkinter 應該會開啟一個展示簡單 Tk 介面的視窗,讓你知道 tkinter 已正確安裝在你的系統上,並且也會顯示已安裝的 Tcl/Tk 版本,這樣你就可以閱讀該版本專屬的 Tcl/Tk 文件。

Tkinter 支援一系列的 Tcl/Tk 版本,這些版本可以是有或沒有執行緒支援的建構版本。官方的 Python 二進位發行版捆綁了有執行緒支援的 Tcl/Tk 8.6。關於支援版本的更多資訊,請參閱 _tkinter 模組的原始碼。

Tkinter 並非一個輕薄的包裝器,而是加入了相當多自身的邏輯,使其體驗更具 Python 風格(pythonic)。本文件將著重於這些新增和變更,對於未變更的細節,則會引導讀者參閱官方的 Tcl/Tk 文件。

備註

Tcl/Tk 8.5(2007)引入了一套現代化的主題式使用者介面元件,以及一個用以操作它們的新 API。新舊 API 目前都還能使用。你在網路上找到的大多數文件仍使用舊版 API,而且可能已經嚴重過時。

這是一個可選模組。如果你的 CPython 副本中缺少它,請查閱你的發行者(即提供 Python 給你的人)的文件。如果你就是發行者,請參閱 可選模組的需求

也參考

  • TkDocs

    關於使用 Tkinter 建立使用者介面的詳盡教學。內容解釋了關鍵概念,並使用現代 API 來說明建議的方法。

  • Tkinter 8.5 reference: a GUI for Python

    Tkinter 8.5 的參考文件,詳細說明了可用的類別、方法和選項。

Tcl/Tk 相關資源:

  • Tk 指令

    Tkinter 所使用的每個底層 Tcl/Tk 指令的綜合參考資料。

  • Tcl/Tk 首頁

    額外的文件,以及連至 Tcl/Tk 核心開發的連結。

書籍:

架構

Tcl/Tk 並非單一函式庫,而是由幾個不同的模組所組成,每個模組都有獨立的功能和官方文件。Python 的二進位發行版也隨附了一個附加模組。

Tcl

Tcl 是一個動態直譯的程式語言,就像 Python 一樣。雖然它可以作為通用程式語言獨立使用,但它最常被嵌入到 C 應用程式中,作為腳本引擎或 Tk 工具集的介面。Tcl 函式庫有一個 C 介面,可用於建立和管理一個或多個 Tcl 直譯器的實例,在這些實例中執行 Tcl 指令和腳本,並新增以 Tcl 或 C 實作的自訂指令。每個直譯器都有一個事件佇列,並有設施可以向其傳送事件並加以處理。與 Python 不同,Tcl 的執行模型是圍繞著協同多工(cooperative multitasking)設計的,而 Tkinter 則彌合了這種差異(詳情請參閱 Threading model)。

Tk

Tk 是一個以 C 實作的 Tcl 套件,它新增了自訂指令來建立和操作 GUI 元件。每個 Tk 物件都嵌入了自己載入 Tk 的 Tcl 直譯器實例。Tk 的元件非常可自訂,但代價是外觀較為過時。Tk 使用 Tcl 的事件佇列來產生和處理 GUI 事件。

Ttk

主題式 Tk(Ttk)是較新的一族 Tk 元件,相較於許多傳統的 Tk 元件,它在不同平台上提供了更好的外觀。從 Tk 8.5 版開始,Ttk 作為 Tk 的一部分發行。Python 的繫結(bindings)則在一個獨立的模組 tkinter.ttk 中提供。

在內部,Tk 和 Ttk 使用底層作業系統的設施,即 Unix/X11 上的 Xlib、macOS 上的 Cocoa、Windows 上的 GDI。

當你的 Python 應用程式使用 Tkinter 中的類別(例如,建立一個元件)時,tkinter 模組會先組合一個 Tcl/Tk 指令字串。它將該 Tcl 指令字串傳遞給一個內部的 _tkinter 二進位模組,後者再呼叫 Tcl 直譯器來對其進行計算。Tcl 直譯器接著會呼叫 Tk 和/或 Ttk 套件,而這些套件又會再呼叫 Xlib、Cocoa 或 GDI。

Tkinter 模組

對 Tkinter 的支援分散在數個模組中。大多數應用程式將需要主要的 tkinter 模組,以及 tkinter.ttk 模組,後者提供了現代主題式元件集和 API:

from tkinter import *
from tkinter import ttk
class tkinter.Tk(screenName=None, baseName=None, className='Tk', useTk=True, sync=False, use=None)

建構一個頂層 Tk 元件,它通常是應用程式的主視窗,並為此元件初始化一個 Tcl 直譯器。每個實例都有其自己關聯的 Tcl 直譯器。

Tk 類別通常使用所有預設值來實例化。然而,目前可辨識下列關鍵字引數:

screenName

當給定(作為字串)時,設定 DISPLAY 環境變數。(僅限 X11)

baseName

設定檔的名稱。預設情況下,baseName 是從程式名稱(sys.argv[0])衍生而來。

className

元件類別的名稱。用作設定檔,也用作呼叫 Tcl 時的名稱(interp 中的 argv0)。

useTk

若為 True,則初始化 Tk 子系統。tkinter.Tcl() 函式會將此設定為 False

sync

若為 True,則同步執行所有 X 伺服器指令,以便立即回報錯誤。可用於偵錯。(僅限 X11)

use

指定要嵌入應用程式的視窗 id,而不是將其建立為獨立的頂層視窗。id 必須以與頂層元件的 -use 選項值相同的方式指定(也就是說,其形式類似 winfo_id() 回傳的形式)。

請注意,在某些平台上,只有當 id 指向一個已啟用 -container 選項的 Tk 框架或頂層視窗時,此功能才能正常運作。

Tk 會讀取名為 .className.tcl.baseName.tcl 的設定檔,並在 Tcl 直譯器中進行直譯,並對 .className.py.baseName.py 的內容呼叫 exec()。設定檔的路徑是 HOME 環境變數,如果未定義,則是 os.curdir

tk

透過實例化 Tk 建立的 Tk 應用程式物件。這提供了對 Tcl 直譯器的存取。每個附加到同一個 Tk 實例的元件,其 tk 屬性都具有相同的值。

master

包含此元件的元件物件。對於 TkmasterNone,因為它是主視窗。masterparent 這兩個術語很相似,有時會作為引數名稱互換使用;然而,呼叫 winfo_parent() 會回傳元件名稱的字串,而 master 則回傳物件。parent/child 反映了樹狀關係,而 master (或 container)/content 反映了容器結構。

children

此元件的直接子代,形式為一個 dict,其中鍵為子元件的名稱,值為子實例物件。

tkinter.Tcl(screenName=None, baseName=None, className='Tk', useTk=False)

Tcl() 函式是一個工廠函式,它建立的物件與 Tk 類別建立的物件非常相似,只是它不會初始化 Tk 子系統。這在驅動 Tcl 直譯器時最為有用,尤其是在不希望建立額外頂層視窗的環境中,或者在無法建立的環境中(例如沒有 X 伺服器的 Unix/Linux 系統)。由 Tcl() 物件建立的物件可以透過呼叫其 loadtk() 方法來建立一個頂層視窗(並初始化 Tk 子系統)。

提供 Tk 支援的模組包括:

tkinter

主要的 Tkinter 模組。

tkinter.colorchooser

讓使用者選擇顏色的對話方塊。

tkinter.commondialog

此處列出的其他模組中定義的對話方塊的基底類別。

tkinter.filedialog

讓使用者指定要開啟或儲存的檔案的通用對話方塊。

tkinter.font

協助處理字型的工具程式。

tkinter.messagebox

存取標準的 Tk 對話方塊。

tkinter.scrolledtext

內建垂直捲軸的文字元件。

tkinter.simpledialog

基本的對話方塊和便利函式。

tkinter.ttk

在 Tk 8.5 中引入的主題式元件集,為主 tkinter 模組中的許多傳統元件提供了現代化的替代方案。

額外模組:

_tkinter

一個包含 Tcl/Tk 低階介面的二進位模組。它由主 tkinter 模組自動引入,應用程式開發者不應直接使用它。它通常是一個共享函式庫(或 DLL),但在某些情況下可能會與 Python 直譯器靜態連結。

idlelib

Python 的整合開發與學習環境(IDLE)。基於 tkinter

tkinter.constants

在將各種參數傳遞給 Tkinter 呼叫時,可用於替代字串的符號常數。由主 tkinter 模組自動引入。

tkinter.dnd

(實驗性)對 tkinter 的拖放支援。當它被 Tk DND 取代時,將會被棄用。

turtle

在 Tk 視窗中的海龜繪圖。

Tkinter 應急指南

本節並非旨在成為 Tk 或 Tkinter 的詳盡教學。為此,請參閱前面提到的外部資源之一。相反地,本節提供了一個快速導覽,介紹 Tkinter 應用程式的外觀、識別基礎的 Tk 概念,並解釋 Tkinter 包裝器的結構。

本節的其餘部分將幫助你識別在 Tkinter 應用程式中所需的類別、方法和選項,以及在哪裡可以找到關於它們的更詳細文件,包括在官方的 Tcl/Tk 參考手冊中。

一個 Hello World 程式

我們將從一個 Tkinter 的 "Hello World" 應用程式開始。這不是我們能寫出的最小程式,但足以說明一些你需要知道的關鍵概念。

from tkinter import *
from tkinter import ttk
root = Tk()
frm = ttk.Frame(root, padding=10)
frm.grid()
ttk.Label(frm, text="Hello World!").grid(column=0, row=0)
ttk.Button(frm, text="Quit", command=root.destroy).grid(column=1, row=0)
root.mainloop()

在引入之後,下一行建立了一個 Tk 類別的實例,它會初始化 Tk 並建立其關聯的 Tcl 直譯器。它還會建立一個頂層視窗,稱為根視窗,作為應用程式的主視窗。

接下來的一行建立了一個框架元件,在本例中,它將包含我們接下來要建立的一個標籤和一個按鈕。該框架被放置在根視窗內。

下一行建立了一個包含靜態文字字串的標籤元件。grid() 方法用於指定標籤在其包含的框架元件中的相對佈局(位置),類似於 HTML 中表格的運作方式。

然後建立一個按鈕元件,並放置在標籤的右側。當按下時,它將呼叫根視窗的 destroy() 方法。

最後,mainloop() 方法將所有內容顯示在螢幕上,並回應使用者輸入,直到程式終止。

重要的 Tk 概念

即使是這個簡單的程式也說明了以下關鍵的 Tk 概念:

元件(widgets)

一個 Tkinter 使用者介面是由個別的元件所組成。每個元件都表示為一個 Python 物件,從 ttk.Framettk.Labelttk.Button 等類別實例化而來。

元件階層(widget hierarchy)

元件被安排在一個階層中。標籤和按鈕被包含在一個框架內,而框架又被包含在根視窗內。在建立每個元件時,其元件會作為第一個引數傳遞給元件的建構函式。

設定選項(configuration options)

元件具有設定選項,可以修改其外觀和行為,例如在標籤或按鈕中顯示的文字。不同類別的元件會有不同的選項集。

佈局管理(geometry management)

元件在建立時不會自動新增到使用者介面中。像 grid 這樣的佈局管理器(geometry manager)會控制它們在使用者介面中的放置位置。

事件迴圈(event loop)

Tkinter 只有在主動執行事件迴圈時,才會對使用者輸入、程式的變更,甚至螢幕刷新做出反應。如果你的程式沒有執行事件迴圈,你的使用者介面將不會更新。

了解 Tkinter 如何包裝 Tcl/Tk

當你的應用程式使用 Tkinter 的類別和方法時,Tkinter 內部正在組合代表 Tcl/Tk 指令的字串,並在附加到你應用程式 Tk 實例的 Tcl 直譯器中執行這些指令。

無論是試圖瀏覽參考文件、尋找正確的方法或選項、改寫一些現有程式碼,還是偵錯你的 Tkinter 應用程式,在某些時候,了解那些底層 Tcl/Tk 指令的樣貌會很有幫助。

為了說明,以下是上面 Tkinter 腳本主要部分的 Tcl/Tk 對等程式碼。

ttk::frame .frm -padding 10
grid .frm
grid [ttk::label .frm.lbl -text "Hello World!"] -column 0 -row 0
grid [ttk::button .frm.btn -text "Quit" -command "destroy ."] -column 1 -row 0

Tcl 的語法類似於許多 shell 語言,第一個字是要執行的指令,後面跟著該指令的引數,以空格分隔。在不深入太多細節的情況下,請注意以下幾點:

  • 用於建立元件的指令(如 ttk::frame)對應於 Tkinter 中的元件類別。

  • Tcl 元件選項(如 -text)對應於 Tkinter 中的關鍵字引數。

  • 在 Tcl 中,元件是透過路徑名稱(如 .frm.btn)來參照,而 Tkinter 不使用名稱,而是使用物件參照。

  • 元件在元件階層中的位置被編碼在其(階層式)路徑名稱中,該路徑名稱使用 .(點)作為路徑分隔符。根視窗的路徑名稱就是 .(點)。在 Tkinter 中,階層不是由路徑名稱定義的,而是在建立每個子元件時指定父元件來定義的。

  • 在 Tcl 中實作為獨立指令的操作(如 griddestroy),在 Tkinter 中則表示為元件物件上的方法。正如你稍後會看到的,在其他時候,Tcl 使用的看起來像是對元件物件的方法呼叫,這更接近 Tkinter 中的用法。

我該如何...?哪個選項可以...?

如果你不確定如何在 Tkinter 中做某件事,並且無法立即在你正在使用的教學或參考文件中找到它,這裡有一些可能有幫助的策略。

首先,請記住,個別元件的運作細節可能會因 Tkinter 和 Tcl/Tk 的不同版本而異。如果你正在搜尋文件,請確保它對應於你系統上安裝的 Python 和 Tcl/Tk 版本。

在搜尋如何使用 API 時,知道你正在使用的類別、選項或方法的確切名稱會很有幫助。自省(Introspection),無論是在互動式 Python shell 中還是使用 print(),都可以幫助你識別所需內容。

要找出任何元件上有哪些可用的設定選項,請呼叫其 configure() 方法,該方法會回傳一個字典,其中包含有關每個物件的各種資訊,包括其預設值和目前值。使用 keys() 僅取得每個選項的名稱。

btn = ttk.Button(frm, ...)
print(btn.configure().keys())

由於大多數元件有許多共通的設定選項,找出特定於某個元件類別的選項會很有用。將選項串列與像框架這樣較簡單的元件的選項串列進行比較,是做到這一點的一種方法。

print(set(btn.configure().keys()) - set(frm.configure().keys()))

同樣地,你可以使用標準的 dir() 函式來尋找元件物件可用的方法。如果你試一下,你會看到有超過 200 個常見的元件方法,所以再次強調,識別特定於某個元件類別的方法是很有幫助的。

print(dir(btn))
print(set(dir(btn)) - set(dir(frm)))