调整 JPanel 的大小以准备打印,而无需将其从原始位置移开

发布于 2024-07-30 09:10:24 字数 505 浏览 2 评论 0原文

在我的程序中,我经常需要打印各种 JComponent(通常是 JPanel),并且我喜欢它们是整页的。 我现在这样做的方法是使用以下代码:

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

但这通常会拉伸或以其他方式使我尝试打印的内容变形,而且我更愿意做一些智能调整大小的事情,也许是以下功能版本:

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

或者说添加将组件放入新的 JFrame 中,然后设置框架大小(问题是组件不能同时存在于两个位置)。 我不在乎调整大小是否会使 GUI 的其余部分看起来很糟糕,只要它可以轻松重置即可。

有什么办法可以做到这一点吗?

In my program I frequently need to print various JComponents (generally JPanels) and I like them to be full-page. The way I do it now is by using the following code:

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

but this often stretches or otherwise deforms whatever I am trying to print, and I would much prefer to do something that re-sized intelligently, perhaps a functional version of:

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

or say adding the component into a new JFrame and then setting the frame size (problem is components can't exist in two place at once). I wouldn't care if the resizing would make the rest of the GUI look terrible, as long as it is something that can easily be reset.

Is there any way to do this?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

柠北森屋 2024-08-06 09:10:25

我认为您正在寻找的解决方案是构建一个包含所需内容的新 JPanel 并打印副本。 如果您使用 CellRendererPane 执行此操作,您可以获得听起来像您正在寻找的精确大小调整行为。

如果您的 JComponent 编写得相当好,那么新建一个新的 JComponent 并将其模型设置为与原始模型相同应该不是问题。

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

}

I think the solution you're looking for would be to construct a new JPanel that contains the desired contents and to print the copy instead. If you do so using the CellRendererPane you can get the exact resizing behaviour it sounds like you're looking for.

If your JComponents are reasonably well written then it shouldn't be a problem to new up a fresh one and to set its model to be the same as the original's.

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

Here's a complete working example. The createPanel() method should create whatever component it is that you want rendered. A real example should be sure to use the same model rather than recreating a new model for a throwaway component.

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

}
姜生凉生 2024-08-06 09:10:25

我认为你面临的问题与规模经营无关。 这是你的逻辑不正确。 在大多数情况下,您的 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);

之后,您可以根据新尺寸将图像转换为适当的坐标。
希望能帮助到你...

I think the problem you're facing has nothing to do with scale operation. It is your logic that is incorrect. In most cases your x and y scales will be different, since the scale of Imageable and and your component's scale will not be the same. This is the reason your component is distorted after the scale operation. You have to do something like:

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

After that you can translate your image to the appropriate coordinates according to it's new size.
Hope it helps...

Spring初心 2024-08-06 09:10:25

我会将 JPanel 实现的 paintComponent() 方法中的绘图代码重构为该面板中的公共/包保护方法,该方法可以绘制到任意 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);
    }
    }
}

这允许 GUI 外部的对象挂接到其绘图算法中。 我发现的一个缺点是,您将在任何想要打印面板的对象与面板的实际实现之间创建依赖关系。 但它仍然比动态调整面板大小要好(我已经尝试过,似乎有一些问题 - 我认为 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() 完成,但使用消息更透明),然后打印框架并将其大小调整回原始形状。 .

--弗拉维·西普西甘

I would refactor the drawing code out of the paintComponent() method of your implementation of JPanel to a public/package protected method in that panel which can draw to an arbitrary Graphics object, of any width/height (presumably that the drawing code is general enough).

This example has a frame, containing a panel, which has some drawing logic (a big X, the size of the panel). The main method of this example shows you a way to get an image and write it to a file, even if the image size is different than the panel's size.


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

This allows objects external to the GUI to hook into its drawing algorithm. One drawback I see is that you will create a dependency between any object wanting to print the panel to the actual implementation of the panel. But it's still better than resizing the panel on the fly (I have tried that and seems to have some issues - I think it takes some time for the setSize() change to propagate.

EDIT:

In response to the comments, I have provided a modified version of the code snippet above. It is probably not the best way to do it, and not very user friendly (so I wouldn't use it in an end-user application), but it does resize everything in the frame according to the rules of the layout manager.


/* 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

    }

}

The code basically resizes the frame, shows the user a confirm message so the thread can be blocked until the repaint is done (could be done by Thread.sleep(), but it's more transparent using a message). Then it prints the frame and resizes it back to its original shape. A bit hacky, but it works.

-- Flaviu Cipcigan

混吃等死 2024-08-06 09:10:25

我想你回答了你自己的问题。 每个组件都有一个方法:

setSize(int width, int height);

I think you answered your own question. Each Component has a method :

setSize(int width, int height);
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文