创建一个 Swing 组件 —— JImageComponent

介绍Introduction

本文展示了如何使用Java来创建一个用来在Java 的applet和/或应用程序中展示图片的Swing类. 它还包括了使得图片渲染加快需要的步骤,还有在滚动容器中的使用方法.

为了更好的理解,特别是对于初学者而言,本文使用了 JImageComponent 的实现作为引用,它扩展了 Swing 的 Component.

说明

1. 创建一个子类

创建一个子类继承扩展你的类。其父类通常是Java Swing诸多类中的一个.

JImageComponent扩展了 Swing 的 JComponent:

public class JImageComponent extends javax.swing.JComponent {

    /**
     * Constructs a new JImageComponent object.
     */
    public JImageComponent() {
    }
}

2. 创建类变量

你的类将需要几个变量来持有重要的数据. 通常,这将在你扩张类的功能时进行. 一般情况下,它至少要包含两个变量:一个BufferedImage对象用来持有需要绘制出来的图像,还有一个对应的 Graphics 对象.

JImageComponent 包含两个私有变量:

/* Holds the <code>BufferedImage for the image. /
    private BufferedImage bufferedImage = null;

    /* Holds the Graphics for the image. /
    private Graphics imageGraphics = null;

3. 实现图像的Set/Change功能

你的类将会绘制其变量所描述的图像。你可能需要实现在构造时设置图像,或者在运行期间设置或者修改图像的功能.

JImageComponent 允许在构造时设置图像,或者在运行期间设置或者修改图像. 简便起见,这里我只列出了一个构造器.

将一个BufferedImage作为参数的构造器.

/**
     * Constructs a new JImageComponent object.
     *
     * @param bufferedImage
     *        Image to load as default image.
     */
    public JImageComponent(BufferedImage bufferedImage) {
        this.setBufferedImage(bufferedImage);
    }

JImageComponent 的方法用来在运行期间设置或者修改图像。至于为什么这个方法还要去设置组件的边界,将会在讨论实现用于滚动容器中的功能时解释.

/**
     * Sets the buffered image, and updates the components bounds.
     *
     * @param bufferedImage
     *        The buffered image to set to.
     */
    public void setBufferedImage(BufferedImage bufferedImage) {
        this.bufferedImage = bufferedImage;

        // Clear the graphics object if null image specified.
        // Clear the component bounds if null image specified.
        if (this.bufferedImage == null) {
            this.imageGraphics = null;
            this.setBounds(0, 0, 0, 0);
        }

        // Set the graphics object.
        // Set the component&apos;s bounds.
        else {
            this.imageGraphics = this.bufferedImage.createGraphics();
            this.setBounds(0, 0, this.bufferedImage.getWidth(), this.bufferedImage.getHeight());
        }
    }

4. 实现图片的设置/加载功能

你的类可能要实现通过从资源中加载一张图片来设置图像的功能.

JImageComponent 允许通过提供一个加载由一个统一资源定位符(URL)作为参数来加载一张图像的方法,来使得位于应用程序包中的图像被加载. 请注意你可能需要在图像被加载之后通过调用 JImageComponet 的 repaint() 方法来对图像进行重新绘制.

/**
     * Loads image from URL.
     *
     * @param imageLocation
     *        URL to image.
     * @throws Exception
     *         Throws an <code>IOException if file cannot be loaded.
     */
    public void loadImage(URL imageLocation) throws IOException {
        this.bufferedImage = ImageIO.read(imageLocation);
        this.setBufferedImage(this.bufferedImage);
    }

你可以任意实现必要多的方法。 例如JImageComponent,还会有一个用来加载一张由一个文件参数指定的图像的方法.

/**
     * Loads image from a file.
     *
     * @param imageLocation
     *        File to image
     * @throws IOException
     *         Throws an <code>IOException if file cannot be loaded
     */
    public void loadImage(File imageLocation) throws IOException {
        this.bufferedImage = ImageIO.read(imageLocation);
        this.setBufferedImage(this.bufferedImage);
    }

5.实现绘制图像的功能

这是你的类真正的核心功能。你将需要确保你的类在图像只部分可见时,能够只画出其自身的一部分, 还要确保其在设置、加载、修改或者编辑时能够重新绘制其自身. 依赖于你扩展的父类,你可能需要重写和绘制图像有关的几个方法.

Swing 的 JComponent 为我们做了大部分的重要工作 . JImageComponent 重写了 paint(java.awt.Graphics) 方法, 还有两个 paintImmediately() 方法. 注意力主要需要放在由组件的可见矩形所指定的图像的绘制上. 这将会在讨论用于滚动容器中的功能的实现时解释.

/```javascript
*
* @see javax.swing.JComponent#paint(java.awt.Graphics)
*/
@Override
public void paint(Graphics g) {

    // Exit if no image is loaded.
    if (this.bufferedImage == null) {
        return;
    }

    // Paint the visible region.
    Rectangle rectangle = this.getVisibleRect();
    paintImmediately(g, rectangle.x, rectangle.y, rectangle.width, rectangle.height);
};

/*
 * @see javax.swing.JComponent#paintImmediately(int, int, int, int)
 */
@Override
public void paintImmediately(int x, int y, int width, int height) {

    // Exit if no image is loaded.
    if (this.bufferedImage == null) {
        return;
    }

    // Paint the region specified.
    this.paintImmediately(super.getGraphics(), x, y, width, height);
}

/*
 * @see javax.swing.JComponent#paintImmediately(java.awt.Rectangle)
 */
@Override
public void paintImmediately(Rectangle rectangle) {

    // Exit if no image is loaded.
    if (this.bufferedImage == null) {
        return;
    }

    // Paint the region specified.
    this.paintImmediately(super.getGraphics(), rectangle.x, rectangle.y, rectangle.width, rectangle.height);
}
简单起见,JImageComponent 会有一个私有的方法 ,paintImmediately(Graphics, int, int, int, int) ,来做实际上的图形的绘制.

```javascript
/**
     * Paints the image onto the component.
     *
     * @param g
     *        The <code>Graphics object of the component onto which the
     *        image region will be painted.
     * @param x
     *        The x value of the region to be painted.
     * @param y
     *        The y value of the region to be painted.
     * @param width
     *        The width of the region to be painted.
     * @param height
     *        The width of the region to be painted.
     */
    private void paintImmediately(Graphics g, int x, int y, int width, int height) {

        // Exit if no image is loaded.
        if (this.bufferedImage == null) {
            return;
        }

        int imageWidth = this.bufferedImage.getWidth();
        int imageHeight = this.bufferedImage.getHeight();

        // Exit if the dimension is beyond that of the image.
        if (x >= imageWidth || y >= imageHeight) {
            return;
        }

        // Calculate the rectangle of the image that should be rendered.
        int x1 = x < 0 ? 0 : x;
        int y1 = y < 0 ? 0 : y;
        int x2 = x + width - 1;
        int y2 = y + height - 1;

        if (x2 >= imageWidth) {
            x2 = imageWidth - 1;
        }

        if (y2 >= imageHeight) {
            y2 = imageHeight - 1;
        }

        // Draw the image.
        g.drawImage(this.bufferedImage, x1, y1, x2, y2, x1, y1, x2, y2, null);
    }

. 实现当处在一个滚动容器中时的组件绘制

需要解决的一个挑战是,如何绘制一张被用于滚动容器中的图像. 例如,一个开发者可能会想要将一张尺寸超出可视边界的图像放到一个 JScrollPane 容器中.

JImageComponent通过做下面三件事情来解决了这个问题:

  • 当图片被设置/加载时,设置组件的边界.
    (这已经是完成了的.)
  • 在绘制图像时,注意组件的可是矩形.
    (这也已经是完成了的.)
  • 提供带有适当布局细节的布局管理器.

你可能需要实现在你想要确保你的组件在滚动容器中可以被正确渲染时,能提供带有适当布局细节的布局管理器的功能.

JImageComponent 通过重写如下的方法提供必要的布局详细:

/**
     * Returns the height of the image.
     */
    @Override
    public int getHeight() {
        if (this.bufferedImage == null) {
            return 0;
        }

        return this.bufferedImage.getHeight();
    }

    /**
     * Returns the size of the image.
     */
    @Override
    public Dimension getPreferredSize() {
        if (this.bufferedImage == null) {
            return new Dimension(0, 0);
        }

        return new Dimension(this.bufferedImage.getWidth(), this.bufferedImage.getHeight());
    }

    /**
     * Returns the size of the image.
     */
    @Override
    public Dimension getSize() {
        if (this.bufferedImage == null) {
            return new Dimension(0, 0);
        }

        return new Dimension(this.bufferedImage.getWidth(), this.bufferedImage.getHeight());
    }

    /**
     * Returns the width of the image.
     */
    @Override
    public int getWidth() {
        if (this.bufferedImage == null) {
            return 0;
        }

        return this.bufferedImage.getWidth();
    }

7. 实现编辑图像的功能

在编辑图像时许多的实例将会比持续的修改图像来得更加高效. 一个例子就是当你想要展示一个动画的时候,修改每张动画图像改变的区域,会比在内存中创建一张图像并在组件中对其进行设置更加高效.

JImageComponent 通过提供一个访问其 BufferedImage的方法,以及一个访问图像的 Graphics 对象的方法来允许这个功能.

/**
     * Returns the <code>BufferedImage object for the image.
     *
     * @return The buffered image.
     */
    public BufferedImage getBufferedImage() {
        return this.bufferedImage;
    }

    /**
     * Returns the Graphics object for the image.
     */
    @Override
    public Graphics getGraphics() {
        return this.imageGraphics;
    }
JImageComponent 还提供了一个用来调整/转换图像的方法:

/**
     * Resizes the image to the given dimensions and type. 

     * Note that the image is "cropped" from the left top corner).
     *
     * @param width
     *        The new width of the image.
     * @param height
     *        The new height of the image.
     * @param imageType
     *        The new image type (<code>BufferedImage type)
     * @see type java.awt.image.BufferedImage#BufferedImage(int, int, int)
     */
    public void resize(int width, int height, int imageType) {

        // Create a new image if none is loaded.
        if (this.bufferedImage == null) {
            setBufferedImage(new BufferedImage(width, height, imageType));
            return;
        }

        // Create a new temporary image.
        BufferedImage tempImage = new BufferedImage(width, height, imageType);
        int w = this.bufferedImage.getWidth();
        int h = this.bufferedImage.getHeight();

        // Crop width if necessary.
        if (width < w) {
            w = width;
        }

        // Crop height if necessary.
        if (height < h) {
            h = height;
        }

        // Copy if the type is the same.
        if (this.bufferedImage.getType() == imageType) {

            Graphics g = tempImage.getGraphics();
            g.drawImage(this.bufferedImage, 0, 0, w, h, null);
        }

        // Copy pixels to force conversion.
        else {

            for (int y = 0; y < h; y++) {
                for (int x = 0; x < w; x++) {
                    tempImage.setRGB(x, y, this.bufferedImage.getRGB(x, y));
                }
            }
        }

        // Set the new image.
        setBufferedImage(tempImage);
    }

使用代码

使用了JImageComponent的开发者应该记得在修改或者编辑了图像之后调用repaint()方法.

那些希望实现并测试他们的 JComponent 子类的开发者必须遵守有关Swing的执行指导方针(http://docs.oracle.com/javase/tutorial/uiswing/concurrency/index.html):

Java Applet 应该使用 SwingUtilities 的 invokeAndWait():

Java 应用程序可能也使用 SwingUtilities 的 invokeAndWait() 或者 invokeLater():

使用invokeAndWait的一个例子:

try {
        javax.swing.SwingUtilities.invokeAndWait(new Runnable() {
            public void run() {
                createAndShowGUI();
            }
        });
    }
    catch (InvocationTargetException exception) {
        // TODO Auto-generated catch block
        exception.printStackTrace();
    }
    catch (InterruptedException exception) {
        // TODO Auto-generated catch block
        exception.printStackTrace();
    }

使用 invokeLater 的一个例子:

javax.swing.SwingUtilities.invokeLater(new Runnable() {
        public void run() {
            createAndShowGUI();
        }
    });

更进一步

这是对创建一个 Java Swing的 JComponent子类用来展示图像的一个简要介绍. 从这里起步,还有许多的方面需要涉及. 例如,JImageComponent对于其便捷的设置和修改没有做出规定.

创建一个Java 抽象窗口工具Abstract Window Toolkit 的 Component 类的子类可能需要要遵循同样的步骤. 这些地方需要特别注意: 有一个缺陷将会需要子类做出规定,以确保有用于消除可能的闪烁的额双缓冲; 另外一个则要扩展类来正确的捕获和/或引发事件.

要点

开发者也许还会对Java的用于图像的 2D 图形 API感兴趣.

扩展Swing的 JComponent 并不要能达成此目的唯一能被扩展的类 .  例如,开发者肯能会选择JPanel类来扩展.

在商业应用程序中,我会坚持使用像JButton和JLable这样的本地类,以方便使用. 在 2014 年, 我改进了用于滚动容器中的 JImageComponent . 现在,我会使用 JImageComponent 来做一些像测试动画,绘制分形以及绘制自定义图像这些有趣的事情.

时间: 2024-12-03 19:42:32

创建一个 Swing 组件 —— JImageComponent的相关文章

Big Faceless PDF Viewer 2.11.19发布 一个Swing组件

Big Faceless PDF Viewer 2.11.19此版本Viewer http://www.aliyun.com/zixun/aggregation/5541.html">Applet: 可以设置外观和感觉的"swing.defaultlaf"参数.Viewer:文件现在可以被标记为"dirty",这意味着他们需要在关闭之前保存.这个可选的功能是默认的,但查看属性"unpromptedDirtyClose"为"

使用Java Swing 创建一个XML编辑器

xml|创建 我想您一定对XML有所了解,说不定您现在还跃跃欲试想写一段XML文本呢,可是现在能找到的跨平台的.免费的XML编辑器太少了.所以在本文中,我想介绍一下或者说带您一步一步的开发一个简单的XML编辑器,当然我们要用到一些最常见的Java 2 Swing组件,不过这些都是免费的,有些是JDK中的,有些是可以从网上下载的.我想通过本文,你就可以创建一个属于你自己的XML编辑器. 先让我介绍一下本文辑写的思路.首先我想简要的讨论一下XML和为什么树型结构比较适合用来显示XML,然后我们来看一

JFC/Swing活学活用之创建自定义图像组件

创建 引言 本文将讲述如何应用JFC/Swing内建的图像组件来创建完全自定义的基于图像的用户接口. 大多数Swing应用程序是通过标准VM提供的,或者是客户提供的外观和感觉(L&F)来获取它们的外在展示.L&F是一个完整的体系架构,VM需要做很多内在的工作,并且它还不是完全自定义的.举个例子来说吧,在基于L&F的前提下,我们可以创建一个按钮,看起来有点像交通岗上的"红灯",随之而来的在你的应用中所有的按钮就都有了这样的"相貌".然而有时我们

使用Java Swing创建一个XML编辑器之三

如果你现在正在寻找一个跨平台.资源开放的XML编辑器的话,您可能很快就能够实现这个愿望了.在这个三部分系列文章中,我将带您利用一些最通常的Java 2 Swing组件开发一个简单的 XML编辑器.本系列将有益于那些想编写他们的自己的XML编辑器的朋友或者帮助您学习或温习Swing. 这是本系列的第三篇文章.在第一个篇文章中,我们简要地讨论了XML和为什么树形结构适合显示XML.如何处理XML 数据.如何使用JTree Swing组件,并且我们还构建一个可重用组件用来解析XML文件并在JTree中

使用Java Swing创建一个XML编辑器之二

这是本系列的第二篇文章.在上一篇文章中,我们简要地讨论了XML以及为什么一个树形结构适合显示XML.如何处理XML数据.如何使用JTree Swing 组件以及如何创建一个可重用的能够分析XML文档以及在Jtree显示数据的组件. 在本文中,我们将创建我们的XML编辑器的框架,为了达到这个目的,我们将用到许多Swing组件(包括JsplitPane.JscrollPane.Jbutton和JtextArea组件). 一.问题的提出与解决 我如何创建一个能够浏览文本和浏览视图的XML文本编辑器呢?

使用Java Swing创建一个XML编辑器

我想您一定对XML有所了解,说不定您现在还跃跃欲试想写一段XML文本呢,可是现在能找到的跨平台的.免费的XML编辑器太少了.所以在本文中,我想介绍一下或者说带您一步一步的开发一个简单的XML编辑器,当然我们要用到一些最常见的Java 2 Swing组件,不过这些都是免费的,有些是JDK中的,有些是可以从网上下载的.我想通过本文,你就可以创建一个属于你自己的XML编辑器. 先让我介绍一下本文辑写的思路.首先我想简要的讨论一下XML和为什么树型结构比较适合用来显示XML,然后我们来看一看JAXP A

一个用组件动态创建Excel文件的实例

excel|创建|动态 一个用组件动态创建Excel文件的实例    在精华区中有一篇关于在ASP中动态创建的Excel文章, 但实际上我们会发现如果我们在ASP中用Set MyExcelChart = server.CreateObject("Excel.Sheet")是行不通的. 这样做的话会出现如下的错误信息:Only InProc server components should be used. If you want to use LocalServer component

在NetBeans 4.1中使用Swing组件

我们创建一个名为 ColorSwitcher 的应用程序.它能将面板的颜色从浅灰切换到中灰和黑色. 一.创建新的 ColorSwitcher 应用程序项目: 选择 "文件"--> "新建项目"(Ctrl+Shift+N) .或者,可以单击 IDE 工具栏中的 "新建项目" 图标. 在 "类别" 窗格中,选择 "常规" 文件夹.在 "项目" 窗格中,选择"Java 应用程序

开发一个Swing功能时的一点总结

  对JTextField进行效验,有两个途径:(1)是使用javax.swing.InputVerifier在获取焦点时进行校验(2)在点击"确定"按钮的监听事件中对控件的值进行校验 鉴于涉及的业务比较多,代码结构已经确定,如果在"确定"按钮的监听事件中进行效验,需要增加一个步骤,并且并不是所有的业务都需要这个效验,就倾向于使用javax.swing.InputVerifier进行,这样做有两个好处,(1)分离业务逻辑与前端 (2)代码更优雅 javax.swin