<rt id="bn8ez"></rt>
<label id="bn8ez"></label>

  • <span id="bn8ez"></span>

    <label id="bn8ez"><meter id="bn8ez"></meter></label>

    Java桌面技術

    Java Desktop Technology

    常用鏈接

    統計

    友情連接

    最新評論

    用SWT實現MSN風格的下拉框

    SWT一個所謂的優點是它的本地化外觀,因為它是通過JNI調用操作系統的組件,從而可以保證外觀上適合大多數用戶的需求,但是一些IM類軟件商往往希望它們的產品有著一套獨特的外觀,這對SWT這種原生組件來說就有些力不從心了,嚴格來說如果你的用戶對外觀要求比較苛刻,那么Swing是首選,因為L&F機制可以確保你做到這一點,另外Swing還有著許多SWT不具備的優點,例如半透明組件、渲染等,但是少數的這些特性用SWT還是可以模擬的,本文就向大家介紹如何通過自定義組件實現MSN風格的下拉框。
      通常來說,SWT提供的組件集基本上能滿足大多數用戶的需求,而自定義組件通常分為2種,一種是將若干基本組件組合成一個復合組件(如日歷組件);第二是對現有組件改善外觀從而符合客戶的要求;或者將這兩種混合使用。利用SWT實現自定義組件通常要繼承Composite或Canvas來實現,但是絕大多數采用繼承Composite實現,如果你查看SWT的源代碼,你會發現很多SWT高級組件(如ExpandBar)都是直接繼承Composite來實現的。
      準備工作,首先將MSN登錄界面的截圖帖出來參考。
                    
    如果要模擬MSN的用戶名輸入組件,你需要采集一些數據,分別是:正常、禁用兩種狀態下邊框的顏色;正常、禁用兩種狀態下的背景色;右邊下拉按鈕的圖標。現在將這幾組數據給出。
    正常狀態下邊框的顏色:RGB 170,183,199
    禁用狀態下邊框的顏色:RGB 208,215,229
    正常狀態下的背景色:RGB 254, 254, 254
    禁用狀態下的背景色:RGB 238, 241, 249
    下拉按鈕的圖標:
    接下來創建一個類叫做ComboSelector繼承自Composite。需要指出的是,這個自定義組件SWT組件庫支持,在Eclipse下如果有VE、swt-designer這樣的插件可以借助向導將必要的庫導入到工程的classpath下,此外如果部署SWT應用程序還需要一個動態庫,關于如何部署本文不作闡述。
    創建以上這些數據常量
    private final Color ENABLED_LINE_COLOR = new Color(Display.getCurrent(), 170, 183, 199);

    private final Color DISABLED_LINE_COLOR = new Color(Display.getCurrent(), 208, 215, 229);

    private final Color ENABLED_BG = new Color(Display.getCurrent(), 254, 254, 254);

    private final Color DISABLED_BG = new Color(Display.getCurrent(), 238, 241, 249);

    private final Image COMBO_ICON = new Image(Display.getDefault(), "combo.png");

    另外你還需要一個基本文本組件用于輸入、一個菜單顯示保存的數據。

    private Text inputText;

    private Menu selectorMenu;

    以上這些是和顯示相關的變量,但是除了這些還要保存臨時的數據,分別是當前用戶選擇了的那一項、下拉框所有數據項的集合。為了實現通用性和移植性這兩組數據均用Object保存。

    private Object selectedItem;

    private Vector dataSet = new Vector();

    接著定義構造函數。

    public ComboSelector(Composite parent) {...}

    需要注意的是,與Swing組件不同,任何SWT組件的構造器一定要有一個不為null的指向其父組件的參數,也就是說,SWT組件一旦被創建,就和它的父組件綁定了,其父組件不會提供任何add(...)、remove(...)方法添加或者移除組件,除非子組件調用dispose()方法銷毀自身。而Swing組件構造時無需指父組件,而是通過父組件調用add(Component comp)將組件加進來,從這一點來說,Swing復合JavaBean規范,這個優勢是SWT所無法比擬的。

    在完成構造函數之前,我們先定義一個輔助函數,用來獲取該組件在屏幕中的坐標,其思想是循環調用getParent()方法獲取父組件,直到為null為止,因為這樣循環調用getParent()總會找到最外層的窗口Shell對象。然后將各個子組件在其父組件上的坐標依次相加。

    方法如下:

    private Point getScreemLocation() {
      Control control = this;
      int width = control.getLocation().x;
      int height = control.getLocation().y;
      while (control.getParent() != null) {
       control = control.getParent();
       width += control.getLocation().x;
       height += control.getLocation().y;
      }
      return new Point(width, height);
    }

    現在讓我們完成構造函數

    super(parent, SWT.FLAT);
    inputText = new Text(this, SWT.FLAT);
    selectorMenu = new Menu(this);
    setMenu(selectorMenu);

    首先實現父組件的構造器,注意,將風格設置為FLAT或者NONE。如果為BORDER,那么運行時會發現組件是凹陷下去的外觀(WindowsXP以前就是這種外觀),通常對于自定義的外觀都需要將風格設置為SWT.FLAT或者SWT.NONE。然后創建基本文本、菜單。對于菜單需要注意的是除了在構造時候要指定父組外,還要調用setMenu將菜單加進來。

    接下來一步很關鍵,是要進行自定義繪制。繪制包括邊框和下拉按鈕的圖標。

    完整代碼如下:

    addPaintListener(new PaintListener() {
       public void paintControl(PaintEvent e) {
        GC gc = e.gc;
        gc.setForeground(isEnabled() ? ENABLED_LINE_COLOR
          : DISABLED_LINE_COLOR);
        gc.drawRectangle(0, 0, getSize().x - 1, getSize().y - 1);
        gc.drawImage(COMBO_ICON, getSize().x
          - COMBO_ICON.getBounds().width - 5,
          (getSize().y - COMBO_ICON.getBounds().height) / 2);
       }
      });
    首先根據組件是否可用決定邊框的顏色。調用drawRectangle完成繪制邊框的操作。

    然后繪制圖標,注意,drawImage后兩個參數是繪制的坐標,也就是從哪里開始畫起,模擬MSN用戶名輸入組件時,下拉按鈕右端點x坐標取距離組件最右端x坐標(getSize().x)5像素處為最佳,因此計算得出下拉按鈕左端點x坐標為getSize().x- COMBO_ICON.getBounds().width - 5。(左端點x坐標與右端點x坐標相差COMBO_ICON.getBounds().width應該很容易理解,另外讀者對坐標系的概念應該有一定了解);對于按鈕的y坐標,計算思想是使按鈕的垂直位置居中,因此計算y坐標公式為(getSize().y - COMBO_ICON.getBounds().height) / 2)。

    接下來一步是確定基本文本組件的位置,完整代碼如下:

    addControlListener(new ControlAdapter() {
       @Override
       public void controlResized(ControlEvent e) {
        inputText.setBounds(1, 1, getSize().x
          - COMBO_ICON.getBounds().width - 15, getSize().y - 2);
       }
      });
    給該組件注冊Control監聽器時,當該組件尺寸發生變化,會觸發controlResized方法,在該方法內對基本文本組件的位置進行調整。模擬MSN用戶名輸入組件原則是,基本文本組件的邊框被隱藏(構造時候通過將Style設為SWT.FLAT),左端點x坐標為1(為0的話會遮擋邊框線的左端),長度是整個組件長度減去下拉按鈕的長度再減15像素為最佳,從而保證與下拉按鈕之間有一段距離,高度是整個組件的高度減2像素,過高會遮擋邊框線。

    接著我們要重寫setEnabled方法,代碼如下:

    public void setEnabled(boolean enabled) {
      super.setEnabled(enabled);
      setBackground(enabled ? ENABLED_BG : DISABLED_BG);
      inputText.setEnabled(enabled);
      redraw();
     }

    第一行的super.setEnabled(enabled);表示保持父類enable屬性不變化,之后是設置背景,并設置inputText的enabled屬性,最后調用redraw方法通知組件重繪。需要闡明的是,redraw方法會調用PaintListener中的方法,也就是說會調用到構造函數中public void paintControl(PaintEvent e){...}這段代碼,如果組件添加了多個繪制監聽器,那么redraw會依次調用每個監聽器的paintControl方法,這與swing的事件機制是相同的。在redraw方法中根據isEnabled()的值決定邊框的顏色,所以每當setEnable方法被調用都應該執行重繪。

    還需要指出,通過添加繪制監聽器來實現個性化的外觀,并在調用影響外觀的操作(比如setEnable)時調用redraw方法強制組件重繪,這是自定義組件常用的實現手段。你會看到接下來的很多方法會經常調用redraw通知組件重繪。

    除了setEnabled方法,還有一些方法需要補充,一并列出:

    public void setEditable(boolean editable) {
      inputText.setEditable(editable);
     }

    public String getText() {
      return inputText.getText();
     }

    public void setText(String text) {
      inputText.setText(text);
     }

    public void setTextLimit(int limit) {
      inputText.setTextLimit(limit);
     }

    這些方法簡單易懂,不作解釋,以上列舉的只是最基本的方法,如果覺得功能不夠還可以定義其他方法,例如可以對用戶的輸入作驗證。

    接下來回到構造函數中來,QQ、MSN等一些軟件的登錄除了點擊登錄按鈕執行還可以在用戶名、口令輸入框上單擊回車來實現,為了實現這一功能,需要為基本文本組件添加一個選擇監聽器。

    inputText.addSelectionListener(new SelectionAdapter() {
       @Override
       public void widgetDefaultSelected(SelectionEvent e) {
        commit();
       }
      });

    這樣,當用戶在文本組件上單擊回車,會執行commit方法。下面是commit方法的定義:

    protected void commit() {
    };

    它不作任何事情,因為組件不知道實際會應用在何種場合,即回車操作具體作什么,這應該通過繼承該組件重寫commit方法實現具體功能。

    然后為組件添加鼠標監聽器,實現用戶單擊下拉按鈕時菜單的彈出。完整代碼如下:

    addMouseListener(new MouseAdapter() {
       @Override
       public void mouseUp(MouseEvent e) {
        if (e.x > getBounds().width - COMBO_ICON.getBounds().width - 15
          && e.x < getBounds().width && e.y > 0
          && e.y < getBounds().height) {
         selectorMenu.setLocation(getScreemLocation().x + 3,
           getScreemLocation().y + getSize().y + 23);
         selectorMenu.setVisible(true);
        }
       }
      });

    if條件句子是判斷鼠標指針的落點是否位于下拉三角的區域內,計算方法讀者可以自己思考,之后設置彈出菜單出現的位置,根據前面定義的getScreemLocation方法可方便得出,需要提出的是計算x坐標的“+3”和y坐標的“+23”,為什么要再加上這個整數呢?是因為Windows窗口的標題欄高20像素,而getScreemLocation是無法自動計算出的,有些窗口可通過設置將標題欄去掉(SWT的Shell通過指定SWT.NO_TRIM樣式實現)“+3是使菜單彈出的位置不至于遮擋組件邊框線,因此偏移3像素為最佳位置”。調用setVisible顯示菜單,不過前提條件是必須添加了菜單項。構造函數最后是一步是設置組件為可用,雖然任何SWT/Swing組件在構造時默認都是可用的,但是正如前面所述,重寫setEnabled并不止設置是否被禁用,重要的是組件在兩態下的外觀,所以在構造函數最后添加setEnabled(true);


    以上講述過多的是如何裝飾組件的外觀,接下來的重點將介紹如何用該組件緩存數據,使用MSN時候會發現,單擊登錄用戶名的下拉按鈕時候,會彈出所有在本機登錄過的用戶名列表(如果保存的話),下面講述如何實現這一功能。


    我們的數據均保存在Vector這個集合中,由于實際應用變化萬千,組件不可能知道實際保存何種類型的數據,因此Vector的元素類型統一設置為Object,這也實在是一個不錯的設計,因為它不強制使用者去實現某某接口,或基類,假如設計成Vector中的元素必須是實現某一特定接口IElement,

    private Vector dataSet = new Vector();

    這樣的話,使用者就必須將其POJO作更改,以適應于此組件,而Object作為所有類的基類,因此可容納任何類型的數據。接下來的一步很重要,是將數據與菜單關聯起來。定義如下方法public void loadMenuItems(Object[] objects),顧名思義是一次性讀取一組元素,完整的代碼如下:

    public void loadMenuItems(Object[] objects) {
      dataSet.clear();
      MenuItem[] items = selectorMenu.getItems();
      for (MenuItem item : items) {
       item.removeSelectionListener(this);
       item.dispose();
      }
      for (int i = 0; i < objects.length; i++) {
       dataSet.add(objects[i]);
       MenuItem item = new MenuItem(selectorMenu, SWT.PUSH);
       item.setText(objects[i].toString());
       item.setData(objects[i]);
       item.addSelectionListener(this);
      }
     }

    因為是load所有數據,所以第一步是將已有的數據清空,包括Vector中的數據和菜單中的菜單項。然后是對傳入的Object數組作遍歷,對于每一項,將之添加進集合然后創建一個菜單項,下一步item.setText(objects[i].toString());是設置菜單項的文字,toString()方法是Object的固有方法,但是實際應用時必須重寫該方法的實現。接下來是item.setData(objects[i]);為菜單項設置數據,這一步非常重要,SWT的每一個組件都具有public void setData (Object data)和public Object getData ()方法。還有Hash結構的public void setData (String key, Object value)和public Object getData (String key)。稍后會看到通過item.getData();取出創建時存入的數據。最后一行是為菜單項添加事件監聽器,并使組件本身作為監聽器,使組件本身實現SelectionListener接口,然后添加下面兩個方法:

    public final void widgetDefaultSelected(SelectionEvent e)

    public final void widgetSelected(SelectionEvent e)

    其中widgetDefaultSelected在單擊回車時觸發,對文本框這樣的組件適用,widgetSelected是鼠標單擊時觸發適用于按鈕、菜單項。因此我們只處理widgetSelected。

    public final void widgetSelected(SelectionEvent e) {
      MenuItem item = (MenuItem) e.getSource();
      selectedItem = item.getData();
      String text = item.getData().toString();
      inputText.setText(text);
      inputText.setSelection(0, text.length());
      selected(item.getData());
     }

    首先取得事件源即單擊的菜單項,然后更新selectedItem引用指向這個菜單項保存的數據(先前通過setData方法添加的),接下來的代碼不作解釋,很容易理解。值得注意的是最后一行selected(item.getData());作用是當用戶選中菜單某一項時,根據當前選擇的那個數據自動執行相應的操作,selected方法定義如下:

    protected void selected(Object object) {
    };

    與commit方法一樣,是需要根據實際情況自定義處理邏輯的。

    最后添加如下2個方法:

    public void select(int index) {
      MenuItem[] items = selectorMenu.getItems();
      if (index < 0 || index >= items.length) {
       throw new ArrayIndexOutOfBoundsException(
         "the index value must between " + 0 + "and "
           + (items.length - 1));
      }
      selectedItem = items[index].getData();
      inputText.setText(items[index].getText());
     }

    select用來設置當前選擇第幾個項,getSelectedItem返回當前用戶選擇的數據。


    到此為止,ComboSelector已經完成,可以作為API使用了,下面我們編寫一個程序測試該組件。

    首先編寫一個POJO,如下:

    package swt.custom;

    public class Person {
     private String userName;

     private String password;

     public Person(String userName, String password) {
      this.userName = userName;
      this.password = password;
     }

     public String getPassword() {
      return password;
     }

     public String getUserName() {
      return userName;
     }

     @Override
     public String toString() {
      return userName;
     }
    }

    簡單至極的一個類,注意它的toString方法,返回用戶名屬性作為顯示。

    接下來通過一個demo看看實際運行效果。
    用swt-designer工具創建一個Shell,在createContents方法體內添加如下代碼:

    final ComboSelector selector = new ComboSelector(this) {
       @Override
       protected void commit() {
        System.out.println("current data is "
          + ((Person) getSelectedItem()).getUserName());
       }
       @Override
       protected void selected(Object object) {
        System.out.println(((Person) object).getPassword());
       }
      };
      selector.setBounds(114, 78, 200, 20);
      Person[] persons = new Person[] {
        new Person("play_station3@sina.com", "111111"),
        new Person("rehte@hotmail.com", "222222"),
        new Person("yitong.liu@bea.com", "password"),
        new Person("使用其他Windows Live ID 登錄", "no") };
      selector.loadMenuItems(persons);
      selector.select(1);

    運行結果如下:

      本程序的完整代碼這里下載

    posted on 2007-10-23 13:33 sun_java_studio@yahoo.com.cn(電玩) 閱讀(10135) 評論(5)  編輯  收藏 所屬分類: SWT

    評論

    # re: 用SWT實現MSN風格的下拉框 2007-10-23 13:44 BeanSoft

    支持樓主!  回復  更多評論   

    # re: 用SWT實現MSN風格的下拉框 2007-10-24 10:50 翔南

    呵呵 不錯
    又是位SWT的愛好者 HOHO!
      回復  更多評論   

    # re: 用SWT實現MSN風格的下拉框 2007-11-01 00:42 Jerry Tao

    為什么要把Eclipse的項目發表在NetBeans分類中?!  回復  更多評論   

    # re: 用SWT實現MSN風格的下拉框 2008-03-26 19:14 gembin

    Good!!!!!!!!!  回復  更多評論   

    # re: 用SWT實現MSN風格的下拉框 2012-07-05 19:55 gaofeng

    你好,您的代碼能共享下嗎?出售也行。有意向請Q我:178714646  回復  更多評論   


    只有注冊用戶登錄后才能發表評論。


    網站導航:
     
    TWaver中文社區
    主站蜘蛛池模板: 亚洲午夜一区二区电影院| 美女无遮挡免费视频网站| 国产精品亚洲精品| 一级毛片高清免费播放| 皇色在线视频免费网站| 亚洲乱码国产乱码精品精| 亚洲丁香婷婷综合久久| 日韩内射激情视频在线播放免费 | 免费看一区二区三区四区| 91情侣在线精品国产免费| 国产成人亚洲综合无码精品| 亚洲国产欧美日韩精品一区二区三区| 成人免费看吃奶视频网站| 亚洲天堂福利视频| 久久久久久一品道精品免费看| 亚洲中文字幕伊人久久无码| 国产精品无码亚洲精品2021| 免费视频专区一国产盗摄| 97久久国产亚洲精品超碰热| 在线永久免费观看黄网站| 亚洲伊人久久大香线蕉AV| 99爱视频99爱在线观看免费| 亚洲国产成人精品无码区在线网站| 免费专区丝袜脚调教视频| 亚洲精品自产拍在线观看动漫| 久久九九久精品国产免费直播 | 国产成人精品男人免费| 最新国产精品亚洲| 免费福利网站在线观看| 最新亚洲人成网站在线观看| 亚洲色婷婷六月亚洲婷婷6月| 一级毛片免费不卡| 亚洲高清免费在线观看| 亚洲高清视频免费| 亚洲人成影院77777| 日韩精品亚洲专区在线观看| 免费播放国产性色生活片| 免费一级毛片正在播放| 特级毛片A级毛片100免费播放| 欧洲亚洲国产清在高| 成人免费视频试看120秒|