`
javagui
  • 浏览: 27596 次
  • 性别: Icon_minigender_1
  • 来自: 北京
社区版块
存档分类
最新评论

用SWT实现MSN风格的下拉框

阅读更多
SWT一个所谓的优点是它的本地化外观,因为它是通过JNI调用操作系统的组件,从而可以保证外观上适合大多数用户的需求,但是一些IM类软件商往往希望它们的产品有着一套独特的外观,这对SWT这种原生组件来说就有些力不从心了,严格来说如果你的用户对外观要求比较苛刻,那么Swing是首选,因为L&F机制可以确保你做到这一点,另外Swing还有着许多SWT不具备的优点,例如半透明组件、渲染等,但是少数的这些特性用SWT还是可以模拟的,本文就向大家介绍如何通过自定义组件实现MSN风格的下拉框。
  通常来说,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() {
  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);
 }

这些方法简单易懂,不作解释,以上列举的只是最基本的方法,如果觉得功能不够还可以定义其他方法,例如可以对用户的输入作验证。

接下来回到构造函数中来,QQ、MSN等一些软件的登录除了点击登录按钮执行还可以在用户名、口令输入框上单击回车来实现,为了实现这一功能,需要为基本文本组件添加一个选择监听器。

inputText.addSelectionListener(new SelectionAdapter() {
   @Override
   public void widgetDefaultSelected(SelectionEvent e) {
    commit();
   }
  });

这样,当用户在文本组件上单击回车,会执行commit方法。下面是commit方法的定义:

protected void commit() {
};

它不作任何事情,因为组件不知道实际会应用在何种场合,即回车操作具体作什么,这应该通过继承该组件重写commit方法实现具体功能。

然后为组件添加鼠标监听器,实现用户单击下拉按钮时菜单的弹出。完整代码如下:

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条件句子是判断鼠标指针的落点是否位于下拉三角的区域内,计算方法读者可以自己思考,之后设置弹出菜单出现的位置,根据前面定义的getScreemLocation方法可方便得出,需要提出的是计算x坐标的“+3”和y坐标的“+23”,为什么要再加上这个整数呢?是因为Windows窗口的标题栏高20像素,而getScreemLocation是无法自动计算出的,有些窗口可通过设置将标题栏去掉(SWT的Shell通过指定SWT.NO_TRIM样式实现)“+3是使菜单弹出的位置不至于遮挡组件边框线,因此偏移3像素为最佳位置”。调用setVisible显示菜单,不过前提条件是必须添加了菜单项。构造函数最后是一步是设置组件为可用,虽然任何SWT/Swing组件在构造时默认都是可用的,但是正如前面所述,重写setEnabled并不止设置是否被禁用,重要的是组件在两态下的外观,所以在构造函数最后添加setEnabled(true);


以上讲述过多的是如何装饰组件的外观,接下来的重点将介绍如何用该组件缓存数据,使用MSN时候会发现,单击登录用户名的下拉按钮时候,会弹出所有在本机登录过的用户名列表(如果保存的话),下面讲述如何实现这一功能。


我们的数据均保存在Vector这个集合中,由于实际应用变化万千,组件不可能知道实际保存何种类型的数据,因此Vector的元素类型统一设置为Object,这也实在是一个不错的设计,因为它不强制使用者去实现某某接口,或基类,假如设计成Vector中的元素必须是实现某一特定接口IElement,

private Vector dataSet = new Vector();

这样的话,使用者就必须将其POJO作更改,以适应于此组件,而Object作为所有类的基类,因此可容纳任何类型的数据。接下来的一步很重要,是将数据与菜单关联起来。定义如下方法public void loadMenuItems(Object[] objects),顾名思义是一次性读取一组元素,完整的代码如下:

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所有数据,所以第一步是将已有的数据清空,包括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) {
  MenuItem item = (MenuItem) e.getSource();
  selectedItem = item.getData();
  String text = item.getData().toString();
  inputText.setText(text);
  inputText.setSelection(0, text.length());
  selected(item.getData());
 }

首先取得事件源即单击的菜单项,然后更新selectedItem引用指向这个菜单项保存的数据(先前通过setData方法添加的),接下来的代码不作解释,很容易理解。值得注意的是最后一行selected(item.getData());作用是当用户选中菜单某一项时,根据当前选择的那个数据自动执行相应的操作,selected方法定义如下:

protected void selected(Object object) {
};

与commit方法一样,是需要根据实际情况自定义处理逻辑的。

最后添加如下2个方法:

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用来设置当前选择第几个项,getSelectedItem返回当前用户选择的数据。


到此为止,ComboSelector已经完成,可以作为API使用了,下面我们编写一个程序测试该组件。

首先编写一个POJO,如下:

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

简单至极的一个类,注意它的toString方法,返回用户名属性作为显示。

接下来通过一个demo看看实际运行效果。
用swt-designer工具创建一个Shell,在createContents方法体内添加如下代码:

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 登录", "no") };
  selector.loadMenuItems(persons);
  selector.select(1);

运行结果如下:

  本程序的完整代码这里下载



分享到:
评论

相关推荐

    QQ登录下拉框QQ登录下拉框

    自己写的一个模仿QQ登陆窗体的下拉框 基本功能都已实现 有动画效果 内附使用说明 工具VS2010 你值得拥有 ------------------------------------------------------------------------------------------------- ...

    VC++中实现复选下拉框CCheckComboBox

    该资源在VS2008SP1 IDE中编写,主要介绍了如何在VC++中实现复选下拉框,其中包含了CheckComboBox.h和CheckComboBox.cpp两个文件,这两个文件实现了复选下拉框的功能。但作者Margin在使用复选下拉框时发现:在模态...

    jquery实现的多选下拉框

    《jQuery实现的多选下拉框深度解析》 在网页开发中,为了提供更好的用户体验,我们经常需要使用到多选下拉框。jQuery库为我们提供了丰富的功能,使得创建交互式的多选下拉框变得轻而易举。在这里,我们将深入探讨...

    jsp实现连动下拉框

    根据提供的文件信息,我们可以深入解析如何使用 JSP(JavaServer Pages)来实现连动下拉框功能。连动下拉框通常用于需要根据一个选择器的选择结果动态改变另一个选择器内容的情况,例如在用户选择了一个国家后,自动...

    Asp.net实现的彩色下拉框源码

    5. **ASP.NET AJAX**:如果项目中使用了ASP.NET AJAX,可以结合UpdatePanel和ScriptManager来实现异步更新,使得彩色下拉框在用户交互时能动态改变颜色。 关于项目文件"ColorDDL",这可能是整个解决方案的文件夹,...

    用WPF实现多选下拉框

    以上就是用WPF实现多选下拉框的基本步骤。虽然这个实现可能还不够完善,但通过不断的优化和调整,可以满足各种复杂的需求。例如,可以增加搜索功能,使用户在大量选项中快速找到想要的项,或者添加排序和过滤功能,...

    jquery实现可多选下拉框

    本文将深入探讨如何使用 jQuery 来实现一个可多选的下拉框功能,这对于创建交互性强的用户界面非常有用。下面我们将详细讲解实现这个功能的关键步骤和涉及的技术点。 首先,我们需要一个 HTML 结构来定义我们的多选...

    使用bootstrap实现多选下拉框

    在Bootstrap中实现多选下拉框可以极大提升用户体验,使得用户在有限的空间内可以选择多个选项。以下是如何使用Bootstrap来创建这样一个功能的详细步骤。 首先,确保你已经在你的项目中引入了Bootstrap的相关资源。...

    一个简单的实现多选的下拉框

    本文将详细讲解如何实现这样一个简单的多选下拉框,并探讨其功能完善性和移植性。 首先,一个基本的下拉框在HTML中通常通过`&lt;select&gt;`标签来实现,但原生的HTML下拉框只支持单选,不支持多选。要实现多选,我们需要...

    c# winform 窗体样式风格 多种漂亮好看的样式风格 下拉框选择

    本文将深入探讨如何实现多种漂亮、好看的样式风格,并关注下拉框选择这一常见控件的美化方法。 首先,让我们了解WinForm窗体的基础样式设置。在C#中,我们可以使用`Form`类的属性来调整窗体的基本样式,例如`...

    纯前端实现下拉框.zip

    下面我们将深入探讨下拉框的实现原理、使用jQuery进行交互以及前端选择下拉框的设计与优化。 一、下拉框的基础知识 下拉框在HTML中由`&lt;select&gt;`元素表示,通常与`&lt;option&gt;`元素配合使用,以展示可供选择的选项。...

    combox实现的可编辑下拉框

    本文将深入探讨如何使用JavaScript实现一个可编辑的下拉框,并阐述其核心知识点。 ### 1. 可编辑下拉框的概念 传统的下拉框允许用户从预定义的选项中进行选择,但不支持用户直接在框内输入自定义值。而可编辑...

    JS实现多选项下拉框智能提示

    JS实现多选项下拉框智能提示,使用说明见js文件。适用于在html的select标签中选项较多的情况,支持拼音和五笔输入的智能提示。

    用VBA实现Excel单元格下拉框复选demo

    本示例“用VBA实现Excel单元格下拉框复选demo”旨在教大家如何利用Visual Basic for Applications(VBA)编程语言在Excel中实现这样的功能。下面我们将详细探讨这一主题。 首先,让我们了解下拉框和复选框的基本...

    Extjs 轻松实现下拉框联动

    最近小弟做了Extjs实现实现下拉框联动的效果,参考了好久才学会,闲下来发一个简单的例子。。呵呵

    Android使用 Spinner控件实现下拉框功能

    通过使用 Spinner 控件,我们可以在 Android 应用程序中实现下拉框的效果。下面我们将通过实例代码来介绍如何使用 Spinner 控件实现下拉框功能。 Spinner 控件的使用步骤 1. 首先,我们需要在 XML 文件中声明 ...

    基于jquery实现的多选下拉框

    **基于jQuery实现的多选下拉框** 在Web开发中,传统的HTML下拉框(`&lt;select&gt;`元素)通常只支持单选,但有时我们可能需要一个可多选的下拉框来提供用户更多的选择灵活性。这篇教程将详细介绍如何利用jQuery库来创建...

    使用XML文件实现3级下拉框级联

    "使用XML文件实现3级下拉框级联"是一种有效的方法,尤其当数据量适中时。这种方法允许我们通过结构化的XML文件来存储和管理级联下拉框的数据,便于读取和更新。 首先,我们需要理解XML(eXtensible Markup Language...

    Cocos Create 实现下拉框效果

    在本文中,我们将深入探讨如何使用Cocos Create实现下拉框效果。Cocos Create是一款强大的2D游戏开发工具,它提供了丰富的UI组件和动画功能,使得开发者能够轻松创建交互式的游戏和应用。在这个自创的下拉框样式中,...

    jQuery扁平化风格下拉框美化插件

    1. **扁平化设计**:FancySelect遵循现代网页设计趋势,使用扁平化设计语言,使得下拉框与整体界面风格协调一致,提升网页的视觉美感。 2. **可定制化**:该插件允许开发者根据需求自定义样式,如颜色、字体、边框等...

Global site tag (gtag.js) - Google Analytics