網頁

顯示具有 GWT 標籤的文章。 顯示所有文章
顯示具有 GWT 標籤的文章。 顯示所有文章

2010年7月1日 星期四

[GWT] GWTCanvas 簡易範例

GWTCanvas 是 Google Web Toolkit Incubator 的一個子項目,其作用是提供了操作 HTML5 Canvas 的 API 供 GWT 使用。

在官方的 wiki 上可以看到簡易的操作說明,因為並不複雜,所以這裡就不多做說明了。有興趣者也可以參考 API


接下來我利用 GWTCanvas,配合 UiBinder, Timer,試著寫了一個 Bubble Sort 的教學範例,其功能就像是以往常見的 Applet。(由於此篇文章的重點是在於 Canvas 而不是 Bubble Sort,所以請原諒我只撰寫了非常陽春的 Demo)

完整的專案可以在此下載: canvas.zip

程式碼內容如下: BubbleSortDemo.java
package canvas.client;

import com.google.gwt.core.client.EntryPoint;
import com.google.gwt.core.client.GWT;
import com.google.gwt.event.dom.client.ClickEvent;
import com.google.gwt.uibinder.client.UiBinder;
import com.google.gwt.uibinder.client.UiFactory;
import com.google.gwt.uibinder.client.UiField;
import com.google.gwt.uibinder.client.UiHandler;
import com.google.gwt.user.client.Timer;
import com.google.gwt.user.client.ui.Button;
import com.google.gwt.user.client.ui.HTMLPanel;
import com.google.gwt.user.client.ui.RootLayoutPanel;
import com.google.gwt.widgetideas.graphics.client.Color;
import com.google.gwt.widgetideas.graphics.client.GWTCanvas;

public class BubbleSortDemo implements EntryPoint {

    interface uiBinder extends UiBinder<HTMLPanel, BubbleSortDemo> {
    }

    private static uiBinder uiBinder = GWT.create(uiBinder.class);
    
    @UiField GWTCanvas canvas;
    @UiFactory GWTCanvas makeCanvas(int width, int height) {
        return new GWTCanvas(width, height);
    }
    
    public static final int LENGTH = 10;
    
    private Timer t;
    private int i;
    private int j;
    private int[] arr;

    @UiField Button nextBtn;
    @UiField Button playBtn;
    @UiField Button stopBtn;
    @UiField Button resetBtn;
    
    private void initArray(){
        arr = new int[LENGTH];
        
        for(int i=0;i<LENGTH;i++){
            arr[i] = (int)(Math.random()*100); 
        }
    }

    private void initVariable() {
        i = LENGTH-1;
        j = 0;
    }
    
    private void display(){
        canvas.clear();
        
        for(int i=0;i<LENGTH;i++){          
            int gray = arr[i]*2;
            canvas.setFillStyle(new Color(gray, gray, gray));

            int height = arr[i];
            int x = 30+i*30;
            int y = 130-height;
            canvas.fillRect(x, y, 20, height);
        }
    }   
    
    private void swap(int a, int b){
        int t = arr[a];
        arr[a] = arr[b];
        arr[b] = t; 
    }

    @Override 
    public void onModuleLoad() {

        HTMLPanel outer = uiBinder.createAndBindUi(this);
        RootLayoutPanel.get().add(outer);
        
        initArray();
        
        display();

        /*
        // This is the original version bubble sort.
        for(int i=LENGTH-1;i>=0;i--){
            for(int j=0;j<i;j++){
                if(arr[j]>arr[j+1]){
                    swap(j, j+1);
                }
            }
        }
        */

        initVariable();
        
        t = new Timer() {
            public void run() {
                while(i>=0){
                    
                    while(j<i){
                        
                        if(arr[j]>arr[j+1]){
                            swap(j, j+1);
                        }
                        
                        display();
                        
                        j++;
                        return;
                    }
                    
                    i--;
                    j = 0;
                    return;
                }
                
                t.cancel();             
            }
        };
        
    }
    
    @UiHandler("nextBtn")
    void handleNext(ClickEvent e){
        t.run();
    }
    
    @UiHandler("playBtn")
    void handlePlay(ClickEvent e){
        t.scheduleRepeating(500);
    }
    
    @UiHandler("stopBtn")
    void handleStop(ClickEvent e){
        t.cancel();
    }
    
    @UiHandler("resetBtn")
    void handleReset(ClickEvent e){
        initArray();
        initVariable();
        display();
    }

}



其對應的 UiBinder: BubbleSortDemo.ui.xml
<!DOCTYPE ui:UiBinder SYSTEM "http://dl.google.com/gwt/DTD/xhtml.ent">
<ui:UiBinder xmlns:ui="urn:ui:com.google.gwt.uibinder"
 xmlns:g="urn:import:com.google.gwt.user.client.ui" xmlns:graphics="urn:import:com.google.gwt.widgetideas.graphics.client">
 <ui:style>  
  .canvas{
   border: solid 1px black;
  }
 </ui:style>
 <g:HTMLPanel>
  <graphics:GWTCanvas ui:field="canvas" styleName="{style.canvas}" width="350" height="200"></graphics:GWTCanvas>
  
  <br />
  <g:Button ui:field="nextBtn">Next</g:Button>
  <g:Button ui:field="playBtn">Play</g:Button>
  <g:Button ui:field="stopBtn">Stop</g:Button>
  <g:Button ui:field="resetBtn">Reset</g:Button>
 </g:HTMLPanel>
</ui:UiBinder> 

2010年2月14日 星期日

[GWT] 使用外部 resource

使用外部 resourceDeclarative Layout with UensureInjected()iBinder 其中的一個章節,並且已經由 PsMonkey 完成此文的翻譯

我在實作這個章節所提及的方法時,遭遇了一點問題,因此提出來與大家分享。

在此說明一下我所遭遇的問題:

  1. 我寫了一個名為 Resources 的 interface,打算在其他的 template file 中使用,內容如下。


    public interface Resources extends ClientBundle {
        @Source("Style.css")
        MyStyle style();
    
        @Source("Logo.jpg")
        ImageResource logo();
    
        public interface MyStyle extends CssResource {
            String red();
        }
    } 
  2. 當然 Style.css 與 Logo.jpg 都是存在的,並且 Sytle.css 中也定義了 .red
  3. 然而當我使用如下的 template file 去使用 Resources 時,雖然 Image 能夠正常顯示,但是所有與 CssResource 相關的設定都沒有作用。


    <ui:UiBinder xmlns:ui='urn:ui:com.google.gwt.uibinder'
        xmlns:g='urn:import:com.google.gwt.user.client.ui'>
    
      <ui:with field='res' type='com.my.app.widgets.logoname.Resources'/>
    
      <g:HTMLPanel>
    
        <g:Image resource='{res.logo}'/>
    
        <div class='{res.style.red}'>
          this text should be red
        </div>
    
      </g:HTMLPanel>
    </ui:UiBinder> 
  4. 如果使用 Firebugs 等工具觀察, 會看到對應的 HTML 元素確實被指定了 CSS class,然而在 CSS file 內卻沒有對應的設定存在。

解決的方法為,建立一個 Resources 實體並呼叫 CssResource 的 ensureInjected() 方法,以確保 CssResource 有被加入 DOM 中。
Resources resources = GWT.create(Resources.class);
resources.style().ensureInjected(); 

值得注意的是,無論創造了幾個 MyStyle 實體(包含在 Resources 內),也只需要執行一次 ensureInjected() 。所以合理的作法應該是在 EntryPoint 內呼叫 ensureInjected(),如果希望所有的頁面都能使用同一個 Resources 實體,可以將此處建立的物件傳給其他頁面,詳細作法參考同一篇文章中的 Share resource instances 章節(中譯: 共用 resource 的 instances)。

2010年1月7日 星期四

[GWT] 使用具有 Constructor 參數的 Widget

本文是根據這篇文章重新以中文說明 (Using a widget that requires constructor args)

下方是使用 uiBinder 建立 UI 的簡單例子:
<!-- UserDashboard.ui.xml -->

<ui:UiBinder xmlns:ui='urn:ui:com.google.gwt.uibinder'
    xmlns:g='urn:import:com.google.gwt.user.client.ui'
    xmlns:my='urn:import:com.my.app.widgets' >

    <g:HTMLPanel>
        <my:CricketScores ui:field='scores' />
    </g:HTMLPanel>
</ui:UiBinder>
執行時 GWT 會偷偷的用 GWT.create() 建立 CricketScores 的實體 (經由Deferred Binding)。

但如果 CricketScores 的 Constructor 必需要傳入參數時,例如:
public CricketScores(String... teamNames) {...} 
這時 GWT.create() 就會出現錯誤:
[ERROR] com.my.app.widgets.CricketScores has no default (zero args) constructor. To fix this, you can define a @UiFactory method on the UiBinder's owner, or annotate a constructor of CricketScores with @UiConstructor.

這時候共有三種解決方法:
  1. 在 UserDashboard.java 中加入 @UiFactory method:
    // method name is insignificant
    @UiFactory CricketScores makeCricketScores() {
        return new CricketScores(teamNames);
    }
    
    @UiFactory method 的 method name 沒有特定的命名規則,GWT 會自動根據 return type 做判斷。也正因如此,如果一個 class 當中出現兩個相同 return type 的 @UiFactory method 時,GWT 也會顯示錯誤訊息:

    [ERROR] Duplicate factory in class UserDashboard for type CricketScores
  2. 使用 @UiConstructor:

    將 CricketScores 的 Constructor 加入 @UiConstructor annotation:
    public @UiConstructor CricketScores(String teamNames) {
        // ....
    } 
    
    並在 UserDashboard.ui.xml 內,CricketScores 標籤內加入與 Constructor 參數相同的屬性即可:
    <my:CricketScores ui:field='scores' teamNames='AUS, SAF, WA, QLD, VIC'/>
    
  3. 由 UiField(provided=true) 提供已建立好的物件:
    於 UserDashboard's Constructor 將 CricketScores 物件傳入,並以 @UiField 宣告 reference 變數指向 CricketScores 物件:
    public class UserDashboard extends Composite {
        interface MyUiBinder extends UiBinder<Widget, UserDashboard>;
        private static final MyUiBinder uiBinder = GWT.create(MyUiBinder.class);
    
        @UiField(provided=true)
        final CricketScores cricketScores; // cannot be private
    
        public UserDashboard(CricketScores cricketScores) {
            this.cricketScores = cricketScores;
            initWidget(uiBinder.createAndBindUi(this));
        }
    }
    

2009年12月20日 星期日

[GWT] 利用 HandlerManager 實作共用的 Event Bus

GWT 從版本 1.6 開始提供了新的 Event Model - Handler,並捨棄了原先的 Listener 方式。

關於 Listener 與 Handler 的差別,我推荐看這篇文章: GWT’s new Event Model – Handlers in GWT 1.6 [1]。

在這篇文章中,我將兩者與本篇文章有關的部份做介紹:


Listener:
  1. 對於每一個 Event Source 會有一個對應的 Listener,例如 MouseListener, KeyboardListener...等。
  2. 在每一個 Widget 中,針對每一種 Event Source 的 Listener,都會用一個獨立的 ListenerCollection 去紀錄。
Handler:
  1. 每一個 Event Type 都會有一個對應的 Handler,因此原先 MouseListener 相關的事件,將會被重新區分成 MouseDownHandler, MouseUpHandler...等。這樣做的一個額外的好處是,當只需要針對一種 Event Type 做處理時,實作 Handler 的類別只需要 over-ridding 一個對應的 method,而不需要像寫 Listener 時,仍然需要 over-ridding 其他沒用到的 method。
  2. 每一個 Widget 中,都只會有一個 HandlerManager,負責紀錄所有的 Event Handler。

由於 HandlerManager 能夠針對不同的 Event,dispatch 其對應的 Handler 去處理,因此我們也可以利用 HandlerManager 來做為整個 Web App 共用的 Event Bus。[2], [3]


先說明一下本篇文章欲使用的例子:
在一個 Web App 上,有許多的 Panel,我們現在希望當按下 A Panel 上的 Button 時,B Panel 上的 TextBox 會輸出文字。

在原本的做法裡,我們會讓 A panel 實作 ClickHandler,當 A panel 收到事件時,就去呼叫 B panel 所提供的方法。

這個作法會使得 A panel 必須持有 B panel 的 reference(就 UML 的角度來說就是 A 對 B 有 Aggregation 關係),這樣會使得 A, B 之間的耦合度高,造成未來擴充、修改的困難。

接下來就是解決辦法啦!

  1. 首先,於 EntryPoint 建立 HandlerManager 物件,並且將 Reference 傳給所有的 sub-component,這個部份可以利用 Constructor 達成。
    HandlerManager eventBus = new HandlerManager(null); 
  2. 接下來為每一個欲處理的事件寫一組 Event / Handler
    public class FooEvent extends GwtEvent<FooEvent.FooHandler> {
    
        public interface FooHandler extends EventHandler{
            void onFoo(FooEvent event);
        }
    
        public static final GwtEvent.Type<FooHandler> TYPE =
                new GwtEvent.Type<FooHandler>();
    
        @Override
        protected void dispatch(FooHandler handler) {
            handler.onFoo(this);
        }
    
        @Override
        public GwtEvent.Type<FooHandler> getAssociatedType() {
            return TYPE;
        }
    }
    
  3. 然後在 B panel 裡頭寫上事件處理,並且向 Event Bus 註冊。
    @Override
    protected void onLoad(){
        // register event handler
        fooRegistration = eventBus.addHandler(FooEvent.TYPE, new FooHandler(){
            @Override
            public void onFoo(FooEvent event) {
                textBox.setText("A panel's button click!");
            }
        });
    }
    
    @Override
    protected void onUnload(){
        // unregister event handler
        fooRegistration.removeHandler();
    }
    
  4. 最後是 A panel 的事件觸發,當按下按鈕時:
    eventBus.fireEvent(new FooEvent());
    

這樣一來,A 與 B 彼此可以完全不知道對方的存在,將可以大大的降低耦合度。

參考資料:
[1]: GWT’s new Event Model – Handlers in GWT 1.6
[2]: Google Web Toolkit Architecture: Best Practices For Architecting Your GWT App
[3]: GWT Event Bus Discussions
[4]: 1.6 版多了什麼新東西?