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:button>
只能塞一個 widget,我之前都誤會它了 [炸]。
Dialog
要知道使用者對 Dialog
做了什麼事情,是掛 addDialogHideHandler()
這個 handler。 從 DialogHideEvent.getHideButton()
可以知道是按下哪個 button。 不知道該說巧妙還是該說 WTF...... Orz
PredefinedButton 的 I18N ###
從 API 上來看,要改變 PredefinedButton
(對應的 TextButton
)的顯示字串, 就是透過 setDialogMessages()
設定 DialogMessages
。 實際上,這個 API 根本有問題...... [怒],
以 ConfirmMessageBox
為例,流程大概是
setDialogMessages()
純粹是 setter,沒有重設顯示字串, 所以 new 完 instance 再 setDialogMessages()
也沒意義。
最直白的 workaround 是
但是被嫌棄這樣違背 API 設計 / OO 原則(阿就原本 API 設計爛阿幹 T__T), 所以找出第二種 workaround
簡單地說,就是再次呼叫 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
有實作 HasDirectionEstimator
跟 AutoDirectionHandler.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
;因为在
DatePicker
的Constructor
中实现了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
影響 Grid
的 Store
, 實際作為是增加 Change
跟 Record
。 目前看起來,Store
只有提供兩個對應的 method:commitChanges()
跟 rejectChanges()
來清空 modifiedRecords
→才會讓畫面上的 dirty 樣子消失(應該是 StoreUpdateEvent
的結果)。 commitChanges()
跟 rejectChanges()
都會觸發 StoreUpdateEvent
, 實驗結果:rejectChanges()
如果影響 n 個 item,就會炸 n 個 StoreUpdateEvent
。
從 Store.getModifiedRecords()
可以得到有改變(dirty)的 item:
要讓 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 當中只能這樣寫
有點討厭,不過就算自己設計好像也沒辦法改變什麼 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 行為。
如果 parent
、child
這兩個 widget 都是 drop target(不考慮 group), parent
跟 child
同時在畫面上、且 parent
包含 child
, 則如果在 child
上作 drop 的動作,只會觸發 child
的 DndDropHandler
、 而不會觸發 parent
的 DndDropHandler
。
Last updated