制作一个强大的,可resize的Swing Chess GUI

我如何制作这款可resize的Chess GUI?


我们公司的任务是制作国际象棋游戏。 它需要在Windows,OS X和Linux / Unix机器上运行,我们选择Java来实现这一点,同时保持一个通用的代码库(便于维护和降低成本)。

我的任务是创建GUI。 用户设计团队已经清除了以下规范。 与客户。

国际象棋比赛(国际象棋冠军)将有力量resize和直接,它包括:

  • 顶部的工具栏,带有UI组件:
    • 按钮
    • 保存按钮
    • 恢复按钮
    • 辞职按钮
    • 用于向玩家提供消息的标签。

在游戏的左侧,我们需要一个可供将来使用的区域,它可能包括以下内容:

  • 捕获的碎片列表
  • 促销棋子时选择棋子的选择器
  • 游戏统计
  • 提示等

客户和逻辑团队仍在解决这方面的细节。 那么暂时,只需用包含?的标签来标记它? 作为文字。

GUI的其余部分将由棋盘本身组成。 它将有:

  • 国际象棋棋盘的主要区域。 如果用户指向棋子,它应该显示带边框的焦点。 它也应该是键盘可访问的。 客户将提供多个棋子(各种尺寸,样式和颜色)的精灵表,以允许用户改变游戏的外观。
  • 棋盘将有标签指示列(从左到右:A,B,C,D,E,F,G和H)和行(从上到下:8,7,6,5,4,3,2) &1)。
  • 国际象棋棋盘和列/行标签将以1px黑色边框为边界,周围有8px填充。
  • 随着玩家增加游戏的大小,棋盘应该保持正方形,否则填充可用空间。
  • 国际象棋棋盘背后的背景色应该是赭色,但在下面的模型中,我们将棋盘背后的区域设为绿色,以突出resize的行为。

在游戏开始之前,最小尺寸的国际象棋冠军

在游戏开始之前,ChessChamp以最小尺寸

激活新游戏按钮后,最小尺寸的国际象棋冠军

激活新游戏按钮后,最小尺寸的国际象棋冠军

国际象棋冠军比最小尺寸更宽

ChessChamp比最小尺寸拉伸得更宽

国际象棋冠军比最小尺寸更高

国际象棋冠军比最小尺寸更高

笔记

  • 国际象棋棋盘左侧及上方的列由9×9 GridLayout 。 网格布局的第一个单元格是没有文本的标签。

  • 为了简化游戏逻辑,我们维护一个单独的8×8按钮arrays。

  • 为了允许键盘function,我们使用棋盘位置的按钮。 这也提供了内置焦点指示。 删除按钮的边距以允许它们缩小到图标的大小。 我们可以向按钮添加一个ActionListener ,它将响应键盘和鼠标事件。

  • 为了维持一块方板,我们采用了一点技巧。 国际象棋棋盘被添加到GridBagLayout作为唯一没有指定GridBagContraints组件。 这样它始终居中。 为了获得所需的resize行为,棋盘查询父组件的实际大小并返回其可以的最大大小,同时仍然是正方形并且不超过父组件的宽度或高度的较小大小。

  • 棋子图像是从示例图像中获得的, 用于代码和标记Q&A ,然后由标签中的“填充”Unicode字符开发。

    使用图像更简单,而填充Unicode字符更通用,而且“更轻”。 IE支持3种不同尺寸的3种不同棋子样式的4种不同颜色需要36个单独的精灵表!


 import java.awt.*; import java.awt.event.*; import java.awt.image.BufferedImage; import javax.swing.*; import javax.swing.border.*; import java.net.URL; import javax.imageio.ImageIO; public class ChessGUI { private final JPanel gui = new JPanel(new BorderLayout(3, 3)); private JButton[][] chessBoardSquares = new JButton[8][8]; private Image[][] chessPieceImages = new Image[2][6]; private JPanel chessBoard; private final JLabel message = new JLabel( "Chess Champ is ready to play!"); private static final String COLS = "ABCDEFGH"; public static final int QUEEN = 0, KING = 1, ROOK = 2, KNIGHT = 3, BISHOP = 4, PAWN = 5; public static final int[] STARTING_ROW = { ROOK, KNIGHT, BISHOP, KING, QUEEN, BISHOP, KNIGHT, ROOK }; public static final int BLACK = 0, WHITE = 1; ChessGUI() { initializeGui(); } public final void initializeGui() { // create the images for the chess pieces createImages(); // set up the main GUI gui.setBorder(new EmptyBorder(5, 5, 5, 5)); JToolBar tools = new JToolBar(); tools.setFloatable(false); gui.add(tools, BorderLayout.PAGE_START); Action newGameAction = new AbstractAction("New") { @Override public void actionPerformed(ActionEvent e) { setupNewGame(); } }; tools.add(newGameAction); tools.add(new JButton("Save")); // TODO - add functionality! tools.add(new JButton("Restore")); // TODO - add functionality! tools.addSeparator(); tools.add(new JButton("Resign")); // TODO - add functionality! tools.addSeparator(); tools.add(message); gui.add(new JLabel("?"), BorderLayout.LINE_START); chessBoard = new JPanel(new GridLayout(0, 9)) { /** * Override the preferred size to return the largest it can, in * a square shape. Must (must, must) be added to a GridBagLayout * as the only component (it uses the parent as a guide to size) * with no GridBagConstaint (so it is centered). */ @Override public final Dimension getPreferredSize() { Dimension d = super.getPreferredSize(); Dimension prefSize = null; Component c = getParent(); if (c == null) { prefSize = new Dimension( (int)d.getWidth(),(int)d.getHeight()); } else if (c!=null && c.getWidth()>d.getWidth() && c.getHeight()>d.getHeight()) { prefSize = c.getSize(); } else { prefSize = d; } int w = (int) prefSize.getWidth(); int h = (int) prefSize.getHeight(); // the smaller of the two sizes int s = (w>h ? h : w); return new Dimension(s,s); } }; chessBoard.setBorder(new CompoundBorder( new EmptyBorder(8,8,8,8), new LineBorder(Color.BLACK) )); // Set the BG to be ochre Color ochre = new Color(204,119,34); chessBoard.setBackground(ochre); JPanel boardConstrain = new JPanel(new GridBagLayout()); boardConstrain.setBackground(ochre); boardConstrain.add(chessBoard); gui.add(boardConstrain); // create the chess board squares Insets buttonMargin = new Insets(0, 0, 0, 0); for (int ii = 0; ii < chessBoardSquares.length; ii++) { for (int jj = 0; jj < chessBoardSquares[ii].length; jj++) { JButton b = new JButton(); b.setMargin(buttonMargin); // our chess pieces are 64x64 px in size, so we'll // 'fill this in' using a transparent icon.. ImageIcon icon = new ImageIcon( new BufferedImage(64, 64, BufferedImage.TYPE_INT_ARGB)); b.setIcon(icon); if ((jj % 2 == 1 && ii % 2 == 1) //) { || (jj % 2 == 0 && ii % 2 == 0)) { b.setBackground(Color.WHITE); } else { b.setBackground(Color.BLACK); } chessBoardSquares[jj][ii] = b; } } /* * fill the chess board */ chessBoard.add(new JLabel("")); // fill the top row for (int ii = 0; ii < 8; ii++) { chessBoard.add( new JLabel(COLS.substring(ii, ii + 1), SwingConstants.CENTER)); } // fill the black non-pawn piece row for (int ii = 0; ii < 8; ii++) { for (int jj = 0; jj < 8; jj++) { switch (jj) { case 0: chessBoard.add(new JLabel("" + (9-(ii + 1)), SwingConstants.CENTER)); default: chessBoard.add(chessBoardSquares[jj][ii]); } } } } public final JComponent getGui() { return gui; } private final void createImages() { try { URL url = new URL("http://sofzh.miximages.com/java/memI0.png"); BufferedImage bi = ImageIO.read(url); for (int ii = 0; ii < 2; ii++) { for (int jj = 0; jj < 6; jj++) { chessPieceImages[ii][jj] = bi.getSubimage( jj * 64, ii * 64, 64, 64); } } } catch (Exception e) { e.printStackTrace(); System.exit(1); } } /** * Initializes the icons of the initial chess board piece places */ private final void setupNewGame() { message.setText("Make your move!"); // set up the black pieces for (int ii = 0; ii < STARTING_ROW.length; ii++) { chessBoardSquares[ii][0].setIcon(new ImageIcon( chessPieceImages[BLACK][STARTING_ROW[ii]])); } for (int ii = 0; ii < STARTING_ROW.length; ii++) { chessBoardSquares[ii][1].setIcon(new ImageIcon( chessPieceImages[BLACK][PAWN])); } // set up the white pieces for (int ii = 0; ii < STARTING_ROW.length; ii++) { chessBoardSquares[ii][6].setIcon(new ImageIcon( chessPieceImages[WHITE][PAWN])); } for (int ii = 0; ii < STARTING_ROW.length; ii++) { chessBoardSquares[ii][7].setIcon(new ImageIcon( chessPieceImages[WHITE][STARTING_ROW[ii]])); } } public static void main(String[] args) { Runnable r = new Runnable() { @Override public void run() { ChessGUI cg = new ChessGUI(); JFrame f = new JFrame("ChessChamp"); f.add(cg.getGui()); // Ensures JVM closes after frame(s) closed and // all non-daemon threads are finished f.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE); // See https://stackoverflow.com/a/7143398/418556 for demo. f.setLocationByPlatform(true); // ensures the frame is the minimum size it needs to be // in order display the components within it f.pack(); // ensures the minimum size is enforced. f.setMinimumSize(f.getSize()); f.setVisible(true); } }; // Swing GUIs should be created and updated on the EDT // http://docs.oracle.com/javase/tutorial/uiswing/concurrency SwingUtilities.invokeLater(r); } } 

我注意到,在resize时,你可以在棋盘和右边/底边线边界之间留一个小间隙。 GridLayout会发生这种情况,因为空格并不总是被9整除。

您可能正在寻找使用标准JDK的解决方案,但如果您想摆脱这个小差距,那么您可以使用相对布局来管理国际象棋棋盘和标签。 差距仍将存在,但我已将其移至标签,因此您无法轻易看出差异。

 import java.awt.*; import java.awt.event.*; import java.awt.image.BufferedImage; import javax.swing.*; import javax.swing.border.*; import java.net.URL; import javax.imageio.ImageIO; public class ChessGUI2 { private final JPanel gui = new JPanel(new BorderLayout(3, 3)); private JButton[][] chessBoardSquares = new JButton[8][8]; private Image[][] chessPieceImages = new Image[2][6]; private JPanel chessBoard; private final JLabel message = new JLabel( "Chess Champ is ready to play!"); private static final String COLS = "ABCDEFGH"; public static final int QUEEN = 0, KING = 1, ROOK = 2, KNIGHT = 3, BISHOP = 4, PAWN = 5; public static final int[] STARTING_ROW = { ROOK, KNIGHT, BISHOP, KING, QUEEN, BISHOP, KNIGHT, ROOK }; ChessGUI2() { initializeGui(); } public final void initializeGui() { // create the images for the chess pieces createImages(); // set up the main GUI gui.setBorder(new EmptyBorder(5, 5, 5, 5)); JToolBar tools = new JToolBar(); tools.setFloatable(false); gui.add(tools, BorderLayout.PAGE_START); Action newGameAction = new AbstractAction("New") { @Override public void actionPerformed(ActionEvent e) { setupNewGame(); } }; tools.add(newGameAction); tools.add(new JButton("Save")); // TODO - add functionality! tools.add(new JButton("Restore")); // TODO - add functionality! tools.addSeparator(); tools.add(new JButton("Resign")); // TODO - add functionality! tools.addSeparator(); tools.add(message); gui.add(new JLabel("?"), BorderLayout.LINE_START); // chessBoard = new JPanel(new GridLayout(0, 9)) { chessBoard = new JPanel() { /** * Override the preferred size to return the largest it can, in * a square shape. Must (must, must) be added to a GridBagLayout * as the only component (it uses the parent as a guide to size) * with no GridBagConstaint (so it is centered). */ @Override public final Dimension getPreferredSize() { Dimension d = super.getPreferredSize(); Dimension prefSize = null; Component c = getParent(); if (c == null) { prefSize = new Dimension( (int)d.getWidth(),(int)d.getHeight()); } else if (c!=null && c.getWidth()>d.getWidth() && c.getHeight()>d.getHeight()) { prefSize = c.getSize(); } else { prefSize = d; } int w = (int) prefSize.getWidth(); int h = (int) prefSize.getHeight(); // the smaller of the two sizes int s = (w>h ? h : w); return new Dimension(s,s); } }; RelativeLayout rl = new RelativeLayout(RelativeLayout.Y_AXIS); rl.setRoundingPolicy( RelativeLayout.FIRST ); rl.setFill(true); chessBoard.setLayout( rl ); chessBoard.setBorder(new CompoundBorder( new EmptyBorder(8,8,8,8), new LineBorder(Color.BLACK) )); // Set the BG to be ochre Color ochre = new Color(204,119,34); chessBoard.setBackground(ochre); JPanel boardConstrain = new JPanel(new GridBagLayout()); boardConstrain.setBackground(ochre); boardConstrain.add(chessBoard); gui.add(boardConstrain); // our chess pieces are 64x64 px in size, so we'll // 'fill this in' using a transparent icon.. ImageIcon icon = new ImageIcon( //new BufferedImage(64, 64, BufferedImage.TYPE_INT_ARGB)); new BufferedImage(48, 48, BufferedImage.TYPE_INT_ARGB)); // create the chess board squares Insets buttonMargin = new Insets(0, 0, 0, 0); for (int ii = 0; ii < chessBoardSquares.length; ii++) { for (int jj = 0; jj < chessBoardSquares[ii].length; jj++) { JButton b = new JButton(); b.setMargin(buttonMargin); b.setIcon(icon); if ((jj % 2 == 1 && ii % 2 == 1) //) { || (jj % 2 == 0 && ii % 2 == 0)) { b.setBackground(Color.WHITE); } else { b.setBackground(Color.BLACK); } chessBoardSquares[jj][ii] = b; } } /* * fill the chess board */ RelativeLayout topRL = new RelativeLayout(RelativeLayout.X_AXIS); topRL.setRoundingPolicy( RelativeLayout.FIRST ); topRL.setFill(true); JPanel top = new JPanel( topRL ); top.setOpaque(false); chessBoard.add(top, new Float(1)); top.add(new JLabel(""), new Float(1)); // fill the top row for (int ii = 0; ii < 8; ii++) { JLabel label = new JLabel(COLS.substring(ii, ii + 1), SwingConstants.CENTER); top.add(label, new Float(1)); } // fill the black non-pawn piece row for (int ii = 0; ii < 8; ii++) { RelativeLayout rowRL = new RelativeLayout(RelativeLayout.X_AXIS); rowRL.setRoundingPolicy( RelativeLayout.FIRST ); rowRL.setFill(true); JPanel row = new JPanel( rowRL ); row.setOpaque(false); chessBoard.add(row, new Float(1)); for (int jj = 0; jj < 8; jj++) { switch (jj) { case 0: row.add(new JLabel("" + (9-(ii + 1)), SwingConstants.CENTER), new Float(1)); default: row.add(chessBoardSquares[jj][ii], new Float(1)); } } } } public final JComponent getChessBoard() { return chessBoard; } public final JComponent getGui() { return gui; } private final void createImages() { try { URL url = new URL("http://sofzh.miximages.com/java/memI0.png"); BufferedImage bi = ImageIO.read(url); for (int ii = 0; ii < 2; ii++) { for (int jj = 0; jj < 6; jj++) { chessPieceImages[ii][jj] = bi.getSubimage( // jj * 64, ii * 64, 64, 64); jj * 64, ii * 64, 48, 48); } } } catch (Exception e) { e.printStackTrace(); System.exit(1); } } /** * Initializes the icons of the initial chess board piece places */ private final void setupNewGame() { message.setText("Make your move!"); // set up the black pieces for (int ii = 0; ii < STARTING_ROW.length; ii++) { chessBoardSquares[ii][0].setIcon(new ImageIcon( chessPieceImages[0][STARTING_ROW[ii]])); } for (int ii = 0; ii < STARTING_ROW.length; ii++) { chessBoardSquares[ii][1].setIcon(new ImageIcon( chessPieceImages[0][PAWN])); } // set up the white pieces for (int ii = 0; ii < STARTING_ROW.length; ii++) { chessBoardSquares[ii][6].setIcon(new ImageIcon( chessPieceImages[1][PAWN])); } for (int ii = 0; ii < STARTING_ROW.length; ii++) { chessBoardSquares[ii][7].setIcon(new ImageIcon( chessPieceImages[1][STARTING_ROW[ii]])); } } public static void main(String[] args) { Runnable r = new Runnable() { @Override public void run() { ChessGUI2 cg = new ChessGUI2(); JFrame f = new JFrame("ChessChamp"); f.add(cg.getGui()); // Ensures JVM closes after frame(s) closed and // all non-daemon threads are finished f.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE); // See http://stackoverflow.com/a/7143398/418556 for demo. f.setLocationByPlatform(true); // ensures the frame is the minimum size it needs to be // in order display the components within it f.pack(); // ensures the minimum size is enforced. f.setMinimumSize(f.getSize()); f.setVisible(true); } }; // Swing GUIs should be created and updated on the EDT // http://docs.oracle.com/javase/tutorial/uiswing/concurrency SwingUtilities.invokeLater(r); } } 

它确实需要更多工作,因为您需要单独管理行,而不是在网格中。 此外,我更改了使用48x48图像的代码,以便在我的小型显示器上更轻松地调整测试大小。