#

這個章節我們會把重點放在控制播放進度的功能,iPlayer將會加入一個滑桿視窗元件,用來提供使用者搜尋音樂片段的能力。關於滑桿視窗元件,由於它是第一次出現在文件裡,所以這篇文章的開頭會先介紹一些滑桿元件的使用方法,然後才進入主題。

24.1 滑桿視窗元件

滑桿是很常見的視窗元件,它時常使用來表示或控制某一範圍的數值。它長得像這樣:

圖 24-1


滑桿的中間有個可以拉動的按鈕,它可以用來設定滑桿的目前值,然後滑桿的最左邊表示可以設定的最小值,而最右邊表示可以設定的最大值。例如下面的程式::ttk::scale可以用來建立個滑桿元件,我設定它最左邊表示為數值的0,最右邊表示為數值的100,然後滑桿上的按鈕是停留在30的位罝。



如上-from可以用來指定scale的下邊界值(左邊),-to用來表示上邊界值(右邊),而-value則用來指定目前的值。

下表是scale常用的選項及說明。

-command 如同button命令的-command選項,其後可以接受一段Tcl程式,而此程式會在滑桿按鈕位置改變時被執行。 值得注意的是,此選項後的程式被執行時,程式的最後面會自動加上滑桿的目前值。
-from 此選項可以用來指定滑桿最左邊(上方)所表示的值。
-orient 此選項可以用來指定滑桿要水平(horizontal)或是垂直(vertical)顯示。
-to 此選項可以用來指定滑桿最右邊(下方)所表示的值。
-value 此選項可以用來指定滑桿的目前值。
-variable 如同entry的同名選項,它可以指定一個變數來即時反應scale的值。

scale常用的視窗元件命令只有兩個,它們分別是用來取得目前值的get命令:



以及用來設定目前值的set命令:



這個例子可以用程式把滑桿的目前值設定為50,而不需要使用者真的去拉滑桿按鈕。

24.2 建立搜尋滑桿

根據之前的規劃控制用的滑桿應該要放在播放清單的下方,也就是整個iPlayer視窗的最下方,所以我們需要在gui_init的最後面加上建立滑桿的程式。



這個程式的重點在bind所在的那一行,因為我希望滑桿按鈕在放開時才去調整MP3的播放的進度,所以我把滑桿綁定了「滑鼠左鍵放開」的事件,如此一來每當滑鼠抓住滑桿按鈕然後放開時就會執行::iPlayer::seek程序(還沒有實作),再由這個程序去調整目前播放的進度。

24.3 實現seek程序

接下來我們要寫一個seek的程序,用它來調整播放進度。但在開始寫seek程序之前,還有兩個問題要先處理。1.滑桿的最大值何時決定? 2.如何取得播放中MP3的總長度? 第一個問題的解法比較好想像,只要在載入MP3時取得播放長度,然後設定給滑桿就好了。第二個問題的解決方法如下:

snack可以用兩種方式來取得MP3的總長度。第一種是取得總秒數:



第二種方法是取得總sample數,即總取樣數。



基本上這兩種方式都是可以使用的,但第一種比較適合人看,所以我打算在每次播放新MP3時把總秒數設定給滑桿當上邊界值,這需要對play程序做點小修改,修改好,第一個問題也就順道解決了。



好,現在可以開始撰寫seek程序了。注意哦!! 這個程式會在你抓住滑桿按鈕然後放下時被執行,也就是說放下按鈕時滑桿的目前值就是我們要調整的播放位置(秒數)。



由於snack只能用sample數定位要播放的位置,所以上面才會有秒數轉換sample數的程式。請測試一下程式然後拉一下滑桿,看可不可以正確控制播放進度。

圖 24-2


24.4 讓滑桿跟隨播放進度

上一個小節的內容,我們做到了讓使用者透過滑桿來控制播放進度的功能,現在要讓滑桿上的按鈕可以跟著音樂的播放一點一點的向前移動。這個功能的原理並不困難,只要寫一個可以週期性執行的程序,然後在這個程序裡不斷的更新滑桿的目前值即可。接下來讓我們看看要怎麼做到。

□ after命令

為了製作可以週期性執行的程序,我們會需要after命令,它可以讓我們把某一片段的程式交給Tcl內部的計時器,然後延遲一段時間後才執行,例如:



請看到第2行,after命令會把後面大括號的程式交給Tcl內部的計時器,並指定它在1000毫秒(1秒)後執行。注意哦!! after把大括號後面的程式交給計時器後就會立刻往下執行,並不會停留在那一行等時間到。所以這個程式執行起來會有如下的輸出。

AAA
BBB
Hello

了解after的特性後,現在我們可以做出一個週期性執行的程序了,技巧大概如下:



timer程序的最後一行使用了after命令讓Tcl內部的計時器在1秒後再度執行自己,所以這個程式每隔約1秒的時間就會輸出一個A。這樣也就達到週期執行的目的了。

□ 實作timer程序

接下來的程式會建立一個叫timer的新程序,這個程序我打算讓它在播放MP3的時候週期性的執行,當然它功能就是用來更新滑桿按鈕的位置。



因為滑桿上的單位是秒數,所以上面的程式做了一些取樣數轉換秒數的動作。最後為了讓timer程序會被啟動,還要在play程序加上觸發點。



在timer程序執行前,為了確保timer程序不會重複被啟動,所以我使用了after cancel命令把處於內部計時器中的timer程序取消掉,注意哦!! 如果內部計時器中沒有timer程序的話,after cancel並不會發生錯誤。現在請度測試程式,滑桿的功能大致上應該都正常。

24.5 改進滑桿不順暢的缺點

測試程式時你應該會發現滑桿拉起來不太順暢,其實這是必然的現像,因為我們在拉滑桿時timer還是持續的在執行,也就是說timer程序會和你搶滑桿按鈕的控制權。改進這個問題的方法很簡單,只需要在按下滑鼠左鍵時先停掉timer程序,然後在放開左鍵時再重新啟動timer程序就好了。這個功能要動到兩個程序,首先是gui_init程序。



我多綁了一個「滑鼠左鍵下壓事件」,每當這個事件發生時都會用after canel命令把計時器中的::iPlayer::timer程序取消。

然後是seek程序的修改。



Ok! 只加了兩行程式而以,改完後應該就順暢多了。

24.6 程式資源

在這邊可以下載到目前已完成的程式 - iPlayer.zip

3 個意見

Mike | 2015年1月16日 上午9:40

Hi Dai大大,又來請問問題,
我這邊不管在ubuntu 14.04內建的tcl,還是另外自己編的tcl 8.6.3想要執行你after的那兩個範例程式都無法得到相同的結果。
第一個範例程式只有show出
AA
BB
就結束了,第二個範例根本甚麼都沒有跑出來。

另外想要問一個基本的問題,中括號和大括號有什麼不同啊?我把中間那行改成 after 1000 [puts hello]
他就會show出
AA
hello
BB
並沒有任何等待,請問這是正常的嗎?
PS:第二個範例則是什麼都沒有就結束了。

Mike | 2015年1月16日 上午9:51

其實我想要的功能是,按下button後背景在跑我的system command,UI 的部分能夠更新進度,但又並不想用 tcl 的thread來處理這樣的function,
主要的原是因為ubuntu內建的tcl/tk 沒有 thread功能,雖然我自己的另外編8.6.3有thread,但是這樣就等於我在附上自己程式的時候,
還要附上tcl/tk的主程式,這樣會有各種Linux版本差異的問題,就失去tcl/tk這種跨平台script的意義了。
還是可否請問,我可以把我編好 thread 的 package 抽出來嗎?

匿名 | 2015年11月13日 下午1:49

用wish來執行該段程式
就不會有你上述的問題了

留下您的意見

Theme Design by devolux.org. Converted by Wordpress To Blogger for WP Blogger Themes. Sponsored by iBlogtoBlog
This template is brought to you by : allblogtools.com | Blogger Templates