简体   繁体   English

使用Java Swing有效地重画线

[英]Redrawing lines with Java Swing efficiently

I am trying to find the most efficient way to create a dynamic java graphical application. 我试图找到创建动态Java图形应用程序的最有效方法。 I want to build a large screen, with many different parts, all of which re-drawn or updated using a distinct thread, such that the screen looks "alive". 我想构建一个大屏幕,其中包含许多不同的部分,所有这些部分都使用不同的线程重新绘制或更新,以使屏幕看起来“处于活动状态”。 However, my initial attempt at doing that was horrible, the screen got very slow, buggy etc - so I figured I need to create different modules (JPanels), each of which contains other graphical parts (lines, circles, etc), and each distinct JPanel being redrawn separately (when needed), instead of the whole main panel (or frame). 但是,我这样做的最初尝试太可怕了,屏幕变得非常缓慢,有错误等-所以我认为我需要创建不同的模块(JPanels),每个模块都包含其他图形部分(线,圆等),并且每个(需要时)分别重绘不同的JPanel,而不是整个主面板(或框架)。

So I have written a small demo program - my program contains a single window, with multiple panels, wrapped in my object called "MyPanel" - each such MyPanel contains several drawn lines (I have a Line object), all lines starting from the top-left corner and have different lengths and angles). 因此,我编写了一个小型演示程序-我的程序包含一个窗口,带有多个面板,包装在称为“ MyPanel”的对象中-每个这样的MyPanel都包含多条绘制的线(我有一个Line对象),所有线均从顶部开始-左角,并具有不同的长度和角度)。 Each distinct MyPanel has a different line color (see this image). 每个不同的MyPanel都有不同的线条颜色(请参见下图)。

初始画面

I instantiate several worker threads, each designated for one MyPanel - the workers wait for 5 seconds, then try to re-draw all lines in the following manner: 我实例化了几个工作线程,每个工作线程都指定一个MyPanel-工作线程等待5秒钟,然后尝试按以下方式重新绘制所有行:

  • Remove all existing lines from the JPanel (MyPanel). 从JPanel(MyPanel)删除所有现有的行。
  • Create new lines with different angles and lengths. 创建具有不同角度和长度的新线。
  • redraw the JPanel (MyPanel) by invoking super.repaint() this is the entire purpose, to update only this panel, have it redraw itself with all of its sub-parts, and not the entire program 通过调用super.repaint()重绘JPanel(MyPanel), 这是整个目的,仅更新此面板,让它重绘其所有子部分,而不是重绘整个程序

However, something weird happens: when the panels are re-drawn, each one is redrawn in a way that probably contains all other MyPanels too, or mirrors the main screen somehow - its very unclear what exactly happens here. 但是,发生了一些奇怪的事情:重新绘制面板时,每个面板都以可能也包含所有其他MyPanels的方式重新绘制,或者以某种方式镜像了主屏幕-尚不清楚此处到底发生了什么。 Also, all "background opacity" of the panels is gone (see this image). 而且,面板的所有“背景不透明度”都消失了(请参见此图像)。

屏幕过后

Before I attach my code, let me say that it uses a null LayoutManager. 在附加代码之前,我要说它使用一个空的LayoutManager。 I know this is a big "no no" in terms of efficiency, modularity and whatnot. 知道 ,就效率,模块化和其他方面而言,这是一个很大的“不反对”。 However I don't have a choice since I need to create a very graphically complicated and exact demo quickly, which only serves as a proof-of-concept, so for now, all of these flaws are negligible. 但是,我别无选择,因为我需要快速创建一个非常复杂的图形且精确的演示,它仅用作概念验证,因此目前所有这些缺陷都可以忽略不计。 I know it's horrible design-wise, it hurts me too, but that's the only way I can make it on time. 我知道这是糟糕透顶的设计,也伤害了我,但这是我按时完成的唯一方法。

Here is the code - what happens? 这是代码-会发生什么? and how can I efficiently re-draw different parts of the program if not using this way? 如果不使用这种方法如何才能有效地重画程序的不同部分? note I cannot "repaint over existing lines with the background color", since there is a background image in my main program. 注意由于我的主程序中有背景图像,因此我不能“用背景颜色对现有线条进行重新绘制”。

Any help would be appreciated! 任何帮助,将不胜感激!

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();
    }
}

} }

So, a litany of errors: 因此,一连串的错误:

Incorrect use of custom painting... 错误使用自定义绘画...

This... 这个...

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);
}

Isn't how custom paint should be done. 定制油漆不应该怎么做。 Graphics is a shared context in Swing, it's shared among all the components been painted in any given paint pass. Graphics是Swing中的共享上下文,它在任何给定的绘制通道中绘制的所有组件之间都是共享的。 This means, that unless you prepared the context first, it will still contain what ever was painted to it from the last component. 这意味着,除非您首先准备了上下文,否则它将仍然包含最后一个组件所绘制的内容。

Equally, it's not recommend to override paint , it's to high in the paint chain and incorrect use can cause no end of issues. 同样,不建议覆盖paintpaint覆盖范围太高,使用不当不会导致任何问题。

Instead, you should start with paintComponent and make sure to call it's super method in order to maintain the paint chain operations... 相反,您应该从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();
}

If you're modifying the state of the context (in particular the transformation, but rendering hints count), you should first create a copy of the state and dispose of it when you're done. 如果要修改上下文的状态(特别是转换,但是渲染提示很重要),则应首先创建状态的副本并在完成后将其处理。 This prevents changes to the state been passed onto other components, which can cause some weird rendering issues 这样可以防止将状态更改传递给其他组件,这可能导致一些奇怪的渲染问题

Have a look at Performing Custom Painting and Painting in AWT and Swing for more details 有关更多详细信息,请参见在AWT和Swing执行自定义绘画绘画。

Incorrect use of opacity 错误使用不透明度

This... 这个...

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

is not how you create a transparent component. 不是创建透明组件的方式。 Swing doesn't know how to handle transparent (alpha based) colors. Swing不知道如何处理透明(基于Alpha)的颜色。 It only deals with opaque and non-opaque components. 它仅处理不透明和不透明组件。 This is achieved through the use of the opaque property. 这是通过使用opaque属性来实现的。

panel.setOpaque(false);

Violation of Swing threading rules... 违反Swing线程规则...

Swing is single threaded AND NOT thread safe. 秋千是单线程的,不是线程安全的。

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

        _panel.createLines();
    }
}

Calling createLines in this context is running the risk of threading issues, as Swing attempts to paint the properties while they are been updated, which can lead to weird painting artefacts. 在这种情况下调用createLines带来线程问题的风险,因为Swing会在更新属性时尝试绘制属性,这会导致奇怪的绘制伪像。

Remember, a paint pass may occur at any time, most of the time without your interaction or knowledge. 请记住,大多数情况下,涂装合格可能随时发生,而无需您的干预或了解。

Instead, I'd recommend maybe using a SwingWorker (but it has its limitations) or ensuring that the call to createLines is done within the context of the Event Dispatching Thread, through the use of EventQueue.invokeLater or EventQueue.invokeAndWait depending on your needs 相反,我建议您也许使用SwingWorker (但是它有其局限性),或者确保根据事件的需要,通过使用EventQueue.invokeLaterEventQueue.invokeAndWait来使用事件调度线程的上下文来完成对createLines的调用

See Concurrency in Swing for more details. 有关更多详细信息,请参见Swing中的并发

More threads != more work done 更多线程!=完成更多工作

Having more threads doesn't always mean you can get more done, it's a balancing act. 拥有更多线程并不总是意味着您可以完成更多工作,这是平衡的举动。

Personally I would start with a single thread, responsible for scheduling updates to each panel, either directly (via createLines ) or indirectly by building the line information itself and passing the result to the component. 我个人将从一个线程开始,负责直接(通过createLines )或通过构建行信息本身并将结果传递给组件来间接调度每个面板的更新。

Remember, when you schedule a paint pass, Swing will attempt to optimise the painting by reducing the number of paint events and simply paint a larger area (as required). 请记住,当您安排绘制过程时,Swing会尝试通过减少绘制事件的次数并简单地绘制较大的区域(根据需要)来优化绘制。 Also, when working with non-opaque components, painting any one component may require that other, overlapping, components also need to be painted. 同样,在使用不透明的组件时,绘制任何一个组件可能都需要绘制其他重叠的组件。

As you expand the number of threads, consider if the threads should create the lines itself, this means, instead of wasting time in the EDT, you're performing the operations in a separate thread and then simply applying the results to the component. 随着线程数量的增加,请考虑线程是否应该自己创建线,这意味着,不是在EDT中浪费时间,而是在单独的线程中执行操作,然后将结果简单地应用于组件。

Equally, more components may increase the amount of work needed to be done. 同样,更多的组件可能会增加需要完成的工作量。

Another approach would be to have the Thread s act as "producers" which generate a List of lines. 另一种方法是让Thread充当生成行List的“生产者”。 A single component would then act as a "consumer" and when a new List of lines is ready, it would repaint itself. 然后,单个组件将充当“消费者”,并且当准备好新的行List时,它将重新绘制自身。

This might need you to produce a mapping between the producer and consumer so you know which List of lines is been updated, but that's beyond the scope of the question 这可能需要您在生产者和消费者之间产生一个映射,因此您知道哪个行List已更新,但这超出了问题的范围。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM