【SWT】创建自己的SWT组件

创建自己的SWT组件

在编写应用程序时,您通常使用 SWT 提供的标准小部件。有时,您需要创建自己的自定义小部件。例如,您可能想要添加标准小部件未提供的新型小部件,或扩展现有小部件的功能。本文阐述了几种不同的 SWT 扩展策略并向您展示如何使用它们。

创建自己的小部件

概述

在编写应用程序时,您通常使用 SWT提供的标准小部件。有时,您需要创建自己的自定义小部件。您可能希望这样做有几个原因:

  • 添加标准小部件未提供的新型小部件·
  • 扩展现有小部件的功能

自定义小部件是通过在现有小部件类层次结构中创建子类来创建的。

可移植性

在编写自定义小部件之前考虑可移植性非常重要。SWT 可以通过以下方式进行扩展:

  • 编写一个 100% Java™ 可移植的新小部件·
  • 以 100% Java 可移植方式扩展现有小部件·
  • 编写一个包装现有本机小部件的新小部件——不可移植·
  • 通过调用本机扩展现有小部件 - 不可移植

此外,这些的组合可以在不同的平台上使用:

  • 编写一个新的小部件,在一个平台上包装现有的原生小部件,但在其他平台上是 100% Java 可移植的·
  • 通过在一个平台上调用本机来扩展现有小部件,但在其他平台上调用 100% Java

可移植代码这当然涉及两次实现小部件——在一个平台上使用本机调用,在另一个平台上使用可移植代码——同时为两者保持相同的 API。

每个 SWT 平台都附带一个共享库(例如,Windows® 上的 DLL)和一个 jar(用于 Java 类文件)。共享库包含 SWT 所需的所有本机函数,但它并不是平台上可用的完整函数集。因此,要公开 SWT 未公开的本机函数或本机小部件,您需要编写自己的共享库。如果您在一个平台上使用本机代码和另一个平台上的可移植代码的组合,请确保您使用本机小部件调用平台上的共享库,并使用可移植小部件调用平台上的 jar。

最后一点:SWT 与其共享库的接口是内部 SWT 代码。它并不是要为应用程序提供一个框架来访问所有平台上所有可能的本机功能——这将是一项艰巨的任务。本文档的目的之一是展示如何将 C 代码与 SWT 集成,而不是为操作系统建模。因此,本文档中编写本机所采用的方法与 SWT 所采用的方法不同。

编写便携式小部件

SWT 库提供了两个小部件类,它们通常用作自定义 100% Java 可移植小部件的基础:

  • Canvas - 创建基本小部件。
  • 复合 - 创建复合小部件。

基本小部件

基本小部件不包含任何其他小部件,并且不是从任何其他小部件构建的。基本小部件自己绘制。基本小部件的一个示例是 Button。另一个例子是文本。要创建自定义基本小部件,请继承 Canvas。

复合小部件

复合小部件包含其他小部件,和/或由其他小部件组成。复合小部件的一个示例是 Combo。它包含一个文本、一个按钮和一个列表。另一个例子是 Group。它可以包含任意数量的孩子。要创建自定义复合小部件,请将 Composite 子类化。

精明的读者可能已经注意到 Canvas 实际上是 Composite 的子类。这是底层实现的产物。我们将 Canvas 视为您可以绘制的东西,而 Composite 视为有孩子的东西。因此,决定子类的规则是这样的:如果您的小部件有或将有子类,则子类化 Composite。如果您的小部件没有并且永远不会有子级,则子类 Canvas。

另请注意,我们不区分旨在包含和布置子级的复合小部件和仅由其他小部件组成的复合小部件。两者都将是 Composite 的子类,因此我们描述的是实现而不是类型继承。在编写 100% Java 可移植小部件时,我们可以将 Composite 视为所有复合小部件的 SWT 类层次结构的可移植入口点,而将 Canvas 视为所有基本小部件的 SWT 类层次结构的可移植入口点,无论小部件类型如何。

基本小部件示例

想象一下,我们正在构建一个应用程序,其中需要一个小部件,该小部件在右侧显示带有一行文本的图像,如下所示:

由于我们计划同时绘制图像和文本,因此我们将 Canvas 子类化。

import org.eclipse.swt.*;
import org.eclipse.swt.graphics.*;
import org.eclipse.swt.widgets.*;
import org.eclipse.swt.events.*;

public class PictureLabel extends Canvas {

  Image image; 

  String text;

}

需要创建我们的小部件。为此,我们必须至少编写一个构造函数。因为 SWT 中的小部件不能在没有父级的情况下创建,所以构造函数必须至少采用一个作为父级的参数。SWT 中的约定是有一个带有两个参数的构造函数,parent 和 style。样式位用于控制小部件的外观。创建小部件后,父级和样式位都不能更改。您的小部件也可以使用样式位。

 PictureLabel(Composite parent, int style) {

     super(parent, style);

  } 

任何小部件的父级都必须是 Composite。样式是一个整数,其中一些位已被系统使用。例如,SWT.BORDER 将导致 Canvas 有边框。

接下来我们需要初始化我们的小部件。SWT 中的约定是在构造函数中进行所有初始化。当然,任何需要父级或样式位的初始化都必须在此处完成。我们已经决定我们的 PictureLabel 小部件将默认为白色背景,因此我们需要添加一个 Color 字段,分配一个 Color,并初始化背景。
颜色是必须处理的图形资源。我们如何处理分配的白色?我们添加了一个 dispose 监听器。每个小部件在销毁时都会提供通知。我们在构造函数中添加了 dispose 监听器。

public class PictureLabel extends Canvas {

  Image image;
  String text;
  Color white;

  PictureLabel(Composite parent, int style) {
     super(parent, style);
     white = new Color(null, 255, 255, 255);
     setBackground(white);
     addDisposeListener(new DisposeListener() {
         public void widgetDisposed(DisposeEvent e) {
                white.dispose();
         }
     });
  }
}

注意:不要仅仅重写 dispose() 来释放颜色。这仅适用于 dispose 实际发送到小部件的情况。处置外壳时不会发生这种情况,因此覆盖处置将泄漏颜色。要确保无论事件如何生成,您的小部件都可以收到通知,请添加一个事件侦听器,而不是覆盖生成事件的方法。

我们的小部件被创建和初始化,它可以在不泄漏图形资源的情况下被销毁。现在它需要一些功能。我们需要绘制图像和文本,这将需要另一个侦听器:绘制侦听器(paint listener)。实现一个小部件通常需要添加许多侦听器。我们可以将侦听器接口实现为我们新的小部件类的一部分,但这会使接口方法在我们的类中公开。相反,SWT 约定是使用匿名内部类将功能转发给同名的非公共方法。为了保持一致性,我们将重写 dispose 监听器以遵循此约定,将颜色 dispose 代码移动到 widgetDisposed 方法中。我们以同样的方式编写paint listener。

 addDisposeListener(new DisposeListener() {
     public void widgetDisposed(DisposeEvent e) {
        PictureLabel.this.widgetDisposed(e);
     }

 });

addPaintListener(new PaintListener() {
     public void paintControl(PaintEvent e) {
        PictureLabel.this.paintControl(e);
     }

 });

通过选择相同的名称,如果我们以后决定这样做,我们可以选择轻松实现接口。这是绘制小部件的paintControl 方法。

  void paintControl(PaintEvent e) {
     GC gc = e.gc;
     int x = 1;
     if (image != null) {
         gc.drawImage(image, x, 1);
         x = image.getBounds().width + 5;
     }

     if (text != null) {
         gc.drawString(text, x, 1);
     } 
  }

现在我们可以绘制图像和文本,但我们需要让用户设置它们。因此,我们为它们中的每一个编写了 set 和 get 方法。


public Image getImage() {
     return image;
  }

public void setImage(Image image) {
	this.image = image;
 	redraw();
}


public String getText() {
 	return text;
}


public void setText(String text) {
 	this.text = text;
 	redraw();
}

get 方法很简单。他们只是返回字段。set 方法设置字段,然后重绘小部件以显示更改。最简单的方法是通过调用 redraw() 来发送绘制事件重绘小部件,这会将小部件的绘制事件排队。这种方法的优点是同时设置图像和文本只会导致一个绘制事件,因为在事件队列中折叠了多个绘制。

我们还没有完成。我们的小部件不知道它的首选尺寸。需要此信息才能布置小部件。在我们的例子中,最好的大小就是文本的大小加上图像的大小,再加上中间的一点空间。此外,我们将一直添加 1 个像素的边距。

要返回小部件的首选大小,我们必须实现 computeSize 方法。computeSize 方法可能相当复杂。它的工作是根据当前内容计算小部件的首选大小。最简单的实现忽略参数,只计算大小。computeSize 方法实现如下:

public Point computeSize(int wHint, int hHint, boolean changed) {
     int width = 0, height = 0;
     if (image != null) {
         Rectangle bounds = image.getBounds();
         width = bounds.width + 5;
         height = bounds.height;
     }

     if (text != null) {
         GC gc = new GC(this);
         Point extent = gc.stringExtent(text);
         gc.dispose();
         width += extent.x;
         height = Math.max(height, extent.y);
     }

     return new Point(width + 2, height + 2);    

  }

什么是 wHint、hHint 和 changed?提示参数允许您向小部件提出问题,例如“给定特定宽度,小部件需要多高才能显示所有内容”?例如,可能会询问一个自动换行的 Label 小部件。为了表明客户端不关心特定提示,使用特殊值 SWT.DEFAULT。以下示例在给定 100 像素宽度的情况下询问标签的首选尺寸:

Point extent = label.computeSize(100, SWT.DEFAULT, false);

对于我们的 PictureLabel 小部件,当宽度太小时,我们可以将图像堆叠在文本上,和/或包装文本以满足宽度要求,但为简单起见,我们决定不这样做。不过,我们需要尊重这些提示。因此,我们的小部件将剪辑。最简单的方法是执行计算,然后过滤结果。

public Point computeSize(int wHint, int hHint, boolean changed) {
     int width = 0, height = 0;
     if (image != null) {
         Rectangle bounds = image.getBounds();
         width = bounds.width + 5;
         height = bounds.height;
     }

     if (text != null) {
         GC gc = new GC(this);
         Point extent = gc.stringExtent(text);
         gc.dispose();
         width += extent.x;
         height = Math.max(height, extent.y);

     }

     if (wHint != SWT.DEFAULT) width = wHint;
     if (hHint != SWT.DEFAULT) height = hHint;         
     return new Point(width + 2, height + 2);    

  }

请注意,我们不会完全按照指定的方式返回提示大小。我们添加了 1 像素边框。我们为什么要做这个?所有小部件都有一个客户区(client area)和修剪(trim)。提示参数指定所需的客户区大小。我们必须设置小部件的大小,以便客户区的大小与提示相同,因此我们从 computeSize 返回的大小必须包括修剪。

change字段是什么意思呢?这与 SWT 布局管理器一起使用,基本小部件被忽略。change将在我们讨论复合小部件时讨论。

基础小部件完整代码如下:

1、组件代码


import org.eclipse.swt.*;

import org.eclipse.swt.graphics.*;
import org.eclipse.swt.widgets.*;
import org.eclipse.swt.events.*;

public class PictureLabel extends Canvas {

	Image image;
	String text;
	Color white;

	public PictureLabel(Composite parent, int style) {
		super(parent, style);
		white = new Color(null, 255, 255, 255);
		setBackground(white);
		addDisposeListener(new DisposeListener() {
			public void widgetDisposed(DisposeEvent e) {
				PictureLabel.this.widgetDisposed(e);
			}

		});

		addPaintListener(new PaintListener() {
			public void paintControl(PaintEvent e) {
				PictureLabel.this.paintControl(e);
			}

		});
	}

	void paintControl(PaintEvent e) {
		GC gc = e.gc;
		int x = 1;
		if (image != null) {
			gc.drawImage(image, x, 1);
			x = image.getBounds().width + 5;
		}

		if (text != null) {
			gc.drawString(text, x, 1);
		}
	}

	void widgetDisposed(DisposeEvent e) {
		white.dispose();
	}

	@Override
	public Point computeSize(int wHint, int hHint, boolean changed) {
		int width = 0, height = 0;
		if (image != null) {
			Rectangle bounds = image.getBounds();
			width = bounds.width + 5;
			height = bounds.height;
		}

		if (text != null) {
			GC gc = new GC(this);
			Point extent = gc.stringExtent(text);
			gc.dispose();
			width += extent.x;
			height = Math.max(height, extent.y);

		}

		if (wHint != SWT.DEFAULT)
			width = wHint;
		if (hHint != SWT.DEFAULT)
			height = hHint;
		return new Point(width + 2, height + 2);

	}

	public Image getImage() {
		return image;
	}

	public void setImage(Image image) {
		this.image = image;
		redraw();
	}

	public String getText() {
		return text;
	}

	public void setText(String text) {
		this.text = text;
		redraw();
	}
}

2、测试代码


import org.eclipse.swt.SWT;
import org.eclipse.swt.graphics.Color;
import org.eclipse.swt.graphics.GC;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Shell;

public class PictureLabelExample {

	public static void main(String[] args) {

		Image image = new Image(null, 20, 20);
		
		Color red = new Color(null, 255, 0, 0);
		GC gc = new GC(image);
		gc.setBackground(red);
		gc.fillRectangle(image.getBounds());
		gc.dispose();
		red.dispose();

		Shell shell = new Shell();
		PictureLabel label = new PictureLabel(shell, 0);
		label.setImage(image);
		label.setText("Hi Simple Picture Label!");
		Point size = label.computeSize(SWT.DEFAULT, SWT.DEFAULT, false);
		label.setSize(size);
		shell.open();

		Display display = shell.getDisplay();
		while (!shell.isDisposed()) {
			if (!display.readAndDispatch())
				display.sleep();

		}
		
		image.dispose();
	}

}

3、效果

在这里插入图片描述

复合小部件示例

现在我们将 PictureLabel 小部件重新编码为复合小部件。请注意,本节假定您已阅读基本小部件示例部分。这次小部件将使用两个子标签来实现:一个显示图像,一个显示文本。由于我们使用其他小部件来实现我们的小部件,因此我们将 Composite 子类化。

除了在构造函数中初始化图形资源外,我们还需要创建子小部件并设置它们的背景颜色。一个常见的错误是将子小部件创建为父小部件的子小部件。这将使它们成为我们小部件的对等点。相反,请确保将它们创建为 this 的子级。dispose 侦听器像以前一样释放颜色。

现在我们已经处理了创建和销毁,我们需要布置孩子。有两种可能:

  • 在调整小部件大小时定位子级·
  • 使用布局管理器

我们将在此处实现两者以进行比较。

在调整大小时定位部件位置

首先,我们将在调整小部件大小时定位子项。我们需要添加一个调整大小的监听器。

public class PictureLabel extends Composite {
  Label image, text;
  Color white;

  PictureLabel(Composite parent, int style) {
     super(parent, style);
     white = new Color(null, 255, 255, 255);
     image = new Label(this, 0);
     text = new Label(this, 0);
     setBackground(white);
     image.setBackground(white);
     text.setBackground(white);
     addDisposeListener(new DisposeListener() {
         public void widgetDisposed(DisposeEvent e) {
            PictureLabel.this.widgetDisposed(e);
         }
     });
     addControlListener(new ControlAdapter() {
         public void controlResized(ControlEvent e) {
            PictureLabel.this.controlResized(e);
         }
     });

  }
 

  void controlResized(ControlEvent e) {
     Point iExtent = image.computeSize(SWT.DEFAULT, SWT.DEFAULT, false);
     Point tExtent = text.computeSize(SWT.DEFAULT, SWT.DEFAULT, false);
     image.setBounds(1, 1, iExtent.x, iExtent.y);
     text.setBounds(iExtent.x + 5, 1, tExtent.x, tExtent.y);
  }

当小部件调整大小时,我们计算每个孩子的大小,然后使用它们的范围和我们的 5 像素间距和 1 像素边距使用 setBounds 定位孩子。

现在我们将编写 set 和 get 方法。因为我们没有绘制图像和文本,所以损坏小部件不会导致正确的行为。必须调整孩子的大小以显示他们的新内容。为此,我们将从调整大小侦听器中获取代码,并将其移动到一个名为 resize 的辅助方法中。

void controlResized(ControlEvent e) {
     resize();
  }

  void resize() {
     Point iExtent = image.computeSize(SWT.DEFAULT, SWT.DEFAULT, false);
     Point tExtent = text.computeSize(SWT.DEFAULT, SWT.DEFAULT, false);
     image.setBounds(1, 1, iExtent.x, iExtent.y);
     text.setBounds(iExtent.x + 5, 1, tExtent.x, tExtent.y);
  }

下面是 set 和 get 方法。

	public Image getImage() {
	     return image.getImage();
	}
	public void setImage(Image image) {
	 this.image.setImage(image);
	 resize();
	}

	public String getText() {
	 return text.getText();
	}

	public void setText(String text) {
	 this.text.setText(text);
	 resize();
	}

现在我们必须实现 computeSize 方法。这是一个简单的问题,询问孩子们他们喜欢的尺寸。

public Point computeSize(int wHint, int hHint, boolean changed) {
     Point iExtent = image.computeSize(SWT.DEFAULT, SWT.DEFAULT, false);
     Point tExtent = text.computeSize(SWT.DEFAULT, SWT.DEFAULT, false);
     int width = iExtent.x + 5 + tExtent.x;
     int height = Math.max(iExtent.y, tExtent.y);
     if (wHint != SWT.DEFAULT) width = wHint;
     if (hHint != SWT.DEFAULT) height = hHint;         
     return new Point(width + 2, height + 2);
  }

使用布局管理器定位子部件

现在我们将重写我们的复合小部件示例,使用布局管理器来定位小部件的子级。我们可以只使用现有的 SWT 布局管理器 - RowLayout - 来定位子级,但我们承诺在 computeSize 方法中解释更改的参数。这也提供了一个示例,说明如何针对更复杂的布局要求完成此操作。在下面的代码中,PictureLabelLayout 类扩展了 Layout,重写后的 PictureLabel 类被完整列出。

布局管理器通过小部件构造函数中的以下代码行设置到小部件中:

	setLayout(new PictureLabelLayout());

我们将在 widget 的两个 set 方法中调用布局管理器,代码如下:

	setLayout(true);

布局方法的参数是 changed 标志。如果为 true,则表示小部件内容已更改(如两个 set 方法中的情况),因此布局管理器可能一直保留的任何缓存都需要刷新。当小部件调整大小时,SWT 系统将 layout(false) 发送到布局管理器,因此不需要刷新缓存。这允许布局管理器仅在必要时执行任何昂贵的计算。

在类 PictureLabelLayout 中,我们知道composite.getChildren() 总是会返回正好两个孩子。一般来说,布局管理器必须处理任意数量的孩子,所以如果你正在实现一个可以有任意数量的孩子的小部件,你将需要遍历它们来进行计算。请注意,正是在这个类中,我们检查了更改标志的值,并可选择刷新我们的两个“范围”缓存。

请注意,PictureLabel 类已通过使用布局管理器进行了简化。computeSize 和 resize 中的代码已移至 PictureLabelLayout 类,不再需要 resize 监听器。

import org.eclipse.swt.*;

import org.eclipse.swt.graphics.*;

import org.eclipse.swt.widgets.*;

import org.eclipse.swt.events.*;

 

class PictureLabelLayout extends Layout {

  Point iExtent, tExtent; // the cached sizes

  protected Point computeSize(Composite composite, int wHint, int hHint,
     boolean changed) {
     Control [] children = composite.getChildren();
     if (changed || iExtent == null || tExtent == null) {
         iExtent = children[0].computeSize(SWT.DEFAULT, SWT.DEFAULT, false);
         tExtent = children[1].computeSize(SWT.DEFAULT, SWT.DEFAULT, false);
     }
     int width = iExtent.x + 5 + tExtent.x;
     int height = Math.max(iExtent.y, tExtent.y);
     return new Point(width + 2, height + 2);

  }

 

  protected void layout(Composite composite, boolean changed) {
     Control [] children = composite.getChildren();
     if (changed || iExtent == null || tExtent == null) {
         iExtent = children[0].computeSize(SWT.DEFAULT, SWT.DEFAULT, false);
         tExtent = children[1].computeSize(SWT.DEFAULT, SWT.DEFAULT, false);
     }
     children[0].setBounds(1, 1, iExtent.x, iExtent.y);
     children[1].setBounds(iExtent.x + 5, 1, tExtent.x, tExtent.y);
  }

}

 

public class PictureLabel extends Composite {
  Label image, text;
  Color white;

  PictureLabel(Composite parent, int style) {

     super(parent, style);
     white = new Color(null, 255, 255, 255);
     image = new Label(this, 0);
     text = new Label(this, 0);
     setBackground(white);
     text.setBackground(white);
     image.setBackground(white);
     addDisposeListener(new DisposeListener() {
         public void widgetDisposed(DisposeEvent e) {
            PictureLabel.this.widgetDisposed(e);
         }
     });
     setLayout(new PictureLabelLayout());
  }

  void widgetDisposed(DisposeEvent e) {
     white.dispose();
  }

  public Image getImage() {
     return image.getImage();
  }

  public void setImage(Image image) {
     this.image.setImage(image);
     layout(true);
  }

  public String getText() {
     return text.getText();
  }
  
  public void setText(String text) {
     this.text.setText(text);
     layout(true);
  }

}

事件与监听器

通常,您需要一个新的小部件来支持一个事件。例如,您可能希望您的小部件在用户选择它时通知侦听器。或者您可能有一个可编辑的小部件,当它的值发生变化时应该通知侦听器。

实现一个名为 AnEvent 的事件的细节与实现一个 Java Bean 监听器完全相同:

  • 创建一个名为 AnEvent 的类,它扩展 java.util.EventObject 并且可能具有与事件相关的附加字段。通常您希望为事件字段提供 get 方法,但您并不总是希望提供 set 方法。字段通常在构造函数中设置。
  • 创建一个名为 AnEventListener 的类,该类实现 java.util.EventListener 接口并提供一个名为 anEventHappened(AnEvent event) 的方法·
  • 在您的小部件类中保留一个 AnEventListener 的 Vector(或其他一些集合)·
  • 实现 addAnEventListener 将指定的监听器添加到 Vector·
  • 实现 removeAnEventListener 从 Vector 中移除指定的监听器·
  • 确定事件何时在您的小部件中发生(可能通过向您的小部件添加侦听器)以及何时发生:
    • 创建一个名为 event 的 AnEvent 实例,根据需要进行初始化·
    • 向 Vector 中的每个 AnEventListener 发送一个EventHappened(event)

假设我们希望 PictureLabel 小部件在用户单击图像中的鼠标左键时通知侦听器。我们使用 x 和 y 字段创建类 ImageClickedEvent,并使用方法 imageClicked(ImageClickedEvent event) 接口 ImageClickedListener。

public class ImageClickedEvent extends java.util.EventObject {
  public int x, y;

  public ImageClickedEvent(Object source, int x, int y) {
     super(source);
     this.x = x;
     this.y = y;
  }

}

ImageClickedListener 代码如下:

public interface ImageClickedListener extends java.util.EventListener {
  public void imageClicked(ImageClickedEvent event);
}

我们在 PictureLabel 中添加一个 Vector 来存储监听器:

  Vector imageClickedListeners = new Vector();
  public void addImageClickedListener(ImageClickedListener listener) {
     imageClickedListeners.addElement(listener);
  }
  public void removeImageClickedListener(ImageClickedListener listener) {
     imageClickedListeners.removeElement(listener);
  }

最后,在 PictureLabel 的构造函数中,我们为图像标签小部件添加了一个鼠标侦听器,当鼠标左键在图像上单击时,它会通知侦听器。

addMouseListener(new MouseAdapter() {
         public void mouseDown(MouseEvent event) {
            if (event.button == 1) {
                PictureLabel.this.mouseDown(event);
            }
         }
     });

  public void mouseDown(MouseEvent event) {
     ImageClickedEvent e = new ImageClickedEvent(this, event.x, event.y);
     int size = imageClickedListeners.size();
     for (int i = 0; i < size; i++) {
         ImageClickedListener listener =
            (ImageClickedListener) imageClickedListeners.elementAt(i);
         listener.imageClicked(e);
     }
  }

简单示例

现在我们将在示例应用程序中使用新的小部件。该应用程序只需创建一个带有 PictureLabel 子项的外壳。然后它将 PictureLabel 的图像设置为一个红色的小方块,并将文本设置为“您好!复杂的图片标签组件”。shell 没有布局管理器,所以我们将设置 PictureLabel 的大小。单击图像时,我们将文本更改为“红色!”。

复杂示例完整代码如下:

1、PictureLabel


import java.util.Vector;

import org.eclipse.swt.events.DisposeEvent;
import org.eclipse.swt.events.DisposeListener;
import org.eclipse.swt.events.MouseAdapter;
import org.eclipse.swt.events.MouseEvent;
import org.eclipse.swt.graphics.Color;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Label;

public class PictureLabel extends Composite {

	Label image, text;
	Color white;
	Vector<ImageClickedListener> imageClickedListeners = new Vector<>();

	PictureLabel(Composite parent, int style) {
		super(parent, style);
		white = new Color(null, 255, 255, 255);
		image = new Label(this, 0);
		text = new Label(this, 0);
		setBackground(white);
		text.setBackground(white);
		image.setBackground(white);
		image.addMouseListener(new MouseAdapter() {
			public void mouseDown(MouseEvent event) {
				if (event.button == 1) {
					PictureLabel.this.mouseDown(event);
				}
			}
		});
		text.addMouseListener(new MouseAdapter() {
			public void mouseDown(MouseEvent event) {
				if (event.button == 1) {
					PictureLabel.this.mouseDown(event);
				}
			}
		});
		addDisposeListener(new DisposeListener() {
			public void widgetDisposed(DisposeEvent e) {
				PictureLabel.this.widgetDisposed(e);
			}
		});
		addMouseListener(new MouseAdapter() {
			public void mouseDown(MouseEvent event) {
				if (event.button == 1) {
					PictureLabel.this.mouseDown(event);
				}
			}
		});

		setLayout(new PictureLabelLayout());

	}

	void widgetDisposed(DisposeEvent e) {
		white.dispose();
	}

	public void mouseDown(MouseEvent event) {
		ImageClickedEvent e = new ImageClickedEvent(this, event.x, event.y);
		int size = imageClickedListeners.size();
		for (int i = 0; i < size; i++) {
			ImageClickedListener listener = (ImageClickedListener) imageClickedListeners.elementAt(i);
			listener.imageClicked(e);
		}
	}

	public void addImageClickedListener(ImageClickedListener listener) {
		imageClickedListeners.addElement(listener);
	}

	public void removeImageClickedListener(ImageClickedListener listener) {
		imageClickedListeners.removeElement(listener);
	}

	public Image getImage() {
		return image.getImage();
	}

	public void setImage(Image image) {
		this.image.setImage(image);
		layout(true);
	}

	public String getText() {
		return text.getText();
	}

	public void setText(String text) {
		this.text.setText(text);
		layout(true);
	}

}

2、PictureLabelLayout


import org.eclipse.swt.SWT;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Layout;

class PictureLabelLayout extends Layout {

	Point iExtent, tExtent; // the cached sizes

	protected Point computeSize(Composite composite, int wHint, int hHint, boolean changed) {
		Control[] children = composite.getChildren();

		if (changed || iExtent == null || tExtent == null) {
			iExtent = children[0].computeSize(SWT.DEFAULT, SWT.DEFAULT, false);
			tExtent = children[1].computeSize(SWT.DEFAULT, SWT.DEFAULT, false);
		}

		int width = iExtent.x + 5 + tExtent.x;
		int height = Math.max(iExtent.y, tExtent.y);
		return new Point(width + 2, height + 2);

	}

	protected void layout(Composite composite, boolean changed) {
		Control[] children = composite.getChildren();
		if (changed || iExtent == null || tExtent == null) {
			iExtent = children[0].computeSize(SWT.DEFAULT, SWT.DEFAULT, false);
			tExtent = children[1].computeSize(SWT.DEFAULT, SWT.DEFAULT, false);
		}

		children[0].setBounds(1, 1, iExtent.x, iExtent.y);
		children[1].setBounds(iExtent.x + 5, 1, tExtent.x, tExtent.y);

	}

}

3、ImageClickedEvent

import java.util.EventObject;

public class ImageClickedEvent extends EventObject {

	private static final long serialVersionUID = 1;
	private int x, y;

	public ImageClickedEvent(Object source, int x, int y) {
		super(source);
		this.x = x;
		this.y = y;
	}
}

4、ImageClickedListener

import java.util.EventListener;

public interface ImageClickedListener extends EventListener{
	 public void imageClicked(ImageClickedEvent event);
}

5、PictureLabelExample


import org.eclipse.swt.SWT;
import org.eclipse.swt.graphics.*;

import org.eclipse.swt.widgets.*;

public class PictureLabelExample {

	public static void main(String[] args) {
		Image image = new Image(null, 20, 20);
		Color red = new Color(null, 255, 0, 0);
	
		GC gc = new GC(image);
		gc.setBackground(red);
		gc.fillRectangle(image.getBounds());
		gc.dispose();
		red.dispose();

		Shell shell = new Shell();
		PictureLabel label = new PictureLabel(shell, 0);
		label.setImage(image);
		label.setText("您好!复杂的图片标签组件");
		Point size = label.computeSize(SWT.DEFAULT, SWT.DEFAULT, false);
		label.setSize(size);
		label.addImageClickedListener(new ImageClickedListener() {
			public void imageClicked(ImageClickedEvent event) {
				((PictureLabel) event.getSource()).setText("Red!");
			}
		});

		shell.open();
		Display display = shell.getDisplay();
		while (!shell.isDisposed()) {
			if (!display.readAndDispatch())
				display.sleep();
		}

		image.dispose();
	}

}

6、效果
在这里插入图片描述
点击 您好!复杂的图片标签组件 或红色图片区,文字变化为 Red!效果如下:
在这里插入图片描述
更多内容请参考原文: Creating Your Own Widgets using SWT

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值