明輝手游網(wǎng)中心:是一個免費(fèi)提供流行視頻軟件教程、在線學(xué)習(xí)分享的學(xué)習(xí)平臺!

開發(fā)Eclipse下的自定義控件

[摘要]本文首先介紹模型轉(zhuǎn)換的基本概念,然后介紹RSA模型轉(zhuǎn)換框架,之后本文以兩個具體的例子介紹如何在RSA開發(fā)平臺中以模型轉(zhuǎn)換框架為基礎(chǔ)創(chuàng)建和擴(kuò)展模型轉(zhuǎn)換。   現(xiàn)在基于Eclipse的應(yīng)用越來越多,很多桌面應(yīng)用都是用Eclipse開發(fā)的。Eclipse提供了一套SWT/JFACE的控件庫,使得人們開...
本文首先介紹模型轉(zhuǎn)換的基本概念,然后介紹RSA模型轉(zhuǎn)換框架,之后本文以兩個具體的例子介紹如何在RSA開發(fā)平臺中以模型轉(zhuǎn)換框架為基礎(chǔ)創(chuàng)建和擴(kuò)展模型轉(zhuǎn)換。  

  現(xiàn)在基于Eclipse的應(yīng)用越來越多,很多桌面應(yīng)用都是用Eclipse開發(fā)的。Eclipse提供了一套SWT/JFACE的控件庫,使得人們開發(fā)界面應(yīng)用極大的方便。但是,SWT/JFACE的控件庫畢竟有限,在應(yīng)用開發(fā)是我們不可避免地要自己開發(fā)一些自定義的控件。本文通過開發(fā)一個顏色列表控件的實(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ā)自己控件的很多基本概念、方法,并且通過實(shí)例進(jìn)行了介紹,非常好。但是其所用的實(shí)例比較簡單,還有很多控件開發(fā)中所要涉及到的內(nèi)容,例如鍵盤、鼠標(biāo)事件的處理,滾動條、焦點(diǎn)的處理等等沒有提及。本文通過開發(fā)一個自定義的顏色列表控件的實(shí)例,全面地介紹了自定義控件所涉及的技術(shù)。同時,讀者也可以對該實(shí)例進(jìn)行擴(kuò)展,實(shí)現(xiàn)自己的列表控件!

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

  我們在開發(fā)自定義控件時主要考慮以下問題:

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

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

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

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

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

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

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

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

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

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


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

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

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

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



  控件開發(fā)最重要的就是控件的繪制了。控件的繪制可以通過添加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)的行,而不是將整個控件重繪。我們程序中用到的onPaint用于繪制一行。

  接下來,我們要讓我們的控件響應(yīng)鍵盤上下鍵對列表項(xiàng)進(jìn)行選擇。我們已對向上鍵的處理為例,首先當(dāng)用戶按了向上鍵時,我們需要改變選擇,并且重繪舊的和新的選擇項(xiàng)。如果選擇項(xiàng)已經(jī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) { //如果需要,滾動滾動條
       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;
    }
   }
  });
   



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


  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)時,選中的列表項(xiàng)需要有虛框表示控件得到焦點(diǎn)。當(dāng)獲得或失去焦點(diǎ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);
   }
  });
   



  我們在繪制每一個列表項(xiàng)時可以加入判斷當(dāng)前控件是否得到焦點(diǎn),如果控件得到了焦點(diǎn),我們就在選中的項(xiàng)目上畫一個虛框。下面是我們繪制一個列表項(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);
}
  



  作為一個可操作的控件,TAB鍵的支持也是很重要的。由于我們的控件是從Canvas繼承過來的,不支持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;
    }
   };
  });




  很多時候,我們需要有滾動條的支持。對于滾動條,我們只要在上面加上selectionListener,處理它的widgetSelected事件就可以。


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




  下面是函數(shù)scrollVertical的代碼。一旦用戶對滾動條操作,我們就可以計(jì)算出要滾動的區(qū)域,然后調(diào)用scroll函數(shù)。對函數(shù)scroll函數(shù)的調(diào)用會導(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)基本成形了,我們來進(jìn)一步完善它。由于我們開發(fā)的控件是提供給程序員的,我們需要提供接口,讓外部知道控件中發(fā)生的事件。其中最重要的是列表項(xiàng)的選中事件。我們需要提供接口讓程序員能夠添加事件監(jiān)控器(listener)來監(jiān)控發(fā)生的事件,并且一旦發(fā)生事件,我們需要通知監(jiān)控器。

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


Vector selectionListeners = new Vector();




  我們再增加一個函數(shù)addSelectionListener,讓程序員可以添加監(jiān)控器


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




  在我們前面的代碼中,我們注意到每次選擇項(xiàng)改變,我們都會調(diào)用selectionChanged函數(shù)。下面是selectionChanged函數(shù)代碼。這里,我們會生成一個SelectionEvent事件,并且逐個調(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)也日益成為軟件重要的部分,它是的殘疾人也能夠方便的使用我們的軟件。美國已經(jīng)立法,不符合Accessibility規(guī)范的軟件不能夠在政府部門銷售。我們開發(fā)的控件也需要支持Accessibility.下面的代碼使我們的控件有Accessibility支持。其中最重要的是getRole和getValue函數(shù)。我們的控件是從Canvas繼承,我們在getRole函數(shù)中返回ACC.ROLE_LIST,這樣我們的控件才能讓屏幕閱讀軟件將我們的控件作為列表控件對待。

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ā)的控件的使用也是非常簡單的。


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ā)一個簡單的自定義控件所需要涉及的技術(shù)。這里我們只以一個簡單的顏色控件為例,但是一旦我們掌握了方法,我們很容易就可以開發(fā)出各種不同的漂亮控件。