??xml version="1.0" encoding="utf-8" standalone="yes"?>
可以看出Q经q自定义的组件在外观上要比SWT直接调用本地lg昑־更加专业。当用户托拽滑动块时Q还会出C个虚拟的滑动块用来标识将要移动到的位|。演C就到此为止Q下面详l介l这个很Cool的组件是如何通过SWT实现的?/p>
基本设计思想Q与其他自定义组件一P是通过l承org.eclipse.swt.widgets.Composite来实玎ͼ定义该类为SliderQ另外滑动块QthumbQ也是CompositeQƈ攑֜Slider之上Q当鼠标UdthumbӞ调用setBoundsҎ定位在Slider在父lgQSliderQ上的位|,从而达到拖拽thumb的目的。此外通过实现PaintListener接口q行自定义绘Ӟl制的对象包括组件边框、被填充的格子、未被填充的格子、虚拟滑块?/p>
接触qGUI~程的程序员都应该知道像Scroll、Slider、ProgressBarq样的控仉有setMaxValue、setMinValue、setValueq样的方法,除了鼠标拖拽thumb来改变当前数值外Q可直接调用setValue来设|当前倹{此外这些控件还有水qIHorizontalQ、垂_VerticalQ两U布局Q对于事件处理一般都要有一个从java.util.EventObjectl承而来的事件类Q还要编写事件监听器QListenerQ接口,因此在开始编写Slider控g之前先定?个类Q代码都不是很长Q如果你熟悉AWT、Swing的事件处理机Ӟ怿你能L跌?/p>
public enum SliderOrientation {
HORIZONTAL, VERTICAL;
}
public class SliderEvent extends EventObject {
private int value;
public SliderEvent(Object source, int value) {
super(source);
this.value = value;
}
public int getValue() {
return value;
}
}
public interface SliderListener {
public void valueChanged(SliderEvent event);
}
接下来着重介lSlider。首先是l承Compositeq实现ControlListener、PaintListener、MouseListener,、MouseMoveListener,、MouseTrackListenerQ然后自动生成接口方法代码,通过Eclipse可以L实现Q需要注意的是MouseListenerQ有java.awt.event.MouseListener和org.eclipse.swt.events.MouseListener两种Q不要淆,否则错误很难扑ֈ。然后是要采集一些数据信息,分别是:Ҏ颜色、已有数据部分的填充颜色Q上图中lg的绿色部分)、未辑ֈ数据部分的填充颜Ԍ上图中组件的白色部分Q、被用时的填充颜色、水qx块的图标Q正常、托拽中两种Q、垂直滑块图标(正常、托拽中两种Q、水q뀁垂直虚拟滑块图标。以上这些数据对应的帔R声明如下Q?/p>
private final Color BORDER_COLOR = new Color(Display.getCurrent(), 180, 188, 203);
private final Color FILL_COLOR = new Color(Display.getCurrent(), 147, 217, 72);
private final Color BLANK_COLOR = new Color(Display.getCurrent(), 254, 254, 254);
private final Color DISABLE_COLOR = new Color(Display.getCurrent(), 192, 192, 192);
private final Image THUMB_ICON_V = new Image(Display.getDefault(), "slider_up_v.png");
private final Image THUMB_OVER_ICON_V = new Image(Display.getDefault(), "slider_over_v.png");
private final Image THUMB_ICON_H = new Image(Display.getDefault(), "slider_up_h.png");
private final Image THUMB_OVER_ICON_H = new Image(Display.getDefault(), "slider_over_h.png");
private final Image TEMP_H = new Image(Display.getDefault(), "temp_h.png");
private final Image TEMP_V = new Image(Display.getDefault(), "temp_v.png");
除了q些帔RQ还应该声明默认最大值的帔RQprivate final int DEFAULT_MAX_VALUE = 100;
接下来定义当前数值和最大|
private int value;
private int maxValue = DEFAULT_MAX_VALUE;
q生成以上两个成员属性的getҎ
然后定义滑动块和布局
private SliderOrientation orientation;
private Composite thumb;
要处理数值变化,需要实Cl监听器Q添加如下代?br />
private List<SliderListener> listeners = new ArrayList<SliderListener>();
public void addSliderListener(SliderListener sliderListener) {
listeners.add(sliderListener);
}
public void removeSliderListener(SliderListener sliderListener) {
listeners.remove(sliderListener);
}
接下来定?个辅助方法,实现value<->pelsLength转换。其中value是当前的数|由具体业务来军_Q下文中U其业务倹{例如一个音量控制器Q音量范围在0~500Q那么从业务上来讲可以将数D|在0~500之间的Q何数Q而pelsLength则由控g的长/高度来决定,单位是像素。但是value与pelsLength之间存在着一个比例关pdQvalue/maxValue=pelsLength/控g长度或高度。这样不隑־Z个函数的定义?br />
private int valueToPels(int value) {
float widgetLength = (orientation == SliderOrientation.HORIZONTAL) ? getBounds().width
: getBounds().height;
return (int) (widgetLength * (float) value / (float) maxValue);
}
private int pelsToValue(int pels) {
float widgetLength = (orientation == SliderOrientation.HORIZONTAL) ? getBounds().width
: getBounds().height;
return (int) ((float) pels * (float) maxValue / (float) widgetLength);
}
最后定义构造器。代码如?br />
public Slider(Composite parent, SliderOrientation orientation) {
super(parent, SWT.FLAT);
this.orientation = orientation;
thumb = new Composite(this, SWT.FLAT);
thumb
.setBackgroundImage(orientation == SliderOrientation.VERTICAL ? THUMB_ICON_V
: THUMB_ICON_H);
addControlListener(this);
addPaintListener(this);
thumb.addMouseListener(this);
thumb.addMouseMoveListener(this);
thumb.addMouseTrackListener(this);
}
在构造器中,注入布局对象Q然后在控g上创建滑动块lgthumbQƈd鼠标处理{?br />
到此为止Q基本的成员和方法的定义完毕Q下面@序渐q讨论如何实现这一Slider?/p>
一、绘制边?br />
׃是绘制操作,所以一切绘制代码均在paintControlҎ内实玎ͼ先将如下代码拯到paintControl?br />
int w = getBounds().width;
int h = getBounds().height;
int fillLength = valueToPels(getValue());
GC gc = e.gc;
switch (orientation) {
case HORIZONTAL:
break;
case VERTICAL:
break;
}
分析如下
首先获取控g的长度与高度Q因为接下来的绘制要l常用到q两个变量?br />
“int fillLength = valueToPels(getValue());”q一行代码稍后作解释Q然后是获得l制上下文对象,下一步是Ҏ布局不同采用不同的处理,除了paintControl函数Q在其他很多地方都对布局q行判断Q但是简单v见,只对水^布局q行介绍Q垂直部分参考完整程序?br />
接下来的l制操作均在case HORIZONTAL中进行,首先颜色设|ؓҎ的颜?br />
gc.setForeground(BORDER_COLOR);然后l制一个矩形gc.drawRectangle(0, 2, w - 1, h - 5);
关于Z么要偏移2像素、长度ؓ什么减1、高度ؓ什么减5Q请参考有关绘囄基本知识Q?a >上一?/a>也有单的介绍?/p>
现在Q你可以编写测试程序来验证l果了,看看Ҏ是否与示例的效果一栗?br />
二、托拽thumb的实?br />
桌面GUI~程领域技术深的度量衡通常?Ҏ标:皮肤Q外观,swinglg体系U其L&FQ、绘图、自定义lg布局QLayoutQ、自定义lg。而托拽是实现自定义组件和l图不可或缺的技术,也是隄之一Q因此掌握的深浅是衡量桌面编E水q的标志?br />
虽然作ؓ隄Q但是也有章可@Q其基本实现单到只监听鼠标事件这么简单,基本程是:当鼠标在thumb上按下时Q记住这个位|,然后按住鼠标左键托拽Q最后松开鼠标计算两个位置之间的距(位移Q,Ҏ位移量移动thumb的位|ƈ换算出等Lvalue增量Q可能ؓ负|q行业务逻辑处理。下面通过代码循序渐进完成?br />
定义一个位|变量用来存储鼠标单ȝ位置Qprivate Point controlPoint;然后实现public void mouseDown(MouseEvent e)和public void mouseUp(MouseEvent e)两个Ҏ?br />
public void mouseDown(MouseEvent e) {
controlPoint = new Point(e.x, e.y);
thumb
.setBackgroundImage(orientation == SliderOrientation.VERTICAL ? THUMB_OVER_ICON_V
: THUMB_OVER_ICON_H);
}
public void mouseUp(MouseEvent e) {
try {
thumb
.setBackgroundImage(orientation == SliderOrientation.VERTICAL ? THUMB_ICON_V
: THUMB_ICON_H);
countValue(e);
} finally {
controlPoint = null;
}
}
“controlPoint = new Point(e.x, e.y);”q一行实现记住鼠标点的位|,注意Q这个位|是相对滑块thumb的,因ؓ是thumb监听的鼠标事件。接下来是设|滑动块背景Q不隄解当鼠标村ּӞ应该背景恢复。然后进行非帔R要的换算工作Q通过countValueҎ实现Q最后务必要把鼠标位|清I,用try-finally是有必要q样的。之所以要在方法结束的时候清IcontrolPointQ是因ؓ在鼠标移动的时候需要对controlPointq行更加复杂的计。稍后讲解mouseMove实现的时候再作解释,接下来着重分析countValueҎ?br />
如前所qͼcountValue完成计算鼠标按下、松开的位U量Q换成与业务相关的数据QvalueQ?br />
private void countValue(MouseEvent e) {
switch (orientation) {
case HORIZONTAL:
int movedX = e.x - controlPoint.x;
setValue(getValue() + pelsToValue(movedX));
break;
case VERTICAL:
......
}
}
“int movedX = e.x - controlPoint.x;”实现了位U量的计,保存到movedXQ调用pelsToValueҎmovedX转换成业务值的增量Q然后调用setValue重新赋|注意pelsToValue得到的是增量Q需要与原|getValue()得到Q叠加。接下来分析setValueҎ?br />
public void setValue(int value) {
if (value < 0) {
this.value = 0;
} else if (value > getMaxValue()) {
this.value = getMaxValue();
} else {
this.value = value;
}
try {
moveThumb();
redraw();
} finally {
for (SliderListener listener : listeners) {
try {
SliderEvent event = new SliderEvent(this, getValue());
listener.valueChanged(event);
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
Ҏ开始处的一pdif语句对valueq行验证后再赋|然后是调用moveThumb实现滑块Ud、调用redraw对组仉新绘Ӟ最后是处理具体的业务,可以看出处理业务是setValueҎ的关键,所以要用try-finallyQ谁也不敢确保moveThumb、redraw不出问题。对于实C务是遍历监听器列表然后执行每个监听器的valueChangedҎQ这U事件源-监听器模型也是Java2以后的GUI事g实现模型?/p>
private void moveThumb() {
Image icon = thumb.getBackgroundImage();
int iconw = (icon != null) ? icon.getBounds().width : 0;
int iconh = (icon != null) ? icon.getBounds().height : 0;
switch (orientation) {
case HORIZONTAL:
int x = valueToPels(getValue()) - iconw / 2;
if (x < 0) {
x = 0;
} else if (x > getBounds().width - iconw) {
x = getBounds().width - iconw;
}
thumb.setBounds(x, 0, iconw, iconh);
break;
case VERTICAL:
...... }
}
不难理解QmoveThumb的Q务就是根据业务值value来将滑块Ud到正的位置?br />
以上代码声明滑块的位|是“x”Q通过转换函数获得Q但是还要减L块的一半,因ؓ具体坐标应该落到滑动块的中间Q仔l想想不隑־出。if-else是对xq行验证Q最后通过setBound来定位thumb?br />
对于redraw,他的作用是触发paintControlҎq行重绘Q因为重l出了边框还要根据valuel制填充格子Q而value已经在redrawҎ调用前被赋了|所以这时候应该进行重l?br />
现在你可以托拽thumb了,中不的是滑动块不能随时跟随鼠标的轨迹UdQ这个稍后会实现?/p>
三、填充格?br />
现在的组件外观只是绘制了ҎQ现在进行格子填充。在讲述Ҏl制的时候提C一行代?#8220;int fillLength = valueToPels(getValue());”现在不难理解吧,是当前业务值value转换成实际的长度。在l制Ҏ之后Q加入如下代码:
if (getEnabled()) {
gc.setBackground(FILL_COLOR);
for (int i = 2; i < w - 2; i += 4) {
if (i > fillLength) {
gc.setBackground(BLANK_COLOR);
}
gc.fillRectangle(i, 4, 3, h - 8);
}
} else {
gc.setBackground(DISABLE_COLOR);
gc.fillRectangle(1, 4, w - 1, h - 8);
}
首先判断是否是enableQ然后设|填充颜色FILL_COLORQ然后在for循环中执行正方Ş格子的填充,递增?#8220;i”?开始是I开2像素间隔Q同理i也不能超qw-2Q对U性)Qi+=4是相M个格子左边坐标间?像素Q然?#8220;gc.fillRectangle(i, 4, 3, h - 8);”q一行进行填充绘制正方Ş。留意,x坐标是iQy坐标是从4开始画的,Z对称高度也要“h-8”Q长度之所以是“3”是保持相M个格子之间保?像素的间隔(x“i+=4”׃隑־出答案)。此外还要对fillLengthq行判断以便军_颜色采用l色q是白色以示区分。对于绘图操作来_千万不要埋怨考虑l节q多Q事实上QGUI~程q程?#8220;坐标p?#8221;q个概念是需要经常被考虑的,试想如果上述代码for循环中把“i+=4”Q写?#8220;i+=5”Q只是一个像素之差绘制效果差之千里,如果想知道笔者是如何得到q些坐标数据的,实话_是靠多次调试得出的结果?br />
现在你运行程序,应该能根据valueq行格子填充了。到此ؓ止,l大多数的功能已l实玎ͼ但是人性化的界面设计应该在托拽时出C个虚拟的滑动块用来标识将要移动到的位|。好Ql实现这一功能?br />
定义一个int变量U录thumb的时位|,private int tempLocation;然后在paintControld如下U色代码
case HORIZONTAL:
gc.setForeground(BORDER_COLOR);
gc.drawRectangle(0, 2, w - 1, h - 5);
if (getEnabled()) {
gc.setBackground(FILL_COLOR);
for (int i = 2; i < w - 2; i += 4) {
if (i > fillLength) {
gc.setBackground(BLANK_COLOR);
}
gc.fillRectangle(i, 4, 3, h - 8);
}
if (controlPoint != null) {
gc.drawImage(TEMP_H, tempLocation, 0);
}
} else {
gc.setBackground(DISABLE_COLOR);
gc.fillRectangle(1, 4, w - 1, h - 8);
}
break;
很直观,对于水^布局是?tempLocation,0)d“TEMP_H”图标。外面的if很重要,q记得mouseDownҎ中的语句“controlPoint = new Point(e.x, e.y);”和mouseUpҎ中的语句“controlPoint = null;”吗,当鼠标按下托拽开始时QؓcontrolPoint赋|当鼠标完成托拽松开ӞcontrolPoint|nullQ在q个托拽q程中controlPoint一直保持非null状态,所以paintControlҎ才将图标d。如果现在就着急运行程序则会发玎ͼ鼠标托拽时候虚拟图标d留在最左边Q如果垂直布局停留在最上边Q因为ƈ没有为tempLocation赋值默认是0Q,不会跟随鼠标Ud。如果要实现真正的托拽那么必d鼠标Ud时反复执行paintControlQ下面花大量的笔墨详l地介绍mouseMoveҎ的实玎ͼq简单介l绘图操作的一些原理和程?br />
开门见山,直接mouseMove函数的全部代码列出?br />
public void mouseMove(MouseEvent e) {
if (controlPoint == null) {
return;
}
int maxLength;
int maxLocator;
switch (orientation) {
case HORIZONTAL:
maxLength = valueToPels(getMaxValue());
maxLocator = maxLength - TEMP_H.getBounds().width;
int movedX = e.x - controlPoint.x;
redraw(tempLocation, 0, TEMP_H.getBounds().width,
TEMP_H.getBounds().height, false);
tempLocation = valueToPels(getValue()) + movedX
- TEMP_H.getBounds().width / 2;
if (tempLocation < 0) {
tempLocation = 0;
} else if (tempLocation > maxLocator) {
tempLocation = maxLocator;
}
break;
case VERTICAL:
......
break;
}
}
最前面的if语句表明Q只有鼠标按下时Ud鼠标才算托拽Q道理前面已l阐明了。maxLength代表最大D{换得到的像素QmaxLocator是虚拟图标左端(上端Q最大坐标,movedX代表托拽的位U量。最下面的if-else if目的很明了。整个mouseMove函数?br />
redraw(tempLocation, 0, TEMP_H.getBounds().width,
TEMP_H.getBounds().height, false);
tempLocation = valueToPels(getValue()) + movedX
- TEMP_H.getBounds().width / 2;
是整个托拽操作最难懂也是技术含量最高的两条语句。简单v见暂时用下面的语句代?br />
tempLocation = valueToPels(getValue()) + movedX
- TEMP_H.getBounds().width / 2;
if (tempLocation < 0) {
tempLocation = 0;
} else if (tempLocation > maxLocator) {
tempLocation = maxLocator;
}
redraw();
其中“tempLocation”的赋D句不变,变化的是redraw函数的调用位|和参数。这L变化使得意思就不难理解了,首先定tempLocation的|{号双的计结果也不难理解Q然后调用redrawҎ重画lg。如果这时候你q行E序Q托拽时实虚拟光标会跟随鼠标移动,但是也会发现lg闪烁得很厉害Q具体程度取决于用户计算机的性能Q关?#8220;lg重绘旉?#8221;的问题是l图操作的一个常见问题,不仅仅是JavaQQ何支持绘囄计算a都可以暴露这L问题Q当q在大学用MFC、VB的编写过d板的人应该熟悉这c问题?br />
在具体讨Z前先单讲q鼠标监听器中的mouseMove操作
一旦ؓGUIlgd鼠标Ud监听器,当鼠标光标在lg上移动时便调用监听器接口的mouseMove(MouseEvent e)ҎQmouseMove调用的频率与鼠标Ud的快慢有养IUd快mouseMove被调用的ơ数p,反之p多。假N标从lg上的A点移动到B点,如果鼠标Ud得够快Q那么就可以理解ZA直接到B而不l过中间的Q何一个点Q那么mouseMove函数仅仅调用一ơ。反之鼠标慢慢从AUd到BQ那么中间可能会l过C、D、E、F......一pd的点Q而mouseMove也会被执行多ơ。M当鼠标在lg上移动时QmouseMove会频J被调用Q之所以闪烁问题出在redrawҎQ如果redrawҎ不加M参数那么对lg全部重绘Q对于鼠标托拽这U操作鼠标每Ud一ơ就要对lg全部重绘Q性能的代价可惌知Q不闪才怪呢。解决的办法是只重l变化的部分?br />
如何做到q一点,要先了解必要的绘图机Ӟ在SWT中(Swingl图原理与其cMQredraw其实会包?个含义,擦除、绘Ӟ所以得名于redrawQ意思就是重l。首先是Ҏ传入redrawҎ的参数确认需要擦除的范围Q如果没有则擦除全部Q然?span style="color: #ff0000">底层会创Z个绘制请求交l操作系l去处理Q但是这个请求不会在redrawҎ调用完毕后立卌处理Q即重绘操作不会立即执行Q它是被送进底层的事仉列中。处理时的绘制操作是由paintControl完成Q所以redraw的调用会DpaintControl的执行?br />
再将那两行代码列出来?br />
redraw(tempLocation, 0, TEMP_H.getBounds().width,
TEMP_H.getBounds().height, false);
tempLocation = valueToPels(getValue()) + movedX
- TEMP_H.getBounds().width / 2;
如果光标从AUdB处,redrawҎ所做的事情是通知重绘光标在AҎ虚拟图标范围内的囑փQ然后立卛_Z个绘制请求送到pȝ事g队列Q然后更新tempLocation的倹{现在再绘制的那部分代码列?br />
if (controlPoint != null) {
gc.drawImage(TEMP_H, tempLocation, 0);
}
这两部分代码列出来做个比较Q还有非帔R要的一炚w要指出,托拽时虚拟光标能呈连l性移动,q一功能之所以能得以实现非常重要的一ҎQ?span style="color: #ff0000">从底层请求送入事g队列到请求被执行存在旉?/span>Q利用这个时间差Q时间非常短Q可以执行一?#8220;更新操作”Q比如上面的更新tempLocation。简单的理解是redrawҎ调用会以异步方式执行擦除、绘制。当执行l制操作“gc.drawImage(TEMP_H, tempLocation, 0);”ӞtempLocation已经是更新后的gQ而redraw表明擦除旧区域的囑փQ因为当paintControl执行时这部分囑փ区域Ҏ计算l果已经不再是虚拟滑块了。下面做一个试验?br />
d下面U色代码
redraw(tempLocation, 0, TEMP_H.getBounds().width,
TEMP_H.getBounds().height, false);
try {
Thread.sleep(1000);
} catch (InterruptedException e1) {
e1.printStackTrace();
}
tempLocation = valueToPels(getValue()) + movedX
- TEMP_H.getBounds().width / 2;
q样在绘制执行时QtempLocationq没有得到更斎ͼ效果q行下知晓?br />
现在Q你可以q行完整的程序了Q看一下托拽时的效果?br />
完整的程?a title="q里" >q里下蝲
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");
另外你还需要一个基本文本组件用于输入、一个菜单显CZ存的数据?/font>
private Text inputText;
private Menu selectorMenu;
以上q些是和昄相关的变量,但是除了q些q要保存临时的数据,分别是当前用户选择了的那一V下拉框所有数据项的集合。ؓ了实现通用性和UL性这两组数据均用Object保存?/font>
private Object selectedItem;
private Vector dataSet = new Vector();
接着定义构造函数?/font>
public ComboSelector(Composite parent) {...}
需要注意的是,与Swinglg不同QQ何SWTlg的构造器一定要有一个不为null的指向其父组件的参数Q也是_SWTlg一旦被创徏Q就和它的父lgl定了,其父lg不会提供Madd(...)、remove(...)Ҏd或者移除组Ӟ除非子组件调用dispose()Ҏ销毁自w。而Swinglg构造时无需指父lgQ而是通过父组件调用add(Component comp)组件加q来Q从q一Ҏ_Swing复合JavaBean规范Q这个优势是SWT所无法比拟的?/font>
在完成构造函C前,我们先定义一个辅助函敎ͼ用来获取该组件在屏幕中的坐标Q其思想是@环调用getParent()Ҏ获取父组Ӟ直到为null为止Q因样@环调用getParent()M扑ֈ最外层的窗口Shell对象。然后将各个子组件在其父lg上的坐标依次相加?/font>
Ҏ如下Q?/font>
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);
}
现在让我们完成构造函?/font>
super(parent, SWT.FLAT);
inputText = new Text(this, SWT.FLAT);
selectorMenu = new Menu(this);
setMenu(selectorMenu);
首先实现父组件的构造器Q注意,风D|ؓFLAT或者NONE。如果ؓBORDERQ那么运行时会发现组件是凚w下去的外观(WindowsXP以前是q种外观Q,通常对于自定义的外观都需要将风格讄为SWT.FLAT或者SWT.NONE。然后创建基本文本、菜单。对于菜单需要注意的是除了在构造时候要指定父组外,q要调用setMenu菜单加q来?/font>
接下来一步很关键Q是要进行自定义l制。绘制包括边框和下拉按钮的图标?/font>
完整代码如下Q?/font>
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);
}
});
首先Ҏlg是否可用军_Ҏ的颜艌Ӏ调用drawRectangle完成l制Ҏ的操作?/font>
然后l制图标Q注意,drawImage后两个参数是l制的坐标,也就是从哪里开始画P模拟MSN用户名输入组件时Q下拉按钮右端点x坐标取距ȝ件最右端x坐标QgetSize().xQ?像素处ؓ最佻I因此计算得出下拉按钮左端点x坐标为getSize().x- COMBO_ICON.getBounds().width - 5。(左端点x坐标与右端点x坐标相差COMBO_ICON.getBounds().width应该很容易理解,另外读者对坐标pȝ概念应该有一定了解)Q对于按钮的y坐标Q计思想是按钮的垂直位|居中,因此计算y坐标公式?getSize().y - COMBO_ICON.getBounds().height) / 2)?/font>
接下来一步是定基本文本lg的位|,完整代码如下Q?/font>
addControlListener(new ControlAdapter() {
@Override
public void controlResized(ControlEvent e) {
inputText.setBounds(1, 1, getSize().x
- COMBO_ICON.getBounds().width - 15, getSize().y - 2);
}
});
l该lg注册Control监听器时Q当该组件尺寸发生变化,会触发controlResizedҎQ在该方法内对基本文本组件的位置q行调整。模拟MSN用户名输入组件原则是Q基本文本组件的Ҏ被隐藏(构造时候通过Style设ؓSWT.FLATQ,左端点x坐标?Qؓ0的话会遮挡边框线的左端)Q长度是整个lg长度减去下拉按钮的长度再?5像素为最佻I从而保证与下拉按钮之间有一D距,高度是整个组件的高度?像素Q过高会遮挡ҎUѝ?/font>
接着我们要重写setEnabledҎQ代码如下:
public void setEnabled(boolean enabled) {
super.setEnabled(enabled);
setBackground(enabled ? ENABLED_BG : DISABLED_BG);
inputText.setEnabled(enabled);
redraw();
}
W一行的super.setEnabled(enabled);表示保持父类enable属性不变化Q之后是讄背景Qƈ讄inputText的enabled属性,最后调用redrawҎ通知lg重绘。需要阐明的是,redrawҎ会调用PaintListener中的ҎQ也是说会调用到构造函Cpublic void paintControl(PaintEvent e){...}q段代码Q如果组件添加了多个l制监听器,那么redraw会依ơ调用每个监听器的paintControlҎQ这与swing的事件机制是相同的。在redrawҎ中根据isEnabled()的值决定边框的颜色Q所以每当setEnableҎ被调用都应该执行重绘?/font>
q需要指出,通过dl制监听器来实现个性化的外观,q在调用影响外观的操作(比如setEnableQ时调用redrawҎ强制lg重绘Q这是自定义lg常用的实现手Dc你会看到接下来的很多方法会l常调用redraw通知lg重绘?/font>
除了setEnabledҎQ还有一些方法需要补充,一q列出:
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);
}
q些Ҏ单易懂,不作解释Q以上列丄只是最基本的方法,如果觉得功能不够q可以定义其他方法,例如可以对用L输入作验证?/font>
接下来回到构造函C来,QQ、MSN{一些Y件的d除了点击d按钮执行q可以在用户名、口令输入框上单d车来实现Qؓ了实现这一功能Q需要ؓ基本文本lgd一个选择监听器?/font>
inputText.addSelectionListener(new SelectionAdapter() {
@Override
public void widgetDefaultSelected(SelectionEvent e) {
commit();
}
});
q样Q当用户在文本组件上单击回RQ会执行commitҎ。下面是commitҎ的定义:
protected void commit() {
};
它不作Q何事情,因ؓlg不知道实际会应用在何U场合,卛_车操作具体作什么,q应该通过l承该组仉写commitҎ实现具体功能?/font>
然后为组件添加鼠标监听器Q实现用户单M拉按钮时菜单的弹出。完整代码如下:
addMouseListener(new MouseAdapter() {
@Override
public void mouseUp(MouseEvent e) {
if (e.x > getBounds().width - COMBO_ICON.getBounds().width - 15
&& e.x < getBounds().width && e.y > 0
&& e.y < getBounds().height) {
selectorMenu.setLocation(getScreemLocation().x + 3,
getScreemLocation().y + getSize().y + 23);
selectorMenu.setVisible(true);
}
}
});
if条g句子是判断鼠标指针的落点是否位于下拉三角的区域内Q计方法读者可以自己思考,之后讄弹出菜单出现的位|,Ҏ前面定义的getScreemLocationҎ可方便得出,需要提出的是计x坐标?#8220;+3”和y坐标?#8220;+23”Qؓ什么要再加上这个整数呢Q是因ؓWindowsH口的标题栏?0像素Q而getScreemLocation是无法自动计出的,有些H口可通过讄标题栏LQSWT的Shell通过指定SWT.NO_TRIM样式实现Q?#8220;+3是菜单弹出的位|不至于遮挡lgҎU,因此偏移3像素为最佳位|?#8221;。调用setVisible昄菜单Q不q前提条件是必须d了菜单项。构造函数最后是一步是讄lg为可用,虽然MSWT/Swinglg在构造时默认都是可用的,但是正如前面所qͼ重写setEnabledq不止设|是否被用Q重要的是组件在两态下的外观,所以在构造函数最后添加setEnabled(true);
以上讲述q多的是如何装饰lg的外观,接下来的重点介l如何用该组件缓存数据,使用MSN时候会发现Q单ȝ录用户名的下拉按钮时候,会弹出所有在本机dq的用户名列表(如果保存的话Q,下面讲述如何实现q一功能?/font>
我们的数据均保存在Vectorq个集合中,׃实际应用变化万千Q组件不可能知道实际保存何种cd的数据,因此Vector的元素类型统一讄为ObjectQ这也实在是一个不错的设计Q因为它不强制用者去实现某某接口Q或基类Q假如设计成Vector中的元素必须是实现某一特定接口IElementQ?/p>
private Vector dataSet = new Vector();
q样的话Q用者就必须其POJO作更改,以适应于此lgQ而Object作ؓ所有类的基c,因此可容UQ何类型的数据。接下来的一步很重要Q是数据与菜单兌h。定义如下方法public void loadMenuItems(Object[] objects)Q顾名思义是一ơ性读取一l元素,完整的代码如下:
public void loadMenuItems(Object[] objects) {
dataSet.clear();
MenuItem[] items = selectorMenu.getItems();
for (MenuItem item : items) {
item.removeSelectionListener(this);
item.dispose();
}
for (int i = 0; i < objects.length; i++) {
dataSet.add(objects[i]);
MenuItem item = new MenuItem(selectorMenu, SWT.PUSH);
item.setText(objects[i].toString());
item.setData(objects[i]);
item.addSelectionListener(this);
}
}
因ؓ是load所有数据,所以第一步是已有的数据清空Q包括Vector中的数据和菜单中的菜单项。然后是对传入的Object数组作遍历,对于每一,之dq集合然后创Z个菜单项Q下一步item.setText(objects[i].toString());是设|菜单项的文字,toString()Ҏ是Object的固有方法,但是实际应用时必重写该Ҏ的实现。接下来是item.setData(objects[i]);单项讄数据Q这一步非帔R要,SWT的每一个组仉hpublic void setData (Object data)和public Object getData ()Ҏ。还有Hashl构的public void setData (String key, Object value)和public Object getData (String key)。稍后会看到通过item.getData();取出创徏时存入的数据。最后一行是单项d事g监听器,qɾlg本n作ؓ监听器,使组件本w实现SelectionListener接口Q然后添加下面两个方法:
public final void widgetDefaultSelected(SelectionEvent e)
public final void widgetSelected(SelectionEvent e)
其中widgetDefaultSelected在单d车时触发Q对文本框这Llg适用QwidgetSelected是鼠标单L触发适用于按钮、菜单项。因此我们只处理widgetSelected?/p>
public final void widgetSelected(SelectionEvent e) {
MenuItem item = (MenuItem) e.getSource();
selectedItem = item.getData();
String text = item.getData().toString();
inputText.setText(text);
inputText.setSelection(0, text.length());
selected(item.getData());
}
首先取得事g源即单击的菜单项Q然后更新selectedItem引用指向q个菜单保存的数据Q先前通过setDataҎd的)Q接下来的代码不作解释,很容易理解。值得注意的是最后一行selected(item.getData());作用是当用户选中菜单某一ҎQ根据当前选择的那个数据自动执行相应的操作QselectedҎ定义如下Q?/p>
protected void selected(Object object) {
};
与commitҎ一P是需要根据实际情况自定义处理逻辑的?/p>
最后添加如?个方法:
public void select(int index) {
MenuItem[] items = selectorMenu.getItems();
if (index < 0 || index >= items.length) {
throw new ArrayIndexOutOfBoundsException(
"the index value must between " + 0 + "and "
+ (items.length - 1));
}
selectedItem = items[index].getData();
inputText.setText(items[index].getText());
}
select用来讄当前选择W几个项QgetSelectedItemq回当前用户选择的数据?/p>
到此为止QComboSelector已经完成Q可以作为API使用了,下面我们~写一个程序测试该lg?/p>
首先~写一个POJOQ如下:
package swt.custom;
public class Person {
private String userName;
private String password;
public Person(String userName, String password) {
this.userName = userName;
this.password = password;
}
public String getPassword() {
return password;
}
public String getUserName() {
return userName;
}
@Override
public String toString() {
return userName;
}
}
单至极的一个类Q注意它的toStringҎQ返回用户名属性作为显C?/p>
接下来通过一个demo看看实际q行效果?br /> 用swt-designer工具创徏一个ShellQ在createContentsҎ体内d如下代码Q?/p>
final ComboSelector selector = new ComboSelector(this) {
@Override
protected void commit() {
System.out.println("current data is "
+ ((Person) getSelectedItem()).getUserName());
}
@Override
protected void selected(Object object) {
System.out.println(((Person) object).getPassword());
}
};
selector.setBounds(114, 78, 200, 20);
Person[] persons = new Person[] {
new Person("play_station3@sina.com", "111111"),
new Person("rehte@hotmail.com", "222222"),
new Person("yitong.liu@bea.com", "password"),
new Person("使用其他Windows Live ID d", "no") };
selector.loadMenuItems(persons);
selector.select(1);
q行l果如下Q?br />
本程序的完整代码q里下蝲