JAVA - JTextArea 在翻译其父图形后有一个假的视觉位置

JAVA - JTextArea has a fake visual location after translate its parent graphics

我正在尝试创建一个 canvas(JPanel),上面可以有文本框 (JTextArea),我还可以拖动 canvas 或周围的文本框。现在拖动文本框功能可以了。我可以使用 setBounds() 来设置 JTextArea.

的位置

但是,当涉及到拖动 canvas 时,问题就来了。我使用 Graphics2D.transform 来移动 canvas。 canvas移动后,会出现可视文字区和隐藏的真实文字区。我已重置 JTextArea 的边界以适应 canvas 的移动偏移,但它不起作用。

下面是一个例子,

import javax.swing.*;
import java.awt.*;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.MouseMotionAdapter;
import java.awt.geom.AffineTransform;

/**
 * @author Zhijie Lan<p>
 * create date: 2020/11/25<p>
 **/
public class TranslateJPanel extends JFrame
{
    public TranslateJPanel() throws HeadlessException
    {
        Canvas canvas = new Canvas();
        add(canvas,BorderLayout.CENTER);

        this.setSize(1280, 720);     
        this.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);   
        this.setLocationRelativeTo(null);                               
        this.setVisible(true);
    }

    public static void main(String[] args)
    {
        new TranslateJPanel();
    }
}

class Canvas extends JPanel
{
    private final JTextArea jTextArea;
    private int offset = 0;

    public Canvas()
    {
        setLayout(null);

        jTextArea = new JTextArea("hello!!!!!!!!!!!");

        jTextArea.setBounds(0,0,100,100);
        jTextArea.setBorder(BorderFactory.createLineBorder(Color.BLACK));
        add(jTextArea);

        this.addMouseMotionListener(new MouseMotionAdapter()
        {
            @Override
            public void mouseDragged(MouseEvent e)
            {
                super.mouseDragged(e);
                offset++;
                revalidate();
                repaint();
            }
        });

        this.addMouseListener(new MouseAdapter()
        {
            @Override
            public void mouseClicked(MouseEvent e)
            {
                super.mouseClicked(e);
                System.out.println("M: "+ e.getX()+"  "+e.getY());
            }
        });

    }

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

        Graphics2D g2 = (Graphics2D) g;

        AffineTransform at = new AffineTransform();
        at.translate(offset, offset);
        g2.transform(at);

        jTextArea.setBounds(offset,offset,100,100);

        System.out.println(offset);
        System.out.println(jTextArea.getX()+"      "+jTextArea.getY());
        System.out.println("***************************");

    }
}

对于可拖动的文本区域,我建议如下:

  1. 不要覆盖 paintComponent 或任何绘画方法,不要使用 AffineTransforms 或任何移动像素的东西。而是自己移动组件。
  2. 这可能是我可能使用空布局的少数几次之一。请注意,有一些布局管理器可以代替它使用(我认为 Camickr 已经为此编写了代码并且可以为您提供 link)。
  3. 永远不要设置 JTextArea 的大小,甚至不要设置它的首选大小。
  4. 改为设置列和行属性。
  5. 将 JTextArea 添加到 JScrollPane(实际上是它的视口)
  6. 向移动它的 JScrollPane 添加 MouseListener 和 MouseMotionListener
  7. 将 MouseListener 和 MouseMotionListener 添加到 JTextArea,将鼠标操作转发到包含它的 JScrollPane,但一定要更改 MouseEvent 的源 属性 以反映 JScrollPane,而不是 JTextArea。

例如:

import java.awt.BorderLayout;
import java.awt.Component;
import java.awt.Container;
import java.awt.Dimension;
import java.awt.Point;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import javax.swing.*;

@SuppressWarnings("serial")
public class DraggingBoxesPanel extends JPanel {
    public DraggingBoxesPanel() {
        int w = 900;
        int h = 600;
        setPreferredSize(new Dimension(w, h));

        setLayout(null); // I usually avoid doing this
        add(createTextBox()); // add first "box" to this JPanel
    }

    // exposed method to allow adding a new "box" to GUI
    public void newBox() {
        add(createTextBox());
        revalidate(); // The JScrollPane requires this
        repaint();
    }

    private JComponent createTextBox() {
        int rows = 10; // jtextarea property
        int cols = 20; // jtextarea property

        // create text area and scroll pane
        JTextArea textArea = new JTextArea(rows, cols);
        JScrollPane scrollPane = new JScrollPane(textArea);

        // let scrollpane size itself
        scrollPane.setSize(scrollPane.getPreferredSize());

        // scrollpane's mouse listeners
        MyMouse myMouse = new MyMouse();
        scrollPane.addMouseListener(myMouse);
        scrollPane.addMouseMotionListener(myMouse);

        // mouse adapter that forwards mouse actions in
        // text area into its containing scrollpane
        MouseAdapter textAreaMouseAdapter = new MouseAdapter() {
            @Override
            public void mouseReleased(MouseEvent e) {
                // make sure source is correct
                e.setSource(scrollPane);
                myMouse.mouseReleased(e);
            }

            @Override
            public void mousePressed(MouseEvent e) {
                e.setSource(scrollPane);
                myMouse.mousePressed(e);
            }

            @Override
            public void mouseDragged(MouseEvent e) {
                e.setSource(scrollPane);
                myMouse.mouseDragged(e);
            }
        };

        textArea.addMouseListener(textAreaMouseAdapter);
        textArea.addMouseMotionListener(textAreaMouseAdapter);

        return scrollPane;
    }

    // code that allows a component to be dragged
    private class MyMouse extends MouseAdapter {
        private Point mousePt1;
        private Point compPt1;

        @Override
        public void mousePressed(MouseEvent e) {
            mousePt1 = e.getLocationOnScreen();
            Component comp = (Component) e.getSource();
            compPt1 = comp.getLocation();
            
            Container container = comp.getParent();
            container.setComponentZOrder(comp, 0);
            container.revalidate();
            container.repaint();
        }

        @Override
        public void mouseDragged(MouseEvent e) {
            if (mousePt1 == null) {
                return;
            }
            dragComponent(e);
        }

        @Override
        public void mouseReleased(MouseEvent e) {
            dragComponent(e);
            mousePt1 = null;
        }

        private void dragComponent(MouseEvent e) {
            Point mousePt2 = e.getLocationOnScreen();
            int x = compPt1.x + mousePt2.x - mousePt1.x;
            int y = compPt1.y + mousePt2.y - mousePt1.y;
            Component comp = (Component) e.getSource();
            comp.setLocation(x, y);
            
            Container container = comp.getParent();
            container.revalidate();
            container.repaint();
        }
    }

    public static void main(String[] args) {
        SwingUtilities.invokeLater(() -> createAndShowGui());
    }

    private static void createAndShowGui() {
        final DraggingBoxesPanel mainPanel = new DraggingBoxesPanel();
        JButton newBoxBtn = new JButton("New Box");
        newBoxBtn.addActionListener(e -> mainPanel.newBox());
        JPanel bottomPanel = new JPanel();
        bottomPanel.add(newBoxBtn);

        JFrame frame = new JFrame("DraggingBoxesPanel");
        frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
        frame.add(mainPanel);
        frame.add(bottomPanel, BorderLayout.PAGE_END);
        frame.pack();
        frame.setLocationRelativeTo(null);
        frame.setVisible(true);
    }
}

我找到了解决这个问题的方法。

void paintComponent(Graphics g)中, 而不是

Graphics2D g2 = (Graphics2D) g;

使用

Graphics2D g2 = (Graphics2D) g.create();

解决问题,但不知道为什么。

我是JAVA的新人,所以我不能深入理解大家的建议中的所有警告,但是还是非常感谢!