简体   繁体   English

使用线程在java中绘制面板

[英]Using threads to paint panel in java

I am writing a program that has a number of different views. 我正在编写一个包含许多不同视图的程序。 One of which is fairly graphics intensive (it displays an interconnected graph). 其中一个是相当图形密集型(它显示一个互连的图形)。 Others just display small but complex diagrams. 其他人只是展示小而复杂的图表。

I'm finding the paint time for the main view is quite long (even just painting the currently visible area) and while it is being painted, the rest of the interface becomes very slow. 我发现主视图的绘制时间很长(甚至只绘制当前可见的区域),并且在绘制时,界面的其余部分变得非常慢。

My question is this, can I create a new thread to handle the painting - and if so, will it result in a performance increase, my suspicion is that it wont. 我的问题是,我可以创建一个新线程来处理绘画 - 如果是这样,它会导致性能提升,我怀疑它不会。 I have tried the following: 我尝试过以下方法:

creating an abstract classs ThreadPaintablePanel, that my complex views inherit from. 创建一个抽象类ThreadPaintablePanel,我的复杂视图继承自。

public abstract class ThreadPaintablePanel extends JPanel{
 private Thread painter;
 public abstract void paintThread(Graphics g);

 protected void callPaintThread(Graphics g){
   if(painter != null){
     painter.interrupt();
   }
   painter = new Thread(new PaintRunnable(this, g));
   painter.start();
 }
} 

Then in my complicated views my paintComponent method is simply: super.callPaintThread(g); 然后在我复杂的视图中,我的paintComponent方法很简单: super.callPaintThread(g);

The overridden paintThread method contains all my painting code. 重写的paintThread方法包含我的所有绘画代码。 However this results in unpainted panels. 然而,这会导致未上漆的面板。 Have I missed something obvious? 我错过了一些明显的事吗?

Thanks 谢谢

You cannot let any thread but the Event Dispatch Thread (EDT) touch the GUI. 你不能让任何线程,但事件发送线程 (EDT)触摸GUI。 Letting other threads mess with the GUI causes trouble and Exceptions. 让其他线程弄乱GUI会导致麻烦和异常。 You can employ a multi-threaded multi-buffering technique. 您可以使用多线程多缓冲技术。 It involves two steps: 它涉及两个步骤:

  1. In order to parallelize complicated drawing routines, you can simply divide the entire "view" into patches and let one thread draw one patch into one image. 为了并行化复杂的绘图例程,您可以简单地将整个“视图”划分为补丁,让一个线程将一个补丁绘制到一个图像中。 Here is a tutorial on working with images in Java . 这是一个使用Java处理图像的教程

  2. Once you have the images, you can override paintComponent and use the Graphics.drawImage method to let the EDT display the full or a partial view, by stitching the images of the corresponding patches together. 获得图像后,可以覆盖paintComponent并使用Graphics.drawImage方法让EDT显示完整或部分视图,方法是将相应修补的图像拼接在一起。

In order to avoid unnecessary work, make sure to perform the first step initially and then only after the view changed, else just draw previously computed results again. 为了避免不必要的工作,请确保最初执行第一步,然后仅在视图更改后执行,否则只需再次绘制先前计算的结果。 Furthermore, try to only update part of the patches, if you can narrow down the regions inside the views that have changed between frames. 此外,如果可以缩小视图内部在帧之间发生更改的区域,请尝试仅更新部分修补程序。

Let us assume that your view is at least as many pixels high as your optimal number of threads, so we can just partition the view vertically. 让我们假设您的视图至少与最佳线程数一样高,因此我们可以垂直分区视图。 Also, let us assume that drawing any pixel takes about the same amount of work as any other, so we can use the same size for every patch. 另外,让我们假设绘制任何像素所需的工作量与其他像素相同,因此我们可以为每个补丁使用相同的大小。 These two assumptions make things a lot easier. 这两个假设使事情变得容易多了。

Code follows. 代码如下。 If you don't need your computer to do anything else, you can set nThreads to your number of cores . 如果您不需要计算机执行任何其他操作,则可以nThreads设置为核心数 Note that the code also uses pseudocode for "parallel for" which is explained here : 请注意,代码也使用伪代码“parallel for”, 这里解释:

// let multiple threads write all patches
public BufferedImage[] writePatches(...)
{
    // Given data:
    const int nThreads = ...;         // the amount of threads that you want
    Rectangle viewBox = ...;        // The view rectangle

    // Immediate data:
    Dimension viewSize = viewBox.getSize();
    int defaultPatchHeight = (int)ceil((float)viewSize.height / nThreads);
    int lastPatchHeight = viewSize.height - (nThreads-1) * defaultPatchHeight;

    // The actual "buffer" is a set of images
    BufferedImage[] images = new BufferedImage[nThreads];

    // ...

    // pseudocode for parallel processing of a for loop
    parallel-for (nThreads, threadId)
    {
        // the starting point and size of this thread's patch
        // handle boundary (last) patch
        int w = viewBox.width;
        int h = threadId == nThread-1 ? lastPatchHeight : defaultPatchHeight;                
        int x = viewBox.x;
        int y = viewBox.y + threadId * defaultPatchHeight;

        BufferedImage patch = new BufferedImage(w, h, BufferedImage.TYPE_INT_ARGB);
        Graphics2D g = off_Image.createGraphics();

        // use g to draw to patch image here
        // better yet: Re-use existing patches and only update the parts that changed.

        images[threadId] = patch;
    }
    return images;
}

// ...

// override paintComponent
@Override
void paintComponent(Graphics gg)
{
    Graphics2D g = (Graphics2D) gg;
    // ...

    // stitch all images together (you can also just display only some images here)
    for (int threadId = 0; threadId < nThreads; ++threadId)
    {
        int w = viewBox.width;
        int h = threadId == nThread-1 ? lastPatchHeight : defaultPatchHeight;                
        int x = viewBox.x;
        int y = viewBox.y + threadId * defaultPatchHeight;

        // use pre-computed images here
        BufferedImage patch = images[threadId];
        g.drawImage(patch, x, y, ...);
    }
}

我想你想要做的是绘制到非UI线程中的缓冲区(所以你管理的那个),并在完成时(在UI线程中)复制缓冲区。

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

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