Изменение размера JPanel для подготовки к печати без удаления его из исходного положения - PullRequest
2 голосов
/ 03 августа 2009

В моей программе мне часто нужно печатать различные JComponents (обычно JPanels), и мне нравится, чтобы они были полностраничными. Теперь я делаю это с помощью следующего кода:

g2d.scale(pf.getImageableWidth()/componentToPrint.getWidth(), pf.getImageableHeight()/componentToPrint.getHeight());

, но это часто растягивает или иным образом искажает то, что я пытаюсь напечатать, и я бы предпочел сделать что-то разумно измененное, возможно, функциональную версию:

componentToPrint.setSize(pf.ImageableWidth(), pf.ImageableHeight);

или, скажем, добавление компонента в новый JFrame, а затем установка размера кадра (проблема в том, что компоненты не могут существовать одновременно в двух местах). Мне было бы все равно, если изменение размера сделает остальную часть графического интерфейса ужасной, если его можно легко сбросить.

Есть ли способ сделать это?

Ответы [ 4 ]

2 голосов
/ 05 ноября 2009

Я думаю, что решение, которое вы ищете, состоит в том, чтобы создать новую JPanel, которая содержит желаемое содержимое, и вместо этого напечатать копию. Если вы сделаете это с помощью CellRendererPane, вы сможете получить точное поведение при изменении размера, которое звучит так, как вы ищете.

Если ваши JComponents достаточно хорошо написаны, тогда не должно быть проблем с обновлением нового и установкой его модели такой же, как у оригинала.

CellRendererPane cellRendererPane = new CellRendererPane();
// It's important to add the cell renderer pane to something
// you can use the same one for all of your exporting if you like and just
// add it to your main frame's content pane - it won't show up anywhere.
add(cellRendererPane);

JPanel printPanel = createCopy(panel);
cellRendererPane.paintComponent(g, printPanel, null, 0, 0, exportDim.width, exportDim.height, true);

Вот полный рабочий пример. Метод createPanel () должен создавать любой компонент, который вы хотите визуализировать. В реальном примере следует обязательно использовать ту же модель, а не воссоздавать новую модель для одноразового компонента.

public class SCCE {

    public static void main(String[] args) throws Exception {
        UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
        final JFrame f = new JFrame("SCCE");
        f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);


        f.getContentPane().setLayout(new BorderLayout());
        f.getContentPane().add(SCCE.createPanel());

        final CellRendererPane backgroundRenderer = new CellRendererPane();

        // Add the renderer somewhere where it won't be seen
        f.getContentPane().add(backgroundRenderer, BorderLayout.NORTH);
        f.getContentPane().add(createSaveButton(backgroundRenderer), BorderLayout.SOUTH);

        f.pack();
        f.setLocationRelativeTo(null);
        f.setVisible(true);
    }

    // Create your custom component from whatever model here..
    private static final Component createPanel() {
        DefaultListModel model = new DefaultListModel();
        for (int i = 0; i < 10; i++) {
            model.addElement("Item number " + i);
        }
        return new JList(model);
    }

    private static JButton createSaveButton(final CellRendererPane backgroundRenderer) {
        return new JButton(new AbstractAction("Save image to file") {
            @Override
            public void actionPerformed(ActionEvent e) {
                Dimension d = new Dimension(400, 300);

                BufferedImage img = new BufferedImage(d.width, d.height, BufferedImage.TYPE_INT_ARGB);
                Graphics2D g = img.createGraphics();
                backgroundRenderer.paintComponent(g, createPanel(), null, 0, 0, d.width, d.height, true);
                g.dispose();

                try {
                    File output = new File("test.png");
                    System.err.println("Saved to " + output.getAbsolutePath());
                    ImageIO.write(img, "png", output);
                } catch (IOException ex) {
                    ex.printStackTrace();
                }
            }
        });
    }

}
1 голос
/ 03 августа 2009

Я думаю, что проблема, с которой вы сталкиваетесь, не имеет ничего общего с масштабированием. Это ваша логика, которая неверна. В большинстве случаев ваши шкалы x и y будут отличаться, поскольку масштаб Imageable и масштаб вашего компонента не будут одинаковыми. По этой причине ваш компонент искажается после операции масштабирования. Вы должны сделать что-то вроде:

double factorX = pf.getImageableWidth() / component.getWidth();
double factorY = pf.getImageableHeight() / component.getHeight();
double factor = Math.min( factorX, factorY );
g2.scale(factor,factor);

После этого вы можете перевести ваше изображение в соответствующие координаты в соответствии с его новым размером. Надеюсь, это поможет ...

1 голос
/ 03 августа 2009

Я бы реорганизовал код рисования из метода paintComponent() вашей реализации JPanel в метод public / package protected на этой панели, который может рисовать произвольный Graphics объект любой ширины / высоты ( по-видимому, код для рисования достаточно общий).

В этом примере есть рамка с панелью, в которой есть некоторая логика рисования (большой X, размер панели). Основной метод этого примера показывает способ получить изображение и записать его в файл, даже если размер изображения отличается от размера панели.


import java.awt.Color;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.image.BufferedImage;
import java.io.File;

import javax.imageio.ImageIO;
import javax.swing.JFrame;
import javax.swing.JPanel;

public class MockFrame extends JFrame {

    // throws Exception, as just an example (not really advised to do this)
    public static void main(String[] args) throws Exception {
        MockFrame frame = new MockFrame();
        frame.setVisible(true);

        // different sizes from the frame
        int WIDTH = 500;
        int HEIGHT = 500;

        BufferedImage b = new BufferedImage(WIDTH, HEIGHT,
            BufferedImage.TYPE_INT_ARGB);

        Graphics2D g2d = (Graphics2D) b.getGraphics();

        // should set some background, as the panel's background
        // is dealt with by super.paintComponent()
        g2d.setBackground(Color.white);

        frame.getPanel().drawingLogic(b.getGraphics(), WIDTH, HEIGHT);

        ImageIO.write(b, "png", new File("test.png"));

    }

    private MockPanel panel;

    public MockFrame() {
        this.setSize(200, 200);
        this.setDefaultCloseOperation(EXIT_ON_CLOSE);

        panel = new MockPanel();
        getContentPane().add(panel);
    }

    public MockPanel getPanel() {
        return panel;
    }

    private class MockPanel extends JPanel {

    @Override
    protected void paintComponent(Graphics g) {
        super.paintComponent(g);

        drawingLogic(g, getWidth(), getHeight());
    }

    public void drawingLogic(Graphics g, int width, int height) {
        g.setColor(Color.black);

        g.drawLine(0, 0, width, height);
        g.drawLine(0, height, width, 0);
    }
    }
}

Это позволяет объектам, внешним по отношению к графическому интерфейсу, подключаться к его алгоритму рисования. Один недостаток, который я вижу, заключается в том, что вы создадите зависимость между любым объектом, желающим напечатать панель, и фактической реализацией панели. Но это все же лучше, чем изменение размера панели на лету (я пробовал это, и у меня, похоже, есть некоторые проблемы - я думаю, что для распространения изменения setSize() требуется некоторое время.

РЕДАКТИРОВАТЬ:

В ответ на комментарии я предоставил измененную версию фрагмента кода выше. Это, вероятно, не лучший способ сделать это, и не очень удобный для пользователя (так что я не буду использовать его в приложении для конечного пользователя), но он изменяет размер всего в кадре в соответствии с правилами менеджера по расположению.


/* This code snippet describes a way to resize a frame for printing at 
 * a custom size and then resize it back.
 * 
 * Copyright (C)   
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see .
 */

import java.awt.Container;
import java.awt.Graphics2D;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.Insets;
import java.awt.image.BufferedImage;
import java.io.File;

import javax.imageio.ImageIO;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JOptionPane;

public class MockFrame extends JFrame {

    // throws Exception, as just an example (not really advised to do this)
    public static void main(String[] args) throws Exception {
        final MockFrame frame = new MockFrame();
        frame.setVisible(true);

        // different sizes from the frame
        final int WIDTH = 500;
        final int HEIGHT = 700;

        final BufferedImage b = new BufferedImage(WIDTH, HEIGHT,
                BufferedImage.TYPE_INT_ARGB);

        final Graphics2D g2d = (Graphics2D) b.getGraphics();

        final int previousWidth = frame.getWidth();
        final int previousHeight = frame.getHeight();

        frame.setSize(WIDTH, HEIGHT);
        frame.repaint();

        JOptionPane.showMessageDialog(null,
                "Press OK when the window has finished resizing");

        frame.print(g2d);
        frame.setSize(previousWidth, previousHeight);
        ImageIO.write(b, "png", new File("test.png"));

    }

    public MockFrame() {
        this.setSize(200, 200);
        this.setDefaultCloseOperation(EXIT_ON_CLOSE);

        boolean shouldFill = true;
        boolean shouldWeightX = true;

        Container pane = getContentPane();

        // code from
        // http://java.sun.com/docs/books/tutorial/uiswing/layout/gridbag.html
        // just to add some components in the frame... :)
        // left out in here for brevity

    }

}

Код в основном изменяет размер фрейма, показывает пользователю подтверждающее сообщение, поэтому поток может быть заблокирован, пока не будет выполнена перекраска (это может быть сделано с помощью Thread.sleep(), но это более прозрачно с помощью сообщения). Затем он печатает рамку и изменяет ее размер до первоначальной формы. Немного бестолково, но это работает.

-- Flaviu Cipcigan

0 голосов
/ 03 августа 2009

Я думаю, что вы ответили на свой вопрос. У каждого компонента есть метод:

setSize(int width, int height);
...