java - 在 Swing 中设置标签文本会撤消所有按钮位置移动

标签 java swing user-interface

我遇到了有史以来最奇怪的错误。

我有这个益智游戏,可以移动拼图 block (实际上是带有图像的按钮)。 一切工作正常,直到我尝试更改某些标签的文本(以指示玩家已完成多少步)。

每次我调用someControl.setText("text");时,移动的拼图都会被设置回它们的第一个位置。我不知道为什么,但他们就是这么做。

这是我的窗口: enter image description here

它由两个面板组成,每个面板都使用 GridBagLayout。 主框架也使用 gridBagLayout,它由两个面板组成。

我知道这很奇怪,但我不知道是什么导致了这个 GUI 错误。有什么想法吗?

代码片段:

increaseSteps 每次我单击拼图按钮时都会调用

void increaseSteps() {
        _steps++;
        _lblSteps.setText("Steps: " + _steps);
    }

创建拼图面板(左侧面板)

private JPanel puzzlePanel() {
        JPanel panel = new JPanel(new GridBagLayout());

        GridBagConstraints gbc = new GridBagConstraints();

        for (int i = 0; i < _splitImage.getSize(); i++)
            for (int j = 0; j < _splitImage.getSize(); j++) {
                int valueAtPos = _board.getMatrix()[i][j];
                if (valueAtPos == 0)
                    continue;

                int imageRow = _board.getImageRowFromValue(valueAtPos);
                int imageCol = _board.getImageColFromValue(valueAtPos);

                ImageIcon imageIcon = new ImageIcon(_splitImage.getImages()[imageRow][imageCol]);

                JButton btn = new JButton(imageIcon);
                _tileButtons[i][j] = new TileButton(btn, i, j);
                btn.setPreferredSize(new Dimension(_splitImage.getImages()[i][j].getWidth(null),
                        _splitImage.getImages()[i][j].getHeight(null)));

                // add action listener
                btn.addActionListener(this);
                btn.addKeyListener(this);

                gbc.gridx = j;
                gbc.gridy = i;
                panel.add(_tileButtons[i][j].getButton(), gbc);
            }

        return panel;
    }

执行的操作:

@覆盖 公共(public)无效actionPerformed(ActionEvent e){ if (!(e.getSource() JButton 实例)) 返回;

JButton btn = (JButton) e.getSource();

TileButton tile = getTileButtonFromBtn(btn);
if (tile == null)
    return;

// check if we can move the tile
String moveDir = _board.canMoveTile(tile.getRow(), tile.getCol());

if (moveDir.equals("no"))
    return;

increaseSteps();

int dirx = 0;
int diry = 0;

if (moveDir.equals("left")) {
    dirx = -1;
    _board.move("left", true);
    tile.setCol(tile.getCol() - 1);
} else if (moveDir.equals("right")) {
    dirx = 1;
    _board.move("right", true);
    tile.setCol(tile.getCol() + 1);
} else if (moveDir.equals("up")) {
    diry = -1;
    _board.move("up", true);
    tile.setRow(tile.getRow() - 1);
} else { // down
    diry = 1;
    _board.move("down", true);
    tile.setRow(tile.getRow() + 1);
}

moveButton(btn, dirx, diry, MOVE_SPEED);

if (_board.hasWon())
    win();

}

moveButton:(在单独的线程中移动按钮,调用btn.setLocation())

private void moveButton(JButton btn, int dirx, int diry, int speed) {
        Point loc = btn.getLocation();

        // get start ticks, calculate distance etc...
        StopWatch stopper = new StopWatch();
        int distance;
        if (dirx != 0)
            distance = _splitImage.getImages()[0][0].getWidth(null) * dirx;
        else
            distance = _splitImage.getImages()[0][0].getHeight(null) * diry;

        if (speed > 0) {
            // run the animation in a new thread
            Thread thread = new Thread() {
                public void run() {
                    int currentTicks;

                    int elapsed;
                    do {
                        int newX = loc.x;
                        int newY = loc.y;
                        elapsed = stopper.getElapsed();

                        int moved = (int) ((double) distance * (double) (elapsed / (double) speed));

                        if (dirx != 0)
                            newX += moved;
                        else
                            newY += moved;

                        btn.setLocation(newX, newY);
                    } while (elapsed <= MOVE_SPEED);

                    // make sure the last location is exact
                    btn.setLocation(loc.x + (dirx == 0 ? 0 : distance), loc.y + (diry == 0 ? 0 : distance));
                }
            };

            thread.start();
        }

        else
            btn.setLocation(loc.x + (dirx == 0 ? 0 : distance), loc.y + (diry == 0 ? 0 : distance));
    }

最佳答案

您正在尝试通过 setLocation(...)setBounds(...) 设置组件的绝对位置,该组件由使用布局管理器的容器。这可能暂时有效,但如果触发容器的布局管理器重新对其包含的组件进行布局,则会失败。当这种情况发生时,GridBagConstraints 将接管并且组件将移动到其 gridbag 约束指定的位置。

解决方案是不这样做,而是将组件的位置与所使用的布局管理器保持一致。

另一个问题是您当前的代码不是 Swing 线程安全的,因为您是在后台线程中更改 Swing 状态。这并不总是会导致问题,但由于这是一个线程问题,因此存在导致间歇性难以调试问题的风险(这些问题通常仅在您的老板或讲师尝试运行您的代码时才会发生)。

可能的解决方案:

  • 对于图像网格,您可以使用保存在使用 GridLayout 的容器中的 JLabels(或 JButton,如果必须的话)网格。当您需要重新定位组件时,请删除该 JPanel 持有的所有组件,然后重新添加,使用添加顺序来帮助您定位组件。
  • 最简单的方法是使用非移动 JLabels 网格,为它们提供 MouseListener,而不是移动 JLabels,而是删除并向其添加图标,包括空白图标。
  • 如果需要做 Swing 动画,请使用 Swing Timer来驱动动画。这将允许您的代码在调用之间有延迟地进行重复调用,并且这些调用是在 Swing 事件线程、EDT(事件调度线程)上进行的。

演示概念验证示例代码,显示交换图标,但没有动画,并且尚未测试解决方案:

enter image description here

import java.awt.GridLayout;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import javax.imageio.ImageIO;
import javax.swing.*;

@SuppressWarnings("serial")
public class ImageShuffle extends JPanel {
    private static final int SIDES = 3;
    public static final String IMG_PATH = "https://upload.wikimedia.org/wikipedia/commons/"
            + "thumb/5/5a/Hurricane_Kiko_Sep_3_1983_1915Z.jpg/"
            + "600px-Hurricane_Kiko_Sep_3_1983_1915Z.jpg";
    private List<Icon> iconList = new ArrayList<>(); // shuffled icons
    private List<Icon> solutionList = new ArrayList<>();  // in order
    private List<JLabel> labelList = new ArrayList<>();  // holds JLabel grid
    private Icon blankIcon;

    public ImageShuffle(BufferedImage img) {
        setLayout(new GridLayout(SIDES, SIDES, 1, 1));
        fillIconList(img); // fill array list with icons and one blank one
        Collections.shuffle(iconList); 
        MyMouseListener myMouse = new MyMouseListener();
        for (Icon icon : iconList) {
            JLabel label = new JLabel(icon);
            label.addMouseListener(myMouse);
            add(label);
            labelList.add(label);
        }
    }

    private class MyMouseListener extends MouseAdapter {
        @Override
        public void mousePressed(MouseEvent e) {
            JLabel selectedLabel = (JLabel) e.getSource();
            if (selectedLabel.getIcon() == blankIcon) {
                return; // don't want to move the blank icon
            }
            // index variables to hold selected and blank JLabel's index location
            int selectedIndex = -1;
            int blankIndex = -1;
            for (int i = 0; i < labelList.size(); i++) {
                if (selectedLabel == labelList.get(i)) {
                    selectedIndex = i;                    
                } else if (labelList.get(i).getIcon() == blankIcon) {
                    blankIndex = i;
                }
            }

            // get row and column of selected JLabel
            int row = selectedIndex / SIDES;
            int col = selectedIndex % SIDES;

            // get row and column of blank JLabel
            int blankRow = blankIndex / SIDES;
            int blankCol = blankIndex % SIDES;

            if (isMoveValid(row, col, blankRow, blankCol)) {
                Icon selectedIcon = selectedLabel.getIcon();
                labelList.get(selectedIndex).setIcon(blankIcon);
                labelList.get(blankIndex).setIcon(selectedIcon);

                // test for win here by comparing icons held by labelList
                // with the solutionList
            } 
        }

        private boolean isMoveValid(int row, int col, int blankRow, int blankCol) {
            // has to be on either same row or same column
            if (row != blankRow && col != blankCol) {
                return false;
            }
            // if same row
            if (row == blankRow) {
                // then columns must be off by 1 -- they're next to each other
                return Math.abs(col - blankCol) == 1;
            } else {
                // or else rows off by 1 -- above or below each other
                return Math.abs(row - blankRow) == 1;
            }
        }

        public void shuffle() {
            Collections.shuffle(iconList);
            for (int i = 0; i < labelList.size(); i++) {
                labelList.get(i).setIcon(iconList.get(i));
            }
        }
    }

    private void fillIconList(BufferedImage img) {
        // get the width and height of each individual icon
        // which is 1/3 the image width and height
        int w = img.getWidth() / SIDES;
        int h = img.getHeight() / SIDES;
        for (int row = 0; row < SIDES; row++) {
            int y = (row * img.getWidth()) / SIDES;
            for (int col = 0; col < SIDES; col++) {
                int x = (col * img.getHeight()) / SIDES;
                // create a sub image
                BufferedImage subImg = img.getSubimage(x, y, w, h);
                // create icon from the image
                Icon icon = new ImageIcon(subImg);
                // add to both icon lists
                iconList.add(icon);
                solutionList.add(icon);
            }
        }

        // create a blank image and corresponding icon as well.
        BufferedImage blankImg = new BufferedImage(w, h, BufferedImage.TYPE_INT_ARGB);
        blankIcon = new ImageIcon(blankImg);
        iconList.remove(iconList.size() - 1);  // remove last icon from list
        iconList.add(blankIcon);   // and swap in the blank one
        solutionList.remove(iconList.size() - 1);  // same for the solution list
        solutionList.add(blankIcon);
    }


    private static void createAndShowGui(BufferedImage img) {
        JFrame frame = new JFrame("ImageShuffle");
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.getContentPane().add(new ImageShuffle(img));
        frame.pack();
        frame.setLocationRelativeTo(null);
        frame.setVisible(true);
    }

    public static void main(String[] args) {
        URL imgUrl = null;
        BufferedImage img;
        try {
            imgUrl = new URL(IMG_PATH);
            img = ImageIO.read(imgUrl);
            SwingUtilities.invokeLater(() -> createAndShowGui(img));
        } catch (IOException e) {
            e.printStackTrace();
        }        
    }
}

如果我想要动画,我会再次将图标提升到 JFrame 的玻璃 Pane 中,使用 Swing Timer 将其设置为新位置,然后将图标放入新的 JLabel 中。我还使用 boolean 字段(“标志”)禁用 MouseListener,直到动画完成移动。

关于java - 在 Swing 中设置标签文本会撤消所有按钮位置移动,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/50222555/

相关文章:

java - 从另一个 Applet 启动另一个 Applet

java - 如何修改数据库而不删除firebase中的数据

java - Service 如何与其 Activity 通信?服务如何调用启动该服务的 Activity 中的方法?

Java堆叠组件

java - 从 java netbeans 中的某些 JtextField 中删除空格

java - Android Studio 和我的设备中的分辨率相同但外观不同

android - 滚动横幅 - 实现它的最佳方式是什么?

java - Android:使用微调器更改主要 Activity 背景颜色

java - 鼠标悬停 - 在屏幕上显示消息 Java 应用程序

user-interface - 黑莓 - 字段布​​局动画