`

SWT:实现自我绘制的Button组件

阅读更多
本文来自Java天下社区:http://www.javatx.cn/index.jsp由网友spider 发表在JavaGUI版
        在所有SWT组件中,Button几乎是最常用的,其功能在对于一般的情况来说也足够丰富了。你可以为Button组件设置要显示在其中的文本或者图像、设定ToolTip,甚至只要修改一个风格样式就能得到一个看上去相当不错的方向箭头按钮。 

  然而,我对Button组件还是不能感到满意。最大的遗憾就是:对它的外观,所能做的工作也就仅限于此了。如果你想让按钮拥有一个漂亮的、渐变色的背景和一些特殊的文字效果,怎么办呢?答案是没有办法。Button类里面似乎没有任何方法提供我想要的功能。

  我曾尝试过的第一个想法是用Button.addPaintListener来修改按钮的外观。但是,结果令人失望——虽然它显示出来的时候的确按照预想进行绘制了,但是当你用鼠标去按它的时候,马上又变回了原本灰头土脸的样子。显然,在按下按钮的时候,它并不是触发paint事件,而是按照自己的想法画出原本的按钮,于是我的工作全部白费了。

  如果尝试为按钮设定图像会怎么样呢?这也不是一个好主意。首先,不管你选择什么样的图像,都没办法去掉按钮四周的边框,而正是这些边框严重破坏了图像的和谐感;其次,如果你的程序有几十甚至上百个按钮,为每个按钮都维护一幅图像(甚至更多—— 理论上每个按钮在普通状态和被按下、禁用的状态下,甚至当鼠标移进移出按钮的时候,都应当显示不同的图像)明显是在浪费系统资源;如果你们的美工听说需要做几百个图片,大概也不会给你好脸色看。此外,图像有一个严重的缺点是:它所拥有的像素数目是固定的,难以随着界面的放大和缩小同时变化。如果强制进行缩放的话,会出现明显的锯齿和失真,最终让你精心设计的窗口变得惨不忍睹。最好还是放弃这个想法。

  如果以Canvas为基础,设计一个伪装的按钮组件又如何呢?听起来好像很不错,因为采用这种办法的话,我们对如何绘制组件的表面就有了完整的控制权。不过这也意味着你必须对按钮的状态进行手工维护。虽然Button本身是一个很简单的组件,但是重复去做标准按钮已经作好的工作似乎还是有点无谓。还有一件事情是应当考虑的:我们知道,JFace中的Action机制可以将标准按钮、菜单项和工具栏按钮这三种界面组件纳入一个统一的事件处理体系。然而,如果我们从Canvas派生去模拟一个按钮的话,不论你模拟到多么相似的地步,它毕竟不是一个真正的Button,Action也不会给它同等的待遇。也就是说手工制作的按钮无法和 JFace  Action体系协同工作——除非你去修改Action的处理方法,让它去接纳新的按钮对象。这可不是一件轻松的工作。

  如果上面的方法都行不通的话,应当怎么办呢?我们知道,和Swing这样的框架不同,SWT中的按钮其实就是操作系统底层所实现的按钮(这一点也可以用 SPY++或者Winsight32之类的工具证实)。同时我们也知道,操作系统——至少是Windows系统,对按钮已经提供了自我绘制的机制,这就是所谓的Owner  Draw(称为所有者绘制的原因是因为默认情况下绘制消息是发送给按钮的父窗口处理的,但是父窗口也可以把这个皮球再踢回给按钮,让它自己解决)。在 Win32  API中,凡是使用BS_OWNERDRAW风格创建、并且能够(通过消息反射)响应WS_DRAWITEM消息的按钮,都可以获得这种定制的能力。  了解这一点,接下来的任务就是研究Button组件有没有开放这个接口供我们修改了。对Button组件的源代码进行粗略的浏览后,我发现了如下的方法:

程序代码:
package org.eclipse.swt.widgets;

public class Button extends Control {
 …
 LRESULT wmDrawChild (int wParam, int lParam) {
 if ((style & SWT.ARROW) == 0) return super.wmDrawChild (wParam, lParam);
 DRAWITEMSTRUCT struct = new DRAWITEMSTRUCT ();
 ....


  其中DRAWITEMSTRUCT结构的出现是一个明显的提示:这里就是WM_DRAWITEM消息的响应函数,很幸运它没有声明为final的,只要重载它并提供自己的实现就行了。

  看起来是个小case,实际上也是。不过,还有一处小麻烦需要克服。注意wmDrawChild方法没有使用任何访问限定符,这意味着它是 package  friendly的——同一个包中的对象可以访问和重载此方法,其他包中的对象就没有这个权力了。也就是说,要定制按钮对象,我们新建的对象也需要放在同一个包(org.eclipse.swt.widgets)中。看起来有点像在使用Hack手段,不过为了突破SWT给我们的限制,眼下也只好稍稍将就一下。好在swt的包没有密封(Sealed),不然我就不得不再次宣称此路不通了。

  既然障碍已经扫清,接下来我们可以来实现前面的想法了。这里我做了一个决定,在上述包中只加入一个抽象类,目的是把必要的接口暴露出来;至于如何绘制按钮,则留给具体的按钮对象根据应用程序的需求来决定。这样,不管你希望实现Windows  XP风格的按钮、还是卡通风格的按钮、或是平面样式的,总之不论什么千奇百怪的风格,只要继承一个类并重载一个绘制方法就行了,而不必每次都要和  Button类的内部打交道。

  基于这种考虑,实现自绘按钮的抽象类如下:

程序代码:
package org.eclipse.swt.widgets;

import org.eclipse.swt.internal.win32.*;

public abstract class OwnerDrawButton extends Button
{
 public OwnerDrawButton( Composite parent, int style )
 {
  super( parent, style );

  int osStyle = OS.GetWindowLong( handle, OS.GWL_STYLE );
  osStyle |= OS.BS_OWNERDRAW;
  OS.SetWindowLong( handle, OS.GWL_STYLE, osStyle );
 }

 LRESULT wmDrawChild( int wParam, int lParam )
 {
  super.wmDrawChild( wParam, lParam );
  DRAWITEMSTRUCT struct = new DRAWITEMSTRUCT();
  OS.MoveMemory( struct, lParam, DRAWITEMSTRUCT.sizeof );
  ownerDraw( struct );
  return null;
 }

 protected abstract void ownerDraw( DRAWITEMSTRUCT dis );
}



  注意这个抽象类所作的工作。在构造函数中,它调用操作系统方法为自己加入了BS_OWNERDRAW风格。如果没有这一步,那么操作系统将不会把这个按钮视为自绘的按钮,也不会向其发送任何绘制消息。接下来是WM_DRAWITEM消息的响应函数。在这个函数中,我们简单的把必要的绘制参数提取出来,然后调用抽象方法ownerDraw去进行实际的绘制工作。任何从OwnerDrawButton类派生的按钮对象必须重载此 ownerDraw方法,来决定如何绘制自身。

 作为一个例子,我实现了一个具体的按钮类。这个按钮用从上至下的渐变色背景添充整个按钮,然后绘制出按钮的文字。如果当前按钮被按下,该类还调整了一下文字的位置,以显示出“按下”的外观效果。代码稍微有些长,这是因为消息函数所提供的是一个操作系统才了解的原生HDC对象,而不是我们所熟悉的GC类,因此也需要相应的用原生API进行处理。不过,其原理是相当简单的——你只需要在给出的 HDC上画出你想要的任何效果就行了。

程序代码:
import org.eclipse.swt.SWT;
import org.eclipse.swt.graphics.*;
import org.eclipse.swt.internal.win32.*;
import org.eclipse.swt.widgets.*;

public class TestButton extends OwnerDrawButton
{
 TestButton( Composite parent )
 {
  super( parent, SWT.PUSH );
 }

 @Override
 protected void ownerDraw( DRAWITEMSTRUCT dis )
 {
  Rectangle rc = new Rectangle( dis.left, dis.top, dis.right - dis.left,dis.bottom - dis.top );
  Color clr1 = new Color( getDisplay(), 0, 255, 128 );
  Color clr2 = new Color( getDisplay(), 0, 128, 255 );
  fillGradientRectangle( dis.hDC, rc, true, clr1, clr2 );
  clr1.dispose();
  clr2.dispose();

  SIZE size = new SIZE();
  String text = getText();
  char[] chars = text.toCharArray();
  int oldFont = OS.SelectObject( dis.hDC, getFont().handle );
  OS.GetTextExtentPoint32W( dis.hDC, chars, chars.length, size );
  RECT rcText = new RECT();
  rcText.left = rc.x;
  rcText.top = rc.y;
  rcText.right = rc.x + rc.width;
  rcText.bottom = rc.y + rc.height;
  if ( (dis.itemState & OS.ODS_SELECTED) != 0 )
   OS.OffsetRect( rcText, 1, 1 );
  OS.SetBkMode( dis.hDC, OS.TRANSPARENT );
  OS.DrawTextW( dis.hDC, chars, -1, rcText, OS.DT_SINGLELINE | OS.DT_CENTER | OS.DT_VCENTER );
  OS.SelectObject( dis.hDC, oldFont );
 }

 private void fillGradientRectangle( int handle, Rectangle rc,boolean vertical, Color clr1, Color clr2 )
 {
  final int hHeap = OS.GetProcessHeap();
  final int pMesh = OS.HeapAlloc( hHeap, OS.HEAP_ZERO_MEMORY,GRADIENT_RECT.sizeof + TRIVERTEX.sizeof * 2 );
  final int pVertex = pMesh + GRADIENT_RECT.sizeof;

  GRADIENT_RECT gradientRect = new GRADIENT_RECT();
  gradientRect.UpperLeft = 0;
  gradientRect.LowerRight = 1;
  OS.MoveMemory( pMesh, gradientRect, GRADIENT_RECT.sizeof );

  TRIVERTEX trivertex = new TRIVERTEX();
  trivertex.x = rc.x;
  trivertex.y = rc.y;
  trivertex.Red = (short)(clr1.getRed() <<;
  trivertex.Green = (short)(clr1.getGreen() <<;
  trivertex.Blue = (short)(clr1.getBlue() <<;
  trivertex.Alpha = -1;
  OS.MoveMemory( pVertex, trivertex, TRIVERTEX.sizeof );

  trivertex.x = rc.x + rc.width;
  trivertex.y = rc.y + rc.height;
  trivertex.Red = (short)(clr2.getRed() <<;
  trivertex.Green = (short)(clr2.getGreen() <<;
  trivertex.Blue = (short)(clr2.getBlue() <<;
  trivertex.Alpha = -1;
  OS.MoveMemory( pVertex + TRIVERTEX.sizeof, trivertex, TRIVERTEX.sizeof );

   boolean success = OS.GradientFill( handle, pVertex, 2, pMesh, 1,vertical ? OS.GRADIENT_FILL_RECT_V : OS.GRADIENT_FILL_RECT_H );
  OS.HeapFree( hHeap, 0, pMesh );

  if ( success )
   return;
 }

 @Override
 protected void checkSubclass()
 {}
}


  如果你使用的是JDK  1.4或者更低的版本,请把@Override标记去掉以后才能编译,因为这是一个Java  5.0中才有的特性。此外,我重载了checkSubclass方法并提供了一个空的实现;如果不这么做的话,那么SWT在默认情况下是不允许你从 Button类继承的。

  这个地方请允许我稍稍跑一下题。上面代码中的fillGradientRectangle方法——从它的名字你大概可以猜到,这个方法的作用是画出一个渐变色的矩形区域。我是从GC.fillGradientRectangle中“偷”来的代码,针对按钮类作了一些修改就可以了。让我感到讶异的是,在整理这段代码的时候,我发现从SWT中调用Win32  API实在是太方便了——比我原先猜想的还要容易得多。即便是微软的P/Invoke也要比这麻烦。当然,这很大程度上要归功于SWT将系统函数很好的封装在了一个OS静态类中。(如果你不知道P/Invoke是什么的话,简单的说它就是微软在.Net平台中提供的、用来调用系统API和自定义DLL中的方法的技术)。

  上面那些绘图的代码基本上是Windows  SDK的编程风格。因为我本人有很多这方面的开发经验,所以这些代码对我来说是相当清晰且直观的。不过我估计纯粹的Java程序员或许对这段代码不会有很大的好感。理论上讲,我可以把这些代码用更加OO的方式包装起来,从而看上去能好看一些。不过,本文的目的在于讲述实现技术,用包装的话反而会破坏效果。如果你感兴趣的话,也可以尝试自己来包装一下。

  需要讲解的地方到这里就全部结束了。为了完整起见,我把程序框架类的代码也列在下面,但是不做什么说明——基本上每个SWT程序中这段代码都是大同小异的。

import  org.eclipse.swt.layout.FillLayout;
import  org.eclipse.swt.widgets.*;

public  class  Application
{
 public  static  void  main(  String[]  args  )
 {
  Display  display  =  Display.getDefault();
  Shell  shell  =  new  Shell(  display  );
  init(  shell  );

  shell.pack();
  shell.open();
  while  (  !shell.isDisposed()  )
  {
   if  (  !display.readAndDispatch()  )
    display.sleep();
  }
 }

 private  static  void  init(  Shell  shell  )
 {
  shell.setText(  "Owner  Draw  Button  Test"  );
  FillLayout  layout  =  new  FillLayout();
  layout.marginWidth  =  layout.marginHeight  =  8;
  shell.setLayout(  layout  );

  Button  btn  =  new  TestButton(  shell  );
  btn.setText(  "Owner  Draw  Button"  );
  btn.setToolTipText(  "Hello,  I'm  a  OwnerDraw  Button!"  );
 }


  下面是程序运行的界面。尽管这远远算不上完美——真正的按钮还应该考虑,是否能够和用户的任何配置下,特别是有窗口主题的时候也能正常工作?完美的按钮实现可能需要至少数百行的代码才行。不过对本文的目的来说,这样已经足够了。可惜的是按下按钮的效果无法从图中体现;你可以自己运行一下这个程序来体验一下实际的感觉。


 
分享到:
评论

相关推荐

    SWT开发组件demo

    这通常涉及到对`Canvas`组件的使用,开发者可以在其上绘制自己的图形,实现特定的功能。 4. **滚动条(ScrollBar)**:当组件内容过多无法完全显示时,滚动条能帮助用户浏览全部信息。SWT提供了垂直和水平滚动条,...

    Java使用SWT技术实现跳棋游戏.rar

    1. SWT组件使用:如Canvas、Button等,以及如何布局和定制界面。 2. Java GUI编程:事件监听和处理,用户交互。 3. 数据结构和算法:用于表示棋盘状态和计算棋子的合法移动。 4. 游戏逻辑实现:棋子移动规则,游戏...

    自定义SWT组件文档,源自Eclispe 官方

    然而,为了满足特定需求或实现独特的功能,有时我们需要创建自定义的SWT组件。本篇文章将深入探讨如何自定义SWT组件,包括创建独立组件和复合组件,并分析如何在保证跨平台兼容性的前提下进行扩展。 创建自定义组件...

    SWT JFace 按键、事件、监听

    SWT提供了低级别的组件,用于创建跨平台的图形用户界面;而JFace则在此基础上提供了更高级别的抽象,简化了复杂用户界面的开发过程。 在SWT中,键盘事件的处理非常关键,尤其是对于那些依赖于键盘输入的应用程序。...

    Eclipse SWT 开发参考

    应用SWT绘制2D图像 SWT提供了强大的绘图能力,支持基本的2D图形绘制,如线条、矩形、圆等。 #### 8. SWT的OpenGL应用 SWT还可以与OpenGL结合使用,用于开发复杂的3D图形应用程序。 #### 9. SWT和Swing、AWT技术...

    SWT开发参考文档

    应用SWT绘制2D图像 SWT提供了强大的图形绘制能力,可以用于创建复杂的2D图形界面。开发者可以利用`GC`(Graphics Context)类来绘制线条、矩形、圆形等各种形状。 #### 8. SWT的OpenGL应用 SWT还支持OpenGL图形...

    swt 监控代码资料

    2. SWT组件:SWT包含一系列的控件,如按钮(Button)、文本框(Text)、标签(Label)、表格(Table)、树(Tree)等,这些组件可以用来构建复杂的用户界面。每个组件都有自己的方法和事件处理机制,可以通过监听器...

    Java swt完整教程

    SWT包括各种常见的GUI组件,如按钮(Button)、文本框(Text)、列表(List)、表格(Table)、树(Tree)等。每个组件都有相应的事件处理机制,开发者可以通过监听事件来实现用户交互。 4. SWT布局管理 SWT提供...

    xia__nested_class.rar_SWT

    1. 创建SWT组件:如按钮(Button)、文本框(Text)、列表(List)等。 2. 布局管理:使用GridLayout、RowLayout、FillLayout等布局管理器来组织这些组件在窗口中的位置。 3. 事件处理:为组件添加监听器,处理用户...

    SWT.zip 图形化

    例如,`Shell`类代表顶级窗口,`Composite`类作为容器可以包含其他组件,`Button`、`Text`、`Label`等则分别对应各种基本交互元素。 2. **事件驱动模型**:SWT遵循事件驱动编程模式,用户与界面交互时产生的事件会...

    Java SWT 图形用户界面教程 PDF

    - **Widgets**: SWT 包含多种基本组件,如按钮(Button)、文本框(Text)、列表(List)、树(Tree)、滚动条(ScrollBar)等,这些组件可以直接在用户界面上使用。 - **Composite**: Composite 是一个容器,可以...

    俄罗斯方块 eclipse+swt 实现

    在SWT中,开发者可以使用`Composite`类来构建复杂的用户界面,通过组合不同的组件,如`Canvas`用于绘制游戏画面,`Button`用于设置游戏控制,`Label`用于显示分数等。这些组件的布局可以通过`GridLayout`、`...

    SWT和JFace开发实例

    - 基本组件:SWT提供了丰富的UI组件,如Button、Label、Text、Shell、Composite等,它们对应着操作系统中的原生控件。 - 事件处理:SWT使用监听器模式来处理用户交互,如MouseListener、KeyListener和...

    swt仿qq截图

    6. **用户交互**:添加按钮和菜单项,如开始截图、保存、取消等,这些可以通过`Button`、`MenuItem`等控件实现。处理对应的事件,如点击按钮触发保存或取消操作。 7. **优化用户体验**:考虑添加阴影效果、拖动时的...

    java swt自定义控件

    自定义控件的核心是重写`paintControl(PaintEvent e)`方法,这是SWT绘制控件的入口点。`PaintEvent`对象提供了绘图所需的信息,如画笔(`GC`)和绘图区域。通过`GC`,我们可以绘制各种图形,如线条、矩形、文本等。 ...

    java swt 跳棋程序 源码

    3. **图形绘制**:Java SWT中的Canvas组件允许自定义绘制,可以用来实现棋子和棋盘的图形化表示。这涉及到Java的Graphics2D API,包括画线、填充、颜色设置等技巧。 4. **数据结构与算法**:为了实现跳棋的逻辑,...

    swt.rar_SWT_swt API SRC_swt api download

    4. 图形和图像:SWT支持绘制基本图形和加载显示图像,开发者可以通过`org.eclipse.swt.graphics`包中的类进行操作。 5. 国际化和本地化:SWT支持多语言,开发者可以通过`org.eclipse.swt.internal.loc`包进行国际化...

    SWT综合教程及安装指导

    1. 组件(Widgets):SWT提供了一系列基本组件,如按钮(Button)、文本框(Text)、列表(List)、树(Tree)、表格(Table)等,这些组件构成了GUI的基础元素。 2. 构建窗口:通过`Shell`类创建窗口,设置其大小...

    SWT计算器

    它可能还涉及到SWT的布局管理器、颜色和字体设置、自定义控件绘制等方面的知识。 总的来说,“SWT计算器”是一个利用SWT库创建的图形用户界面应用,结合了基本的计算功能和可能的日历选择功能。通过研究这个项目,...

    SWT 资料

    8. **自定义组件**:SWT 允许开发者创建自己的组件,通过继承现有组件并覆盖其方法,或者使用 `Canvas` 类作为画布自定义绘制。 9. **国际化与本地化**:SWT 支持应用程序的国际化,可以轻松地切换不同语言的界面。...

Global site tag (gtag.js) - Google Analytics