Swing JLayeredPane не рисует все элементы при вызове paintAll

У меня есть JLayeredPane, который содержит Canvas на уровне 0 (заполняется желтым цветом в методе Paint) и JPanel на уровне 1 (устанавливает его фон красным в конструкторе).

При нажатии кнопки в методе paintAllToImage я создаю BufferedImage и рисую JLayerePane на этом изображении с помощью component.paintAll(image.getGraphics());
Проблема в том, что на этом изображении окрашено только Canvas (оно полностью заполнено желтым цветом). Пожалуйста, смотрите прикрепленное изображение.

введите здесь описание изображения

(часть над кнопкой — это то, что на самом деле нарисовано, часть под кнопкой — это изображение, созданное из JLayeredPane)

Вот полный код:

public class LayeredPaneEx extends JPanel {

    private JLayeredPane layeredPane;
    public LayeredPaneEx()    {
        setLayout(new BoxLayout(this, BoxLayout.PAGE_AXIS));

        layeredPane = new JLayeredPane();
        layeredPane.setPreferredSize(new Dimension(300, 310));
        layeredPane.setLayout(null);

        Canvas panel = new CustomCanvas();
        panel.setSize(300, 400);
        CustomPanel customPanel = new CustomPanel();
        layeredPane.add(panel, new Integer(0));
        layeredPane.add(customPanel, new Integer(1));

        add(layeredPane);

        JButton paintBtn = new JButton("Paint All");
        paintBtn.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                ImageIcon icon = new ImageIcon(paintAllToImage(layeredPane));
                JLabel imageLabel = new JLabel(icon);
                add(imageLabel);
            }
        });
        add(paintBtn);

        JLabel paintLabel = new JLabel();
        paintLabel.setPreferredSize(new Dimension(300, 300));
    }

    private class CustomCanvas extends Canvas  {
        @Override
        public void paint(Graphics g) {
            g.setColor(Color.YELLOW);
            g.fillRect(0, 0, getWidth(), getHeight());
        }
    }

    private class CustomPanel extends JPanel {
        CustomPanel() {
            setSize(100, 100);
            setBackground(Color.RED);
        }
    }

    private static void createAndShowGUI() {
        JFrame frame = new JFrame("LayeredPaneDemo");
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

        JComponent newContentPane = new LayeredPaneEx();
        newContentPane.setOpaque(true);
        frame.setContentPane(newContentPane);

        frame.pack();
        frame.setVisible(true);
    }

    public static void main(String[] args) {
        javax.swing.SwingUtilities.invokeLater(new Runnable() {
            public void run() {
                createAndShowGUI();
            }
        });
    }

    public static BufferedImage paintAllToImage(Component component) {
        BufferedImage image;
        image = new BufferedImage(
                component.getWidth(),
                component.getHeight(),
                BufferedImage.TYPE_INT_RGB
        );
        component.paintAll(image.getGraphics());
        return image;
    }
}

person Oleh Toder    schedule 16.12.2015    source источник
comment
Остерегайтесь смешивать тяжелые вещества (Canvas) с легкими компонентами. Поскольку компоненты AWT не имеют концепции z-упорядочения, вы обнаружите, что это вызовет у вас бесконечные проблемы. Кроме того, вы должны предпочесть printAll paintAll   -  person MadProgrammer    schedule 16.12.2015


Ответы (1)


Изменить: новый ответ

Адаптируя этот ответ на переполнение стека, кажется возможным поместить легкий CustomPanel в тяжелый Panel и поставить их сверху другого тяжеловеса Panel. Вот скриншот:

Скриншот, показывающий легкий вес поверх тяжелого

А вот и программа:

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

/**
 * Adapted from https://stackoverflow.com/a/1428298/1694043.
 */
public class GuiTest {
    public static void main(String[] arguments) {
        new GuiTest();
    }

    public GuiTest() {
        JFrame frame = new JFrame("Heavyweight versus lightweight");
        frame.setSize(500, 500);
        frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
        addPanelsToFrame(frame);

        SwingUtilities.invokeLater(() -> frame.setVisible(true));
    }

    private void addPanelsToFrame(JFrame frame) {
        CustomCanvas customCanvas = new CustomCanvas(300, 400, Color.YELLOW);
        Panel awtPanel1 = new Panel(new BorderLayout());
        awtPanel1.setSize(300, 400);
        awtPanel1.add(customCanvas, BorderLayout.CENTER);
        frame.getLayeredPane().add(awtPanel1, JLayeredPane.DEFAULT_LAYER);

        CustomPanel customPanel = new CustomPanel(100, 100, Color.RED);
        Panel awtPanel2 = new Panel(new BorderLayout());
        awtPanel2.setSize(100, 100);
        awtPanel2.add(customPanel, BorderLayout.CENTER);
        frame.getLayeredPane().add(awtPanel2, JLayeredPane.PALETTE_LAYER);
    }


    private class CustomCanvas extends Canvas {
        private Color backgroundColor;

        public CustomCanvas(int width, int height, Color backgroundColor) {
            setSize(width, height);
            this.backgroundColor = backgroundColor;
        }

        @Override
        public void paint(Graphics g) {
            g.setColor(backgroundColor);
            g.fillRect(0, 0, getWidth(), getHeight());
        }
    }


    private class CustomPanel extends JPanel {
        public CustomPanel(int width, int height, Color backgroundColor) {
            setSize(width, height);
            setBackground(backgroundColor);
        }
    }
}

Старый ответ

Воспользовавшись советом MadProgrammer, чтобы избежать класса Canvas, вы можете использовать два экземпляра класса CustomPanel. Этот класс расширяет облегченный JPanel на основе Swing вместо тяжеловесного Canvas на основе AWT. См. https://stackoverflow.com/a/13769255/1694043 для получения дополнительной информации о облегченных и тяжелых компонентах графического интерфейса Java.

Вот скриншот:

Скриншот измененной программы

Вот измененный код:

import java.awt.*;
import java.awt.event.*;
import java.awt.image.*;
import javax.swing.*;

public class LayeredPaneEx extends JPanel {
    private JLayeredPane layeredPane;

    public LayeredPaneEx()    {
        setLayout(new BoxLayout(this, BoxLayout.PAGE_AXIS));

        layeredPane = new JLayeredPane();
        layeredPane.setPreferredSize(new Dimension(300, 310));
        layeredPane.setLayout(null);

        //Canvas panel = new CustomCanvas();
        //panel.setSize(300, 400);
        //CustomPanel customPanel = new CustomPanel();
        //layeredPane.add(panel, new Integer(0));
        //layeredPane.add(customPanel, new Integer(1));
        layeredPane.add(new CustomPanel(300, 400, Color.YELLOW), new Integer(0));
        layeredPane.add(new CustomPanel(100, 100, Color.RED), new Integer(1));

        add(layeredPane);

        JButton paintBtn = new JButton("Paint All");
        paintBtn.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                ImageIcon icon = new ImageIcon(paintAllToImage(layeredPane));
                JLabel imageLabel = new JLabel(icon);
                add(imageLabel);
            }
        });
        add(paintBtn);

        JLabel paintLabel = new JLabel();
        paintLabel.setPreferredSize(new Dimension(300, 300));
    }

//    private class CustomCanvas extends Canvas  {
//        @Override
//        public void paint(Graphics g) {
//            g.setColor(Color.YELLOW);
//            g.fillRect(0, 0, getWidth(), getHeight());
//        }
//    }

    private class CustomPanel extends JPanel {
        public CustomPanel(int width, int height, Color backgroundColor) {
            setSize(width, height);
            setBackground(backgroundColor);
        }
    }

    private static void createAndShowGUI() {
        JFrame frame = new JFrame("LayeredPaneDemo");
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

        JComponent newContentPane = new LayeredPaneEx();
        newContentPane.setOpaque(true);
        frame.setContentPane(newContentPane);

        frame.pack();
        frame.setVisible(true);
    }

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

    public BufferedImage paintAllToImage(Component component) {
        BufferedImage image = new BufferedImage(
                component.getWidth(),
                component.getHeight(),
                BufferedImage.TYPE_INT_RGB
        );
        component.paintAll(image.getGraphics());

        return image;
    }
}
person Freek de Bruijn    schedule 16.12.2015
comment
Спасибо, это определенно сработает, но что, если мне придется рисовать JPanel поверх Canvas? Как говорится в статье Oracle Mixing Heavyweight and Lightweight Components, на Java 8 (которую я использую) не должно быть проблем. Хотя в этой ситуации Canvas рисуется поверх JPanel. - person Oleh Toder; 16.12.2015
comment
Мое предложение состояло бы в том, чтобы использовать две легкие панели. Тогда не должно иметь значения, какая панель находится сверху. На мой взгляд, нет смысла использовать тяжеловесный компонент в этой программе. Если вы хотите рисовать, вы можете переопределить метод paintComponent панели. - person Freek de Bruijn; 16.12.2015
comment
Хороший учебник по пользовательскому рисованию в Swing: Выполнение пользовательского рисования . - person Freek de Bruijn; 16.12.2015
comment
К сожалению, эта программа представляет собой всего лишь песочницу реальной задачи, в которой Canvas и JPanel перекрываются в одном Container. - person Oleh Toder; 16.12.2015
comment
Я так понимаю, у вас наследственная ситуация. Насколько сложно было бы заменить компонент Canvas компонентом JPanel? - person Freek de Bruijn; 16.12.2015
comment
Заменить не вариант. Вероятно, я мог бы попытаться обернуть Canvas в JPanel, чтобы панель отрисовывала холст на paint() и передавала все события на холст. Хотя я все еще надеюсь на более чистое решение. - person Oleh Toder; 16.12.2015
comment
Не могли бы вы использовать метод paintAllToImage для компонента Canvas и добавить это изображение на панель в JLayeredPane? Таким образом, у вас есть тяжеловесный компонент в качестве изображения в легковесных компонентах. - person Freek de Bruijn; 16.12.2015