0

I am trying to make a Swing GUI that includes some 3D stuff using Java3D's Canvas3D object. The problem is that it takes a while for a Canvas3D object to initialize, and I want the Swing GUI to come up right away. My solution to this problem is to initialize the Canvas3D in a separate thread, and then add it to the JFrame once it is initialized. However, when that separate thread adds the Canvas3D to the JFrame, the window loses focus for a moment, which is undesirable. How can I prevent that from happening? I have included a simple example to illustrate what I am trying to do:

public class Main extends JFrame {
    public static void main(String[] args) {
        SwingUtilities.invokeLater(new Main()::setup);
    }

    private void setup() {
        setSize(600, 600);
        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        setVisible(true);

        Thread thread = new Thread() {
            @Override
            public void run() {
                Canvas3D canvas = new Canvas3D(SimpleUniverse.getPreferredConfiguration());
                SwingUtilities.invokeLater(() -> {
                    Main.this.add(canvas); //the window loses focus for a moment here
                    Main.this.revalidate();
                });
            }
        };

        thread.start();
    }
}

I am using Java3D 1.7.1.


I have modified my code as per R VISHAL's comment, but the problem still persists.

public class Main extends JFrame {
    public static void main(String[] args) {
        SwingUtilities.invokeLater(new Main()::setup);
    }

    private void setup() {
        setSize(600, 600);
        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        setVisible(true);

        SwingWorker<Canvas3D, Object> worker = new SwingWorker<Canvas3D, Object>() {
            @Override
            public Canvas3D doInBackground() {
                return new Canvas3D(SimpleUniverse.getPreferredConfiguration());
            }

            @Override
            public void done() {
                try {
                    Main.this.add(get());
                } catch (InterruptedException|ExecutionException e) {
                    throw new RuntimeException();
                }

                Main.this.requestFocusInWindow();
                Main.this.revalidate();
            }
        };

        worker.execute();
    }
}
Charlie Armstrong
  • 2,332
  • 3
  • 13
  • 25
  • 1
    1)Don't use a Thread class to alter gui in swing but an SwingWorker, initialise your canvas inside the doBackground() method and add the canvas to your frame inside the done() method 2)After adding the canvas try calling Main.this.requestFocusInWindow() or any of the requestFocus() methods – Sync it Jun 24 '20 at 05:27
  • @RVISHAL Thank you for the response! I went ahead and changed my code to use a `SwingWorker`, and called `Main.this.requestFocusInWindow()`, but the problem is still there. The window still loses focus. I have included my new code in an edit to my question. I like the idea of using `SwingWorker` either way, though, I didn't know such a thing existed. – Charlie Armstrong Jun 24 '20 at 16:52
  • Can you first try revalidating and then requestFocus()?.Tell me if it works – Sync it Jun 25 '20 at 03:19
  • I just tried switching those two lines, and the issue is still there. – Charlie Armstrong Jun 25 '20 at 16:15

1 Answers1

0

Ok so this might not be the answer but it was to large to put it as an comment

Use a JPanel having a CardLayout as your frame's contentpane, have one screen inside this panel set as the background[or whatever initial screen you want to display before the canvas is displayed], and then once the canvas is initialized add it to the content pane as the second screen and then call the CardLayout's show() method do display the canvas

public class Add
{
 public static void main(String args[])
 {
  JFrame frame=new JFrame("Test");
  
  JPanel mainPanel=new JPanel(new CardLayout());
  mainPanel.addMouseListener(new MouseAdapter()
  {
   int count=0;
   @Override
   public void mouseClicked(MouseEvent m)
   {
    System.out.println("Focus "+(++count));
   }
  });
  
  JPanel background=new JPanel();
  background.setBackground(Color.WHITE);
  mainPanel.add("Screen1",background);     //Initial Screen To Show Something To The User While Canvas Is Being Initialized
  frame.setContentPane(mainPanel);
  
  SwingWorker worker=new SwingWorker<Canvas3D,Object>()
  {
   @Override
   public Canvas3D doInBackground(){return new Canvas3D();}
   
   @Override
   public void done()
   {
    try
    {
     mainPanel.add("Screen2",get());    //Add Canvas To MainPanel
     
     CardLayout layout=(CardLayout)mainPanel.getLayout();
     
     layout.show(mainPanel,"Screen2"); //Remember This While Using CardLayout
    }
    catch(InterruptedException | ExecutionException ex){}
   }
  };
  
  frame.setSize(500,500);
  
  frame.setVisible(true);
  
  frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
  
  worker.execute();
 }
 
 private static final class Canvas3D extends JPanel
 {
  private Canvas3D()
  {
   super(new BorderLayout());
   
   try{Thread.sleep(5000);}//Mimicing Long Operations
   catch(Exception ex){}
  }
  
  @Override
  public void paintComponent(Graphics g)
  {
   super.paintComponent(g);
   
   g.setColor(Color.BLACK);
   
   g.fillRect(0,0,500,500);
   
   g.setFont(new Font("",Font.BOLD,15));
   
   g.setColor(Color.RED);
   
   g.drawString("CANVAS 3D",100,100);
  }
 }
}

Of course you would use your actual canvas3d instead of this custom one and there is no need to revalidate or requestFocus()

If you are still worried about focus you could always create an javax.swing.Timer class to request focus to your mainPanel or frame[Or Both See What Works For You] every second/or millisecond

  Timer timer=new Timer(1000,new ActionListener()  //milliseconds make it 1 if your are dead serious about focus
  {
   @Override
   public void actionPerformed(ActionEvent e)
   {
    mainPanel.requestFocusInWindow();
    
    frame.requestFocusInWindow();  //May not be required since we are already requesting focus in mainPanel
   }
  });
  timer.start();

If you want to get even more paranoid about focus you could always add an focus listener

 mainPanel.addFocusListener(new FocusListener()
  {
   @Override
   public void focusLost(FocusEvent e) 
   {
    mainPanel.requestFocusInWindow();
    
    frame.requestFocusInWindow();
   }
   
   @Override
   public void focusGained(FocusEvent e){}
  });

If any of these suggestions did/didn't work comment below :)

Sync it
  • 1,180
  • 2
  • 11
  • 29
  • Thank you for your answer. If I copy and paste your code into my IDE and run it, it works, which is a good start. However, you used a custom class called Canvas3D, and I was looking to use the built-in [Canvas3D](https://download.java.net/media/java3d/javadoc/1.4.0/javax/media/j3d/Canvas3D.html) in [Java3D](http://www.java3d.org/). When I switched out your Canvas3D for the real Canvas3D, the issue came back. I tried using your suggestion with a Timer, and I tried playing around with the frequency (even tried 1), but it had no effect. I also tried your FocusListener idea, but still no effect. – Charlie Armstrong Jun 26 '20 at 17:27
  • I think Canvas3D must do something on startup that is more than just taking a long time in the constructor. – Charlie Armstrong Jun 26 '20 at 17:29
  • I see but exactly what focus are you referring to? Your frame dosen't receive user input after adding the canvas? Do you have java3d 'Behaviour' classes which receive input that stops working after the canvas in on screen(That's an issue with viewing/influencing bounds)? Which one? – Sync it Jun 27 '20 at 05:06
  • When I use behaviour classes I also face your same issue in that case I usually generate an user event with one of the Robot classes methods https://docs.oracle.com/javase/7/docs/api/java/awt/Robot.html a few seconds after the canvas shows up wih an timer – Sync it Jun 27 '20 at 05:10
  • I'm just referring to the window focus. The title bar turns grey for a second and then turns back to the color it's supposed to be. After that, the program behaves normally. I just tried using a Robot like you suggested, and it actually did solve the problem, but I was looking for a cleaner solution that doesn't involve taking over the mouse. – Charlie Armstrong Jun 27 '20 at 19:19
  • You could use an focus listener and an timer on your tittle bar or just accept the few seconds of grey if you're ok with it either way glad I could help :) – Sync it Jun 28 '20 at 03:46