在玻璃面板上放置组件

11
我有一个JLabel的子类,它是我的GUI组件的一部分。我已经实现了将该组件从一个容器拖放到另一个容器的功能,但没有任何视觉效果。我希望在从一个容器拖动项目时,这个JLabel能跟随光标移动。我想创建一个玻璃板,并在上面绘制它。然而,即使我将组件添加到玻璃板中,将组件设置为可见,将玻璃板设置为可见,并将玻璃板设置为不透明,我仍然看不到组件。我知道组件是有效的,因为我可以将其添加到内容窗格并显示出来。
如何将组件添加到玻璃板中?
最后终于弄清楚了如何让简单示例起作用。谢谢@akf。我能够将这个解决方案应用到我的原始问题上,从而允许我删除大约60行Java2D代码,手动渲染JLabel的表示形式。
package test;

import java.awt.Color;

import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.border.LineBorder;

public class MainFrame extends JFrame {

    /**
     * @param args
     */
    public static void main(String[] args) {
        MainFrame mf = new MainFrame();
        mf.setSize(400, 400);
        mf.setLocationRelativeTo(null);
        mf.setDefaultCloseOperation(DISPOSE_ON_CLOSE);
        mf.setGlassPane(new JPanel());

        JLabel l = new JLabel();
        l.setText("Hello");
        l.setBorder(new LineBorder(Color.BLACK, 1));
        l.setBounds(10, 10, 50, 20);
        l.setBackground(Color.RED);
        l.setOpaque(true);
        l.setPreferredSize(l.getSize());

        //mf.add(l);
        ((JPanel)mf.getGlassPane()).add(l);
        mf.getGlassPane().setVisible(true);

        mf.setVisible(true);
    }
}

你尝试过将组件设置为玻璃面板而不是将其添加到玻璃面板中吗? - willcodejavaforfood
此组件代表单词游戏中的一个方块,所以它只有37*37大小,并使用边框效果。我不知道如何将其转化为玻璃窗格,因为这会影响到方块的尺寸。 - Chris Lieb
我们能看到一些代码吗?具体来说,您是如何将组件添加到玻璃窗格并设置其可见的? - Jason Nichols
@Jason Nichols 这是代码。请注意,TileViewJLabel 的子类。 - Chris Lieb
4个回答

13
下面的示例代码展示了如何在棋盘上拖动一个棋子。它使用JLayeredPane而不是玻璃板,但我相信概念是一样的。具体来说,需要做以下几点:
a) 将玻璃板添加到根窗格
b) 使玻璃板可见
c) 将组件添加到玻璃板,确保边界有效
d) 使用setLocation()方法来动画拖动组件

编辑:添加代码以修复SSCCE

JLabel l = new JLabel();
l.setText("Hello");
l.setBorder(new LineBorder(Color.BLACK, 1));
// l.setPreferredSize(l.getSize());
// l.setBounds(10, 10, 50, 20);
((JPanel)mf.getGlassPane()).add(l);

mf.setVisible(true);
mf.getGlassPane().setVisible(true);

当使用布局管理器时,您不会使用setSize()或setBounds()方法。在您的情况下,您只需将首选大小设置为(0,0),因为这是所有组件的默认大小。
当您将标签添加到框架中时,它可以工作,因为框架内容窗格的默认布局管理器是边界布局,因此标签的首选大小被忽略,标签的大小与框架的大小相同。
但是,默认情况下,JPanel使用FlowLayout,它确实尊重组件的首选大小。由于首选大小为0,因此没有可绘制的内容。
另外,必须使玻璃板可见才能进行绘制。
我建议您阅读Swing tutorial。有关布局管理器如何工作以及玻璃板如何工作的部分,每个部分都有工作示例。
编辑:以下是示例代码:
import java.awt.*;
import java.awt.event.*;
import java.util.*;
import javax.swing.*;

public class ChessBoard extends JFrame implements MouseListener, MouseMotionListener
{
    JLayeredPane layeredPane;
    JPanel chessBoard;
    JLabel chessPiece;
    int xAdjustment;
    int yAdjustment;

    public ChessBoard()
    {
        Dimension boardSize = new Dimension(600, 600);

        //  Use a Layered Pane for this this application

        layeredPane = new JLayeredPane();
        layeredPane.setPreferredSize( boardSize );
        layeredPane.addMouseListener( this );
        layeredPane.addMouseMotionListener( this );
        getContentPane().add(layeredPane);

        //  Add a chess board to the Layered Pane

        chessBoard = new JPanel();
        chessBoard.setLayout( new GridLayout(8, 8) );
        chessBoard.setPreferredSize( boardSize );
        chessBoard.setBounds(0, 0, boardSize.width, boardSize.height);
        layeredPane.add(chessBoard, JLayeredPane.DEFAULT_LAYER);

        //  Build the Chess Board squares

        for (int i = 0; i < 8; i++)
        {
            for (int j = 0; j < 8; j++)
            {
                JPanel square = new JPanel( new BorderLayout() );
                square.setBackground( (i + j) % 2 == 0 ? Color.red : Color.white );
                chessBoard.add( square );
            }
        }

        // Add a few pieces to the board

        ImageIcon duke = new ImageIcon("dukewavered.gif"); // add an image here

        JLabel piece = new JLabel( duke );
        JPanel panel = (JPanel)chessBoard.getComponent( 0 );
        panel.add( piece );
        piece = new JLabel( duke );
        panel = (JPanel)chessBoard.getComponent( 15 );
        panel.add( piece );
    }

    /*
    **  Add the selected chess piece to the dragging layer so it can be moved
    */
    public void mousePressed(MouseEvent e)
    {
        chessPiece = null;
        Component c =  chessBoard.findComponentAt(e.getX(), e.getY());

        if (c instanceof JPanel) return;

        Point parentLocation = c.getParent().getLocation();
        xAdjustment = parentLocation.x - e.getX();
        yAdjustment = parentLocation.y - e.getY();
        chessPiece = (JLabel)c;
        chessPiece.setLocation(e.getX() + xAdjustment, e.getY() + yAdjustment);

        layeredPane.add(chessPiece, JLayeredPane.DRAG_LAYER);
        layeredPane.setCursor(Cursor.getPredefinedCursor(Cursor.MOVE_CURSOR));
    }

    /*
    **  Move the chess piece around
    */
    public void mouseDragged(MouseEvent me)
    {
        if (chessPiece == null) return;

        //  The drag location should be within the bounds of the chess board

        int x = me.getX() + xAdjustment;
        int xMax = layeredPane.getWidth() - chessPiece.getWidth();
        x = Math.min(x, xMax);
        x = Math.max(x, 0);

        int y = me.getY() + yAdjustment;
        int yMax = layeredPane.getHeight() - chessPiece.getHeight();
        y = Math.min(y, yMax);
        y = Math.max(y, 0);

        chessPiece.setLocation(x, y);
     }

    /*
    **  Drop the chess piece back onto the chess board
    */
    public void mouseReleased(MouseEvent e)
    {
        layeredPane.setCursor(null);

        if (chessPiece == null) return;

        //  Make sure the chess piece is no longer painted on the layered pane

        chessPiece.setVisible(false);
        layeredPane.remove(chessPiece);
        chessPiece.setVisible(true);

        //  The drop location should be within the bounds of the chess board

        int xMax = layeredPane.getWidth() - chessPiece.getWidth();
        int x = Math.min(e.getX(), xMax);
        x = Math.max(x, 0);

        int yMax = layeredPane.getHeight() - chessPiece.getHeight();
        int y = Math.min(e.getY(), yMax);
        y = Math.max(y, 0);

        Component c =  chessBoard.findComponentAt(x, y);

        if (c instanceof JLabel)
        {
            Container parent = c.getParent();
            parent.remove(0);
            parent.add( chessPiece );
            parent.validate();
        }
        else
        {
            Container parent = (Container)c;
            parent.add( chessPiece );
            parent.validate();
        }
    }

    public void mouseClicked(MouseEvent e) {}
    public void mouseMoved(MouseEvent e) {}
    public void mouseEntered(MouseEvent e) {}
    public void mouseExited(MouseEvent e) {}

    public static void main(String[] args)
    {
        JFrame frame = new ChessBoard();
        frame.setDefaultCloseOperation( DISPOSE_ON_CLOSE );
        frame.setResizable( false );
        frame.pack();
        frame.setLocationRelativeTo( null );
        frame.setVisible(true);
     }
}

我的示例使用了GridLayout,而DRAG_LAYER并不需要使用相同的布局管理器。实际上,它不使用任何布局管理器,因为你是手动定位组件的位置。我并不是建议你需要切换并使用分层面板,只是概念应该是相同的。也就是说,由于玻璃窗格也不使用布局管理器,所以你需要负责正确设置组件的边界。无论如何,我无法提供更多帮助。我已经给你提供了可工作的代码,我不知道你的代码有什么不同。 - camickr
3
首先创建一个简单的SSCCE(http://sscce.org),在特定位置显示一个具有JLabel的玻璃窗格。一旦掌握这个,就可以将标签的位置随着鼠标拖动而进行动画化。如果无法使SSCCE运作,则您可以发布简单的代码,我们可以提供更具体的建议。 - camickr
我已经按照您的要求发布了一个最小化的示例,但是我无法让它正常工作。 - Chris Lieb
@camickr:我喜欢这个例子,但是在新的Oracle论坛中我找不到它;这个是正确的吗? - trashgod
@trashgod,是的,那看起来就是正确的链接。我已经用最新的版本更新了这篇文章。 - camickr
显示剩余2条评论

13

虽然与问题不直接相关,但是@camickr引用的JLayeredPane 示例允许进行以下适应,以突出现有组件上mouseReleased()的效果。

public ChessBoard() {
    ...
    // Add a few pieces to the board
    addPiece(3, 0, "♛"); 
    addPiece(4, 0, "♚");
    addPiece(3, 7, "♕");
    addPiece(4, 7, "♔");
}

static Font font = new Font("Sans", Font.PLAIN, 72);

private void addPiece(int col, int row, String glyph) {
    JLabel piece = new JLabel(glyph, JLabel.CENTER);
    piece.setFont(font);
    JPanel panel = (JPanel) chessBoard.getComponent(col + row * 8);
    panel.add(piece);
}

@DavidKroukamp:有关自定义字形的更多信息,请单击此处 - trashgod

3
除了已经提供的LayerPane示例指针之外,您原来的代码问题集中在设置标签的首选大小上。您在JLabel被调整大小之前设置了它,因此您的:
l.setPreferredSize(l.getSize());

无效。然而,如果您在调用setBounds之后再进行此调用,则会看到所需的结果。请按照此顺序重新排列:

l.setPreferredSize(l.getSize());
l.setBounds(10, 10, 50, 20);

要看起来像这样:

l.setBounds(10, 10, 50, 20);
l.setPreferredSize(l.getSize());

2

我长期关注Romain Guy在Swing方面的博客。我有一个链接,你可能会感兴趣。他发布了源代码-其中使用了GlassPane实现DnD效果。

http://jroller.com/gfx/entry/drag_and_drop_effects_the

我自己从未在DnD上使用过动画效果,因此无法进一步评论:-|


不幸的是,这使用在玻璃窗格上直接绘制的图像而不是由Swing渲染的组件。 - Chris Lieb
1
不应该关注示例绘制的图像。要使用组件,您只需将组件添加到玻璃窗格中,并确保已正确设置边界。 - camickr
问题在于我将组件添加到玻璃窗格并设置其边界,但它仍未显示。我唯一能想到的是我不知道如何正确设置组件的边界。 - Chris Lieb
不幸的是,现在该链接只会被重定向到一个垃圾网站;我认为 Wayback Machine 可能已经捕捉到了一些原始内容,但没有时间深入研究。 - Ti Strga

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接