How do I add a Canvas3D to a JFrame without losing window focus?

208 Views Asked by At

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

There are 1 best solutions below

6
Sync it On

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 :)