快捷搜索:

java指南之使用图形:自定义绘图概览

自定义绘图概览

假如你还没有读过 绘图 一节,请现在就看看。那节描述了Swing 组件是若何被绘制的 --假如你要写自定义绘图代码,那些便是根基常识。

在实现一个自定义绘图的组件前首先请确认你真的必要这样做。你可能可以应用 标签, 按钮或者 文本组件 的功能代替。记着,你可以应用 界限自定义组件的外部界限。

假如你真的必要进行自定义绘图,那么就必要抉择应用哪个超类。我们保举要么扩展JPanel要么应用一个更特殊的Swing组件。例如,假如你想创建一个自定义按钮,你大概应该经由过程扩展一个像JButton 或者 JToggleButton 这样的按钮类来实现它。用那个措施,你就可以承袭那些类供给的状态治理功能。假如你正创建一个在图像上绘图的组件,你大概想创建一个JLabel 的子类。另一方面,假如你正实现一个在空缺的或者透明的背景上孕育发生和显示图表的组件,那么你可能想应用JPanel作为超类。

在实现自定义绘图代码的时刻,记着两件事:

你的绘图代码应该在一个名字为 paintComponent的措施里面。

你可以 -- 而且可能是应该 -- 应用一个界限绘制你的组件的外边缘。

自定义绘图的一个典型

下面的代码给出了一个自定义绘图的典型。它将一个图像显示两次,一次以图像的原始大年夜小一次异常宽。

class ImagePanel extends JPanel {

...

public void paintComponent(Graphics g) {

super.paintComponent(g); //paint background

//首先以图像的原始大年夜小显示。

g.drawImage(image, 0, 0, this); //85x62 image

//现在显示缩放的图像。

g.drawImage(image, 90, 0, 300, 62, this);

}

}

下面是结果:

这个图片是该applet的GUI。要运行那个applet,单击图片。该applet将在一个新浏览窗口显示。

典型代码来自 ImageDisplayer.java,它的更进一步的评论争论在 显示图像中。那个典型示范了在组件进行自定义绘图时的一些新规则:

绘图代码履行一些标准的Swing组件所没有的动作。假如我们只想将图像以它的原始大年夜小显示一次,我们应该应用JLabel 工具而不是应用自定义组件。

自定义组件是JPanel的子类。这是自定义组件的一个常用的超类。

所有的自定义绘图代码都在paintComponent措施里面。

在履行任何自定义绘图前,经由过程调用super.paintComponent让组件绘制自己的背景。假如你没有调用它,要么自己的代码绘制组件的背景,要么对组件调用setOpaque(false)。应用后者将看护Swing绘图系统在透明的组件后面的组件可能是可见的是以应该被绘制。

这个组件没有斟酌的一个工作便是界限。它不仅没有应用界限,而且没有调剂它的绘图坐标以斟酌有界限的环境。一个产品级的组件应该像下一小节描述的那样为界限进行调剂。

坐标系统

每个组件都有自己的整型坐标系统,范围从(0, 0) 到 (width - 1, height - 1),单位是象素。像下面的图片显示的那样,组件的绘图区的左上角是(0, 0)。X坐标向右增添而Y坐标向下增添。

在绘制一个组件时,你不仅要斟酌组件的尺寸而且在必要时还要斟酌组件的界限的尺寸。例如组件周围绘制一个象素宽的界限将左上角的坐标从(0,0)变成(1,1)而且将绘图区的宽度和高度各减小2个象素(每边一个象素)。下面的图片阐清楚明了这个:

要获得组件的宽度和高度可以应用它的getWidth和 getHeight 措施。要获得界限的尺寸,应用getInsets措施。下面是一个组件抉择自定义绘图区的可用宽度和高度的可能的代码:

public void paintComponent(Graphics g) {

...

Insets insets = getInsets();

int currentWidth = getWidth() - insets.left - insets.right;

int currentHeight = getHeight() - insets.top - insets.bottom;

...

.../* 第一次绘图发生在(x,y), x不能小于

insets.left, 而y不能小于insets.height。 */...

}

为了让你自己认识坐标系统,你可以运行下面的applet。无论你在框架区域的任何地方点击都将绘制一个点而且下面的标签会显示该点的坐标。假如你点击界限,点就不是很清楚,由于组件的界限是在履行自定义绘图后被绘制的。假如你不想要这个效果,一个简单的办理措施是将组件的界限从它移动到一个新建的包孕该组件的JPanel工具上。

这是applet的GUI的一个截图。要运行这个applet,单击图片。applet将在一个新窗口中

这个法度榜样在 CoordinatesDemo.java中被实现。虽然这个典型代码没有在任何地方被评论争论,然则它和 RectangleDemo 法度榜样的代码很相似,那个法度榜样将在稍后的 绘制外形中评论争论。

repaint措施的参数

记着调用组件的repaint 措施哀求组件被预定去绘制自己。当绘图系统不能跟上repaint哀求的要求时(译者注:主如果由于过于频繁的调用repaint,例如在paint措施中调用repaint(1),每隔一毫秒重绘一次),它会将多个哀求合并为一个。

repaint措施有两个有用的形式:

void repaint()

哀求全部组件被重绘。

void repaint(int, int, int, int)

哀求组件中指定的区域被重绘。参数指定区域的左上角的X,Y坐标和区域的宽度和高度。

虽然应用四参数形式的repaint措施平日没有实用代价,然则它可以显明的前进绘图机能。下面的图片显示的法度榜样在频繁哀求绘制以显示用户当前选定区域时应用四参数的repaint措施。这样做避免了绘制从上次的绘图操作后没有被改变的区域。

这是applet的GUI的一个截图。要运行这个applet,单击图片。applet将在一个新窗口中

这个法度榜样在 SelectionDemo.java中实现。下面是谋略绘制区域并绘制它的代码:

class SelectionArea extends JLabel {

...

public SelectionArea(ImageIcon image, ...) {

super(image); //使这个组件显示一个图像。

...

}

...//在一个鼠标拖动事故处置惩罚器中:

Rectangle totalRepaint = rectToDraw.union(previousRectDrawn);

repaint(totalRepaint.x, totalRepaint.y,

totalRepaint.width, totalRepaint.height);

...

public void paintComponent(Graphics g) {

super.paintComponent(g); //绘制背景和图像

...

//在图像上绘制一个矩形。

g.setColor(Color.white);

g.drawRect(rectToDraw.x, rectToDraw.y,

rectToDraw.width - 1, rectToDraw.height - 1);

...

}

...

}

就像你看到的,这个自定义组件扩展JLabel,是以它承袭了显示图像的能力。用户可以经由过程拖动鼠标选定一个矩形区域。组件继续的显示一个矩形以指出当前选定的尺寸。为了前进绘制速率,组件的鼠标拖动事故处置惩罚器为repaint指定一个绘制区域。

经由过程限定重绘的区域,事故处置惩罚器避免了不需要的重绘图像外的区域。对付这个小图像,这个策略没有显明的机能上的前进。然则对付一个伟大年夜的图像,这可能就有真正的好处了。并且假如是用从文件里面绘制图像的环境代替,你还必须谋略在矩形下面绘制什么--例如,在一个拖动法度榜样中谋略外形--然后应用绘制区域的常识限定你履行的谋略可能会显明的提升机能。

指定给repaint措施的区域不但包括要被绘制的区域,还有所有必要擦除的区域。否则原本被绘制的器械维持可见直到别的的绘制操作可巧擦除了它。前面的代码经由过程结合将被绘制的矩形和先前绘制的矩形来谋略总的区域。

为repaint措施指定的绘制区域被反应到通报给paintComponent措施的Graphics工具中。你可以应用getClipBounds 措施抉择哪个矩形区域被绘制。下面是一个应用剪切区域的例子:

public void paintComponent(Graphics g) {

Rectangle clipRect = g.getClipBounds();

if (clipRect != null) {

//假如它有效,只绘制由clipRect指定的区域。

//最左上角坐标为 (clipRect.x, clipRect.y)

//宽度,高度为 clipRect.width, clipRect.height

} else {

//绘制全部组件

}

}

Graphics工具

被通报到paintComponent措施的 Graphics 工具供给绘制情况和履行绘制的措施。那些措施将在稍后评论争论,他们有诸如 drawImage, drawString, drawRect和 fillRect这样的名字。

图形情况由诸如当前绘图色,当前字体和当前绘制区(就像你已经见过的)这样的状态组成,颜色和字体被初始化为在调用paintComponent前的背景致和组件的字体。你可以应用getColor和getFont措施获得它们,用setColor 和 setFont措施设置它们。

假如你乐意,你可以安然的轻忽当前的绘制区,这对组件的坐标系统没有任何影响,任何区域外的绘图被轻忽。然而假如在绘图区域减小时你的绘图代码包括可以被简化的繁杂的操作,那么你应该应用绘图区的常识赞助前进绘图的机能。就像前面的代码显示的那样,你经由过程调用getClipBounds措施从Graphics 工具获得绘图区的矩形范围。

你可以应用两种措施减小绘图区。首先是在任何可能的环境下指定repaint 的参数。另一个便是实现paintComponent,让它调用Graphics 工具的setClip 措施。假如你应用setClip,确保在返回前恢回覆再肇始的绘图区。否则组件可能被不精确的绘制。下面是一个减小然后规复绘图区的例子:

Rectangle oldClipBounds = g.getClipBounds();

Rectangle clipBounds = new Rectangle(...);

g.setClip(clipBounds);

...//履行自定义绘制...

g.setClip(oldClipBounds);

在写你的绘图代码时记着你不能依附除了供给的Graphics工具外的任何图形情况。例如你不能依附你对repaint指定的绘图区和随后调用的paintComponent中的绘图区一样。一种环境是多个重绘哀求可以被合并到一个paintComponent 调用,对应的绘图区进行调剂。另一种环境是绘图系统无意偶尔候自己会调用 paintComponent措施,没有从你的法度榜样中调用任何重绘哀求。一个例子是绘图系统在第一次显示组件的GUI时调用组件的paintComponent 措施,同样当GUI被其它的窗口覆盖而又呈现时,绘图系统调用paintComponent 措施绘制近来呈现的部分。

Swing绘图措施

paintComponent是JComponent工具用于绘制自身的三个绘图措施之一,三个措施的调用顺序是:

paintComponent -- 绘图的主要措施。 缺省时,假如组件不透明它首先绘制背景,然后它履行其它自定义绘图操作。

paintBorder -- 奉告组件的界限(假如有的话)进行绘制。 不用调用或者重写这个措施。

paintChildren -- 奉告这个组件包孕的所有组件绘制它们自己。 不用调用或者重写这个措施。

--------------------------------------------------------------------------------

留意: 不要重写或者调用调用了paintXxx措施的措施:paint。虽然重写paint措施在先前的Swing组件中是合法的,然则平日在一个从JComponent派生的组件中这样做不是一件好事。除非你异常小心,重写paint 很轻易搅散绘图系统,绘图系统依附JComponent实现的 paint 措施进行精确的绘图、机能增强和诸如双缓冲这样的特点。

--------------------------------------------------------------------------------

标准的Swing组件将它们的look-and-feel-specific绘制委托给一个称为UI delegate(UI代理)的工具。当这样一个组件的paintComponent 措施被调用时,措施哀求UI 代理绘制组件。平日,UI代理首先反省组件是否是不透明的,假如是,绘制组件的全部背景。然后UI代理履行任何look-and-feel-specific绘制。

我们保举扩展JPanel而不是JComponent的缘故原由是JComponent类今朝没有设置一个UI代理 -- 只有它的子类设置了。这意味着假如你扩展JComponent,你的组件在你自己不绘制的环境下不会被绘制。当你扩展JPanel并且在你的paintComponent措施的开始调用super.paintComponent措施时,那么面板的UI代理在组件不透明的环境下绘制组件的背景。

假如你必要关于绘图的更多信息,参看 AWT和Swing中的绘图。它是 Swing Connection中的一篇深入评论争论绘图的繁杂细节的论文。

您可能还会对下面的文章感兴趣: