4

Current state: I have a JPanel object which contains complex components(3D-canvas written by myself).

Problem: There is two screen devices now. I want to use one for control, another just for display, just like PowerPoint. How can I display this JPanel on another screen efficiently(static view is enough, but I want it to reflect the change on control screen?

What I have tried: I have tried to draw the static picture to another JPanel every 200ms.

            Graphics2D g2 = (Graphics2D) contentPanel.getGraphics();
            g2.translate(x, y);
            g2.scale(scale, scale); 
            displayPanel.paintAll(g2);

Notes: contentPanel is in a JFrame of display screen, displayerPanel is the panel I want to copy

But I get a problem that the display screen flicker so seriously that I can not accept this...Is it the problem of my CPU or graphics card? Or is these any efficient method I can use? Please help, thanks so much!

And here is the MCVE (sorry, this is my first time asking question in stackoverflow, but I am touched by your helps! Thanks again!)

import javax.swing.*;
import java.awt.*;
import java.util.*;
import java.util.Timer;

public class DisplayWindow {

private static JFrame frame = new JFrame();
private static JPanel contentPanel = new JPanel();
private static JPanel displayPanel = null;
private static Timer timerDisplay = null;


static {
    frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    frame.setBounds(50, 50, 1366, 768);
    frame.getContentPane().add(contentPanel);
    frame.setVisible(true);
    if (timerDisplay == null) {
        timerDisplay = new java.util.Timer();
        timerDisplay.schedule(new DisplayAtFixedRate(), 1000, 250);
    }
}

public static void display(JPanel panel) {
    displayPanel = panel;
}

private static class DisplayAtFixedRate extends TimerTask {
    @Override
    public void run() {
        if (displayPanel != null && displayPanel.getWidth() != 0) {
            double windowWidth = frame.getWidth();
            double windowHeight = frame.getHeight();
            double panelWidth = displayPanel.getWidth();
            double panelHeight = displayPanel.getHeight();
            double scale = Math.min(windowWidth / panelWidth, windowHeight / panelHeight);
            int x = (int) (windowWidth - panelWidth * scale) / 2;
            int y = (int) (windowHeight - panelHeight * scale) / 2;

            Graphics2D g2 = (Graphics2D) contentPanel.getGraphics();
            g2.translate(x, y);
            g2.scale(scale, scale);
            displayPanel.paintAll(g2);
        }
    }
}

public static void main(String[] args) {
    JFrame controlFrame = new JFrame();
    controlFrame.setBounds(50, 50, 1366, 768);
    JPanel controlPanel = new JPanel();
    controlFrame.getContentPane().add(controlPanel, BorderLayout.CENTER);
    controlPanel.add(new JLabel("Hello Stackoverflow!"));
    controlFrame.setVisible(true);
    DisplayWindow.display(controlPanel);
}
}
Bode
  • 61
  • 6
  • Do you need to translate and scale every time the timer is triggered? May be calling repaint on the panel will result in less flicker as it won't involve resizing etc of component. – Ashwinee K Jha Aug 22 '16 at 06:30
  • 2
    1) For better help sooner, post a [MCVE] or [Short, Self Contained, Correct Example](http://www.sscce.org/). 2) See [The Use of Multiple JFrames, Good/Bad Practice?](http://stackoverflow.com/q/9554636/418556) – Andrew Thompson Aug 22 '16 at 06:38
  • 1
    I have tried to draw the static picture to another JPanel every 200ms. - did you tried by using SwingTImer, because signal from TimerTask doesn't notify an EDT – mKorbel Aug 22 '16 at 06:38
  • `Graphics2D g2 = (Graphics2D) contentPanel.getGraphics();` Nope. This is the wrong way to do custom painting. Go through the [Performing Custom Painting](https://docs.oracle.com/javase/tutorial/uiswing/painting/) lesson of the tutorial to find out how to do it correctly. – Andrew Thompson Aug 22 '16 at 06:41
  • I see your edit, but where is *the MCVE?* – Andrew Thompson Aug 22 '16 at 07:17
  • @AndrewThompson sorry~it's my first time to ask question here, I have re-edited now – Bode Aug 22 '16 at 08:09
  • @AshwineeKJha if I just call repaint(), the latest changes of the displayPanel can not reflect in the contentPanel – Bode Aug 22 '16 at 08:12
  • Are you telling us that if you remove `frame.setUndecorated(true);` that the problem disappears? If not, then *remove that line of code* from the MCVE! Same with *every other* line of code. An MCVE is meant to be the ***Minimal*** code that compiles cleanly & runs to display the problem. And since I made a comment, I'll add.. See [Detection/fix for the hanging close bracket of a code block](http://meta.stackexchange.com/q/251795/155831) for a problem I could no longer be bothered fixing. – Andrew Thompson Aug 22 '16 at 08:14
  • @AndrewThompson Thanks for your advice! That's so embarrassing.....T T... – Bode Aug 22 '16 at 08:26

2 Answers2

2

Here is an example for you:

import java.awt.BorderLayout;
import java.awt.Component;
import java.awt.Image;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.image.BufferedImage;

import javax.swing.ImageIcon;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JScrollPane;
import javax.swing.JTextArea;
import javax.swing.JTree;
import javax.swing.SwingUtilities;
import javax.swing.Timer;
import javax.swing.WindowConstants;

public class PaintImage {

    public static void main(String[] args) {
        SwingUtilities.invokeLater(new Runnable() {

            @Override
            public void run() {
                startUI();
            }
        });
    }

    private static void startUI() {
        final JFrame mainFrm = new JFrame("Main");
        final JFrame paintFrame = new JFrame("Copy");
        mainFrm.add(new JScrollPane(new JTree()), BorderLayout.WEST);
        mainFrm.add(new JScrollPane(new JTextArea("Write your text here...")), BorderLayout.CENTER);
        mainFrm.pack();
        mainFrm.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
        mainFrm.setLocationRelativeTo(null);

        final JLabel label = new JLabel("");
        paintFrame.add(label);
        paintFrame.setSize(mainFrm.getSize());
        paintFrame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);

        mainFrm.setVisible(true);
        paintFrame.setVisible(true);

        final Timer t = new Timer(200, new ActionListener() {

            @Override
            public void actionPerformed(ActionEvent e) {
                final Image img = getScreenShot(mainFrm.getContentPane());
                label.setIcon(new ImageIcon(img));
            }
        });
        t.start();
    }

    public static BufferedImage getScreenShot(Component component) {

        final BufferedImage image = new BufferedImage(
                component.getWidth(),
                component.getHeight(),
                BufferedImage.TYPE_INT_RGB
                );
        // call the Component's paint method, using
        // the Graphics object of the image.
        component.paint( image.getGraphics() ); // alternately use .printAll(..)
        return image;
    }
}

Origin of method getScreenShot is here

Community
  • 1
  • 1
Sergiy Medvynskyy
  • 11,160
  • 1
  • 32
  • 48
  • Amazing! It perform much better using your example! I think the SwingTimer is the key problem. Thanks so much! – Bode Aug 22 '16 at 08:56
0

The painting can be done by passing the displayPanel to draw itself.

class DuplicatePanel extends JPanel {
    private final JPanel displayPanel;

    DuplicatePanel(JPanel displayPanel) {
        this.displayPanel = displayPanel;
    }

    @Override
    public void paintComponent(Graphics g) {
        displayPanel.paintComponent(g);
    }
}

To trigger the repaints, like repaint(50L) either use a SwingTimer or your own manual repaints.

Joop Eggen
  • 107,315
  • 7
  • 83
  • 138