gembin

          OSGi, Eclipse Equinox, ECF, Virgo, Gemini, Apache Felix, Karaf, Aires, Camel, Eclipse RCP

          HBase, Hadoop, ZooKeeper, Cassandra

          Flex4, AS3, Swiz framework, GraniteDS, BlazeDS etc.

          There is nothing that software can't fix. Unfortunately, there is also nothing that software can't completely fuck up. That gap is called talent.

          About Me

           

          SWT 自定義控件

          現(xiàn)在基于Eclipse的應(yīng)用越來(lái)越多,很多桌面應(yīng)用都是用Eclipse開發(fā)的。Eclipse提供了一套SWT/JFACE 的控件庫(kù),使得人們開發(fā)界面應(yīng)用極大的方便。但是,SWT/JFACE的控件庫(kù)畢竟有限,在應(yīng)用開發(fā)是我們不可避免地要自己開發(fā)一些自定義的控件。本文通 過(guò)開發(fā)一個(gè)顏色列表控件的實(shí)例介紹了Eclipse自定義控件開發(fā)中所要用到的技術(shù)。

          目標(biāo)讀者必須熟悉Java開發(fā),并且有一定的Eclipse開發(fā)經(jīng)驗(yàn)。

          在Eclipse網(wǎng)站上有一篇相關(guān)的文章"Creating Your Own Widgets using SWT",該文介紹了開發(fā)自己控件的很多基本概念、方法,并且通過(guò)實(shí)例進(jìn)行了介紹,非常好。但是其所用的實(shí)例比較簡(jiǎn)單,還有很多控件開發(fā)中所要涉及到的內(nèi) 容,例如鍵盤、鼠標(biāo)事件的處理,滾動(dòng)條、焦點(diǎn)的處理等等沒(méi)有提及。本文通過(guò)開發(fā)一個(gè)自定義的顏色列表控件的實(shí)例,全面地介紹了自定義控件所涉及的技術(shù)。同 時(shí),讀者也可以對(duì)該實(shí)例進(jìn)行擴(kuò)展,實(shí)現(xiàn)自己的列表控件。

          SWT中提供的標(biāo)準(zhǔn)列表控件非常簡(jiǎn)單,只能提供字符串的選擇。我們經(jīng)常需要提供一些圖形列表供用戶選擇,這就需要自己開發(fā)自定義的列表控件。顏色選擇列表是我們常用的一種圖形列表,我們就以此為例進(jìn)行介紹。以下是我們將要開發(fā)的顏色列表。

          我們?cè)陂_發(fā)自定義控件時(shí)主要考慮以下問(wèn)題:

          1、 自定義控件的繪制:通常我們需要自己對(duì)控件的形狀或圖案進(jìn)行繪制;

          2、 控件對(duì)鍵盤事件的響應(yīng):當(dāng)焦點(diǎn)進(jìn)入控件,用戶進(jìn)行鍵盤操作,通過(guò)鍵盤對(duì)控件進(jìn)行控制時(shí),我們需要讓控件對(duì)用戶的操作進(jìn)行響應(yīng)。例如在列表中,用戶會(huì)通過(guò)上下箭頭改變列表的選擇項(xiàng);

          3、 控件對(duì)鼠標(biāo)事件的響應(yīng):當(dāng)用戶用鼠標(biāo)選中控件,進(jìn)行操作時(shí),控件必須作出相應(yīng)的反應(yīng);

          4、 控件對(duì)焦點(diǎn)事件的響應(yīng):當(dāng)界面焦點(diǎn)進(jìn)入或移出控件,通常我們需要將控件繪制成得到或失去焦點(diǎn)的形狀。例如,當(dāng)焦點(diǎn)進(jìn)入列表時(shí),一般被選中的列表項(xiàng)會(huì)有虛框表示選中。

          5、 響應(yīng)TAB鍵:對(duì)于一個(gè)可操縱的控件,用戶可以用TAB鍵將焦點(diǎn)移入或移出。

          6、 響應(yīng)滾動(dòng)條事件:當(dāng)控件有滾動(dòng)條時(shí),我們需要響應(yīng)用戶對(duì)滾動(dòng)條的操作,完成對(duì)控件的繪制工作。

          7、 提供事件監(jiān)聽機(jī)制:程序員使用你的控件時(shí)通常需要監(jiān)聽控件中發(fā)生的一些事件,這樣當(dāng)事件發(fā)生時(shí),他們能夠進(jìn)行相應(yīng)處理。

          8、 提供輔助功能(Accessibility):輔助功能是方便殘障人士使用時(shí)必須的,標(biāo)準(zhǔn)控件都會(huì)提供相應(yīng)的支持,我們自定義的控件也不例外。

          9、 提供功能接口方便程序員訪問(wèn):通常為方便程序員使用時(shí)獲取控件中的信息或進(jìn)行設(shè)置,我們需要提供一些接口。

          首先我們要開發(fā)的列表控件是一個(gè)基本控件,所以我們選擇Canvas作為我們開發(fā)的基類。


          		public class ColorList extends Canvas {
          Vector colors = new Vector(); // 用于保存我們顏色控件中的顏色值
          Vector colorNames = new Vector(); // 用于保存顏色控件中的顏色名字

          int rowSel = -1; // 用于保存當(dāng)前選中的行號(hào)
          int oldRowSel = -1; // 用于保存上一次選中的行號(hào)

          int maxX, maxY; // 用于保存列表的寬度和高度
          int lineHeight; // 用于設(shè)置行高

          int cx = 0; // 滾動(dòng)條滾動(dòng)后,控件的圖形相對(duì)于控件可見區(qū)域左上角的x坐標(biāo)
          int cy = 0; // 滾動(dòng)條滾動(dòng)后,控件的圖形相對(duì)于控件可見區(qū)域左上角的y坐標(biāo)
          }

          控件開發(fā)最重要的就是控件的繪制了。控件的繪制可以通過(guò)添加PaintListener,在它的paintControl方法中進(jìn)行。


          		addPaintListener(new PaintListener() {
          public void paintControl(PaintEvent e) {
          GC gc = e.gc;
          Point size = getSize();
          int beginx = e.x;
          int beginy = (e.y / lineHeight) * lineHeight;
          int beginLine = (e.y - cy) / lineHeight;
          int endLine = beginLine + e.height / lineHeight + 1;
          if (endLine > getItemCount())
          endLine = getItemCount();
          for (int i = beginLine; i < endLine; i++) {
          boolean selected = false;
          if (i == rowSel)
          selected = true;
          onPaint(gc, i, cx, beginy + (i - beginLine) * lineHeight,
          selected);
          }
          }
          });

          這里要注意的是從PaintEvent中獲取的x,y,height,width是需要重繪的區(qū)域,x,y是以控件的左上角為原點(diǎn)的坐標(biāo)。在我們的 程序中,為了性能起見,我們先根據(jù)需要重繪的區(qū)域計(jì)算出需要重繪的行數(shù),只重繪相應(yīng)的行,而不是將整個(gè)控件重繪。我們程序中用到的onPaint用于繪制 一行。

          接下來(lái),我們要讓我們的控件響應(yīng)鍵盤上下鍵對(duì)列表項(xiàng)進(jìn)行選擇。我們已對(duì)向上鍵的處理為例,首先當(dāng)用戶按了向上鍵時(shí),我們需要改變選擇,并且重繪舊的和新的選擇項(xiàng)。如果選擇項(xiàng)已經(jīng)到了列表的頂部,我們還需要同時(shí)滾動(dòng)滾動(dòng)條。


          		addListener(SWT.KeyDown, new Listener() {
          public void handleEvent(Event event) {
          switch (event.keyCode) {
          case SWT.ARROW_UP: // 處理向上鍵
          if (rowSel != 0) {
          oldRowSel = rowSel;
          rowSel--;
          if (oldRowSel != rowSel) { //發(fā)送消息讓控件重繪
          ((Canvas) event.widget).redraw(cx, (rowSel + cy
          / lineHeight)
          * lineHeight, maxX, lineHeight*2, false);
          }
          if (rowSel < -cy / lineHeight) { //如果需要,滾動(dòng)滾動(dòng)條
          ScrollBar bar = ((Canvas) event.widget)
          .getVerticalBar();
          bar.setSelection(bar.getSelection() - lineHeight);
          scrollVertical(bar);
          }
          selectionChanged(); // 發(fā)送selectionChanged事件
          }
          break;
          case SWT.ARROW_DOWN: // down arror key

          break;
          }
          }
          });

          接下來(lái),我們要讓我們的控件響應(yīng)鼠標(biāo)對(duì)列表項(xiàng)進(jìn)行選擇。首先我們要計(jì)算出鼠標(biāo)選中的行號(hào),注意MouseEvent中的y值只是相對(duì)于控件左上角的坐標(biāo),我們需要加上滾動(dòng)出了控件的部分。


          		addMouseListener(new MouseListener() {
          public void mouseDoubleClick(MouseEvent e) {
          }
          public void mouseDown(MouseEvent e) {
          int row = (e.y - cy) / lineHeight; //計(jì)算選中的行
          if (row >= 0) {
          oldRowSel = rowSel;
          rowSel = row;
          }
          if (oldRowSel != rowSel) { // 重畫舊的和新的選擇項(xiàng)
          ((Canvas) e.getSource()).redraw(cx, (e.y / lineHeight)
          * lineHeight, maxX, lineHeight, false);
          ((Canvas) e.getSource()).redraw(cx, (oldRowSel + cy
          / lineHeight)
          * lineHeight, maxX, lineHeight, false);
          }
          selectionChanged();
          }
          public void mouseUp(MouseEvent e) {
          }
          });

          當(dāng)我們的控件獲得焦點(diǎn)時(shí),選中的列表項(xiàng)需要有虛框表示控件得到焦點(diǎn)。當(dāng)獲得或失去焦點(diǎn)是,我們這里只需要簡(jiǎn)單的通知選中的項(xiàng)重畫。


          		addFocusListener(new FocusListener() {
          public void focusGained(FocusEvent e) {
          ((Canvas) e.getSource()).redraw(cx, rowSel * lineHeight, maxX,
          lineHeight, true);
          }
          public void focusLost(FocusEvent e) {
          ((Canvas) e.getSource()).redraw(cx, rowSel * lineHeight, maxX,
          lineHeight, true);
          }
          });

          我們?cè)诶L制每一個(gè)列表項(xiàng)時(shí)可以加入判斷當(dāng)前控件是否得到焦點(diǎn),如果控件得到了焦點(diǎn),我們就在選中的項(xiàng)目上畫一個(gè)虛框。下面是我們繪制一個(gè)列表項(xiàng)的代碼,注意在代碼的最后繪制焦點(diǎn)的虛框。


          	void onPaint(GC gc, int row, int beginx, int beginy, boolean isSelected) {
          Color initColor = gc.getBackground();
          Color initForeColor = gc.getForeground();
          if (isSelected) {
          gc.setBackground(Display.getCurrent().getSystemColor(
          SWT.COLOR_LIST_SELECTION));
          gc.fillRectangle(beginx, beginy, maxX, lineHeight);
          gc.setForeground(Display.getCurrent().getSystemColor(
          SWT.COLOR_LIST_SELECTION_TEXT));
          } else {
          gc.setBackground(initColor);
          }
          gc.drawString((String) colorNames.get(row), beginx + 24, beginy);
          Color color = Display.getCurrent().getSystemColor(
          ((Integer) colors.get(row)).intValue());
          gc.setBackground(color);
          gc.fillRectangle(beginx + 2, beginy + 2, 20, lineHeight - 4);
          gc.setBackground(initColor);
          gc.setForeground(initForeColor);
          if (isFocusControl() && isSelected)
          gc.drawFocus(cx, beginy, maxX, lineHeight);
          }

          作為一個(gè)可操作的控件,TAB鍵的支持也是很重要的。由于我們的控件是從Canvas繼承過(guò)來(lái)的,不支持TAB鍵。下面的代碼使我們的控件有TAB鍵的支持:


          addTraverseListener(new TraverseListener() {
          public void keyTraversed(TraverseEvent e) {
          if (e.detail == SWT.TRAVERSE_TAB_NEXT
          || e.detail == SWT.TRAVERSE_TAB_PREVIOUS) {
          e.doit = true;
          }
          };
          });

          很多時(shí)候,我們需要有滾動(dòng)條的支持。對(duì)于滾動(dòng)條,我們只要在上面加上selectionListener,處理它的widgetSelected事件就可以。


          bar = getVerticalBar();
          if (bar != null) {
          bar.addSelectionListener(new SelectionAdapter() {
          public void widgetSelected(SelectionEvent event) {
          scrollVertical((ScrollBar) event.widget);
          }
          });
          }

          下面是函數(shù)scrollVertical的代碼。一旦用戶對(duì)滾動(dòng)條操作,我們就可以計(jì)算出要滾動(dòng)的區(qū)域,然后調(diào)用scroll函數(shù)。對(duì)函數(shù)scroll函數(shù)的調(diào)用會(huì)導(dǎo)致相應(yīng)區(qū)域的重繪。


          void scrollVertical(ScrollBar scrollBar) {
          Rectangle bounds = getClientArea();
          int y = -scrollBar.getSelection();
          if (y + maxY < bounds.height) {
          y = bounds.height - maxY;
          }
          if( y%lineHeight !=0 )
          y = y - y % lineHeight - lineHeight;
          scroll(cx, y, cx, cy, maxX, maxY, false);
          cy = y;
          }

          現(xiàn)在我們的程序已經(jīng)基本成形了,我們來(lái)進(jìn)一步完善它。由于我們開發(fā)的控件是提供給程序員的,我們需要提供接口,讓外部知道控件中發(fā)生的事件。其中最 重要的是列表項(xiàng)的選中事件。我們需要提供接口讓程序員能夠添加事件監(jiān)控器(listener)來(lái)監(jiān)控發(fā)生的事件,并且一旦發(fā)生事件,我們需要通知監(jiān)控器。

          首先,我們添加一個(gè)成員來(lái)保存添加的事件監(jiān)控器:


          Vector selectionListeners = new Vector();

          我們?cè)僭黾右粋€(gè)函數(shù)addSelectionListener,讓程序員可以添加監(jiān)控器


          public void addSelectionListener(SelectionListener listener) {
          selectionListeners.addElement(listener);
          }

          在我們前面的代碼中,我們注意到每次選擇項(xiàng)改變,我們都會(huì)調(diào)用selectionChanged函數(shù)。下面是selectionChanged函數(shù) 代碼。這里,我們會(huì)生成一個(gè)SelectionEvent事件,并且逐個(gè)調(diào)用事件監(jiān)控器的widgetSelected方法。這樣別人就可以監(jiān)聽到我們的 事件了。


          public void selectionChanged() {
          Event event = new Event();
          event.widget = this;
          SelectionEvent e = new SelectionEvent(event);
          for (int i = 0; i < selectionListeners.size(); i++) {
          SelectionListener listener = (SelectionListener) selectionListeners.elementAt(i);
          listener.widgetSelected(e);
          }
          }

          現(xiàn)在輔助功能(Accessibility)也日益成為軟件重要的部分,它是的殘疾人也能夠方便的使用我們的軟件。美國(guó)已經(jīng)立法,不符合 Accessibility規(guī)范的軟件不能夠在政府部門銷售。我們開發(fā)的控件也需要支持Accessibility.下面的代碼使我們的控件有 Accessibility支持。其中最重要的是getRole和getValue函數(shù)。我們的控件是從Canvas繼承,我們?cè)趃etRole函數(shù)中返 回ACC.ROLE_LIST,這樣我們的控件才能讓屏幕閱讀軟件將我們的控件作為列表控件對(duì)待。

          Accessible accessible = getAccessible(); accessible.addAccessibleControlListener(new AccessibleControlAdapter() { public void getRole(AccessibleControlEvent e) { int role = 0; int childID = e.childID; if (childID == ACC.CHILDID_SELF) { role = ACC.ROLE_LIST; } else if (childID >= 0 && childID < colors.size()) { role = ACC.ROLE_LISTITEM; } e.detail = role; } public void getValue(AccessibleControlEvent e){ int childID = e.childID; if (childID == ACC.CHILDID_SELF) { e.result = getText(); } else if (childID >= 0 && childID < colors.size()) { e.result = (String)colorNames.get(childID); } } public void getChildAtPoint(AccessibleControlEvent e) { Point testPoint = toControl(new Point(e.x, e.y)); int childID = ACC.CHILDID_NONE; childID = (testPoint.y - cy)/lineHeight; if (childID == ACC.CHILDID_NONE) { Rectangle location = getBounds(); location.height = location.height - getClientArea().height; if (location.contains(testPoint)) { childID = ACC.CHILDID_SELF; } } e.childID = childID; } public void getLocation(AccessibleControlEvent e) { Rectangle location = null; int childID = e.childID; if (childID == ACC.CHILDID_SELF) { location = getBounds(); } if (childID >= 0 && childID < colors.size()) { location = new Rectangle(cx,childID*lineHeight+cy,maxX,lineHeight); } if (location != null) { Point pt = toDisplay(new Point(location.x, location.y)); e.x = pt.x; e.y = pt.y; e.width = location.width; e.height = location.height; } } public void getChildCount(AccessibleControlEvent e) { e.detail = colors.size(); } public void getState(AccessibleControlEvent e) { int state = 0; int childID = e.childID; if (childID == ACC.CHILDID_SELF) { state = ACC.STATE_NORMAL; } else if (childID >= 0 && childID < colors.size()) { state = ACC.STATE_SELECTABLE; if (isFocusControl()) { state |= ACC.STATE_FOCUSABLE; } if (rowSel == childID) { state |= ACC.STATE_SELECTED; if (isFocusControl()) { state |= ACC.STATE_FOCUSED; } } } e.detail = state; } });

          最后,我們需要提供一些方法方便程序員使用我們的控件。


          	public void setSelection(int index) {
          if (index >= getItemCount() || index < 0)
          return;
          oldRowSel = rowSel;
          rowSel = index;
          selectionChanged();
          }
          public int getSelectionIndex() {
          return rowSel;
          }
          public int getItemHeight() {
          return lineHeight;
          }
          public void setItemHeight(int height) {
          lineHeight = height;
          }
          public int getItemCount() {
          return colors.size();
          }
          public void add(int colorIndex, String colorName) {
          colorNames.add(colorName);
          colors.add(new Integer(colorIndex));
          }

          我們開發(fā)的控件的使用也是非常簡(jiǎn)單的。


          CustomList customlist = new CustomList( parent, SWT.V_SCROLL | SWT.H_SCROLL );
          customlist.add(SWT.COLOR_BLACK,"BLACK");
          customlist.add(SWT.COLOR_BLUE,"BLUE");
          customlist.setSelection(1);
          customlist.setSize(400,400);
          customlist.setBackground(Display.getDefault().getSystemColor(SWT.COLOR_LIST_BACKGROUND));

          以上我們介紹了如何開發(fā)一個(gè)簡(jiǎn)單的自定義控件所需要涉及的技術(shù)。這里我們只以一個(gè)簡(jiǎn)單的顏色控件為例,但是一旦我們掌握了方法,我們很容易就可以開發(fā)出各種不同的漂亮控件。

          整個(gè)程序完整的代碼清參考:ColorList.java

          posted on 2008-03-19 18:01 gembin 閱讀(3131) 評(píng)論(1)  編輯  收藏 所屬分類: Eclipse RCPSWT

          評(píng)論

          # re: SWT 自定義控件 2008-05-22 16:15 fy_kenny

          好文章  回復(fù)  更多評(píng)論   

          導(dǎo)航

          統(tǒng)計(jì)

          常用鏈接

          留言簿(6)

          隨筆分類(440)

          隨筆檔案(378)

          文章檔案(6)

          新聞檔案(1)

          相冊(cè)

          收藏夾(9)

          Adobe

          Android

          AS3

          Blog-Links

          Build

          Design Pattern

          Eclipse

          Favorite Links

          Flickr

          Game Dev

          HBase

          Identity Management

          IT resources

          JEE

          Language

          OpenID

          OSGi

          SOA

          Version Control

          最新隨筆

          搜索

          積分與排名

          最新評(píng)論

          閱讀排行榜

          評(píng)論排行榜

          free counters
          主站蜘蛛池模板: 和田市| 恭城| 江永县| 滦平县| 额敏县| 深水埗区| 普格县| 土默特左旗| 乌兰察布市| 电白县| 洛川县| 北票市| 华安县| 乐清市| 团风县| 大洼县| 应城市| 洪湖市| 开封市| 始兴县| 双城市| 怀安县| 隆化县| 梁河县| 龙山县| 集安市| 汉沽区| 涟源市| 大兴区| 随州市| 凤山市| 循化| 夹江县| 吉安县| 沁源县| 郎溪县| 独山县| 元氏县| 梁山县| 大邑县| 花莲县|