tkinter 是 Python 內建模組, 無須安裝即可使用, 它提供了 22 種元件 (widge), 另外其主題化模組 (themed) tkinter.ttk 則提供 17 種美化的元件, 美學至上的我當然要使用增強版的 ttk. 注意, tkinter 在 Python 2.x 之模組名稱是首字母大寫的 Tkinter, 但在 Python 3 則改為全小寫的 tkinter, 由於 Python 3 日漸普及, 故以下測試均使用 Python 3.
本系列之前的文章參考 :
# Python 內建 GUI 模組 tkinter 測試 (一) : 建立視窗
本篇測試參考了下面幾本書 :
# Python GUI 設計活用 tkinter 之路王者歸來 (深石, 洪錦魁)
# Python 程式設計學習經典:工程分析x資料處理x專案開發 (碁峰, 黃立政等, 第 9 章)
# 科學運算 : Python 程式理論與應用 (上奇, 楊珮璐等, 第 12 章)
# Tkinter GUI Application Development Blueprints (Packt, Bhaskar Chaudhary)
# Tkinter GUI Application Development Hotshot (Packt, Bhaskar Chaudhary)
# Python GUI Programming Cookbook (Packt, Burkhard A. Meier)
# Python and Tkinter Programming (Manning, John E. Grayson)
# Python GUI programming with Tkinter (Packt, Alan D Moore)
# Tkinter GUI Programming by Example (Packt, David Love)
tkinter 的說明文件參考 :
# https://pythonspot.com/en/tkinter/
# Graphical User Interfaces with Tk
在上一篇文章中對 Tkinter 的頂級視窗 (root window) 做了簡單測試, 建立一個 Tkinter GUI 視窗的程式只要先後匯入 tkinter 以及旗下的元件美化子模組 tkinter.ttk 即可, 最基本的 Tkinter GUI 程式架構模版如下 :
#1. 匯入模組與類別
import tkinter as tk
from tkinter import ttk
#2. 定義元件之事件處理函數
def event_handler():
pass
#3. 建立最上層視窗, 設定標題與大小
root=tk.Tk()
root.title("ttk GUI")
root.geometry("400x300")
#4. 加入 tk/ttk 元件並指定事件處理函數
ttk.Button(root, text="OK", command=event_handler).pack()
#5. 啟始事件迴圈顯示視窗
root.mainloop()
注意, 為了避免汙染 (polute) 程式目前的命名空間, 盡量不要使用方便的 star import, 雖然那樣可以直接呼叫 tkinter 模組裡面的方法, 但很容易因為方法覆蓋 (method overwritting) 使得除錯困難, 應該直接使用類別名稱, 為了方便可使用簡單之別名, 例如 tkinter 使用別名 (alias) tk.
對話框是 GUI 應用程式最常用的介面之一, 用來輸出訊息或讀取使用者輸入之資料. Python 3 的 tkinter 模組提供了三種常用對話框子模組 (sub-module) :
tkinter 對話框類別 | 說明 |
messagebox | 提供訊息輸出與確定, 取消, 是, 否等按鈕回應功能 (輸出) |
simpledialog | 提供輸入框與確定, 取消等按鈕回應功能 (輸入) |
filedialog | 提供檔案開啟與儲存對話框功能 |
注意, 在 Python 2 時, 對話框用的是 TkMessageBox; 但在 Python 3 則改為 messagebox, 但方法部分則大致維持不變. Tkinter 訊息框的文件參考 :
# https://pythonspot.com/tk-message-box/
一. 使用 messagebox 對話框輸出訊息 :
messagebox 是一種 modal (強制) 對話框, 亦即它會暫停目前的 GUI 應用程式, 並且獨佔所有桌面的控制權, 除非關閉或完成對話框, 其他 GUI 應用程式無法操作. messagebox 提供了 8 種方法來產生訊息輸出對話框, 其介面如下 :
messagebox.functionName(title, message [, detail])
第一參數 title 是對話框的標題, 第二參數 message 與備選的第三參數 detail 都是要輸出的訊息, message 主要是用作簡短之訊息摘要; 而 detail 則是用作詳細說明, 在某些平台 message 會自動以粗體顯示, 通常簡短之訊息只要用到 message 參數即可. 注意, 前兩個參數是必要參數, 不需要使用參數名稱; 但 detail 是備選參數必須指定參數名稱, 例如 detail="this is a detail info".
messagebox 方法 | 說明 | 按鈕 | 圖示 | 回傳值 |
showinfo(title, massage [, detail]) | 通知對話框 | 確定 | i | "ok" |
showwarning(title, massage [, detail]) | 警告對話框 | 確定 | ! | "ok" |
showerror(title, massage [, detail]) | 錯誤對話框 | 確定 | x | "ok" |
askyesno(title, massage [, detail]) | 詢問對話框 | 是/否 | ? | True/False |
askokcancel(title, massage [, detail]) | 詢問對話框 | 是/取消 | ? | True/None |
askyesnocancel(title, massage[, detail]) | 詢問對話框 | 是/否/取消 | ? | True/False/None |
askquestion(title, massage[, detail]) | 詢問對話框 | 是/否 | ? | "yes"/"no" |
askretrycancel(title, massage[, detail]) | 詢問對話框 | 重試/取消 | ? | True/False |
參考 :
# tkinter showinfo python 3
# https://pythonspot.com/tk-message-box/
使用這些方法產生對話框之前須先從 tkinter 模組匯入 messagebox 子模組, 可以用 as 定義別名較為精簡 :
import tkinter as tk
from tkinter import messagebox as mb
注意, 由於 messagebos 是子模組, 必須明確指定上層模組名稱 tkinter, 不可以使用 tkinter 的別名 tk, 例如下面這種匯入方式是錯誤的, 會找不到 tk 模組 :
>>> import tkinter as tk
>>> from tk import messagebox as mb
Traceback (most recent call last):
File "<pyshell>", line 1, in <module>
ModuleNotFoundError: No module named 'tk'
直接呼叫 messagebox 的方法會自動建立一個 Tk 最上層視窗物件, 然後再彈出一個 modal 視窗, 例如 :
測試 1 : 直接呼叫 messagebox 之方法
import tkinter as tk
from tkinter import messagebox as mb
r=mb.showinfo("info", "this is message", detail="this is detail")
print(r)
print(type(r))
關閉對話框後才會執行 print(), 不管是按右上角的 X 鈕還是按 "確定" 鈕, showinfo() 的傳回值都是 "ok" 字串 :
>>> %Run test.py
ok
<class 'str'>
一般 GUI 應用需先建立一個 Tk 最上層視窗, 然後於元件的事件處理函數中再呼叫 messagebox 的方法來產生對話框, 例如 :
測試 2 : 使用按鈕觸發事件建立對話框
import tkinter as tk
from tkinter import ttk
from tkinter import messagebox
def event_handler():
messagebox.showinfo("Info", "Hello World!")
root=tk.Tk()
root.title("ttk GUI")
button=ttk.Button(root, text="Hello World!", command=event_handler)
button.pack()
root.mainloop()
此範例中由於沒有幫 messagebox 取別名, 因此必須直接使用 messagebox 參考. 此例在視窗中放置一個按鈕 button, 並指定事件處理函數 event_handler() 來處理按鈕事件, 當按下按鈕時呼叫 messagebox 的 showinfo() 來產生一個訊息對話框.
下面範例 3 在視窗中放置 8 個按鈕, 分別觸發 messagebox 的 8 個方法 :
測試 3 : 使用按鈕觸發事件建立 8 種對話框
import tkinter as tk
from tkinter import ttk
from tkinter import messagebox as mb
def showinfo_handler():
mb.showinfo("showinfo", "This is showinfo!")
def showwarning_handler():
mb.showwarning("showwarning", "This is showwarning!")
def showerror_handler():
mb.showerror("showerror", "This is showerror!")
def askyesno_handler():
mb.askyesno("askyesno", "This is askyesno!")
def askokcancel_handler():
mb.askokcancel("askokcancel", "This is askokcancel!")
def askyesnocancel_handler():
mb.askyesnocancel("askyesnocancel", "This is askyesnocancel!")
def askquestion_handler():
mb.askquestion("askquestion", "This is askquestion!")
def askretrycancel_handler():
mb.askretrycancel("askquestion", "This is askretrycancel!")
root=tk.Tk()
root.title("messagebox 測試")
ttk.Button(root, text="showinfo", command=showinfo_handler)\
.grid(row=0, column=0)
ttk.Button(root, text="showwarning", command=showwarning_handler)\
.grid(row=0, column=1)
ttk.Button(root, text="showerror", command=showerror_handler)\
.grid(row=0, column=2)
ttk.Button(root, text="askyesno", command=askyesno_handler)\
.grid(row=0, column=3)
ttk.Button(root, text="askokcancel", command=askokcancel_handler)\
.grid(row=1, column=0)
ttk.Button(root, text="askyesnocancel", command=askyesnocancel_handler)\
.grid(row=1, column=1)
ttk.Button(root, text="askquestion", command=askquestion_handler)\
.grid(row=1, column=2)
ttk.Button(root, text="askretrycancel", command= askretrycancel_handler)\
.grid(row=1, column=3)
root.mainloop()
比較長的訊息可以存在字串變數中以提高可讀性, 也可以使用 \n 控制跳行, 例如 :
測試 4 : 使用字串變數儲存訊息
import tkinter as tk
from tkinter import messagebox as mb
url="http://www.myweb.com/renewpassword"
message="Authentification Failed!"
detail="Your account or password is not valid. Please try again or visit "\
"\n{}".format(url)
r=mb.showinfo("info", message, detail=detail)
注意, 對話框沒有卷軸功能, 因此若字串太長或太多會使對話框大小增大, 甚至超過螢幕大小導致操作困難. 有卷軸的對話框需使用 Canvas, Frame, ScrollBar 等元件來客製化, 在 "Tkinter GUI Programming by Example" 照本書的第 8 章有探討.
二. 使用 simpledialog 對話框讀取使用者輸入資料 :
在命令列介面讀取單一輸入使用 input() 函數; 在 GUI 介面則可使用 tkinter.simpledialog 類別來讀取, 由於 simpledialog 是 tkinter 的子模組, 其匯入方法與 messagebox 一樣 :
from tkinter import simpledialog
或者使用別名 :
from tkinter import simpledialog as sd
simpledialog 提供三個方法可分別讀取字串, 整數, 以及浮點數 :
simpledialog 的方法 | 說明 | 回傳值 |
askstring(title, message) | 顯示字串輸入對話框 | 字串 |
askinteger(title, message) | 顯示整數輸入對話框 | 整數 |
askfloat(title, message) | 顯示浮點數輸入對話框 | 浮點數 |
除了 title 與 message 這兩個必要參數外, 數值輸入的 askinteger() 與 askfloat() 還有 minvalue(最小值), maxvalue(最大值), 以及 initialvalue(預設初始值) 等參數可設定輸入值的範圍與預設值, 輸入值若超出範圍會出現提示對話框.
測試 5 : 使用 simpledialog 讀取字串與數值
import tkinter as tk
from tkinter import ttk
from tkinter import simpledialog as sd
def do_askstring():
str_input=sd.askstring("askstring", "請輸入姓名")
print("{} {}".format(str_input, type(str_input)))
def do_askinteger():
int_input=sd.askinteger("askinteger", "請輸入年齡", minvalue=0, maxvalue=120)
print("{} {}".format(int_input, type(int_input)))
def do_askfloat():
float_input=sd.askfloat("askfloat", "請輸入體重", initialvalue=50.0)
print("{} {}".format(float_input, type(float_input)))
root=tk.Tk()
root.title("simpledialog")
ttk.Button(root, text="askstring", command=do_askstring).pack()
ttk.Button(root, text="askinteger", command=do_askinteger).pack()
ttk.Button(root, text="askfloat", command=do_askfloat).pack()
root.mainloop()
此程式放置了三個按鈕分別觸發 askstring(), askinteger(), 與 askfloat() 方法, 事件處理函數會顯示所輸入之資料以及其資料型態.
依序執行結果輸出如下 :
南志鉉 <class 'str'>
23 <class 'int'>
40.6 <class 'float'>
可見這三個函數都分別處理好傳回值得資料型態了.
上面程式中年齡部分有用 minvalue 與 maxvalue 參數設定 0~120 範圍, 如果輸入此範圍外之數值, 將出現提示訊息框 :
參考 :
# Single Value Data Entry
# https://svn.python.org/projects/python/tags/r32/Lib/tkinter/simpledialog.py
三. 使用 filedialog 對話框選取或輸入檔案名稱 :
tkinter 的檔案開啟儲存對話框功能寫在 filedialog 類別裡, 使用前需先匯入 (可用別名) :
import tkinter.filedialog as fd
或
from tkinter import filedialog as fd
from tkinter.filedialog import askopenfilename
filedialog 類別的方法如下表 :
filedialog 的方法 | 說明 | 回傳值 |
askopenfilename(initialdir, title, filetypes) | 顯示檔案開啟對話框 (單選) | 檔案路徑字串 |
askopenfilenames(initialdir, title, filetypes) | 顯示檔案開啟對話框 (複選) | 檔案路徑字串 tuple |
asksaveasfilename(initialdir, title, filetypes) | 顯示檔案儲存對話框 | 檔案路徑字串 |
askdirectory() | 顯示資料夾開啟對話框 | 資料夾路徑字串 |
askopenfile(initialdir, title, filetypes) | 顯示檔案開啟對話框 (單選) | 檔案 IO 串流物件 |
askopenfiles(initialdir, title, filetypes) | 顯示檔案開啟對話框 (複選) | 檔案 IO 串流物件 list |
asksaveasfile(initialdir, title, filetypes) | 顯示檔案儲存對話框 | 檔案 IO 串流物件 |
參數 initialdir, title, filetypes 都是可有可無的參數, 說明如下 :
- initialdir : 指定預設開啟之目錄, 例如 "/", "D:\\Python", "D:/Python"
- title : 設定對話框標題, 例如 "開啟檔案"
- filetypes : 篩選檔案型態之 tuple, 例如 (("text files","*.txt"),("all files","*.*"))
參考 :
# https://pythonspot.com/tk-file-dialogs/
# Python:GUI之tkinter學習筆記之messagebox、filedialog
下列範例程式測試 filedialog 的七種方法 :
測試 6 : filedialog 對話框函數測試
import tkinter as tk
from tkinter import ttk
from tkinter import filedialog as fd
def do_askopenfilename():
file=fd.askopenfilename()
print(type(file))
print(file)
def do_askopenfilenames():
files=fd.askopenfilenames()
print(type(files))
print(files)
def do_asksaveasfilename():
file=fd.asksaveasfilename()
print(type(file))
print(file)
def do_askdirectory():
dir=fd.askdirectory()
print(type(dir))
print(dir)
def do_askopenfile():
file=fd.askopenfile()
print(type(file))
print(file)
def do_askopenfiles():
files=fd.askopenfiles()
print(type(files))
print(files)
def do_asksaveasfile():
file=fd.asksaveasfile()
print(type(file))
print(file)
root=tk.Tk()
root.title("檔案開啟儲存")
ttk.Button(root, text="askopenfilename", command=do_askopenfilename).pack()
ttk.Button(root, text="askopenfilenames", command=do_askopenfilenames).pack()
ttk.Button(root, text="asksaveasfilename", command=do_asksaveasfilename).pack()
ttk.Button(root, text="askdirectory", command=do_askdirectory).pack()
ttk.Button(root, text="askopenfile", command=do_askopenfile).pack()
ttk.Button(root, text="askopenfiles", command=do_askopenfiles).pack()
ttk.Button(root, text="asksaveasfile", command=do_asksaveasfile).pack()
root.mainloop()
依序按鈕操作輸出如下 :
呼叫 askopenfilename() : 單選
<class 'str'>
D:/app.php
呼叫 askopenfilenames() : 複選
<class 'tuple'>
('D:/app.php', 'D:/dir.txt', 'D:/test.avi')
呼叫 asksaveasfilename() :
<class 'str'>
D:/test.txt
呼叫 askdirectory() :
<class 'str'>
D:/APK
呼叫 askopenfile() :
<class '_io.TextIOWrapper'>
<_io.TextIOWrapper name='D:/dir.txt' mode='r' encoding='cp950'>
呼叫 askopenfiles() :
<class 'list'>
[<_io.TextIOWrapper name='D:/dir.txt' mode='r' encoding='cp950'>, <_io.TextIOWrapper name='D:/tensorflow.txt' mode='r' encoding='cp950'>]
呼叫 askopenfiles() :
<class '_io.TextIOWrapper'>
<_io.TextIOWrapper name='D:/keras.py' mode='w' encoding='cp950'>
可見 askopenfile(), askopenfiles(), 與 asksaveasfile() 這三個函數的傳回值是 IO 串流物件, 可用傳回值參考來存取其中的屬性. 注意, askopenfile() 與 asksaveasfile() 這兩個對話框若按 "取消" 會傳回 None, 其餘方法按 "取消" 都是傳回空字串.
<class 'NoneType'>
None
<class 'str'>
上面範例只是測試 filedialog 的函數呼叫, 下面則實際測試文字檔案之讀取與儲存. 參考 :
# https://stackoverflow.com/questions/19476232/save-file-dialog-in-tkinter
測試 7 : 檔案開啟與儲存對話框
import tkinter as tk
from tkinter import ttk
from tkinter import filedialog as fd
def open_file():
openfilename=fd.askopenfilename(initialdir="/",title="Select file",\
filetypes = (("text files","*.txt"),("all files","*.*")))
print (openfilename)
try:
with open(openfilename,'r') as file:
print(file.read())
except:
print("檔案不存在!")
def save_file():
f=fd.asksaveasfile(mode='w', defaultextension=".txt")
if f is None:
return
f.write("Hello World!")
f.close()
root=tk.Tk()
root.title("檔案開啟 & 儲存檔案")
ttk.Button(root, text="開啟檔案", command=open_file).pack()
ttk.Button(root, text="儲存檔案", command=save_file).pack()
root.mainloop()
檔案儲存後輸出如下 :
D:/hello.txt
Hello World!
參考 :
# Tkinter tkFileDialog module
# Tkinter Standard Dialog Boxes
# https://github.com/PacktPublishing/Python-GUI-Programming-with-Tkinter/
# Learn Tkinter in 20 Minutes
# Python:GUI之tkinter學習筆記之messagebox、filedialog
大大您好,非常感謝您的教學,我參考您的方法寫了一個askfloat的變數輸入。
回覆刪除但我後續需要用該變數當作數值做數據上的運算,想請問該如何將輸入後的數字輸出出來呢?
換個角度問就是我的數字在輸入後是儲存在哪個地方。
謝謝您。
Hi, 放在指定的全域變數裡, 或者用 return 傳回去即可.
回覆刪除我想問一下,如果我想自定義對話框選項的話,我應該怎麼做???
回覆刪除因為我新的作業報告需要用到這個,但是我真的不知道怎麼做。
Hi, 您所謂自訂選項是指在對話框放下拉式選單/單選圓鈕/核取方塊嗎?
回覆刪除