锁定老帖子 主题:用SWT实现MSN风格的下拉框
精华帖 (0) :: 良好帖 (0) :: 新手帖 (0) :: 隐藏帖 (0)
|
|
---|---|
作者 | 正文 |
发表时间:2008-04-18
通常来说,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() { 现在让我们完成构造函数 super(parent, SWT.FLAT); 首先实现父组件的构造器,注意,将风格设置为FLAT或者NONE。如果为BORDER,那么运行时会发现组件是凹陷下去的外观(WindowsXP以前就是这种外观),通常对于自定义的外观都需要将风格设置为SWT.FLAT或者SWT.NONE。然后创建基本文本、菜单。对于菜单需要注意的是除了在构造时候要指定父组外,还要调用setMenu将菜单加进来。 接下来一步很关键,是要进行自定义绘制。绘制包括边框和下拉按钮的图标。 完整代码如下: addPaintListener(new PaintListener() { 然后绘制图标,注意,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() { 接着我们要重写setEnabled方法,代码如下: public void setEnabled(boolean enabled) { 第一行的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) { public String getText() { public void setText(String text) { public void setTextLimit(int limit) { 这些方法简单易懂,不作解释,以上列举的只是最基本的方法,如果觉得功能不够还可以定义其他方法,例如可以对用户的输入作验证。 接下来回到构造函数中来,QQ、MSN等一些软件的登录除了点击登录按钮执行还可以在用户名、口令输入框上单击回车来实现,为了实现这一功能,需要为基本文本组件添加一个选择监听器。 inputText.addSelectionListener(new SelectionAdapter() { 这样,当用户在文本组件上单击回车,会执行commit方法。下面是commit方法的定义: protected void commit() { 它不作任何事情,因为组件不知道实际会应用在何种场合,即回车操作具体作什么,这应该通过继承该组件重写commit方法实现具体功能。 然后为组件添加鼠标监听器,实现用户单击下拉按钮时菜单的弹出。完整代码如下: addMouseListener(new MouseAdapter() { if条件句子是判断鼠标指针的落点是否位于下拉三角的区域内,计算方法读者可以自己思考,之后设置弹出菜单出现的位置,根据前面定义的getScreemLocation方法可方便得出,需要提出的是计算x坐标的“+3”和y坐标的“+23”,为什么要再加上这个整数呢?是因为Windows窗口的标题栏高20像素,而getScreemLocation是无法自动计算出的,有些窗口可通过设置将标题栏去掉(SWT的Shell通过指定SWT.NO_TRIM样式实现)“+3是使菜单弹出的位置不至于遮挡组件边框线,因此偏移3像素为最佳位置”。调用setVisible显示菜单,不过前提条件是必须添加了菜单项。构造函数最后是一步是设置组件为可用,虽然任何SWT/Swing组件在构造时默认都是可用的,但是正如前面所述,重写setEnabled并不止设置是否被禁用,重要的是组件在两态下的外观,所以在构造函数最后添加setEnabled(true);
private Vector dataSet = new Vector(); 这样的话,使用者就必须将其POJO作更改,以适应于此组件,而Object作为所有类的基类,因此可容纳任何类型的数据。接下来的一步很重要,是将数据与菜单关联起来。定义如下方法public void loadMenuItems(Object[] objects),顾名思义是一次性读取一组元素,完整的代码如下: public void loadMenuItems(Object[] objects) { 因为是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) { 首先取得事件源即单击的菜单项,然后更新selectedItem引用指向这个菜单项保存的数据(先前通过setData方法添加的),接下来的代码不作解释,很容易理解。值得注意的是最后一行selected(item.getData());作用是当用户选中菜单某一项时,根据当前选择的那个数据自动执行相应的操作,selected方法定义如下: protected void selected(Object object) { 与commit方法一样,是需要根据实际情况自定义处理逻辑的。 最后添加如下2个方法: public void select(int index) { select用来设置当前选择第几个项,getSelectedItem返回当前用户选择的数据。
首先编写一个POJO,如下: package swt.custom; public class Person { private String password; public Person(String userName, String password) { public String getPassword() { public String getUserName() { @Override 简单至极的一个类,注意它的toString方法,返回用户名属性作为显示。 接下来通过一个demo看看实际运行效果。 final ComboSelector selector = new ComboSelector(this) { 运行结果如下: 本程序的完整代码这里下载 sun_java_studio@yahoo.com.cn(电玩) 2007-10-23 13:33 发表评论
声明:ITeye文章版权属于作者,受法律保护。没有作者书面许可不得转载。
推荐链接
|
|
返回顶楼 | |
浏览 2820 次