0

0

Swing 应用中动态绘制图形的刷新机制与优化

碧海醫心

碧海醫心

发布时间:2025-11-13 13:14:24

|

734人浏览过

|

来源于php中文网

原创

Swing 应用中动态绘制图形的刷新机制与优化

在 swing 应用中,图形元素拖动时未能实时更新是一个常见问题,通常是由于 repaint() 方法调用目标不正确所致。本教程将深入探讨 swing 的绘制机制,指出导致此问题的核心原因,并提供正确的 repaint() 调用方式。此外,还将介绍通过优化组件结构和封装图形对象来提升代码可维护性和性能的最佳实践,确保图形界面响应流畅、更新及时。

理解 Swing 的绘制机制

Swing 应用程序的图形渲染依赖于其事件分发线程(Event Dispatch Thread, EDT)和组件的绘制方法。当一个 Swing 组件需要更新其视觉表示时,它不会立即重绘,而是通过调用 repaint() 方法向 EDT 发送一个重绘请求。EDT 会将这些请求聚合,并在合适的时机调用组件的 paintComponent(Graphics g) 方法来执行实际的绘制操作。

问题的核心在于,repaint() 必须在负责绘制特定内容的组件上调用。如果在一个不负责绘制该内容的组件上调用 repaint(),或者在一个未被添加到可见容器中的组件上调用,那么屏幕上的图形将不会更新。

诊断图形不实时刷新的问题

在提供的代码示例中,PentominoShape 类继承自 JFrame,但它实际上被作为 JPanel 添加到了主 JFrame (Pentomino 类中的 frame) 中。所有的自定义图形(Polygon 对象)都在 shapePane 这个匿名的 JPanel 实例的 paintComponent 方法中绘制。

原始 mouseDragged 方法中的 repaint() 调用如下:

public void mouseDragged(MouseEvent e) {
    try {
        if (currPolygon.contains(x, y)) {
            // ... 移动多边形逻辑 ...
            repaint(); // 问题所在:此处的 repaint() 调用在 PentominoShape (JFrame) 实例上
        }
    }catch (NullPointerException ex){
        // 不推荐的空指针处理方式
    }
}

这里的 repaint() 实际上是调用了 PentominoShape 实例(它是一个 JFrame)的 repaint() 方法。然而,这个 JFrame 实例本身并没有被显示出来,真正显示并承载绘制内容的组件是 shapePane 这个 JPanel。因此,对 PentominoShape 这个“幽灵” JFrame 的重绘请求,并不会触发 shapePane 的 paintComponent 方法,导致图形不更新。只有当主窗口被最小化或最大化时,系统级别的重绘事件才会强制整个窗口区域重新绘制,从而间接更新了 shapePane 上的图形。

解决方案:正确调用 repaint()

要解决此问题,只需确保 repaint() 方法在实际进行自定义绘制的 JPanel 实例上被调用。在当前代码结构中,这个 JPanel 是 shapePane。

修改后的 mouseDragged 方法应如下所示:

public void mouseDragged(MouseEvent e) {
    // 优先处理 currPolygon 为 null 的情况,避免 NullPointerException
    if (currPolygon == null) {
        return;
    }

    // 原始逻辑中 currPolygon.contains(x, y) 的判断可能不准确,
    // 因为 x, y 是上次鼠标按下的位置,而不是当前拖动点的起始位置。
    // 如果目的是判断拖动是否发生在当前选中的多边形内部,
    // 应该在 mousePressed 中确定 currPolygon,并在 mouseDragged 中直接移动 currPolygon。
    // 这里我们假设 currPolygon 已经被正确选中且就是要移动的对象。

    System.out.println("Dragged");
    int dx = e.getX() - x;
    int dy = e.getY() - y;
    currPolygon.translate(dx, dy);
    x = e.getX(); // 更新 x, y 为当前鼠标位置,为下一次拖动计算位移做准备
    y = e.getY();

    // 关键修复:在承载绘制内容的 JPanel 上调用 repaint()
    shapePane.repaint();
}

通过将 repaint() 从 this (即 PentominoShape 这个 JFrame 实例) 更改为 shapePane (即实际绘制多边形的 JPanel 实例),可以确保当多边形位置更新时,shapePane 会被正确地请求重绘,从而实现实时拖动动画。

优化 Swing 组件结构和代码设计

除了修复 repaint() 的调用目标,我们还可以进一步优化代码结构,使其更符合 Swing 的设计原则,提高可维护性和可扩展性。

Designify
Designify

拖入图片便可自动去除背景✨

下载

1. 避免不必要的 JFrame 继承

PentominoShape 类不应该继承 JFrame。一个类如果只是为了绘制内容或作为容器,通常应该继承 JPanel 或 JComponent。JFrame 应该作为应用程序的主窗口,管理其他组件。

推荐的结构调整:

  • 创建一个 DrawingPanel 类,它继承自 JPanel,负责所有的自定义绘制和鼠标事件处理。
  • Pentomino 类(主应用窗口)将包含并显示这个 DrawingPanel。

示例:

// DrawingPanel.java
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
import java.util.ArrayList;
import java.util.List;

public class DrawingPanel extends JPanel implements MouseListener, MouseMotionListener {
    private List polygons = new ArrayList<>();
    private List colors = new ArrayList<>();
    private Polygon currPolygon;
    private int x, y;

    public DrawingPanel() {
        // 初始化多边形和颜色
        initShapes();
        // 添加鼠标监听器到自身
        addMouseListener(this);
        addMouseMotionListener(this);
    }

    private void initShapes() {
        // 省略大量多边形和颜色初始化代码,与原 PentominoShape 相同
        // ...
        Polygon fig1 = new Polygon(new int[]{10, 50, 50, 10}, new int[]{10, 10, 200, 200}, 4);
        polygons.add(fig1);
        colors.add(new Color(25, 165, 25));
        // 添加所有其他多边形和颜色
        // ...
    }

    @Override
    protected void paintComponent(Graphics g) {
        super.paintComponent(g);
        Graphics2D g2 = (Graphics2D) g;
        for (int i = 0; i < polygons.size(); i++) {
            g2.setColor(colors.get(i));
            g2.fill(polygons.get(i));
        }
    }

    @Override
    public void mousePressed(MouseEvent e) {
        for (Polygon polygon : polygons) {
            if (polygon.contains(e.getPoint())) {
                currPolygon = polygon;
                x = e.getX();
                y = e.getY();
                break; // 找到即退出
            }
        }
    }

    @Override
    public void mouseDragged(MouseEvent e) {
        if (currPolygon != null) {
            int dx = e.getX() - x;
            int dy = e.getY() - y;
            currPolygon.translate(dx, dy);
            x = e.getX();
            y = e.getY();
            repaint(); // 在 DrawingPanel 自身上调用 repaint()
        }
    }

    @Override
    public void mouseReleased(MouseEvent e) {
        currPolygon = null;
    }

    // 实现其他 MouseListener/MouseMotionListener 接口方法
    @Override public void mouseClicked(MouseEvent e) {}
    @Override public void mouseEntered(MouseEvent e) {}
    @Override public void mouseExited(MouseEvent e) {}
    @Override public void mouseMoved(MouseEvent e) {}
}

主应用 Pentomino 类:

// Pentomino.java
import javax.swing.*;
import java.awt.*;

public class Pentomino extends JFrame {
    public Pentomino() {
        initUI();
    }

    private void initUI() {
        setTitle("Пентамино");
        setDefaultCloseOperation(EXIT_ON_CLOSE);
        setSize(1500, 900);
        setResizable(false);

        // 创建并添加 DrawingPanel
        DrawingPanel drawingPanel = new DrawingPanel();
        add(drawingPanel); // JFrame 的默认布局是 BorderLayout,会填充整个中央区域

        setLocationRelativeTo(null);
        setVisible(true);
    }

    public static void main(String[] args) {
        // 在 EDT 中运行 Swing 应用程序
        SwingUtilities.invokeLater(Pentomino::new);
    }
}

2. 封装图形对象

将 Polygon 和其对应的 Color 封装成一个自定义的图形类,可以使 DrawingPanel 的 paintComponent 方法更加清晰,并且便于管理图形属性。

示例:CustomShape 类

// CustomShape.java
import java.awt.*;

public class CustomShape {
    private Polygon polygon;
    private Color color;

    public CustomShape(Polygon polygon, Color color) {
        this.polygon = polygon;
        this.color = color;
    }

    public void draw(Graphics g) {
        Graphics2D g2 = (Graphics2D) g;
        g2.setColor(color);
        g2.fill(polygon);
    }

    public Polygon getPolygon() {
        return polygon;
    }

    public Color getColor() {
        return color;
    }

    public boolean contains(Point p) {
        return polygon.contains(p);
    }

    public void translate(int dx, int dy) {
        polygon.translate(dx, dy);
    }
}

更新 DrawingPanel 使用 CustomShape:

// DrawingPanel.java (部分修改)
// ...
public class DrawingPanel extends JPanel implements MouseListener, MouseMotionListener {
    private List shapes = new ArrayList<>(); // 存储 CustomShape 对象
    private CustomShape currShape; // 当前拖动的 CustomShape
    private int x, y;

    public DrawingPanel() {
        initShapes();
        addMouseListener(this);
        addMouseMotionListener(this);
    }

    private void initShapes() {
        // 示例:初始化 CustomShape
        Polygon fig1 = new Polygon(new int[]{10, 50, 50, 10}, new int[]{10, 10, 200, 200}, 4);
        shapes.add(new CustomShape(fig1, new Color(25, 165, 25)));
        // 添加所有其他 CustomShape 对象
        // ...
    }

    @Override
    protected void paintComponent(Graphics g) {
        super.paintComponent(g);
        for (CustomShape shape : shapes) {
            shape.draw(g); // 调用 CustomShape 的 draw 方法
        }
    }

    @Override
    public void mousePressed(MouseEvent e) {
        for (CustomShape shape : shapes) {
            if (shape.contains(e.getPoint())) {
                currShape = shape;
                x = e.getX();
                y = e.getY();
                break;
            }
        }
    }

    @Override
    public void mouseDragged(MouseEvent e) {
        if (currShape != null) {
            int dx = e.getX() - x;
            int dy = e.getY() - y;
            currShape.translate(dx, dy); // 调用 CustomShape 的 translate 方法
            x = e.getX();
            y = e.getY();
            repaint();
        }
    }

    @Override
    public void mouseReleased(MouseEvent e) {
        currShape = null;
    }
    // ... 其他方法 ...
}

总结与注意事项

  • repaint() 的正确使用:始终在需要重绘内容的 实际组件(通常是 JPanel 或 JComponent 的子类)上调用 repaint()。
  • Swing 组件层次:理解 JFrame、JPanel 和 JComponent 的角色。JFrame 是顶级窗口,JPanel 常用于组织组件或进行自定义绘制。
  • 避免 NullPointerException:在访问可能为 null 的对象之前进行显式检查,而不是依赖 try-catch 块来处理程序逻辑流程。
  • 代码封装:将相关的属性和行为封装到独立的类中(例如 CustomShape),可以显著提高代码的可读性、可维护性和可扩展性。
  • EDT (Event Dispatch Thread):所有 Swing UI 操作都应在 EDT 上执行。对于应用程序启动,使用 SwingUtilities.invokeLater() 是最佳实践。

通过遵循这些原则,可以构建出响应迅速、易于维护且符合 Swing 最佳实践的图形用户界面应用。

相关文章

Windows激活工具
Windows激活工具

Windows激活工具是正版认证的激活工具,永久激活,一键解决windows许可证即将过期。可激活win7系统、win8.1系统、win10系统、win11系统。下载后先看完视频激活教程,再进行操作,100%激活成功。

下载

本站声明:本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn

相关专题

更多
c语言中null和NULL的区别
c语言中null和NULL的区别

c语言中null和NULL的区别是:null是C语言中的一个宏定义,通常用来表示一个空指针,可以用于初始化指针变量,或者在条件语句中判断指针是否为空;NULL是C语言中的一个预定义常量,通常用来表示一个空值,用于表示一个空的指针、空的指针数组或者空的结构体指针。

229

2023.09.22

java中null的用法
java中null的用法

在Java中,null表示一个引用类型的变量不指向任何对象。可以将null赋值给任何引用类型的变量,包括类、接口、数组、字符串等。想了解更多null的相关内容,可以阅读本专题下面的文章。

434

2024.03.01

线程和进程的区别
线程和进程的区别

线程和进程的区别:线程是进程的一部分,用于实现并发和并行操作,而线程共享进程的资源,通信更方便快捷,切换开销较小。本专题为大家提供线程和进程区别相关的各种文章、以及下载和课程。

472

2023.08.10

Java 并发编程高级实践
Java 并发编程高级实践

本专题深入讲解 Java 在高并发开发中的核心技术,涵盖线程模型、Thread 与 Runnable、Lock 与 synchronized、原子类、并发容器、线程池(Executor 框架)、阻塞队列、并发工具类(CountDownLatch、Semaphore)、以及高并发系统设计中的关键策略。通过实战案例帮助学习者全面掌握构建高性能并发应用的工程能力。

54

2025.12.01

php源码安装教程大全
php源码安装教程大全

本专题整合了php源码安装教程,阅读专题下面的文章了解更多详细内容。

65

2025.12.31

php网站源码教程大全
php网站源码教程大全

本专题整合了php网站源码相关教程,阅读专题下面的文章了解更多详细内容。

45

2025.12.31

视频文件格式
视频文件格式

本专题整合了视频文件格式相关内容,阅读专题下面的文章了解更多详细内容。

40

2025.12.31

不受国内限制的浏览器大全
不受国内限制的浏览器大全

想找真正自由、无限制的上网体验?本合集精选2025年最开放、隐私强、访问无阻的浏览器App,涵盖Tor、Brave、Via、X浏览器、Mullvad等高自由度工具。支持自定义搜索引擎、广告拦截、隐身模式及全球网站无障碍访问,部分更具备防追踪、去谷歌化、双内核切换等高级功能。无论日常浏览、隐私保护还是突破地域限制,总有一款适合你!

41

2025.12.31

出现404解决方法大全
出现404解决方法大全

本专题整合了404错误解决方法大全,阅读专题下面的文章了解更多详细内容。

232

2025.12.31

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
Kotlin 教程
Kotlin 教程

共23课时 | 2.2万人学习

C# 教程
C# 教程

共94课时 | 5.8万人学习

Java 教程
Java 教程

共578课时 | 40.5万人学习

关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送

Copyright 2014-2026 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号