java - 线程中的实例化组件不会重新绘制到 Java 中的 JFrame 中

标签 java multithreading components jframe paint

我有一个这样的类

public class BlockSpawner implements Runnable{

public static long timeToSpawn;
private GtrisJFrame frame;

public BlockSpawner(GtrisJFrame frame)
{

    this.frame = frame;
    timeToSpawn = 2000;
}

public void run()
{
    while(true)
    {
        try
        {
            Thread.sleep(timeToSpawn);
        }
        catch(InterruptedException e)
        {
            //Unhandled exception
        }

        //After awake, instanciate 2 blocks
        //get the position of the first one
        int index = Block.getRandomStartPosition();
        new Block(frame, index);
        new Block(frame, index+1);
    }
}

我在 JFrame 主类中实例化了这个类,并像这样启动它的线程:

private void initBlockSpawner()
{
    spawner = new BlockSpawner(this);
    new Thread(spawner).start();
}

我从 JFrame 构造函数中调用这个 initBlockSpawner() 函数。 Block类确实有点大,但是简单来说就是实现了runnable,在构造器的最后调用了它的run()方法。 run() 方法只会让方 block 以一定的速度下落。我曾尝试在 JFrame 构造函数中手动实例化新 block ,它们可以工作,重新绘制并掉落。但是每当我想从其他线程实例化 block 时,它们似乎会下降(即它的属性更新每个循环),但它们不会在 JFrame 中绘制。

作为附加信息,我正在使用 NetBeans,并且由于应用程序入口点在 JFrame 类上,所以主要方法如下所示:

public static void main(String args[])
{
    java.awt.EventQueue.invokeLater(new Runnable() {
        public void run() {
            new GtrisJFrame().setVisible(true);
        }
    });

}

我在 Java 线程、awt 事件和 swing 组件方面没有太多经验。但是我读到的东西here让我觉得我的问题是只有一个线程可以控制 swing 组件,或者其他什么......有什么办法可以解决我的问题吗?

提前致谢。

编辑:附加信息,每当我从线程检查实例化多维数据集上的方法 toString 时,它们都会给我这个 [,0,0,0x0],但是当我在同一个 JFrame 类中实例化它们时,它们会给我这个结果 [ ,0,0,328x552] 并且它们出现在框架上。这个 328x552 值与 getPreferredSize() 返回的组件维度相同...我试图通过像这样实例化它们来强制它们进入该维度:

new Block(this, index).setPreferredSize(new Dimension(328, 552));

但它不起作用,有人知道这个 [,0,0,328x552] 值可能意味着什么吗?

谢谢大家,我想我们快到了!

编辑 2: 我意识到组件的大小是 x:0 y:0,这是为什么?我将 BlockSpawner 的 run() 方法更改为如下所示:

public void run()
{
    while(true)
    {
        System.out.println("SPAWN");
        int index = Block.getRandomStartPosition();
        new Thread(new Block(frame, index)).start();
        new Thread(new Block(frame, index+1)).start();

        try
        {
            Thread.sleep(timeToSpawn);
        }
        catch(InterruptedException e)
        {
            //Unhandled exception
        }
    }
}

第一次运行时,一切正常!甚至一对 block 在 JFrame 上绘制并正确落下,但在 Thread.sleep() 之后,其余 block 只是被实例化,但它们的 getSize() 方法给了我 x:0 y:0;这仍然与 One Dispatcher Thread 问题有某种关系吗?还是现在有所不同?

最佳答案

在我看来(尽管我无法从上面的代码中看出)您正在尝试从线程以外的线程将组件添加到实时 JFrame(即已在屏幕上显示或“实现”的组件)事件派发线程。这违反了 Swing 线程模型,会给您带来无穷无尽的问题。

如果您想从不同的线程更改 Swing 对象,您可以将更改打包到一个 Runnable 中,然后使用 EventQueue.invokeLater() 或 invokeAndWait() 将其提交到调度线程。

编辑:更多信息

一些额外的评论(与您的问题没有直接关系,但仍然很重要):在构造函数中执行 Activity 可能不是一个好主意。子类化 JFrame 以向其添加组件也可能不是一个好主意。就此而言,在 JFrame 而不是 JPanel 中执行这些操作可能也不是最佳方法。

依次考虑这些:

  1. 构造函数应该用于对对象执行初始配置 - 而不是调用行为。这种分离有助于保持您的设计清洁和可维护。尽管这样做似乎更容易,但我建议不要这样做。在您的设计中的某个时刻,您可能会决定提前创建这些对象并仅在以后使用它们更有效。

  2. 子类化 JFrame 以添加组件通常不是一个热门想法。为什么?如果您决定要使用具有您想要的某些其他行为的专用 JFrame,该怎么办?如果您决定使用为 提供 JFrame 的应用程序框架(这在框架可能想要跟踪窗口关闭事件以便保存窗口大小和位置的情况下很典型)。不管怎样——有很多原因。将您的行为打包到一个非 GUI 相关的类中,并使用它来将行为注入(inject)到 JFrame(或 JPanel)中。

  3. 考虑使用 JPanel 而不是 JFrame。如果需要,您始终可以将 JPanel 添加到 JFrame。如果您直接使用 JFrame,当您决定要将其中两个面板并排放置在一个容器中时会发生什么情况?

因此,我建议您按照以下方式做更多的事情:

BlockAnimator animator = new BlockAnimator();
DispatchThread.invokeLater( 
  new Runnable(){
    public void run(){
      JPanel blockAnimationPanel = new JPanel();
      Block block = new Block(...);
      blockAnimationPanel.add(block);
      JFrame mainFrame = new JFrame();
      mainFrame.add(blockAnimationPanel);
      animator.start(); // note that we probably should start the thread *after* the panel is realized - but we don't really have to.
    }
  }

public class BlockAnimator extends Thread{
  private final List<Block> blocks = new CopyOnWriteArray<Block>(); // either this, or synchronize adds to the list
  public void addBlock(Block block){
    blocks.add(block);
  }
  public void run(){
    while(true){ // either put in a cancel check boolean, or mark the thread as daemon!
      DispatchThread.invokeAndWait(
        new Runnable(){
          public void run(){
            for(Block block: blocks){
              block.moveTo(....); // do whatever you have to do to move the block
            }
          }
        }
      ); // I may have missed the brace/paren count on this, but you get the idea
      spawnNewBlockObjects();
      Thread.sleep(50);
    }
  }
}

上面的代码没有经过准确性等检查......

理论上,您可以有一个单独的线程来生成新 block ,但上面的方法非常简单。如果您确实决定像我上面显示的那样使用单个后台线程来实现,您可以对 block 列表使用一个简单的 ArrayList,因为该对象不会出现竞争条件。

关于此的一些其他想法:

  1. 在上面, block 动画器可以独立于 block 本身进行管理。例如,您可以添加一个暂停所有 block 的 pause() 方法。

  2. 我为同一个分派(dispatch)线程调用​​中出现的所有 block 设置了动画更新。根据动画的成本,在后台线程上计算新坐标可能更好,并且只在 EDT 上发布实际位置更新。您可以选择为每个 block 更新发出单独的 invokeAndWait(或者可能使用 invokeLater)。这实际上取决于您所做工作的性质。

如果移动方 block 的位置的计算很复杂,请考虑将计算与实际移动分开。所以你会有一个调用来获取对象的新 Point,然后另一个调用会实际执行移动。

关于java - 线程中的实例化组件不会重新绘制到 Java 中的 JFrame 中,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/2301184/

相关文章:

java - 来自 HttpURLConnection 的输入流 : When to disconnect?

java - 那个 "knows"插件当前打开的文件是什么?

Delphi RAR 组件请求存档密码

Joomla:多次覆盖类别博客输出?

java - 在我调整大小之前,组件不会显示在 Applet 中

java设计模式的使用

java - 关于对象生命周期的问题

java - 如何在spring webapp中创建后台进程?

java - Display.getDefault().asyncExec 运行不正常

c++ - 异常情况下关闭线程