服务热线:13616026886

技术文档 欢迎使用技术文档,我们为你提供从新手到专业开发者的所有资源,你也可以通过它日益精进

位置:首页 > 技术文档 > JAVA > 新手入门 > 基础入门 > 查看文档

技术分享――开发eclipse自定义控件


  现在基于 eclipse 的应用越来越多,很多桌面应用都是用eclipse开发的。eclipse提供了一套 swt/jface 的控件库,使得人们开发界面应用极大的方便。但是,swt/jface的控件库毕竟有限,在应用开发是我们不可避免地要自己开发一些自定义的控件。本文通过开发一个颜色列表控件的实例介绍了eclipse自定义控件开发中所要用到的技术。
  
  目标读者必须熟悉java开发,并且有一定的eclipse开发经验。
  
  在eclipse网站上有一篇相关的文章"creating your own widgets using swt",该文介绍了开发自己控件的很多基本概念、方法,并且通过实例进行了介绍,非常好。但是其所用的实例比较简单,还有很多控件开发中所要涉及到的内容,例如键盘、鼠标事件的处理,滚动条、焦点的处理等等没有提及。本文通过开发一个自定义的颜色列表控件的实例,全面地介绍了自定义控件所涉及的技术。同时,读者也可以对该实例进行扩展,实现自己的列表控件。
  
  swt中提供的标准列表控件非常简单,只能提供字符串的选择。我们经常需要提供一些图形列表供用户选择,这就需要自己开发自定义的列表控件。颜色选择列表是我们常用的一种图形列表,我们就以此为例进行介绍。以下是我们将要开发的颜色列表。
  
  我们在开发自定义控件时主要考虑以下问题:
  
  1、 自定义控件的绘制:通常我们需要自己对控件的形状或图案进行绘制;
  
  2、 控件对键盘事件的响应:当焦点进入控件,用户进行键盘操作,通过键盘对控件进行控制时,我们需要让控件对用户的操作进行响应。例如在列表中,用户会通过上下箭头改变列表的选择项;
  
  3、 控件对鼠标事件的响应:当用户用鼠标选中控件,进行操作时,控件必须作出相应的反应;
  
  4、 控件对焦点事件的响应:当界面焦点进入或移出控件,通常我们需要将控件绘制成得到或失去焦点的形状。例如,当焦点进入列表时,一般被选中的列表项会有虚框表示选中。
  
  5、 响应tab键:对于一个可操纵的控件,用户可以用tab键将焦点移入或移出。
  
  6、 响应滚动条事件:当控件有滚动条时,我们需要响应用户对滚动条的操作,完成对控件的绘制工作。
  
  7、 提供事件监听机制:程序员使用你的控件时通常需要监听控件中发生的一些事件,这样当事件发生时,他们能够进行相应处理。
  
  8、 提供辅助功能(accessibility):辅助功能是方便残障人士使用时必须的,标准控件都会提供相应的支持,我们自定义的控件也不例外。
  
  9、 提供功能接口方便程序员访问:通常为方便程序员使用时获取控件中的信息或进行设置,我们需要提供一些接口。
  
  首先我们要开发的列表控件是一个基本控件,所以我们选择canvas作为我们开发的基类。
  
  public class colorlist extends canvas {
  vector colors = new vector(); // 用于保存我们颜色控件中的颜色值
  vector colornames = new vector(); // 用于保存颜色控件中的颜色名字
  
  int rowsel = -1; // 用于保存当前选中的行号
  int oldrowsel = -1; // 用于保存上一次选中的行号
  
  int maxx, maxy; // 用于保存列表的宽度和高度
  int lineheight; // 用于设置行高
  
  int cx = 0; // 滚动条滚动后,控件的图形相对于控件可见区域左上角的x坐标
  int cy = 0; // 滚动条滚动后,控件的图形相对于控件可见区域左上角的y坐标
  }
  
  控件开发最重要的就是控件的绘制了。控件的绘制可以通过添加paintlistener,在它的paintcontrol方法中进行。
  
  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是需要重绘的区域,x,y是以控件的左上角为原点的坐标。在我们的程序中,为了性能起见,我们先根据需要重绘的区域计算出需要重绘的行数,只重绘相应的行,而不是将整个控件重绘。我们程序中用到的onpaint用于绘制一行。
  
  接下来,我们要让我们的控件响应键盘上下键对列表项进行选择。我们已对向上键的处理为例,首先当用户按了向上键时,我们需要改变选择,并且重绘旧的和新的选择项。如果选择项已经到了列表的顶部,我们还需要同时滚动滚动条。
  
  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) { //发送消息让控件重绘
  ((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(); // 发送selectionchanged事件
  }
  break;
  case swt.arrow_down: // down arror key
  …
  break;
  }
  }
  });
  
  接下来,我们要让我们的控件响应鼠标对列表项进行选择。首先我们要计算出鼠标选中的行号,注意mouseevent中的y值只是相对于控件左上角的坐标,我们需要加上滚动出了控件的部分。
  
  addmouselistener(new mouselistener() {
  public void mousedoubleclick(mouseevent e) {
  }
  public void mousedown(mouseevent e) {
  int row = (e.y - cy) / lineheight; //计算选中的行
  if (row >= 0) {
  oldrowsel = rowsel;
  rowsel = row;
  }
  if (oldrowsel != rowsel) { // 重画旧的和新的选择项
  ((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) {
  }
  });
  
  当我们的控件获得焦点时,选中的列表项需要有虚框表示控件得到焦点。当获得或失去焦点是,我们这里只需要简单的通知选中的项重画。
  
  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);
  }
  });
  
  我们在绘制每一个列表项时可以加入判断当前控件是否得到焦点,如果控件得到了焦点,我们就在选中的项目上画一个虚框。下面是我们绘制一个列表项的代码,注意在代码的最后绘制焦点的虚框。
  
  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;
  }
  };
  });

扫描关注微信公众号