Component

Component 基本上是 GXT widget 的共同祖先。

Component 有提供 enable()disable() 也有 setter 形式的 setEnable()。 也就是說,有別於 GWT 只有某些 widget 才有 setEnable(), 所有的 GXT widget 都可以有 enable / disable 的狀態。 各式 container 的 setEnable() 會連小孩也一起呼叫 setEnable()

Container 系

(謎之聲:為什麼 TabPanel 不是... ==?)

Container

所有的 widget 都可以設定 HasMargins 的 layout data, 無論是加在哪一個 container 上都會有 margin 的效果。 這是因為 Container.onInser() 會特別檢查 child.getLayoutData() 是否為 HasMargins 並作處理。

SimpleContainer

會自動讓小孩的大小跟自己的大小一樣

BorderLayoutContainer

假設 northLD 的 size 設定為 100、split 設定為 true(北邊區域可以動態調整大小), 調整北邊區域的大小會改變 northLD.getSize() 值。

ContentPanel

在 ui.xml 當中作 contentPanel.addButton() 的方法:

    <core:ContentPanel>
        <core:button>
            <foo:Foo />
        <core:button>
        <core:button>
            <foo:Foo />
        <core:button>        
    </core:ContentPanel>

原來一個 <core:button> 只能塞一個 widget,我之前都誤會它了 [炸]。

Dialog

要知道使用者對 Dialog 做了什麼事情,是掛 addDialogHideHandler() 這個 handler。 從 DialogHideEvent.getHideButton() 可以知道是按下哪個 button。 不知道該說巧妙還是該說 WTF...... Orz

PredefinedButton 的 I18N ###

從 API 上來看,要改變 PredefinedButton(對應的 TextButton)的顯示字串, 就是透過 setDialogMessages() 設定 DialogMessages。 實際上,這個 API 根本有問題...... [怒],

ConfirmMessageBox 為例,流程大概是

constructor
    setPredefinedButtons()
        清空既有 button
        createButtons()
            getText()
                讀 getDialogMessages() 設定對應字串

setDialogMessages() 純粹是 setter,沒有重設顯示字串, 所以 new 完 instance 再 setDialogMessages() 也沒意義。

最直白的 workaround 是

msgBox.getButton(PredefinedButton.OK).setText("OK 的啦");

但是被嫌棄這樣違背 API 設計 / OO 原則(阿就原本 API 設計爛阿幹 T__T), 所以找出第二種 workaround

msgBox.setDialogMessages(myDialogMessages);
msgBox.setPredefinedButton(PredefinedButton.YES, PredefinedButton.NO);

簡單地說,就是再次呼叫 setPredefinedButtons()、重新製造 button, 此時就會用 myDialogMessages 來設定顯示字串啦。

雖然這某種程度還是破壞了 ConfirmMessageBox 的封裝, 不過從程式碼當中可以清楚知道到底有哪些 button, 然後學理上 myDialogMessages 整個 project 只會有一份,好像也不錯啦。

AccordionLayoutContainer

小孩只能是 ContentPanel,而且(應該說「所以」?) ContentPanel 還能塞 AccordionLayoutAppearance 來改變 ContentPanel 的外觀。

HBox / VBox

做了 setPack(),會發現小孩的 layoutData 不管怎麼設定 flex 值, 都不會有預其中的大小變化,而是以小孩原本的大小呈現。 目前這是實驗結果,還沒追 source code [死]。

HorizontalLayoutContainer / VerticalLayoutContainer

HorizontalLayoutData 的 width 數值邏輯為 (VerticalLayoutData 的 height 比照辦理):

  • x > 1:小孩的寬度就是 x

  • x = -1:小孩自己決定自己的寬度(容易錯,使用注意)

  • x < -1:小孩的寬度是「爸爸的寬度加上 x」

    換句話說,就是只留 -x 的寬度給兄弟姊妹

  • 0 < x <= 1:小孩的寬度是「爸爸剩下的寬度乘上 x」。

舉實際的例子,假設一個 HorizontalLayoutContainer 的寬度是 1000, 底下有四個小孩、layoutData 的 width 設定值與實際計算完的寬度分別為:

  • 100→100

  • 0.5→(1000 - 100 - 200 - 600) * 0.5 = 50

  • -1(實際上是 200)→200

  • -400→600

備註 ######

GXT API 文件上針對 0 < x <= 1 這段沒有說明仔細,導致跟實際結果有出入。 以上是追 source code 的結果,詳情可以參閱 HorizontalLayoutContainer.doLayout(), 因為 0 < x <=1 的需求,所以要兩次迴圈才有辦法決定(如果有小孩設定 -1 可能不只)。 第一次迴圈決定 pw,然後第二次迴圈才依照 pw 實際指定小孩的寬度。

輸入系

ComboBox

label 必須是 unique 的, 這是因為 ComboBox.getValue() 會用當下的 getText() 去反找實際的 item instance, 所以如果有兩個 item 是 label 是一樣的, 會導致選第二個 item 後的 getValue() 還是得到第一個 item instance。 以 UI 面來說,這是沒有問題的──使用者本來就無法分辨他到底是選到哪一個...... Orz

setTriggerAction(TriggerAction.ALL) 可以在使用者再次要求下拉選單時顯示所有值。 像 TimeField 就非常需要... Orz

要注意 ComboBox 的顯示值(或著說 LabelProvider.getLabel() 的回傳值)不能是 \n 結尾。 顯示上是不會有問題、也會觸發 SelectionEvent, 但是在 onblur 的時候不會觸發 ValueChangeEvent、ComboBox 的值會消失。

如果 item 的值是 null,顯示時不會觸發 LabelProvider.getLabel(), 在 expand 時選單會多一個高度很詭異的空白行。

setEditable(false) 好像直接包含 setSelectOnFocus()setForceSelection() 的功能, 此乃實驗結果,有待 trace code 證明 [死]。 另外,如果搭配 FieldLabel 使用,居然點 FieldLabel 的 label 也會觸發 focus 效果, 這什麼黑魔法阿阿阿阿... Orz

如果 extend ComboBox,然後想要在裡頭用自己定義的 enum PKG_NAME.Direction, 在 Eclipse 當中會預設使用 com.google.gwt.i18n.client.HasDirection.Direction (還不用 import... WTF ==")。 目前唯一合理的解釋是 ComboBox 的祖先 ValueBaseField 有實作 HasDirectionEstimatorAutoDirectionHandler.Target, 裡頭有 import com.google.gwt.i18n.client.HasDirection.Direction, 可是這還是不科學阿阿阿...... Orz

详细说明 setTriggerAction()setForceSelection()setEditable()setSelectOnFocus() 的作用和相互影响。

  • setTriggerAction()是否显示全部下拉框中全部内容。 如果是 TriggerAction.QUERY,只能显示根据 ComboBox 内容筛选过后的内容。 如果是 TriggerAction.ALL,在输入的时候会筛选对应内容, 但是在按下下拉框按钮的时候会显示全部内容。 默认值:TriggerAction.QUERY

  • setForceSelect()是否允许 ComboBox 中的值不在下拉框中。 如果是 true,ComboBox 输入的值不在下拉框中,lose focus 后会清空。 (如果使用setValue()设定一个不在下拉框的输入值不会清空) 默认值:false

  • setEditable()是否允许 ComboBox 中直接输入值。 如果是 false,无法在 ComboBox 中直接输入值。 默认值:true

  • setSelectOnFocus()是否在 focus 的时候出现反白的效果。 如果是 true,会在 focus 的时候选中 ComboBox 中的值,出现反白的效果 (只在鼠标点击的时候出现效果,松开后效果消失),使用 Tab 键进入 ComboBox 不会出现这个效果。 默认值:false

  • 打V表示符合描述。打X表示不符合描述。 (包括使用 setValue() 的情况)

setTriggerAction()

setForceSelect()

setEditable()

setSelectOnFocus()

下拉框只显示 ComboBox 筛选过内容

允许 ComboBox 中的值不在下拉框中

允许 ComboBox 中输入值

在 focus 的时候不出现反白的效果

TriggerAction.QUERY

true

true

true

V

X

V

X

TriggerAction.QUERY

true

true

false

V

X

V

V

TriggerAction.QUERY

X

false

X

V

V

X

V

TriggerAction.QUERY

false

true

true

V

V

V

X

TriggerAction.QUERY

false

true

false

V

V

V

V

TriggerAction.ALL

true

true

true

X

X

V

X

TriggerAction.ALL

true

true

false

X

X

V

V

TriggerAction.ALL

X

false

X

X

V

X

V

TriggerAction.ALL

false

true

true

X

V

V

X

TriggerAction.ALL

false

true

false

X

V

V

V

DateField

觸發 setValue() 的來源 ###

DatePicker ####

  • 当 new 一个 DateField 时,就 new 了一个 DateCell 和一个 DateTimePropertyEditor

  • 当使用者点击画面上的 DateCell 时,会通过 DateCell.onTriggerClick() 呼叫 DateCell.expand(),在这里 new 了一个 DatePicker

  • 因为在 DatePickerConstructor 中实现了 sinkEvents(),而且在 onBrowserEvent() 中实现了 ONCLICK|ONMOUSEOVER 等的具体操作,所以当画面上发生 ONCLICK Event 则会呼叫 DatePicker.onClick(Event)

按下 today #####

  • 使用者在出现的 date picker 上点击【today】时,作 addSelectHandler() 让 select event 发生时去呼叫 selectToday(),结果呼叫 DatePicker.setValue(Date, boolean),设置 value 的值;

點擊日曆上的日期 #####

  • 当使用者触发 ONCLICK Event 时,呼叫 DatePicker.onClick(Event),若触发为当月的某一天则呼叫 DatePicker.onDayClick(XElement);然后通过 DatePicker.handleDateClick(XElement, String) 呼叫 DatePicker.setValue(Date, boolean),设置 value 的值;

直接輸入字串 ####

  • 当 new 一个 DateField 时,就 new 了一个 DateTimePropertyEditor 和一个 DateCell

  • 当使用者在 text 中输入数据时,呼叫 ValueBaseField.setValue(T),实际上是呼叫了 CellComponent.setValue(C, boolean, boolean),在这里设置 value 的值;

  • (确实没有找到回到 DatePicker.setValue() 的地方,但确实这里应该会做 resetTime(),我也没有找到在什么时候做 resetTime() 的,/(ㄒoㄒ)/~~)

setValue() 的處理 ####

  • DatePicker.setValue(date, true) 時會作 this.value = new DateWrapper(date).resetTime()

    resetTime() 的 source code 與結果有點對不起來。

    就結果而言,會將指定日期的時間部份設定為零點零分零秒。

其他

Grid

GridView ###

GridView.setForceFit(),如果設定 true 則 Grid 不會出現橫向 scroll bar; 沒有作 setFixed(true) 的 column 會依設定 width 為比例分配剩餘的寬度。

GridView 可以設定 GridViewConfig,藉此設定 row / column 的 style── 正確的說是加掛 style name。 實務上很吃 CSS 技能所以不好用,還有導致其他 style 亂掉的風險。 要處理 style 還是用 ColumnConfig.setCell(), 在自訂的 cell 裡頭設計比較容易且影響範圍可控。

可以透過 GridView.getScroller() 控制 Grid 的 scroll 狀態。 當 refresh() 結束之後不想強制 scroll 回左上角,就必須得靠這招。

GroupingView ####

如果希望資料初次載入後呈現 collapse 的樣子, 在操作完 store 之後對 view 作 collapseAllGroups() 是不正確的作法, 因為之後只要再對 view 作 refresh(), 原本沒有 expand 的 group 也會 expand。 正確作法是在 grid 初始時作 GroupingView.setStartCollapsed(true)

AbstractGridEditing ###

setEditableGrid() 如果傳入 null 值是表示移除 edit 的效果, 因為一開始會作 groupRegistration.removeHandler(),然後在傳入值不為 null 才會掛相關 handler。 這在初始化設定(尤其搭配 ui.xml)時要特別注意 [淚目]

使用者的編輯行為會透過 GridEditing 影響 GridStore, 實際作為是增加 ChangeRecord。 目前看起來,Store 只有提供兩個對應的 method:commitChanges()rejectChanges() 來清空 modifiedRecords→才會讓畫面上的 dirty 樣子消失(應該是 StoreUpdateEvent 的結果)。 commitChanges()rejectChanges() 都會觸發 StoreUpdateEvent, 實驗結果:rejectChanges() 如果影響 n 個 item,就會炸 n 個 StoreUpdateEvent

Store.getModifiedRecords() 可以得到有改變(dirty)的 item:

    for (Store<Foo>.Record r : store.getModifiedRecords()) {
        Foo fooInstance = r.getModel();
        //此時 fooInstance 的內容為初始值,可以趁機作一些事情(?)
    }

要讓 fooInstance 的內容變成現在的值,有幾種作法:

  • r.commit(boolean):單筆

  • store.commitChanges():整個 store

  • 根據 Record / Change 來修改 fooInstance [暈]

至於要發 RPC、萬一 RPC 失敗要能 rollback... 目前還沒有想法 [死]

LabelToolItem

LabelToolItem 轉換成 DOM 就只是一個 div 包一個字串,當然有掛一些 css(沒有特別的內容), 除了 package name 很奇怪之外,基本上應該可以視為 GXT 版的 label。 (謎之聲:之前測試不知道是測試到哪裡去了... (艸 )

TabPanel

在 ui.xml 當中只能這樣寫

    <ui:with field="tabPanelConfig" type="com.sencha.gxt.widget.core.client.TabItemConfig">
        <ui:attributes text="tab 名字" />
    </ui:with>
    <gxt:TabPanel>
        <gxt:child config="tabPanelConfig">
            <foo:Foo />
        </gxt:child>
    </gxt:TabPanel>

有點討厭,不過就算自己設計好像也沒辦法改變什麼 Orz

Toggle

如果只是把 HasValue 加到 Toggle 當中、畫面也還沒進行任何操作, 畫面顯示是會正常的,但是 toggle.getValue() 會是 null。 所以 toggle.add() 完還是 toggle.setValue() 一下比較保險。

DrawComponent

獨立出去紀錄在 DrawComponent

DnD

new DragSource(dSource) 會讓 dSource 變成可以 drag 的 widget, 實際上要看實作,像 TreeDragSource 就是讓 tree 的 item 變成可以 DnD。 new DropTarget(dTarget) 會讓 dTarget 變成可 drop 的對象。 在沒有特別的設定下(也就是 default group),任何的 DropTarget 都會接收任何 DragSource 的 DnD 行為。 反過來說,如果 dSource.setGroup("5566"),那麼 dTarget 必須也要是 5566 這個 group, 才有辦法接收源自於 dSource 的 DnD 行為。

如果 parentchild 這兩個 widget 都是 drop target(不考慮 group), parentchild 同時在畫面上、且 parent 包含 child, 則如果在 child 上作 drop 的動作,只會觸發 childDndDropHandler、 而不會觸發 parentDndDropHandler

Last updated