我正在做一个跳棋比赛,performance的很奇怪。 到目前为止,我只是想要获得网格布局等基础知识,并且能够获得可移动的部分。 我的8乘8格开放得很好,所有的部分都正确地放在面板上,使用两个组件:
JPanels
在一个单独的class级,64总黑色和白色,并使用GridLayout
填充到中心centerPanel
。 每个面板都有一个特定的行和列值( checkerPanel[row][col]
)。 JLabels
的文本“•”他们被放大,以便他们填写面板。 他们被放置在红队前3排的白色面板上,并放在灰色队的最后3排。 当我尝试拖动一块时,从左上方的一块打开一个新的块。 当左上角的一块被拖到不动的旧件上时,旧件被删除,新的件代替。 我怎样才能使这些作品的行为正确,从旧的地方,而不是左上角? 查看java.awt.Graphics
会更容易吗?
我已经看了这个StackOverflow的问题 ,并search了Oracle JavaDocs ,但仍然无法弄清楚什么是错的。 这是我目前的计划:
import javax.swing.*; import java.awt.*; import java.awt.event.*; public class Checkers extends JFrame { private static final long serialVersionUID = 1l; private static final int GRIDSIZE = 8; private CheckerPanel[][] checkerPanel = new CheckerPanel[GRIDSIZE][GRIDSIZE]; public Checkers() { initGUI(); setTitle("Checkers"); pack(); setLocationRelativeTo(null); setVisible(true); setResizable(true); setDefaultCloseOperation(EXIT_ON_CLOSE); } private void initGUI() { JLabel titleLabel = new JLabel("Checkers"); Font titleFont = new Font(Font.SERIF, Font.BOLD, 32); titleLabel.setFont(titleFont); titleLabel.setHorizontalAlignment(JLabel.CENTER); titleLabel.setBackground(Color.BLACK); titleLabel.setForeground(Color.WHITE); titleLabel.setOpaque(true); add(titleLabel, BorderLayout.NORTH); JPanel centerPanel = new JPanel(); centerPanel.setLayout(new GridLayout(GRIDSIZE, GRIDSIZE)); // makes 8*8 grid add(centerPanel, BorderLayout.CENTER); for (int row=0; row<GRIDSIZE; row++) { for (int col=0; col<GRIDSIZE; col++) { JLabel grayPiece = new JLabel("•"); JLabel redPiece = new JLabel("•"); Font font = new Font(Font.SANS_SERIF, Font.PLAIN, 70); grayPiece.setFont(font); redPiece.setFont(font); grayPiece.setForeground(Color.GRAY); redPiece.setForeground(Color.RED); // used so that the pieces are not offset grayPiece.setBorder(BorderFactory.createEmptyBorder(-27, 0, 0, 0)); // used so that the pieces are not offset redPiece.setBorder(BorderFactory.createEmptyBorder(-27, 0, 0, 0)); checkerPanel[row][col] = new CheckerPanel(row, col); final MouseAdapter ma = new MouseAdapter() { // Clicked label. private JLabel selectedLabel = null; // Location of label in panel when it was clicked. private Point selectedLabelLocation = null; // Panel's click point. private Point panelClickPoint = null; public void mousePressed(final MouseEvent e) { final Component pressedComp = centerPanel.findComponentAt(e.getPoint()); if (pressedComp != null && pressedComp instanceof JLabel) { selectedLabel = (JLabel) pressedComp; selectedLabelLocation = selectedLabel.getLocation(); panelClickPoint = e.getPoint(); centerPanel.setComponentZOrder(selectedLabel, 0); selectedLabel.repaint(); } else { selectedLabel = null; //selectedLabelLocation = null; panelClickPoint = null; } if (selectedLabel != null && selectedLabelLocation != null && panelClickPoint != null) { final int newX = selectedLabelLocation.x; final int newY = selectedLabelLocation.y; selectedLabel.setLocation(newX, newY); } } public void mouseDragged(final MouseEvent e) { if (selectedLabel != null && selectedLabelLocation != null && panelClickPoint != null) { final Point newPanelClickPoint = e.getPoint(); // The new location is the press-location plus // the length of the drag for each axis: final int newX = selectedLabelLocation.x + (newPanelClickPoint.x - panelClickPoint.x), newY = selectedLabelLocation.y + (newPanelClickPoint.y - panelClickPoint.y); selectedLabel.setLocation(newX, newY); } } }; centerPanel.addMouseMotionListener(ma); //For mouseDragged(). centerPanel.addMouseListener(ma); //For mousePressed(). if ((row%2 == 0 && col%2 == 0) || ((row+1)%2 == 0 && (col+1)%2 == 0)) { checkerPanel[row][col].setBackground(Color.WHITE); checkerPanel[row][col].isWhite = true; } else { checkerPanel[row][col].setBackground(Color.BLACK); checkerPanel[row][col].isBlack = true; } if (row < 3 && checkerPanel[row][col].isWhite) { checkerPanel[row][col].add(redPiece); checkerPanel[row][col].isPiece = true; } else if (row > 4 && checkerPanel[row][col].isWhite) { checkerPanel[row][col].add(grayPiece); checkerPanel[row][col].isPiece = true; } centerPanel.add(checkerPanel[row][col]); } } } public static void main(String[] args) { try { String className = UIManager.getCrossPlatformLookAndFeelClassName(); UIManager.setLookAndFeel(className); } catch (Exception e) {} EventQueue.invokeLater(new Runnable() { public void run() { new Checkers(); } }); } class CheckerPanel extends JPanel { //the following class makes Panels private static final long serialVersionUID = 1L; private static final int SIZE = 50; public boolean isBlack = false; //if the panel is white public boolean isWhite = false; //if the panel is black public boolean isPiece = false; //if the panel has a piece public CheckerPanel(int row, int col) { Dimension size = new Dimension(SIZE, SIZE); setPreferredSize(size); } } }
这是窗口的屏幕截图。 当一块被拖动时,一个新的从一个红色的左上角的地方出来。
这种影响是由您的代码中的许多问题引起的。
首先,请注意,您正在两个嵌套的for-loops中创建MouseAdapter
。 实际上,这意味着你不只是创建一个 MouseAdapter
,而是实际上创建了GRIDSIZE x GRIDSIZE = 64
实例。
但是这不是问题的原因。 相反,它与你如何find用户点击的部分有关。 在mousePressed(...)
的顶部,您正在调用centerPanel.findComponentAt(e.getPoint())
。 但是,由于这种方法的定义方式,它实际上不会find你以后的作品。 这是为什么?
findComponentAt
内部工作是这样的:在Container
上调用它,遍历所有的子组件。 如果一个孩子本身就是Container
一个实例,则recursion调用该孩子上的findComponentAt
; 否则检查给定的点是否在孩子的边界内。 这个方法的问题是(a)当没有孩子通过testing时,它返回this
(而不是null
),和(b) JLabel
inheritance自Container
。 这两点结合意味着:在centerPanel
下面的组件树中的第一个叶子是一个非Container
并且包含e.getPoint()
或者只是一个Container
将被返回。 由于你的作品是JLabel
和JLabel
是容器, findComponentAt
总是会返回第一个作品,不管它是否包含给定的作品。
但是等等,还有更多! 事实上,这些仍然不是您当前版本的代码中唯一的问题。 即使你把findComponentAt
的调用replace成了一些真正find正确的东西,比如说:
private JLabel findPiece(Point point) { for (int row = 0; row < GRIDSIZE; row++) { for (int col = 0; col < GRIDSIZE; col++) { CheckerPanel cp = checkerPanel[row][col]; Point cpPoint = cp.getLocation(); for (Component comp : cp.getComponents()) { if (comp instanceof JLabel && comp.contains(point.x - cpPoint.x, point.y - cpPoint.y)) { return (JLabel) comp; } } } } return null; }
由于组件层次结构的组织方式,您仍然有问题。 您的centerPanel
包含64个CheckerPanel
,而CheckerPanel
又可以包含或不包含JPanel
片断。 不幸的是,这意味着你不能从一个面板到另一个面板绘制一个合适的块运动,因为对于绘制来说,子组件( JLabels
)被限制在它们所在的父容器( CheckerPanel
)的边界上。 你试图通过调用centerPanel.setComponentZOrder(selectedLabel, 0)
来绕过这个问题。 但是,如果您查看setComponentZOrder
的文档,您将注意到以下内容:
如果组件是某个其他容器的子组件,则在添加到此容器之前,该组件将从该容器中移除。
这适用于你在这里的情况: selectedLabel
最初不是centerPanel
的孩子,而是一些CheckerPanel
实例。 因此,通过调用setComponentZOrder
您将从CheckerPanel
centerPanel
其删除,并将其添加到centerPanel
,这基本上破坏了您的整个数据结构,其中centerPanel
包含CheckerPanel
,其中包含JLabel
。
在我看来,如果忘记游戏的组件层次结构,就可以让自己的生活变得更加轻松。 组件实际上是为桌面应用程序中的传统GUI而制作的:菜单,button,下拉列表等等。相反,如果您以如下方式实现centerPanel
,将会消除许多问题:覆盖您绘制的paintComponent
方法包含所有代码的完整板,即, CenterPanel
和这些代码块不再inheritance任何AWT或Swing类,而仅仅是用于数据结构化 – 如果您需要这些类的话!
如果你想更进一步,并有animation,如拖动一块特别平滑,你甚至可以忘记paintComponent
,而是使用一个BufferStrategy
主动绘制在选定的帧速率,而不是重绘,当发生某些事件。 然而,这又是一个更多的参与,对于纯粹用于练习的跳棋游戏,你可以坚持前一段的paintComponent
方法。