【问题标题】:JButton on top of Animated Timed paintComponent动画定时paintComponent之上的JButton
【发布时间】:2019-02-06 22:01:44
【问题描述】:

我有一个 BVH 播放器,它有 9 个视图,它们在计时器上运行以对其进行动画处理,我试图在每个面板的顶部放置控件以单独控制变换操作。

这些按钮正在我的paintComponent 后面呈现,但有时可以单击。我还有一个 MouseListener 来调整骨架的变换,有时它可以工作。我可以发布听众,但我认为这并不重要。

我尝试了 graphics.dispose 和 graphics2d.dispose 的各种组合,并尝试更改 super.paintComponent(g) 的位置,结果很奇怪,但效果不佳。

下面是显示问题的视频。如果您仔细观察,当您将鼠标悬停在它们应该在的位置时,您会看到弹出的按钮。您还会看到,当我 g.dispose() 时,它在两个地方的按钮变得陌生,但只在一个地方单击(这不是它们最明显的地方)。 https://youtu.be/CyFpUlbFI1U

这是我的源代码:

public SkeletonPlayer(int camera, double scale, int rotLeftRight, int rotUpDown, double translateLeftRight, double translateUpDown) {
    this.camera = camera;
    this.scale = scale;
    this.rotLeftRight = rotLeftRight;
    this.rotUpDown = rotUpDown;
    this.translateLeftRight = translateLeftRight;
    this.translateUpDown = translateUpDown;
    panel = new JPanel() {
        @Override
        protected void paintComponent(Graphics g) {
            super.paintComponent(g);
            Graphics2D g2d = (Graphics2D) g;
            g2d.translate(getWidth() / 2, getHeight() / 2);

            AffineTransform at = AffineTransform.getTranslateInstance(0, 0);
            at.rotate(Math.toRadians(90), Math.toRadians(90), Math.toRadians(90));

            for (int n = 0; n < MainFrame.skeleton[camera].getNodes().size(); n++) {
                Node node = MainFrame.skeleton[camera].getNodes().get(n);
                int x1 = (int) (scale * node.getPosition().getX());
                int y1 = (int) (-scale * node.getPosition().getY());
                g2d.setColor(Color.RED);
                g2d.fillOval((int) (x1 - 2), (int) (y1 - 2), 4, 4);

                g2d.setColor(Color.BLACK);
                //g2d.drawString(node.getName(), x1 + 10, y1);

                for (Node child : node.getChildrens()) {
                    int x2 = (int) (scale * child.getPosition().getX());
                    int y2 = (int) (-scale * child.getPosition().getY());
                    g2d.drawLine(x1, y1, x2, y2);
                }
            }

            int x1 = (int) (scale * groundPlane.rootNode.getPosition().getX());
            int y1 = (int) (-scale * groundPlane.rootNode.getPosition().getY());
            g2d.setColor(Color.BLACK);
            g2d.fillOval((int) (x1 - 2), (int) (y1 - 2), 4, 4);

            for (int n = 1; n < groundPlane.nodes.size(); n++) {
                Node node = groundPlane.nodes.get(n);

                int x2 = (int) (scale * node.getPosition().getX());
                int y2 = (int) (-scale * node.getPosition().getY());
                g2d.setColor(Color.BLACK);
                g2d.fillOval((int) (x2 - 2), (int) (y2 - 2), 4, 4);

                g2d.setColor(Color.GREEN);

                g2d.drawLine(x1, y1, x2, y2);
                //System.out.println("line:" + x1 + "," + y1 + " " + x2 + "," + y2);
            }
        }
    };
    panel.addMouseListener(this);
    panel.addMouseMotionListener(this);
    panel.addMouseWheelListener(this);
    MigLayout layout = new MigLayout(
            "insets 10, gap 10, wrap", // Layout Constraints
            "[fill,grow][fill,grow][fill,grow]", // Column constraints with default align
            "[fill,grow][fill,grow][fill,grow]");
    panel.setLayout(layout);
    panel.add(new JButton("here"));
    panel.add(new JButton("here"));
    panel.add(new JButton("here"));
    panel.add(new JButton("here"));
    panel.add(new JButton("here"));
    panel.add(new JButton("here"));
    panel.add(new JButton("here"));
    panel.add(new JButton("here"));
    panel.add(new JButton("here"));
}

这是计时器:

new Timer().scheduleAtFixedRate(new TimerTask() {

        @Override
        public void run() {
            update();
        }
    }, 0, (long) Math.round(motion.getFrameTime() * 1000));

private static void update() {
    for (int i = 0; i < NUM_SKELETON_PLAYERS; i++) {
        MainFrame.skeleton[i].setPose((int) frameIndex, i);
        skeletonPlayers[i].groundPlane.setPose(i);
        skeletonPlayers[i].panel.repaint();
    }
    //skeleton.setPose(null);
    frameIndex += 1;
    if (frameIndex > motion.getFrameSize() - 1) {
        frameIndex = 0;
    }
}

【问题讨论】:

  • 首先,Swing 不是线程安全的,因此您不应该从事件调度线程之外对其进行更新。其次,您会看到Graphics 上下文的转换(平移和旋转),因为Graphics 是一个共享上下文,您对其所做的任何更改都会影响之后绘制的任何其他组件。最好使用Graphics2D g2d = (Graphics2D) g.create();(完成后使用g2d.dispose()
  • 我确实使用 Graphics2D... 请参阅发布的代码。我不知道你在调度线程之外更新是什么意思,你指的是定时器吗?我应该使用其他一些触发机制来计时绘图帧吗?
  • “我确实使用 Graphics2D... 请参考发布的代码” 但是,正如我所说,您正在更改上下文的状态,然后用于之后绘制所有其他组件,应用相同的转换,这将搞砸其他组件的渲染方式,这就是为什么我建议使用Graphics#create 来“复制”状态信息,所以你不要'不要搞砸原件。 “我应该使用其他触发机制来计时绘制帧吗?” - Swing Timer 将是我的第一个建议
  • 您还应该阅读文档,因为at.rotate(Math.toRadians(90), Math.toRadians(90), Math.toRadians(90)); 没有意义

标签: java swing jpanel paintcomponent jcomponent


【解决方案1】:

如果您仔细观察,您会看到当您将鼠标悬停在它们应该在的位置时弹出按钮。

这意味着您遇到了 ZOorder 问题。 ZOrder 是组件在 z 轴上相互堆叠时绘制的顺序。

Swing 以与组件添加到面板的相反顺序绘制组件,因此最后添加的组件首先被绘制。所以听起来你有多个面板堆叠在一起,你添加的最后一个面板包含按钮,但它首先被绘制,所以所有其他面板都在它上面绘制。

当鼠标移动到按钮上时,按钮需要重新绘制自身以显示按钮的滚动效果,因此按钮会暂时绘制在顶部,直到重新建立正确的 ZOrder 时再次重新绘制父面板。

我对您的程序结构了解得不够多,但我会质疑为什么您将一堆面板堆叠在一起。您只需要一个带有自定义绘画的面板,然后将按钮放在此面板上。

【讨论】:

  • 这是一个在 JFrame 中重复 9 次的面板。在 MIGLayout 的每个区域中只有一个 JPanel,我向其中添加了一个按钮并覆盖了paintComponent。
  • @HaskellMcRavin,您没有按照您的描述进行操作,否则面板上会显示按钮。在面板上显示按钮没有技巧。或者您可能没有正确使用布局管理器。无论如何,发布一个正确的minimal reproducible example 来证明问题。并且不要使用 MigLayout,因为它不是标准的 JDK 类。因此,首先让您使用一个带有自定义绘画和添加到面板的按钮的面板进行编程。然后您尝试添加第二个面板。当它停止工作时,你就会知道你刚刚改变了什么。我们无法提供帮助,因为我们不知道您当前代码的上下文。
  • 我正在做我描述的事情。忽略布局,因为它发生在单个实例中。说不使用 MIGLayout 因为它不标准是可笑的。这是演示问题的相关代码。我已经重新排序了要首先添加的按钮,这没有任何区别。
  • @HaskellMcRavin “这是演示问题的相关代码” - 但是我们可以运行它吗?我们可以复制它吗?不,这不是Minimal, Complete, and Verifiable example - 请花时间阅读并理解所询问的内容
  • @HaskellMcRavin, This is the pertinent code which demonstrates the problem. - 在问题解决之前,您不知道“相关”代码是什么。 Saying not to use MIGLayout because it's not standard is laughable. - 我说过不要将它用于您的“MCVE”。我不使用那个库,所以我无法编译和测试你的代码。您已经说过问题是面板上的自定义绘画和显示按钮。所以布局管理器与问题无关。首先获取一个简单的示例,以便您了解基本概念。然后使用你想要的任何布局管理器。
【解决方案2】:

您的主要问题就在这里...

Graphics2D g2d = (Graphics2D) g;
g2d.translate(getWidth() / 2, getHeight() / 2);

AffineTransform at = AffineTransform.getTranslateInstance(0, 0);

传递给组件的Graphics 上下文是共享资源,它会传递给在绘制周期中绘制的所有组件,这意味着当您更改原点和旋转等内容时,它会影响之后绘制的所有组件它。

相反,您应该在应用更改之前创建Graphics 状态的副本,这会使原始状态保持不变

Graphics2D g2d = (Graphics2D) g.create();
g2d.translate(getWidth() / 2, getHeight() / 2);

AffineTransform at = AffineTransform.getTranslateInstance(0, 0);
//...
g2d.dispose();   

你的第二个错误是……

new Timer().scheduleAtFixedRate(new TimerTask() {
    @Override
    public void run() {
        update();
    }
}, 0, (long) Math.round(motion.getFrameTime() * 1000));

Swing 不是线程安全的,更新 UI 或 UI 所依赖的状态可能会产生意外且难以诊断的结果。

在这种情况下,您有两个选择,根据您的需要使用 Swing TimerSwingWorker

查看Concurrency in Swing了解更多详情

另一个问题是at.rotate(Math.toRadians(90), Math.toRadians(90), Math.toRadians(90));。这根本没有意义。

documentation 状态

 public void rotate​(double theta,
                   双锚,
                   双锚)

将此变换与围绕锚点旋转坐标的变换连接。这 操作相当于平移坐标,使得 锚点位于原点 (S1),然后围绕新的 原点(S2),最后翻译使得中间原点 恢复到原始锚点的坐标(S3)。这 操作等价于以下调用序列:

 translate(anchorx, anchory);      // S3: final translation
 rotate(theta);                    // S2: rotate around anchor
 translate(-anchorx, -anchory);    // S1: translate anchor to origin   Rotating by a positive angle theta rotates points on the

正 X 轴朝向正 Y 轴。还要注意讨论 处理上述 90 度旋转。
参数:
theta - 角度 以弧度测量的旋转
anchorx - 的 X 坐标 旋转锚点
anchory - 旋转的 Y 坐标 锚点

这就是说,第二个和第三个参数实际上是旋转发生的点。在你的情况下,at.rotate(Math.toRadians(90), 0, 0); 可能是你真正想要的。

使用Graphics#create

import java.awt.Color;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.GridBagLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.geom.AffineTransform;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.Timer;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;

public class Test {

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

    public Test() {
        EventQueue.invokeLater(new Runnable() {
            @Override
            public void run() {
                try {
                    UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
                } catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
                    ex.printStackTrace();
                }

                JFrame frame = new JFrame("Testing");
                frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
                frame.add(new TestPane());
                frame.pack();
                frame.setLocationRelativeTo(null);
                frame.setVisible(true);
            }
        });
    }

    public class TestPane extends JPanel {

        private double angle;

        public TestPane() {
            setLayout(new GridBagLayout());
            for (int index = 0; index < 5; index++) {
                add(new JButton("Here"));
            }

            Timer timer = new Timer(5, new ActionListener() {
                @Override
                public void actionPerformed(ActionEvent e) {
                    angle += 0.01;
                    repaint();
                }
            });
            timer.start();
        }

        @Override
        public Dimension getPreferredSize() {
            return new Dimension(400, 400);
        }

        protected void paintComponent(Graphics g) {
            super.paintComponent(g);
            Graphics2D g2d = (Graphics2D) g.create();
            //Graphics2D g2d = (Graphics2D) g;
            g2d.translate(getWidth() / 2, getHeight() / 2);

            AffineTransform at = AffineTransform.getTranslateInstance(0, 0);
            at.rotate(Math.toRadians(90), 0, 0);
            g2d.drawRect(-50, -50, 100, 100);
            g2d.setColor(Color.RED);
            g2d.rotate(angle, 0, 0);
            g2d.drawRect(-100, -100, 200, 200);
            g2d.dispose();
        }

    }

}

不使用Graphics#create

哦,我的按钮在哪里,哦,我的按钮在哪里 - 根据您对 Graphics 上下文所做的更改,可能围绕容器的中心点旋转了 90 度,所以现在它们可能不在屏幕上

【讨论】:

  • 我试图创建一个图形的副本,但它并没有改变任何东西。甚至没有使用仿射变换,我没有意识到它的垃圾代码仍然存在。
  • @HaskellMcRavin 没有可运行的例子,没有答案 - 我们已经到了我们能做的尽头☹️
【解决方案3】:

我已移至 JLayeredPane 以获得我正在寻找的效果。我可以单击顶层并拖动底层。

    LayerUI backgroundUI = new WallpaperLayerUI();
    jlayer = new JLayer<JPanel>(panel, backgroundUI);

    LayerUI controlsUI = new LayerUI();
    JPanel controlPanel = new JPanel();
    MigLayout layout = new MigLayout(
            "debug, insets 10, gap 10, wrap", // Layout Constraints
            "[fill,grow][fill,grow][fill,grow]", // Column constraints with default align
            "[fill,grow][fill,grow][fill,grow]");
    controlPanel.setLayout(layout);
    controlPanel.add(new JButton("here"));
    controlPanel.add(new JButton("here"));
    controlPanel.add(new JButton("here"));
    controlPanel.add(new JButton("here"));
    controlPanel.add(new JButton("here"));
    controlPanel.add(new JButton("here"));
    controlPanel.add(new JButton("here"));
    controlPanel.add(new JButton("here"));
    controlPanel.add(new JButton("here"));

    jlayer2 = new JLayer<JPanel>(controlPanel, controlsUI);

    panel.addMouseListener(this);
    panel.addMouseMotionListener(this);
    panel.addMouseWheelListener(this);

    finalPanel = new JLayeredPane();
    finalPanel.setPreferredSize(new Dimension(300, 310));
    //finalPanel.setLayout(null);
    jlayer.setBounds(0, 0, (808-40)/3, (608-40)/3);
    jlayer2.setBounds(0, 0, (808-80)/3, (608-80)/3);
    controlPanel.setBackground(new Color(0,0,0,0));
    finalPanel.add(jlayer, new Integer(0));
    finalPanel.add(jlayer2 ,new Integer(1));

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2017-03-10
    • 2012-07-23
    • 2012-02-27
    • 1970-01-01
    • 1970-01-01
    • 2013-01-20
    • 2017-01-08
    相关资源
    最近更新 更多