2021年9月21日 星期二

MicroPython v1.17 釋出

很久沒關注 MicroPython了, 今日追蹤發現 v1.17 已在 9/2 發布, 而我居然跳過了 v1.16! 


映像檔下載位址如下 : 


此次更新主要的新增功能如下 :
  1. 支援 f 字串語法糖 (部分受限)
  2. 新增 machine 模組的 I2S 類別 (支援 ESP32 與 STM32 板)
  3. json 模組的 dump/dumps 函數支援 separators 引數
  4. 檔案目錄重構, 第三方套件均放在 /lib 目錄下, 原本在 /lib 的第一方套件改放至 /shared
  5. ESP8266/ESP32 的 WLAN 掃描結果支援顯示隱藏之網路

我用 esptools 燒錄映像檔到 4MB 的 NodeMCU (ESP32) OK (先按住右下角的 IO0 鈕) :

D:\ESP32>esptool.py --chip esp32 --port COM9 erase_flash     
esptool.py v2.6
Serial port COM9
Connecting....
Chip is ESP32D0WDQ6 (revision 1)
Features: WiFi, BT, Dual Core, 240MHz, VRef calibration in efuse, Coding Scheme None
MAC: 80:7d:3a:b7:c0:8c
Uploading stub...
Running stub...
Stub running...
Erasing flash (this may take a while)...
Chip erase completed successfully in 5.2s
Hard resetting via RTS pin...

D:\ESP32>esptool.py --chip esp32 --port COM9 write_flash -z 0x1000 esp32-20210902-v1.17.bin   
esptool.py v2.6
Serial port COM9
Connecting....
Chip is ESP32D0WDQ6 (revision 1)
Features: WiFi, BT, Dual Core, 240MHz, VRef calibration in efuse, Coding Scheme None
MAC: 80:7d:3a:b7:c0:8c
Uploading stub...
Running stub...
Stub running...
Configuring flash size...
Auto-detected Flash size: 4MB
Compressed 1527504 bytes to 987584...
Wrote 1527504 bytes (987584 compressed) at 0x00001000 in 88.0 seconds (effective 138.9 kbit/s)...
Hash of data verified.

Leaving...
Hard resetting via RTS pin...

燒錄完畢用 Putty 連線 ESP32, 使用 os 模組的 uname() 函數檢查韌體版本 : 

ets Jun  8 2016 00:22:57

rst:0x1 (POWERON_RESET),boot:0x13 (SPI_FAST_FLASH_BOOT)
configsip: 0, SPIWP:0xee
clk_drv:0x00,q_drv:0x00,d_drv:0x00,cs0_drv:0x00,hd_drv:0x00,wp_drv:0x00
mode:DIO, clock div:2
load:0x3fff0030,len:5656
load:0x40078000,len:12696
load:0x40080400,len:4292
entry 0x400806b0
MicroPython v1.17 on 2021-09-02; ESP32 module with ESP32
Type "help()" for more information.
>>> import os     
>>> os.uname()   
(sysname='esp32', nodename='esp32', release='1.17.0', version='v1.17 on 2021-09-02', machine='ESP32 module with ESP32')

用 sys 模組的 version 屬性檢查 Python 版本 : 

>>> import sys   
>>> print("Version Python ", sys.version)     
Version Python  3.4.0

跟以前一樣還是 Python 3.4.0. 

測試 f 字串嵌入功能 :

>>> name="Tony"   
>>> age=21   
>>> print(f"My name is {name} and I am {age} years old")   
My name is Tony and I am 21 years old

果然已有支援 F 字串. 參考 :


中秋與 921

今天是中秋, 921 大地震 22 周年, 也是母親七周年忌日, 時間過得好快, 一轉眼七年就過去了, 但我每天仍誦念經咒迴向, 除了緬懷, 也是修行科目之一. 最近重看花甲男孩轉大人, 我覺得劇中花甲的阿嬤跟母親個性很像, 樂於助人捨得給予, 我至今都還蒙德澤. 我雖不是達官貴人, 但當年母親告別式仍來了非常多親朋好友送行. 花甲的二叔說母親所做的他連一樣都做不到, 我也是, 真是慚愧. 

常用亂數種子 42 的由來

今天在 "精通機器學習" 這本書第二章關於如何建立測試組的部分讀到, 在呼叫排列組合函數產生隨機的測試組索引前, 可以先設定一個亂數產生器種子例如 np.random.seed(42) 來避免每次執行程式時產生不同的測試組資料集 (這樣多執行幾次就會將整個資料集看光光, 有違設立測試組之目的).

作者在此標註 : 你可能會在很多資料科學與機器學習的書上看到亂數產生器種子被設為 42, 這是為什麼? 其實使用 42 這個數值並沒有特殊的意義, 那只是工程師在寫程式時隨手參考所形成的流行文化 (pop culture) 而已. 

這個數字 42 出自道格拉斯-亞當斯 (Douglas Adams) 1979 年開始發行的科幻小說 "銀河便車指南" 系列 (The Hitchhiker’s Guide to the Galaxy), 這些小說曾被改編成廣播劇, 電視劇, 漫畫等形式, 2005 年還被好萊塢改編成電影 "星際大奇航". 亞當斯在大學畢業後工作並不順利, 曾當過保鑣, 保全, 建築工, 以及養雞場工人, 直到 1979 年他的小說被改編成廣播劇才步上成功之路. 亞當斯於 2001 年因心臟病離世, 享年 49 歲, 當時他正在寫銀河便車指南三部曲系列中的第六本小說. 

在這本小說的末尾, 主角 Authur Dent 在馬格西亞星球上得知地球的存在只是一個實驗, 其實幾百萬年前的老鼠是一種高智慧動物, 它們建造了一部超級電腦叫做 Deep Thought (深思), 這些老鼠詢問 Deep Thought : 生命, 宇宙, 以及萬事萬物的終極答案 (Great Question) 是甚麼? 經過很長時間的計算後, 老鼠的後代得到 42 這個答案. 

Deep Thought 表示它只能算出答案是甚麼, 背後的原因必須用更高級的電腦才能解釋, 而這部電腦就是地球本身, 參考 :


在 Deep Thought 得出這答案後的五分鐘, 地球就被來自 Vogsphere 行星的 Vogons 人毀滅了 (為了建造超太空星際公路, 這是都更概念嗎? ), Arthur Dent 與他來自參宿四的朋友 Ford Prefect 搭乘 Vogons 人的飛碟離去. 由於地球所有資料被毀, 但 Arthur Dent 腦中儲存了所有資料, 老鼠們為了探求 Great Question 的原因, 決定拿 Arthur Dent 的腦袋開刀 ..... 哈哈, 好有趣的故事. 

2021年9月20日 星期一

2021 年第 38 周記事

鄉下老家的油漆與防漏工程本周終於完成了, 因二樓祖堂前的磨石地磚部分膨起, 做 PU 前須先挖掉重鋪, 所以請松和先生的媳婦看日子, 9/14 周二巳時起工, 昨日上完兩層 PU, 今天做第三層, 早上老闆來時, 說又添加一樓後面及二三樓樓板邊邊粉刷, 再加兩萬五, 爸已領五千給他, 剩尾款六萬元, 哇哩上回說剩四萬元不會再加了, 又找別的名目加做, 此人雖做生意有點不老實, 但工程倒是有認真做 (其實都是他的越南老婆在做), 我想就算了, 結算此次修繕總計花費 26.5 萬. 





本周專心 Python GUI Tkinter 學習, 總算離終點越來越近了, 雖然還是沒有如預期的兩周搞定, 但也還算 OK, 因為也分了一點時間在看 ML 與 NLP (為了還書, 哈). Tkinter 原本不在優先順序名單的, 只是整理書櫃時偶然拿起 Tkinter 的書本, 突然想把它學完而已. 所以臨時起意有時也能做出一些成果出來, 計畫常常趕不上變化. 學完 Tkinter 順便把日常的工作輔助軟體改寫成桌上型 GUI 介面, 徹底擺脫 IE 的牽制 (IE 也快要走入歷史了).

本周因為油漆工恢復工作, 小雖貓看到生人來, 就叼著它兒子轉移到車庫冷氣口下方的紙箱內側躲起來了, 可見貓很會認人, 對主人很放心, 之前在前門口育兒時, 我去摸小貓咪它都不會緊張, 但陌生人一來就趕緊另覓處所. 但我昨晚趁它在曬穀場邊休息, 把小貓咪帶到客廳給菁菁看, 小貓咪已開目, 看到我就一直喵喵叫, 而且越來越大聲, 小雖貓聽到立刻跑到客廳門口攀在鋁門上看, 我趕緊將小貓咪抱回去, 小雖貓緊張得用衝的衝回來紙箱看, 然後就一直躲在裡面顧, 過了好久才出來.  

姊姊周六傍晚回高雄過節, 公司發了一盒月餅 (我也在周五去辦公室附近的吳記買了一盒 9 顆的綠豆椪, 540 元), 我們家沒有中秋烤肉習慣, 故中秋節就是吃月餅柚子賞月, 看 Netflix 而已. 因姊姊回台北高鐵為周二中秋節早上, 周一晚上我們就得回高雄, 所以周六晚上等菁菁下班就回鄉下了. 

2021年9月18日 星期六

重看花甲男孩轉大人

最近藝壇又損失了一員, 曾抱回兩座金鐘視帝獎座的本土劇資深演員龍劭華猝逝, 令人感到惋惜. 他生前作品我印象最深的是五年前看過的 "花甲男孩轉大人", 劇中他飾演鄭家二哥光煌, 這幾天在 Youtube 找出來重看了一遍, 真的好好看 (好好笑但有時又讓人噴淚), 植劇場這部作品我認為是台灣鄉土劇的經典. 

對於童年在三合院大家族中長大的我來說, 這部戲看起來格外親切呢. 可惜我家的三合院多年前早已被拆除, 宗族四散, 各自到自己分得的農地上蓋農舍了. 當兵前曾回去破落無人居住的三合院回憶童年時光, 可惜當時我還沒有照相機, 沒能留下最後影像. 之後祖宅土地被家族協議賣給建商蓋透天厝, 我的童年記憶就只留下模糊的映像了. 

2021年9月17日 星期五

好書 : 實戰 TensorFlow x Keras工作現場開發

此書借自市圖, 作者為日本人太田滿久等人, 主要介紹 MLP, CNN, 以及 Encoder-decoder 等 (不含 RNN 等), 內容簡要故篇幅不大, 前半部基礎部分有清晰圖解幫助初學者理解神經網路運算架構, 後半部則主要介紹利用 Encode-decode 進行圖片上色與生成等處理. 



Source : 博客來


雖然算是一本好書, 但日文原文書乃 2018 年出版, 使用的是 TensorFlow 1.5 版, 年代有點久遠了. 書中程式碼下載網址 :

2021年9月16日 星期四

Python 內建 GUI 模組 tkinter 測試 (十四) : Combobox 元件

今天繼續測試的 Combobox (下拉式選單) 元件. 

本系列之前的文章參考 :   


Combobox 元件 (下拉式選單) 與 Spinbox 類似, 也是一種複合元件, 由 Entry 與附捲軸之 Listbox 組成, 可讓使用者在下拉清單中選取一個選項,  也可以直接在 Entry 中輸入內容. 注意, 此元件僅在 ttk 中有, tk 中無此元件. 建立 Combobox 元件之語法如下 : 

Combobox(父容器, **參數列)



Combobox 元件常用參數如下表 : 


 Combobox 常用參數 說明
 width Combobox 輸入 Entry 的寬度 (字元數, 預設 20 個字元)
 height 設定下拉式選單最大顯示列數 (預設 20), 超出會顯示捲軸
 values 設定下拉式選單之選項, 可用字串之元組或串列 
 textvariable 與 Combobox 綁定之類別變數名稱 (通常用 StringVar)
 state 設定 Combobox 狀態 : NORMAL (預設)/DISABLED/READONLY


注意, state 參數也可以使用字串 "normal", "disabled", 或 "readonly". 

Combobox 元件常用方法如下 :


 Combobox 常用方法 說明
 get() 取得 Combobox 元件之現值
 set(option) 設定選項 option 為被選取
 current([index]) 設定索引=index 之項目被選取, 未傳入參數則傳回被選取項之索引


取得 Combobox 被選取之現值除了呼叫 Combobox 物件的 get() 方法外, 也可以呼叫與其 textvariable 參數綁定的類別變數之 get() 方法. 

建立 Combobox 元件時只要傳入 values 參數即可, 例如 : 


測試 1 : 預設的 Combobox 下拉式選單 [看原始碼]

import tkinter as tk
from tkinter import ttk

win=tk.Tk()                                                      
win.title("tkinter GUI 測試")
win.geometry("300x200")

values_1=("岳不群", "左冷禪", "定逸師太", "任盈盈", "令狐沖") 
combobox_1=ttk.Combobox(win, values=values_1)
combobox_1.pack()
values_2=["張無忌", "趙敏", "楊不悔", "朱九貞", "周芷若"]
combobox_2=ttk.Combobox(win, values=values_2)
combobox_2.pack()
win.mainloop()

此例的兩個下拉式選單分別以 list 與 tuple 形式傳入 values 參數, 結果如下 : 




此例僅設定 values 參數, 預設 Entry 寬度為 20 個字元, 看來有點寬, 這可以用 width 參數設定, 下面範例同時也指定了 height 參數來限制最大顯示列數 :


測試 2 : 設定 width (輸入區寬度) 與 height (清單顯示列數) 參數 [看原始碼]

import tkinter as tk
from tkinter import ttk

win=tk.Tk()                                                      
win.title("tkinter GUI 測試")
win.geometry("300x200")

values_1=("岳不群", "左冷禪", "定逸師太", "任盈盈", "令狐沖") 
combobox_1=ttk.Combobox(win, values=values_1, width=10, height=3)
combobox_1.pack()
values_2=["張無忌", "趙敏", "楊不悔", "朱九貞", "周芷若"]
combobox_2=ttk.Combobox(win, values=values_2, width=10, height=3)
combobox_2.pack()
win.mainloop()

此例添加了 width 與 height 參數, 結果如下 : 




可見下拉式清單只顯示三個選項, 這時清單會自動出現捲軸, 方便點選其他位顯示之選項. 

如果要取得被選取的選項可呼叫 Combobox 物件的 get() 方法, 呼叫 set() 方法則可指定某選項為被選取, 而呼叫 current() 則可傳回被選取選項之索引, 例如 :


測試 3 : 呼叫 get(), set(), 與 current() 方法存取被選取之選項 [看原始碼]

import tkinter as tk
from tkinter import ttk

def show_value():
    label["text"]="選取 : " + combobox.get() + \
                  " 索引 : " + str(combobox.current())        # 取得被選取選項與其索引

win=tk.Tk()                                                      
win.title("tkinter GUI 測試")
win.geometry("300x200")

values=("Python", "Javascript", "R", "Julia", "PHP")
combobox=ttk.Combobox(win, values=values)
combobox.pack()
combobox.set("Python")        # 設定被選取選項
ttk.Button(win, text="確定", command=show_value).pack()
label=ttk.Label(win)
label.pack()
win.mainloop()

此例在建立 Combobox 物件後呼叫其 set() 方法將選項 "Python" 設定為預設被選取項目. 按下確定鈕時其 command 觸發按鈕事件, 呼叫 Combobox 的 get() 方法取得被選取之選項, current() 方法則傳回該項目之索引, 結果如下 :





除了呼叫 Combobox 物件的 get() 方法取得被選取之選項外, 還可以利用 textvariable 參數綁定一個 StringVar 類別變數, 透過其 get() 方法也可以取得被選擇之選項, 而且呼叫類別變數的 set() 亦可設定 Combobox 的預設選項, 例如 :


測試 4 : 用 textvariable 綁定類別變數來存取 Combobox [看原始碼]

import tkinter as tk
from tkinter import ttk

def show_value():
    label["text"]=var.get()        # 呼叫類別變數的 get() 方法取得被選取項目

win=tk.Tk()                                                      
win.title("tkinter GUI 測試")
win.geometry("300x200")

var=tk.StringVar()          # 與 Combobox 綁定之類別變數
var.set("Python")            # 設定預設被選取選項
values=("Python", "Javascript", "R", "Julia", "PHP")
combobox=ttk.Combobox(win, values=values, textvariable=var)    # 綁定類別變數
combobox.pack()
ttk.Button(win, text="確定", command=show_value).pack()
label=ttk.Label(win)
label.pack()
win.mainloop()

此例利用 textvariable 參數綁定一個類別變數 var, 呼叫其 set() 方法可設定被選取之項目, 呼叫 get() 則可取得被選取項目, 結果如下 :





由於 Combobox 沒有像 Spinbox 那樣提供 command 參數, 故上面範例都需要一個按鈕來觸發事件. 不過可以利用 Tkinter 的事件處理機制, 透過元件的 bind() 方法替 ComboboxSelected 事件綁定一個處理函式, 就可以在選取時觸發事件, 例如 :


測試 5 : 呼叫 bind() 方法綁定 ComboSelected 事件處理函式 [看原始碼]

import tkinter as tk
from tkinter import ttk

def show_value(value):
    label["text"]=var.get()

win=tk.Tk()                                                      
win.title("tkinter GUI 測試")
win.geometry("300x200")

var=tk.StringVar()
var.set("Python")
values=("Python", "Javascript", "R", "Julia", "PHP")
combobox=ttk.Combobox(win, values=values, textvariable=var)
combobox.bind('<<ComboboxSelected>>', show_value)
combobox.pack()
label=ttk.Label(win)
label.pack()
win.mainloop()

此例呼叫 Combobox 物件的 bind() 方法為 ComboSelected 事件綁定處理函式, 當使用者選取項目時就會觸發此事件, 不需透過額外的按鈕, 結果如下 :





Tkinter 元件之事件處理參考 :


下面範例測試 state 參數, 預設 "normal" 表示 Combobox 除了可從下拉式選單中選擇項目外, 也可以直接在 Entry 中直接輸入清單以外的任何內容; 若設為 "readonly" 表示只能從下拉式選單中挑選項目, 不能直接輸入; 若設為 "disabled" 則表示禁能, 連下拉式選單也無法使用, 例如 :


測試 6 : 將 state 參數設為 "readonly" [看原始碼]

import tkinter as tk
from tkinter import ttk

def show_value_1():
    label_1["text"]=combobox_1.get()
def show_value_2():
    label_2["text"]=combobox_2.get()

win=tk.Tk()                                                      
win.title("tkinter GUI 測試")
win.geometry("300x200")

values=("Python", "Javascript", "R", "Julia", "PHP")
combobox_1=ttk.Combobox(win, values=values)
combobox_1.pack()
ttk.Button(win, text="確定", command=show_value_1).pack()
label_1=ttk.Label(win)
label_1.pack()
combobox_2=ttk.Combobox(win, values=values, state="readonly")   # 無法直接輸入
combobox_2.pack()
ttk.Button(win, text="確定", command=show_value_2).pack()
label_2=ttk.Label(win)
label_2.pack()
win.mainloop()

此例有兩個 Combobox 元件, combobox_1 未設定 state 參數, 預設為 "normal", 故可輸入可挑選; 但 combobox_2 則設為 "readonly", 無法輸入只能挑選, 結果如下 :




可見點上面的 Combobox 可以輸入任何內容, 但下面的 Combobox 因為是唯讀, 故一點其 Entry 馬上就跳出選單, 無法輸入內容. 

參考 :


銀河與星斗

這學期正常開學後, 又可以在車上聽菁菁播放的新歌了, 最近聽到一首很溫柔的歌 :





此曲不獨旋律溫柔宛如清風拂過, 詞也寫得甚好, 歌者苡慧是馬來西亞人 :


2021年9月15日 星期三

Python 內建 GUI 模組 tkinter 測試 (十三) : Spinbox 元件

本篇測試 tkinter 的 Spinbox (數值微調器) 元件. 

本系列之前的文章參考 :   


Spinbox 元件用來在指定的範圍內選取數值, 事實上它是由一個 Entry 元件與兩個按鈕組成的複合元件, 按上下鈕可讓 Entry 元件中的數值以既定的步階上下變動, 在 tk 與 ttk 中都有提供此元件. 建立 Spinbox 元件之語法如下 : 

Spinbox(父容器, **參數列)



Spinbox 元件常用參數如下表 : 


 Spinbox 常用參數 說明
 from_ Spinbox之起始值 (無預設值, 必須設定)
 to  Spinbox之結束值 (無預設值, 必須設定)
 increment 按 Spinbox 上下鈕時數值的增減步階值 (預紹 1 或 1 格)
 format 數值格式字串, 例如 "%10.4f" 表共 10 個數字浮點數含 4 位小數
 command 按 Spinbox 上下鈕時要呼叫之函式名稱
 textvariable 與 Spinbox 綁定之類別變數名稱 (IntVar, DoubleVar, 或 StringVar)
 width Spinbox 數值顯示區的寬度 (字元數, 預設 20 個字元)
 wrap 按向上鈕到最大值時是否變最小值, 反之亦然=True/False (預設)
 values 指定非數值選項的 tuple 或 list (比 from_, to, 與 increment 優先)
 state 設定 Spinbox 狀態=NORMAL (預設)/DISABLED/READONLY


與 Scale 元件不同的是, Spinbox 的參數 from_ 與 to 無預設值, 故必須自行設定才能使用, 建立物件時不指定雖然不會出現錯誤, 但按上下鈕沒有反應, 無法使用. 

Spinbox 元件常用方法如下表 : 


 Spinbox 常用方法 說明
 get() 傳回 Spinbox 的現值 (字串, 即使內容是數值)
 invoke(element) 模擬按上下鍵動作 (element="buttonup"/"buttondown") (ttk 不支援)


Spinbox 元件若沒有設定 from_ 與 to 參數雖然會顯示元件, 但沒有作用, 例如 : 


測試 1 : 未設定 from_ 與 to 參數沒有作用 [看原始碼]

import tkinter as tk
from tkinter import ttk

def show_selection():
    result_label["text"]=var.get()

win=tk.Tk()                                                      
win.title("tkinter GUI 測試")
win.geometry("300x200")

spinbox_tk=tk.Spinbox(win)     # 建立 tk 版 Spinbox
spinbox_tk.pack()
spinbox_ttk=ttk.Spinbox(win)   # 建立 ttk 版 Spinbox
spinbox_ttk.pack()
win.mainloop()

此例分別建立 tk 與 ttk 版的 Spinbox 物件, 但在建立物件時未傳入 from_ 與 to 參數, 建立後也未使用 [] 運算子或 config() 方法設定, 雖然可順利執行, 但是按上下鍵並無作用, 結果如下 :




可見下方 ttk 版的上下鍵按鈕較大較寬, 按 tk 版的上下鈕無反應, 按 ttk 版的則固定顯示 0, 就算在 Entry 欄位輸入數值後按上下鍵也無作用. Entry 輸入的寬度預設 20 個字元. 數值型 Spinbox 要正常可用需傳入 from_ 與 to 兩個參數, 例如 : 


測試 2 : 數值型 Spinbox 需設定 from_, to 與 width 參數 [看原始碼]

import tkinter as tk
from tkinter import ttk

def show_selection():
    result_label["text"]=var.get()

win=tk.Tk()                                                      
win.title("tkinter GUI 測試")
win.geometry("300x200")

spinbox_tk=tk.Spinbox(win, from_=-10, to=10, width=10)      # 建立 tk 版 Spinbox
spinbox_tk.pack()
spinbox_ttk=ttk.Spinbox(win, from_=-10, to=10, width=10)    # 建立 ttk 版 Spinbox
spinbox_ttk.pack()
win.mainloop()

此例在建立 Spinbox 物件時傳入數值範圍參數 from_ 與 to 使其有作用, 這樣按上下鍵時數字就會以預設的 increment=1 上下跳動. 另外還用 width 參數設定 Entry 輸入欄位寬度為 10 個字元 (預設 20), 結果如下 : 




tk 版的 Spinbox 預設顯示 to 參數的數值, ttk 版的預設不顯示, 但按上下鍵就會顯示 to 參數之值. 按上下鍵時預設增減量為 1, 但可用 increment 參數設定, 例如 :


測試 3 : 用 increment 參數設定增減步階值 (正=順向) [看原始碼]

import tkinter as tk
from tkinter import ttk

def show_selection():
    result_label["text"]=var.get()

win=tk.Tk()                                                      
win.title("tkinter GUI 測試")
win.geometry("300x200")

spinbox_tk=tk.Spinbox(win, from_=-10, to=10, increment=2)     # 順向增減步階值=2
spinbox_tk.pack()
spinbox_ttk=ttk.Spinbox(win, from_=-10, to=10, increment=2)   # 順向增減步階值=2
spinbox_ttk.pack()
win.mainloop()

此例以 increment 參數設定增減步階為 2, 故按上下鍵時每次上下跳兩格, 按向上鍵會按 -10, -8, -6, ... 次序變化 (都顯示偶數值), 結果如下 : 




上例 increment 為正, 表示按向上鍵數值增加, 按向下鍵數值減少 (順向); 但 increment 也可以設為負值, 這樣方向與數值就會變成逆向關係, 即按向上鍵數值減少, 按向下鍵數值增加, 例如 : 


測試 4 : 用 increment 參數設定增減步階值 (負=逆向) [看原始碼]

import tkinter as tk
from tkinter import ttk

def show_selection():
    result_label["text"]=var.get()

win=tk.Tk()                                                      
win.title("tkinter GUI 測試")
win.geometry("300x200")

spinbox_tk=tk.Spinbox(win, from_=-10, to=10, increment=-2)      # 逆向增減步階值=-2
spinbox_tk.pack()
spinbox_ttk=ttk.Spinbox(win, from_=-10, to=10, increment=-2)    # 逆向增減步階值=-2
spinbox_ttk.pack()
win.mainloop()

此例將 increment 設為 -2, 因此一開始只能按向下鍵 (因為按向上鍵就超出範圍了), 其值會從 -10 變 -8, -6, ... 因為減少 -2 即加 2. 然後按向上鍵數值反而減少, 因為加 -2 即減 2 之意, 結果如下 : 




不過這種逆向邏輯與常識相反, 故 increment 還是用正值為宜. 

下面範例測試 wrap 參數, 此參數用來設定當按上下鍵到達數值上下限 (即 from_ 與 to 所限定之邊界) 時是否要反折到另一邊, 設為 True 的話值會從 from_ 變成 to, 或從 to 變成 from_, 亦即數值變化變成一個環, 而非碰頂後停住不動. 此 wrap 參數預設值為 False (不反折), 即到達上下限時再按值也不變, 例如 : 


測試 5 : 用 wrap 參數設定到達上下限時數值是否要反折 [看原始碼]

import tkinter as tk
from tkinter import ttk

def show_selection():
    result_label["text"]=var.get()

win=tk.Tk()                                                      
win.title("tkinter GUI 測試")
win.geometry("300x200")

spinbox_tk=tk.Spinbox(win, from_=-10, to=10, wrap=True)       # 到達上下限時反折
spinbox_tk.pack()
spinbox_ttk=ttk.Spinbox(win, from_=-10, to=10, wrap=True)     # 到達上下限時反折
spinbox_ttk.pack()
win.mainloop()

此例將 wrap 參數設為 True, 當按向上鍵到達 10 時, 再按就變成 -10, 即反折到 to 去; 同理按向下鍵到達 -10 時, 再按一次就變成 10, 即反折到 from 去, 結果如下 : 




下面範例測試 format 參數, 此參數值為一個表示數值的格式字串, 用法參考 print() 函式, 例如 "%10.4f" 表示數值格式為最多 10 個數字, 其中有 4 位小數, 亦即整數部分最多 6 位數, 例如 :


測試 6 : 用 format 參數設定數值格式 [看原始碼]

import tkinter as tk
from tkinter import ttk

def show_selection():
    result_label["text"]=var.get()

win=tk.Tk()                                                      
win.title("tkinter GUI 測試")
win.geometry("300x200")

spinbox_tk=tk.Spinbox(win, from_=-1, to=1, increment=0.0005)
spinbox_tk["format"]="%5.4f"               # 設定數值格式為 4 位小數, 1 位整數
spinbox_tk.pack()
spinbox_ttk=ttk.Spinbox(win, from_=-1, to=1, increment=0.0005)
spinbox_ttk.config(format="%5.4f")      # 設定數值格式為 4 位小數, 1 位整數
spinbox_ttk.pack()
win.mainloop()

此例使用 [] 與 config() 兩種方式設定 Spinbox 物件之 format 參數, "%5.4f" 表示小數為 4 位數, 故整數為 1 位數, 這是配合 from_=-1, to=1, 與 increment=0.0005 之設定, 結果如下 :




Spinbox 元件的 command 參數用來將上下鍵的按鈕事件綁定到一個事件處理函式, 在此函式中可呼叫 Spinbox 物件的 get() 方法以取得微調器之現值, 例如 : 


測試 7 : 用 get() 方法取得微調器之值 [看原始碼]

import tkinter as tk
from tkinter import ttk

def show_value_tk():
    label_tk["text"]=spinbox_tk.get()              # 取得微調器之值
def show_value_ttk():
    label_ttk.config(text=spinbox_ttk.get())    # 取得微調器之值

win=tk.Tk()                                                      
win.title("tkinter GUI 測試")
win.geometry("300x200")

spinbox_tk=tk.Spinbox(win, from_=-10, to=10, command=show_value_tk)
spinbox_tk.pack()
label_tk=tk.Label(win)
label_tk.pack()
spinbox_ttk=ttk.Spinbox(win, from_=-10, to=10, command=show_value_ttk)
spinbox_ttk.pack()
label_ttk=ttk.Label(win)
label_ttk.pack()
win.mainloop()

此例添加了 Label 元件來顯示 Spinbox 的現值, Spinbox 則用 command 參數設定了按鈕事件處理函式, 當按上下鍵時會呼叫 Spinbox 的 get() 方法傳回現值, 用來設定 Label 的文字, tk 版使用 [] 運算子, 而 ttk 版則使用 config(), 兩種方法都可以, 結果如下 : 




注意, 即使微調器的內容是數值, 但 get() 方法傳回的是個字串. 除了值接呼叫 Spinbox 物件的 get() 取得其值外, 也可以用 textvariable 參數綁定一個類別變數來存取微調器, 這種方式除了可以c取得 Spinbox 之值, 也可以用來設定初始值, 例如 : 


測試 8 : 用 textvariable 參數綁定類別變數以動態存取微調器 [看原始碼]

import tkinter as tk
from tkinter import ttk

def show_value_tk():
    label_tk.config(text=var_tk.get())    # 透過類別變數取得 Spinbox 現值
def show_value_ttk():
    label_ttk["text"]=var_ttk.get()            # 透過類別變數取得 Spinbox 現值

win=tk.Tk()                                                      
win.title("tkinter GUI 測試")
win.geometry("300x200")

var_tk=tk.StringVar()
var_tk.set(5)                    # 設定微調器初始值
spinbox_tk=tk.Spinbox(win,
                      from_=-10,
                      to=10,
                      textvariable=var_tk,    
                      command=show_value_tk)    # 綁定類別變數
spinbox_tk.pack()
label_tk=tk.Label(win)
label_tk.pack()

var_ttk=tk.StringVar()
var_ttk.set(7)                    # 設定微調器初始值
spinbox_ttk=ttk.Spinbox(win,
                        from_=-10,
                        to=10,
                        textvariable=var_ttk,    
                        command=show_value_ttk)    # 綁定類別變數
spinbox_ttk.pack()
label_ttk=ttk.Label(win)
label_ttk.pack()
win.mainloop()

此例 Spinbox 元件經由 textvariable 參數綁定了類別變數, 呼叫類別變數的 set() 方法即可設定 Spinbox 的初始值, 呼叫 get() 方法則可取得微調器現值, 結果如下 : 




原本 ttk 的微調器一開始預設是不顯示值的 (tk 則是預設顯示 from_ 之值), 用類別變數的 set() 設定預設值後, 程式一執行就會顯示初始值了. 

除了作為數值微調器外, Spinbox 也可以當選項微調器使用, 意即 Entry 輸入欄位內容是選項字串而非數值, 按上下鈕時選項會上下跳動, 這時 from_, to 與 increment 這三個參數失效 (被 override), 改由 values 參數 (選項字串之 list 或 tuple) 決定微調器的值, 例如 : 


測試 9 : 用 values 參數設定選項 (字串) [看原始碼]

import tkinter as tk
from tkinter import ttk

def show_value_tk():
    label_tk["text"]=spinbox_tk.get()
def show_value_ttk():
    label_ttk.config(text=spinbox_ttk.get())

win=tk.Tk()                                                      
win.title("tkinter GUI 測試")
win.geometry("300x200")

values_tk=("岳不群", "左冷禪", "定逸師太", "任盈盈", "令狐沖")     # 使用元組
spinbox_tk=tk.Spinbox(win, from_=-10, to=10, values=values_tk)       # 設定選項
spinbox_tk["command"]=show_value_tk
spinbox_tk.pack()
label_tk=tk.Label(win)
label_tk.pack()
values_ttk=["張無忌", "趙敏", "楊不悔", "朱九貞", "周芷若"]              # 使用串列
spinbox_ttk=ttk.Spinbox(win, from_=-10, to=10, values=values_ttk)       # 設定選項
spinbox_ttk["command"]=show_value_ttk
spinbox_ttk.pack()
label_ttk=ttk.Label(win)
label_ttk.pack()
win.mainloop()

此例分別使用元組與串列儲存選項, 然後設定為 values 參數之值, 結果如下 :




按上下鍵即可挑選選項作為 Spinbox 之值, 相當於 Radiobutton 的單選功能. 除了字串選項外, values 當然也可以用數值串列, 這樣按上下鍵時就會在這些值之間跳動, 例如 :


測試 10 : 用 values 參數設定選項 (數值) [看原始碼]

import tkinter as tk
from tkinter import ttk

def show_value_tk():
    label_tk["text"]=spinbox_tk.get()
def show_value_ttk():
    label_ttk.config(text=spinbox_ttk.get())

win=tk.Tk()                                                      
win.title("tkinter GUI 測試")
win.geometry("300x200")

values_tk=(1, 3, 5, 7, 9, 11, 13, 15)       # 數值選項
spinbox_tk=tk.Spinbox(win, values=values_tk)
spinbox_tk["command"]=show_value_tk
spinbox_tk.pack()
label_tk=tk.Label(win)
label_tk.pack()
values_ttk=[1.2, 4.3, 8.7, 9.3, 11.1, 13.2, 34.1, 78.9]     # 數值選項
spinbox_ttk=ttk.Spinbox(win, values=values_ttk)
spinbox_ttk["command"]=show_value_ttk
spinbox_ttk.pack()
label_ttk=ttk.Label(win)
label_ttk.pack()
win.mainloop()

結果如下 :




Spinbox 物件的 invoke() 方法可用來模擬按上鍵 (傳入 "buttomup") 與按下鍵 (傳入 "buttondown") 的動作, 但此方法僅 tk 可用, ttk 不支援, 例如 : 


測試 11 : 呼叫 invoke() 方法模擬按上下鍵之動作 [看原始碼]

import tkinter as tk
from tkinter import ttk

def show_value_tk():
    label_tk["text"]=spinbox_tk.get()
def show_value_ttk():
    label_ttk.config(text=spinbox_ttk.get())
def move_up_tk():
    spinbox_tk.invoke("buttonup")             # 模擬按向上鍵動作
def move_down_tk():
    spinbox_tk.invoke("buttondown")        # 模擬按向下鍵動作

win=tk.Tk()                                                      
win.title("tkinter GUI 測試")
win.geometry("300x200")

values_tk=("岳不群", "左冷禪", "定逸師太", "任盈盈", "令狐沖")
spinbox_tk=tk.Spinbox(win, from_=-10, to=10, values=values_tk)
spinbox_tk["command"]=show_value_tk
spinbox_tk.pack()
tk.Button(win, text="上", command=move_up_tk).pack()
tk.Button(win, text="下", command=move_down_tk).pack()
label_tk=tk.Label(win)
label_tk.pack()
win.mainloop()

此例以兩個 Button 元件來模擬 Spinbox 的上下鍵, 按下時會呼叫 invoke(), 結果如下 :




最後來測試 state 參數, 此參數預設為 "normal" 或 tk.NORMAL, 此狀態下 Spinbox 的 Entry 除了可按上下鍵來自動填入跳動的數值或選項外, 也可以直接輸入內容, 這時呼叫 get() 取值時會是輸入之內容. 如果設為 "readonly" 或 tk.READONLY, 則 Entry 欄位無法直接輸入內容, 只能按上下鍵改變內容, 如果設為 "disabled" 或 tk.DISABED 則連上下鍵也動不了, 被禁能了, 例如 : 


測試 12 : 用 state 參數設定 Spinbox 之狀態 [看原始碼]

import tkinter as tk
from tkinter import ttk

def show_value_tk():
    label_tk["text"]=spinbox_tk.get()
def show_value_ttk():
    label_ttk["text"]=spinbox_ttk.get()    

win=tk.Tk()                                                      
win.title("tkinter GUI 測試")
win.geometry("300x200")

spinbox_tk=tk.Spinbox(win, from_=-10, to=10)       # 預設 NORMAL
spinbox_tk.pack()
tk.Button(win, text="確定", command=show_value_tk).pack()            
label_tk=tk.Label(win)
label_tk.pack()
spinbox_ttk=ttk.Spinbox(win, from_=-10, to=10, state="readonly")    # 設為唯讀狀態
spinbox_ttk.pack()
ttk.Button(win, text="確定", command=show_value_ttk).pack()
label_ttk=ttk.Label(win)
label_ttk.pack()
win.mainloop()

此例 tk 版的微調器預設為 NORMAL 狀態, 故既可按上下鍵改變內容, 也可以直接在 Entry 中輸入內容, 按下確定鈕呼叫 get() 方法將取得輸入之值. 而 ttk 版的 state 被設為 "readonly", 故無法於 Entry 中直接輸入內容, 只能按上下鍵, 結果如下 : 




可見 tk 版的 Spinbox 若按上下鍵應取得整數, 但直接輸入浮點數按確定即取得輸入之浮點數. 

2021年9月14日 星期二

曲肖冰的倆倆相忘

之前在看 2019 年版倚天屠龍記時, 張無忌與小昭被成昆困在明教密道時, 小昭唱了一首歌給無忌聽, 這首歌旋律非常悅耳, 詞也非常寫意, 演唱者為中國歌手曲肖冰 :





此曲原唱者為辛曉琪, 是為 1994 年台視倚天屠龍記片尾曲所唱 :





我聽過倆人唱腔後, 覺得曲肖冰翻唱得更好聽ㄟ.