Maya UI 裏的 Callback

Feb 4 2009

animationpython

GUI 是個非常偉大的發明,它讓電腦就這麼快速地滲透進家家戶戶與各大小商辦或公家機關,因為我們有 Mac OS、Windows、X Window System,再加上現下當紅的掌上型界面(iPhone、NDSL、PDS、Android)與從 web browser 裏出生的 web interface,世界就此進入資訊高科技時代,人人都會使用這些電腦設備,因為它們都提供了一套容易使用的 GUI。

我依稀記得我的第一個有 GUI 的程式,是在新竹中學的軟研社22th招生影片)裏頭,使用 QuickBasic 呼叫底層的 VGA interrupt 來存取 video buffer,在螢幕上畫了兩個眼睛,裏頭的眼珠子會依據滑鼠游標所在的方位,往那對應的方向看過去。這隻程式除了滑鼠移動時所造成的 event 以外,就沒別的了,既沒有 button 也沒有 textField 供輸入什麼的,非常的陽春,但我寫得很愉快,這經驗一直是那麼的深刻。事實上,這經驗讓我不討厭在寫程式之餘,花點力氣刻一下 UI,要多花點時間也行。這無形中讓我不至於學到某種壞習慣:programmer 覺得 GUI 是最最討人厭與沒有用的技術。

時間拉回到進入太極影音以降。

在 Maya 裏頭,scripts 提供 GUI programming 的函式,算是進入「MFC 之前,event driven 之後」的時代。所以 Maya 提供的 control(ex, button)大都可以針對一些 event(ex, click)指定一個 callback ,寫這類的程式對於一般修讀過四年資訊課程的學生來說,不是什麼問題。

遺憾的是,Maya 的 callback 機制很原始,callback 裏頭的程式碼並不會繼承所在的 namespace(or scope)的資訊,而是在 global namespace 下執行的,拿下頭這個例子來看:

proc dgCallbackCancel() {
    deleteUI "ooxx";
}

global proc dgShiftAnimationKeysGUI() {
    if(`window -exists "ooxx"`) {
        deleteUI "ooxx";
    }

    string $win = `window -title "ooxx by Drake" "ooxx"`;

    rowColumnLayout -nc 2 -cal 1 "right" -cal 2 "left" -cw 1 100 -cw 2 200 -w 300;
    text -label "Mode : ";
    string $mode = `radioButtonGrp -nrb 2 -labelArray2 "All" "Selected" -cw2 80 80 -select 1 "mode"`;
    text -label "Frames to shift : ";
    string $shift = `textField "shift"`;
    button -label "Do It" -c "dgCallbackDoIt()";
    button -label "Cancel" -c "dgCallbackCancel()";

    showWindow();
}

dgShiftAnimationKeysGUI();

兩個 button 的個別 callback functions 裏頭,為了可以順利存取到其它 control,使用了 hard-coded control name 的方式(直接假設 window name 是 “ooxx”, 另外兩個 control 分別是 “mode” 與 “shift”),另一種作法是使用全域變數的方式,直接把這些 control 的名稱存到全域變數,然後在 callback functions 裏頭使用他們。

透過 pymel (0.7.8) 的包裝,加上 python 動態執行時仍擁有 local scope variables 的特性,可以既不用寫死 control name(寫死也沒關係,但已經沒有這個必要了),也不需要使用到全域變數,就可以做同與上頭一樣的事。

from pymel import *

def dgCallbackCancel(win):
    # just delete the window
    win.delete()

def dgCallbackDoIt(win, shift, mode):
    # do something and then delete the window
    # ...
    modeValue = mode.getSelect()
    shiftValue = shift.getText()

    win.delete()

def dgShiftAnimationKeysGUI():
    winName = "ooxx"
    if window(winName, exists=1):
        deleteUI(winName, window=1)
    if windowPref(winName, exists=1):
        windowPref(winName, remove=1)
    win = window(winName, title="ooxx by Drake")

    rowColumnLayout(nc=2, cal=[(1, "right"), (2, "left")], cw=[(1, 100), (2, 200)], w=300)
    text("Mode : ")
    mode = radioButtonGrp(nrb=2, labelArray2=["All", "Selected"], cw2=[80, 80])
    text("Frames to shift : ")
    shift = textField()
    button("Do It", align="center", c=lambda *args: dgCallbackDoIt(win, shift, mode))
    button("Cancel", align="center", c=lambda *args: dgCallbackCancel(win))

    showWindow(win)
dgShiftAnimationKeysGUI()

請仔細看 button 那兩行的最後。pymel(0.7.8)建議使用 lambda expression 來包裝,然後依自己的需要把 local variables 給傳進 callback function 裏頭。所以在這個例子裏題頭,我可以把 window 與兩個 controls(mode, shift)直接傳給 callback function,而不用去理會任何細節,同時很意外地減少了 programming side-effect。

pymel(0.7.8)對於 Maya GUI control 的包裝還非常的陽春,雖然每個 UI control 都包成一個 class,但用起來與直接呼叫 mel 無異,並沒有提供多少的抽像與方便的設計。舉個最明顯的例子:當你要對任一個 control 取值時,你得記得是使用 getText()、getSelect()、getLabel()、getValue()、getMaxValue()…多少有點強制要求 programmer 要熟習 mel UI programming 那一套,或是得很勤勞地查閱 API 說明文件或是利用 pydoc 之類的。但我想這一點在未來的發展會獲得改善。不過我也意外注意到,這問題不管是古老的 MFC 還是 wxWidgets, Qt,也都一樣有這類的問題就是了。我總覺得,讓使用者愈少去猜測或是記憶要呼叫哪個 member function,就是一個很好的設計 :p

comments powered by Disqus