Placing component on Glass Pane
Asked Answered
F

4

11

I have a subclass of JLabel that forms a component of my GUI. I have implemented the ability to drag and drop the component from one container to another, but without any visual effects. I want to have this JLabel follow the cursor during the drag of the item from one container to another. I figured that I could just create a glass pane and draw it on there. However, even after I add the component to the glass pane, set the component visible, and set the glass pane visible, and set the glass pane as opaque, I still so not see the component. I know the component works because I can add it to the content pane and have it show up.

How do I add a component to the glass pane?


Finally figured how to get the simple example working. Thanks, @akf. I was able to adapt this solution to my original problem, allowing me to remove ~60 lines of Java2D code that manually rendered a representation of the 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);
    }
}
Factotum answered 1/4, 2010 at 15:48 Comment(4)
Have you tried setting your component AS the glasspane instead of adding it to the glasspane?Underdog
This component represents a tile in a word game, so it is only 37*37 and uses borders for effect. I don't see how I could transform this into a glass pane since it would mess with the dimensions of the tile.Factotum
Can we see some code? Specifically how you add the component to the glass pane and set it visible.Midpoint
@Jason Nichols here's the code. Just to note, TileView is a subclass of JLabel.Factotum
A
3

Besides the pointers to the LayerPane examples already provided, the issue with your original code centers around the setting of the preferred size of your label. You set it before the JLabel has been sized, so your:

l.setPreferredSize(l.getSize());

is ineffectual. If, on the other hand, you make that call after you make your call to setBounds, you will see your desired results. With that in mind, reorder this:

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

to look like this:

l.setBounds(10, 10, 50, 20);
l.setPreferredSize(l.getSize());
Apostolate answered 3/4, 2010 at 3:6 Comment(0)
F
13

The example code below shows how to drag a chess piece around a chess board. It uses JLayeredPane instead of a glass pane, but I'm sure the concepts would be the same. That is:

a) add the glass pane to the root pane
b) make the glass pane visible
c) add the component to the glass pane making sure the bounds are valid
d) use setLocation() to animate the dragging of the component

Edit: added code to fix 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);

When using layout managers you never use the setSize() or setBounds() methods. In your case you just set the preferred size to (0, 0) since this is the default size of all components.

It works when you add the label to the frame because the default layout manger for the content pane of the frame is a border layout, therefore the preferred size of the label is ignored and the label is made the size of the frame.

However, by default a JPanel uses a FlowLayout which does respect the preferred size of the component. Since the preferred size is 0, there is nothing to paint.

Also, the glass pane needs to made visible in order for it to be painted.

I suggest you read the Swing tutorial. There are section on how layout managers work and on how glass panes work and each section has working examples.

Edit: Example code added below:

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);
     }
}
Frantic answered 1/4, 2010 at 18:29 Comment(7)
I'm having a little trouble switching the JLayeredPane since I use GridBagLayout for my main form's layout. This messes with the DRAG_LAYER since it is required to have the same layout manager as the rest of the content pane, but I don't want a layout manager since this is only going to be used for dragging.Factotum
My example uses a GridLayout and the DRAG_LAYER does not need to use the same layout manager. In fact it uses not layout managers since you are manually positioning the components. I'm not suggesting you need to swith and use a layered pane only that the concepts should be the same. That is since a glass pane also does not use a layout manager you are responsible for setting the bounds of the component properly. Anyway I can't help futher. I gave you working code, I don't know how your code is different.Frantic
Start by creating a simple SSCCE (sscce.org) that displays a glass pane with a JLabel at a specific position. Once you master that, then you move on to animating the location of the label as you drag it with the mouse. If you can't get the SSCCE working then you have simple code to post and we can provide more concrete suggestions.Frantic
I have posted a minimal example that I cannot get to work, just like you asked.Factotum
@camickr: I like this example, but I had trouble finding it in the new Oracle forums; is this the right one?Agbogla
@trashgod, yes, that looks like the correct link. I updated this posting with my most recent copy.Frantic
Thanks! I've cited this answer here and here.Agbogla
A
13

Although tangential to the question, the JLayeredPane example cited by @camickr admits the following adaptation, which highlights the effect of mouseReleased() over an existing component.

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);
}
Agbogla answered 1/4, 2010 at 20:12 Comment(1)
@DavidKroukamp: More on customizing the glyphs here.Agbogla
A
3

Besides the pointers to the LayerPane examples already provided, the issue with your original code centers around the setting of the preferred size of your label. You set it before the JLabel has been sized, so your:

l.setPreferredSize(l.getSize());

is ineffectual. If, on the other hand, you make that call after you make your call to setBounds, you will see your desired results. With that in mind, reorder this:

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

to look like this:

l.setBounds(10, 10, 50, 20);
l.setPreferredSize(l.getSize());
Apostolate answered 3/4, 2010 at 3:6 Comment(0)
M
2

Since I had been following Romain Guy's blogs on Swing for a long time. I have a link that you might be interested in. He released the source - which used a GlassPane for DnD effects.

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

I myself never did use a fizzy animation/effect on DnD, so can't comment any further :-|

Modestia answered 1/4, 2010 at 18:55 Comment(4)
Unfortunately, that uses images drawn directly onto the glass pane rather than components rendered by Swing.Factotum
It shouldn't matter that the example draws images. To use components, you just add the component to the glass pane and make sure you have set the bounds properly.Frantic
The problem was that I was adding the component to the glass pane and setting its bounds, but it still wasn't displaying. The only thing I can figure is that I have no idea how to set the bounds correctly on a component.Factotum
sadly, the link now just gets redirected to a spam site; I think the Wayback Machine may have captured some of the original, but didn't have time to poke around in depthPansir

© 2022 - 2024 — McMap. All rights reserved.