Размещение компонента на стеклянной панели

У меня есть подкласс JLabel, который является компонентом моего графического интерфейса. Я реализовал возможность перетаскивания компонента из одного контейнера в другой, но без визуальных эффектов. Я хочу, чтобы этот 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);
    }
}

person Chris Lieb    schedule 01.04.2010    source источник
comment
Вы пытались установить свой компонент как стеклянную панель вместо того, чтобы добавлять ее в стеклянную панель?   -  person willcodejavaforfood    schedule 01.04.2010
comment
Этот компонент представляет плитку в словесной игре, поэтому он имеет размер всего 37*37 и использует границы для эффекта. Я не понимаю, как я мог бы превратить это в стеклянную панель, поскольку это испортило бы размеры плитки.   -  person Chris Lieb    schedule 01.04.2010
comment
Можем ли мы увидеть код? В частности, как вы добавляете компонент на стеклянную панель и делаете его видимым.   -  person Jason Nichols    schedule 01.04.2010
comment
@ Джейсон Николс, вот код. Просто отметим, что TileView является подклассом JLabel.   -  person Chris Lieb    schedule 01.04.2010


Ответы (4)


Помимо уже предоставленных указателей на примеры 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());
person akf    schedule 03.04.2010

В приведенном ниже примере кода показано, как перетаскивать шахматную фигуру по шахматной доске. Он использует JLayeredPane вместо стеклянной панели, но я уверен, что концепции будут такими же. Это:

а) добавить стеклянную панель в корневую панель
б) сделать стеклянную панель видимой
в) добавить компонент в стеклянную панель, убедившись, что границы действительны
г) использовать 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. Есть раздел о том, как работают менеджеры компоновки и о том, как работают стеклянные панели, и в каждом разделе есть рабочие примеры.

Изменить: пример кода добавлен ниже:

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);
     }
}
person camickr    schedule 01.04.2010
comment
У меня возникли небольшие проблемы с переключением JLayeredPane, так как я использую GridBagLayout для макета моей основной формы. Это мешает DRAG_LAYER, поскольку требуется иметь тот же менеджер компоновки, что и остальная часть панели содержимого, но мне не нужен менеджер компоновки, поскольку он будет использоваться только для перетаскивания. - person Chris Lieb; 01.04.2010
comment
В моем примере используется GridLayout, а DRAG_LAYER не требует использования того же менеджера компоновки. На самом деле он использует не менеджеры компоновки, поскольку вы вручную позиционируете компоненты. Я не предлагаю вам переключаться и использовать многоуровневую панель только в том случае, если концепции должны быть одинаковыми. Это связано с тем, что стеклянная панель также не использует менеджер компоновки, и вы несете ответственность за правильную установку границ компонента. В любом случае, я не могу помочь дальше. Я дал вам рабочий код, я не знаю, чем ваш код отличается. - person camickr; 01.04.2010
comment
Начните с создания простого SSCCE (sscce.org), который отображает стеклянную панель с JLabel в определенной позиции. Как только вы освоите это, вы переходите к анимации расположения метки при перетаскивании ее мышью. Если вы не можете заставить SSCCE работать, у вас есть простой код для публикации, и мы можем предоставить более конкретные предложения. - person camickr; 01.04.2010
comment
Я опубликовал минимальный пример того, что я не могу работать, как вы и просили. - person Chris Lieb; 03.04.2010
comment
@camickr: мне нравится этот пример, но мне было трудно найти его на новых форумах Oracle; это правильный? - person trashgod; 19.03.2011
comment
@trashgod, да, похоже, это правильная ссылка. Я обновил эту публикацию своей последней копией. - person camickr; 19.03.2011
comment
Спасибо! Я процитировал этот ответ здесь и здесь< /а>. - person trashgod; 19.03.2011

Несмотря на то, что это касается вопроса, JLayeredPane пример, приведенный @camickr, допускает следующую адаптацию, которая подчеркивает влияние 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);
}
person trashgod    schedule 01.04.2010
comment
@DavidKroukamp: Подробнее о настройке глифов здесь. - person trashgod; 09.09.2013

Так как я долгое время следил за блогами Ромена Гая на Swing. У меня есть ссылка, которая может вас заинтересовать. Он опубликовал исходный код, в котором использовалась GlassPane для эффектов DnD.

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

Сам я никогда не использовал шипучую анимацию/эффект в DnD, поэтому не могу комментировать дальше :-|

person ring bearer    schedule 01.04.2010
comment
К сожалению, при этом используются изображения, нарисованные непосредственно на стеклянной панели, а не компоненты, визуализируемые Swing. - person Chris Lieb; 01.04.2010
comment
Неважно, что пример рисует изображения. Чтобы использовать компоненты, вы просто добавляете компонент на стеклянную панель и убедитесь, что вы правильно установили границы. - person camickr; 01.04.2010
comment
Проблема заключалась в том, что я добавлял компонент на стеклянную панель и устанавливал его границы, но он все еще не отображался. Единственное, что я могу понять, это то, что я понятия не имею, как правильно установить границы для компонента. - person Chris Lieb; 02.04.2010