使用Java Swing有效地重绘线条

时间:2018-03-18 21:16:39

标签: java swing jpanel

我正在尝试找到创建动态Java图形应用程序的最有效方法。我想构建一个包含许多不同部分的大屏幕,所有部分都使用不同的线程重新绘制或更新,以使屏幕看起来“活着”。 然而,我最初的尝试是非常可怕的,屏幕变得很慢,越野车等 - 所以我想我需要创建不同的模块(JPanels),每个模块包含其他图形部分(线条,圆圈等),每个不同的JPanel分别重新绘制(需要时),而不是整个主面板(或框架)。

所以我写了一个小的演示程序 - 我的程序包含一个单独的窗口,有多个面板,包裹在我的对象中,名为“MyPanel” - 每个这样的MyPanel包含几个绘制的行(我有一个Line对象),所有行都开始从左上角开始,有不同的长度和角度)。每个不同的MyPanel都有不同的线条颜色(见图)。

Initial Screen

我实例化几个工作线程,每个线程指定一个MyPanel - 工作人员等待5秒,然后尝试以下列方式重新绘制所有行:

  • 从JPanel(MyPanel)中删除所有现有行。
  • 创建具有不同角度和长度的新线条。
  • 通过调用super.repaint()重新绘制JPanel(MyPanel)这是完整的目的,只更新此面板,让它自己重绘所有子部分,而不是整个程序

但是,会发生一些奇怪的事情:当重新绘制面板时,每个面板都会以一种可能包含所有其他MyPanel的方式重新绘制,或者以某种方式镜像主屏幕 - 它非常不清楚这里究竟发生了什么。此外,面板的所有“背景不透明度”都消失了(见此图片)。

Screen after

在我附加代码之前,让我说它使用null LayoutManager。我知道这在效率,模块化等方面是一个很大的“不”。但是我没有选择,因为我需要快速创建一个非常图形复杂和精确的演示,这只是一个概念验证,所以目前所有这些缺陷都可以忽略不计。我知道它在设计方面很糟糕,它也伤害了我,但这是我能按时完成它的唯一方法。

这是代码 - 会发生什么?并且如果不使用这种方式,如何有效地重新绘制程序的不同部分?注意我不能“重新绘制具有背景颜色的现有行”,因为我的主程序中有一个背景图像

任何帮助将不胜感激!

import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import javax.swing.JFrame;
import javax.swing.JPanel;

/**
 * Displays the main windows (this is the "JFrame" object).
 */
public class GUI extends JFrame
{
/**
 * A customized panel which contains several lines with different coordinates, all starting from
 * the top left corner of the panel with coordinates (1,1). The object contains a method which
 * removes all drawn lines from the panel, then redraws lines with different vectors.
 */
public static class MyPanel extends JPanel
{
    private List<Line> _lines;

    private Color _color;

    private int _facet;

    private int _numLines;

    public MyPanel(int facet, int numLines, Color color)
    {
        _facet = facet;
        _color = color;
        _numLines = numLines;
        _lines = new ArrayList<>();

        super.setLayout(null);
        createLines();
    }

    public void createLines()
    {
        for(Line line : _lines)
        {
            remove(line);
        }

        _lines.clear();

        Random r = new Random();

        for(int i = 0; i < _numLines; i++)
        {
            int lengthX = r.nextInt(_facet) + 1;
            int lengthY = r.nextInt(_facet) + 1;

            Line line = new Line(1, 1, 1 + lengthX, 1 + lengthY, 1, _color);

            line.setBounds(1, 1, 1 + lengthX, 1 + lengthY);
            super.add(line);

            _lines.add(line);
        }

        super.repaint();
    }
}

/**
 * Represents a line, drawn with antialiasing at a given start and end coordinates
 * and a given thickness.
 */
public static class Line extends JPanel
{
    private int _startX;
    private int _startY;
    private int _endX;
    private int _endY;

    private float _thickness;
    private Color _color;

    public Line(int startX, int startY, int endX, int endY, float thickness, Color color)
    {
        _startX = startX;
        _startY = startY;
        _endX = endX;
        _endY = endY;

        _thickness = thickness;
        _color = color;
    }

    public void paint(Graphics g)
    {
        Graphics2D g2d = (Graphics2D)g;

        g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);

        g2d.setColor(_color);
        g2d.setStroke(new BasicStroke(_thickness));
        g2d.drawLine(_startX, _startY, _endX, _endY);
    }
}

/**
 * Stores all "MyPanel" panels of the GUI.
 * The "MyPanels" are rectangular panels containing lines of the same color
 * (different color across different panels).
 */
public List<MyPanel> panels;

public GUI()
{
    setSize(800, 800);
    setLayout(null);
    setTitle("Y U no work??");

    panels = new ArrayList<>();

    // The starting positions (x,y) of the "MyPanel"s. All panels are squares of
    // height = 300 and width = 300.
    int[][] coords = {{1, 1}, {100, 100}, {200, 100}, {50, 300}, {300, 300}, 
                      {0, 400}, {300, 400}, {350, 250}, {370, 390}};

    // The colors of the lines, drawn in the panels.
    Color[] colors = {Color.RED, Color.GREEN, Color.BLUE, Color.ORANGE, Color.CYAN,
                      Color.MAGENTA, Color.YELLOW, Color.PINK, Color.darkGray};


    for(int i = 0; i < colors.length; i++)
    {
        MyPanel panel = new MyPanel(300, 50, colors[i]);
        panel.setBackground(new Color(0, 0, 0, 0));
        // Set the *exact* start coordinates and width/height (null layout manager).
        panel.setBounds(coords[i][0], coords[i][1], 300, 300);
        add(panel);
        panels.add(panel);
    }
}

/**
 * A runnable used to instantiate a thread which waits for 5 seconds then redraws
 * the lines of a given "MyPanel".
 */
public static class Actioner implements Runnable
{
    private MyPanel _panel;

    public Actioner(MyPanel panel)
    {
        _panel = panel;
    }

    public void run()
    {
        while(true)
        {
            try
            {
                Thread.sleep(5000);
            }
            catch(Exception e) {}

            _panel.createLines();
        }
    }
}

public static void main(String[] args)
{
    GUI GUI = new GUI();

    EventQueue.invokeLater(() ->
    {
        GUI.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        GUI.setVisible(true);
    });

    // Create all operating threads (one per "MyPanel").
    for(MyPanel panel : GUI.panels)
    {
        new Thread(new Actioner(panel)).start();
    }
}

}

1 个答案:

答案 0 :(得分:4)

所以,一连串的错误:

自定义绘画的使用不正确...

此...

public void paint(Graphics g)
{
    Graphics2D g2d = (Graphics2D)g;

    g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);

    g2d.setColor(_color);
    g2d.setStroke(new BasicStroke(_thickness));
    g2d.drawLine(_startX, _startY, _endX, _endY);
}

不应该如何定制油漆。 Graphics是Swing中的共享上下文,它在任何给定的绘制过程中绘制的所有组件之间共享。这意味着,除非你先准备好上下文,否则它仍将包含从最后一个组件中绘制的内容。

同样,我们不建议覆盖paint,它在油漆链中处于高位,不正确的使用可能导致问题无法结束。

相反,您应该从paintComponent开始,并确保将其称为super方法,以便维护绘画链操作......

protected void paintComponent(Graphics g)
{
    super.paintComponent(g);
    Graphics2D g2d = (Graphics2D)g.create();

    g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);

    g2d.setColor(_color);
    g2d.setStroke(new BasicStroke(_thickness));
    g2d.drawLine(_startX, _startY, _endX, _endY);

    g2d.dispose();
}

如果您正在修改上下文的状态(特别是转换,但渲染提示计数),您应首先创建状态的副本并在完成后处理它。这可以防止对状态的更改传递到其他组件,这可能会导致一些奇怪的渲染问题

有关详细信息,请查看Performing Custom PaintingPainting in AWT and Swing

不透明度的使用不正确

此...

panel.setBackground(new Color(0, 0, 0, 0));

不是您创建透明组件的方式。 Swing不知道如何处理透明(基于alpha)的颜色。它只处理不透明和不透明的组件。这是通过使用opaque属性来实现的。

panel.setOpaque(false);

违反Swing线程规则......

Swing是单线程的,而不是线程安全的。

public void run()
{
    while(true)
    {
        try
        {
            Thread.sleep(5000);
        }
        catch(Exception e) {}

        _panel.createLines();
    }
}

在此上下文中调用createLines存在线程问题的风险,因为Swing尝试在更新属性时绘制属性,这可能会导致奇怪的绘制工件。

请记住,在没有您的互动或知识的情况下,大多数时间可能会在任何时间出现油漆过程。

相反,我建议使用SwingWorker(但它有其局限性)或确保对createLines的调用是在事件调度线程的上下文中完成的,通过根据您的需要使用EventQueue.invokeLaterEventQueue.invokeAndWait

有关详细信息,请参阅Concurrency in Swing

更多主题!=完成更多工作

拥有更多线程并不意味着你可以做得更多,这是一种平衡行为。

我个人会从单个线程开始,负责直接(通过createLines)或通过构建线路信息本身并将结果传递给组件来间接安排每个面板的更新。

请记住,当您安排绘制过程时,Swing将尝试通过减少绘制事件的数量来优化绘画,并简单地绘制更大的区域(根据需要)。此外,在处理非不透明组件时,绘制任何一个组件可能还需要绘制其他重叠组件。

当您扩展线程数时,考虑线程是否应该自己创建线,这意味着,您不是在EDT中浪费时间,而是在单独的线程中执行操作,然后简单地应用结果到组件。

同样,更多组件可能会增加完成工作量。

另一种方法是让Thread作为&#34;生产者&#34;生成List行。然后,单个组件将充当消费者&#34;当新的List行准备就绪时,它会自行重绘。

这可能需要您在生产者和消费者之间生成映射,以便您知道哪些List行已更新,但这超出了问题的范围