|
【java大本营】http://www.javadby.com ,java实力站点,更新速度快,内容全面,鼓励原创,吸引了相当数量的编程学习者。欢迎加入java交流群41970496,共同进步。 SWT一个所谓的优点是它的本地化外观,因为它是通过JNI调用操作系统的组件,从而可以保证外观上适合大多数用户的需求,但是一些IM类软件商往往希望它们的产品有着一套独特的外观,这对SWT这种原生组件来说就有些力不从心了,严格来说如果你的用户对外观要求比较苛刻,那么Swing是首选,因为L&F机制可以确保你做到这一点,另外Swing还有着许多SWT不具备的优点,例如半透明组件、渲染等,但是少数的这些特性用SWT还是可以模拟的,本文就向大家介绍如何通过自定义组件实现MSN风格的下拉框。
通常来说,SWT提供的组件集基本上能满足大多数用户的需求,而自定义组件通常分为2种,一种是将若干基本组件组合成一个复合组件(如日历组件);第二是对现有组件改善外观从而符合客户的要求;或者将这两种混合使用。利用SWT实现自定义组件通常要继承Composite或Canvas来实现,但是绝大多数采用继承Composite实现,如果你查看SWT的源代码,你会发现很多SWT高级组件(如ExpandBar)都是直接继承Composite来实现的。
如果要模拟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() { Control control = this; int width = control.getLocation().x; int height = control.getLocation().y; while (control.getParent() != null) { control = control.getParent(); width += control.getLocation().x; height += control.getLocation().y; } return new Point(width, height); } |
现在让我们完成构造函数
super(parent, SWT.FLAT); inputText = new Text(this, SWT.FLAT); selectorMenu = new Menu(this); setMenu(selectorMenu); |
首先实现父组件的构造器,注意,将风格设置为FLAT或者NONE。如果为BORDER,那么运行时会发现组件是凹陷下去的外观(WindowsXP以前就是这种外观),通常对于自定义的外观都需要将风格设置为SWT.FLAT或者SWT.NONE。然后创建基本文本、菜单。对于菜单需要注意的是除了在构造时候要指定父组外,还要调用setMenu将菜单加进来。
接下来一步很关键,是要进行自定义绘制。绘制包括边框和下拉按钮的图标。
完整代码如下:
addPaintListener(new PaintListener() { public void paintControl(PaintEvent e) { GC gc = e.gc; gc.setForeground(isEnabled() ? ENABLED_LINE_COLOR : DISABLED_LINE_COLOR); gc.drawRectangle(0, 0, getSize().x - 1, getSize().y - 1); gc.drawImage(COMBO_ICON, getSize().x - COMBO_ICON.getBounds().width - 5, (getSize().y - COMBO_ICON.getBounds().height) / 2); } }); |
首先根据组件是否可用决定边框的颜色。调用drawRectangle完成绘制边框的操作。
然后绘制图标,注意,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() { @Override public void controlResized(ControlEvent e) { inputText.setBounds(1, 1, getSize().x - COMBO_ICON.getBounds().width - 15, getSize().y - 2); } }); |
给该组件注册Control监听器时,当该组件尺寸发生变化,会触发controlResized方法,在该方法内对基本文本组件的位置进行调整。模拟 MSN用户名输入组件原则是,基本文本组件的边框被隐藏(构造时候通过将Style设为SWT.FLAT),左端点x坐标为1(为0的话会遮挡边框线的左端),长度是整个组件长度减去下拉按钮的长度再减15像素为最佳,从而保证与下拉按钮之间有一段距离,高度是整个组件的高度减2像素,过高会遮挡边框线。
接着我们要重写setEnabled方法,代码如下:
public void setEnabled(boolean enabled) { super.setEnabled(enabled); setBackground(enabled ? ENABLED_BG : DISABLED_BG); inputText.setEnabled(enabled); redraw(); } |
第一行的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) { inputText.setEditable(editable); }
public String getText() { return inputText.getText(); }
public void setText(String text) { inputText.setText(text); }
public void setTextLimit(int limit) { inputText.setTextLimit(limit); } |
这些方法简单易懂,不作解释,以上列举的只是最基本的方法,如果觉得功能不够还可以定义其他方法,例如可以对用户的输入作验证。共2页: 上一页 1 [2] 下一页
|