`
gdwrx_winson
  • 浏览: 131577 次
  • 性别: Icon_minigender_1
  • 来自: 广州
社区版块
存档分类
最新评论

开发Eclipse自定义控件

阅读更多

转自 http://www.ibm.com/developerworks/cn/opensource/os-eclipcntl/

 

开发Eclipse自定义控件

developerWorks
文档选项
将打印机的版面设置成横向打印模式

打印本页

将此页作为电子邮件发送

将此页作为电子邮件发送


级别: 初级

梁 骞 (liangq@cn.ibm.com), IBM 中国软件开发中心, IBM

2005 年 9 月 01 日

现在基于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;
				}
			};
		});

很多时候,我们需要有滚动条的支持。对于滚动条,我们只要在上面加上selectionListener,处理它的widgetSelected事件就可以。


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

下面是函数scrollVertical的代码。一旦用户对滚动条操作,我们就可以计算出要滚动的区域,然后调用scroll函数。对函数scroll函数的调用会导致相应区域的重绘。


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;
	}

现在我们的程序已经基本成形了,我们来进一步完善它。由于我们开发的控件是提供给程序员的,我们需要提供接口,让外部知道控件中发生的事件。其中最重要的是列表项的选中事件。我们需要提供接口让程序员能够添加事件监控器(listener)来监控发生的事件,并且一旦发生事件,我们需要通知监控器。

首先,我们添加一个成员来保存添加的事件监控器:


Vector selectionListeners = new Vector();

我们再增加一个函数addSelectionListener,让程序员可以添加监控器


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

在我们前面的代码中,我们注意到每次选择项改变,我们都会调用selectionChanged函数。下面是selectionChanged函数代码。这里,我们会生成一个SelectionEvent事件,并且逐个调用事件监控器的widgetSelected方法。这样别人就可以监听到我们的事件了。


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);
		}
	}

现在辅助功能(Accessibility)也日益成为软件重要的部分,它是的残疾人也能够方便的使用我们的软件。美国已经立法,不符合Accessibility规范的软件不能够在政府部门销售。我们开发的控件也需要支持Accessibility.下面的代码使我们的控件有Accessibility支持。其中最重要的是getRole和getValue函数。我们的控件是从Canvas继承,我们在getRole函数中返回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));
	}
	

我们开发的控件的使用也是非常简单的。


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));

以上我们介绍了如何开发一个简单的自定义控件所需要涉及的技术。这里我们只以一个简单的颜色控件为例,但是一旦我们掌握了方法,我们很容易就可以开发出各种不同的漂亮控件。

整个程序完整的代码清参考:ColorList.java



参考资料



关于作者

分享到:
评论

相关推荐

    java swt自定义控件

    如果你正在开发Eclipse Rich Client Platform (RCP) 应用程序,自定义控件可以增强应用程序的用户体验。你可以将这些控件集成到工作台视图、编辑器或其他SWT部件中。 ### 8. 性能优化 尽管SWT提供了高效的本地化...

    自定义eclipse插件

    自定义Eclipse插件是扩展其功能、提高开发效率的重要方式。通过编写插件,开发者可以根据自己的需求定制工作环境,实现特定的任务自动化,如代码分析、调试辅助、版本控制集成等。 创建自定义Eclipse插件涉及以下几...

    自定义控件实例源码

    "自定义控件实例源码"这个项目,显然为我们提供了一些实际的代码示例,帮助我们理解和学习如何在Eclipse环境中创建并使用自定义控件。 首先,让我们来理解自定义控件的基本概念。自定义控件通常分为两种类型:继承...

    SWT 自定义控件

    ### SWT 自定义控件开发详解 #### 背景与需求 随着基于Eclipse平台的应用程序日益增多,开发者越来越依赖于SWT/JFace等提供的工具包来构建丰富的用户界面。然而,这些内置组件库虽然提供了大量的基础控件,但在特定...

    android自定义控件文档

    3. **开发工具**:本文档将指导如何在Eclipse和Android Studio这两种主流的Android开发环境中实现自定义控件。 #### 三、自定义控件的关键步骤 ##### 1. 使用`declare-styleable`定义自定义属性 - **作用**:`...

    自定义控件

    在Android开发中,自定义控件是提升应用独特性和用户体验的重要手段。自定义控件允许开发者根据需求扩展标准Android组件,实现独特的交互效果或者视觉样式。在这个项目中,使用Eclipse IDE进行开发,展示了如何创建...

    安卓自定义控件相关-自定义View画圆随指标移动eclipse.rar

    在Android开发中,自定义控件是提升应用独特性和用户体验的重要手段。本资源"安卓自定义控件相关-自定义View画圆随指标移动_eclipse.rar"主要关注如何在Android中自定义一个能够画圆并随指标移动的View。下面我们将...

    EclipseGUI控件可视化设计插件

    7. **可扩展性**:除了内置的Swing控件,开发者还可以自定义控件并将其添加到设计环境中。 安装此插件的方法非常简单,只需将下载的压缩包解压后覆盖到Eclipse的安装目录下的features和plugins子目录。覆盖完成后,...

    自定义组合控件一例

    在Android开发中,自定义控件是提升应用界面独特性和用户体验的重要手段。本文将通过一个实例——“自定义组合控件一例”来探讨如何创建一个带斜分的表头,以此加深对Android自定义控件的理解。我们将涵盖以下几个...

    android开发自定义控件学习 侧拉菜单

    自定义的侧拉菜单 适合新手学习 代码简单易懂 自定义的侧拉菜单 适合新手学习 代码简单易懂

    android+myeclipse+mysql自定义控件下拉框的数据绑定

    在Android应用开发中,自定义控件是一种常见的需求,它能提供更为丰富的用户交互体验。在本项目中,开发者结合了Android Studio、MyEclipse以及MySQL数据库,实现了自定义控件下拉框的数据绑定功能。这涉及到多个...

    自定义控件仿天气折线图的绘制,有天气图片 温度显示等

    在Android开发中,自定义控件是提升应用界面独特性和用户体验的重要手段。本文将深入探讨如何根据提供的标题和描述,创建一个自定义控件来模仿天气的折线图,其中包括天气图片和温度显示功能。我们将从以下几个方面...

    android mycalender 自定义日历控件

    在Android开发中,自定义日历控件是一个常见的需求,特别是在构建日程管理或时间规划类应用时。`mycalender`项目显然提供了一个自定义的日历视图,以满足开发者对日历显示和交互的个性化需求。下面将详细介绍自定义...

    自己动手写开发工具--基于eclipse的工具开发

    3. SWT/JFace:Eclipse的UI组件库,提供了丰富的控件和设计模式,使得创建用户界面更加便捷。 总之,通过学习和实践Eclipse插件开发,你可以根据自身需求定制开发工具,增强工作流,提升开发效率。无论是创建新的...

    Eclipse 开发 Android, Hello, TimePicker (学习8)

    在本教程中,我们将深入探讨如何使用Eclipse IDE来开发Android应用程序,并特别关注"Hello, TimePicker"这个示例。Eclipse是一个广泛使用的Java集成开发环境(IDE),它也支持Android应用开发,提供了丰富的功能和...

    开发Eclipse插件之天气预报

    在IT行业中,开发Eclipse插件是一个非常实用的技能,特别是在软件工程领域,因为Eclipse作为一款强大的集成开发环境(IDE),广泛应用于Java及其他语言的开发。本篇将基于"开发Eclipse插件之天气预报"这个主题,深入...

    android特效

    这里我们将深入探讨如何利用Eclipse IDE进行自定义控件的开发。 自定义控件在Android开发中的意义在于,它允许开发者超越标准UI组件(如Button、TextView等)的限制,创造出具有特定功能或独特设计的界面元素。这种...

Global site tag (gtag.js) - Google Analytics