为什么仅在某些 JPanel 上调用 paintComponent?
Why does paintComponent get called only on some JPanels?
我正在尝试为不同的 JPanel 添加背景(以后它们将被称为 Window)。这些 Windows 是我创建的 classes 并使它们继承 JPanel。然后根据程序的状态,将 Window 设置为程序的 JFrame 的内容面板。当在某些 Windows 中设置了背景而在其他人中没有设置时,问题就来了。背景设置是使用paintComponent(Graphics g)方法进行的,但是尽管我尝试修复了这个错误,但我没有成功。
这是我认为可能对那些想要帮助的人有用的代码:
主循环:
public class Game{
//here comes other stuff (constructor, main, other methods...)
private void run(){
while(true){
if(GameState.changed){
Screen.getInstance().seeWindow(state);
GameState.changed = false;
}else {
Screen.getInstance().requestFocus(state);
}
}
}
}
屏幕class:
package view;
import game.GameState;
import view.wins.*;
import javax.swing.*;
import java.awt.*;
public class Screen extends JFrame {
private final int WIDTH;
private final int HEIGHT;
private static Screen instance = null;
private JComponent titleWindow, menuWindow, settingsWindow;
private Screen(){
WIDTH = 1152;
HEIGHT = 768;
setTitle("Game");
setDefaultCloseOperation(EXIT_ON_CLOSE);
Dimension dim = Toolkit.getDefaultToolkit().getScreenSize();
this.setLocation(dim.width/2-WIDTH/2, (dim.height - 50)/2-HEIGHT/2);
Dimension size = new Dimension(WIDTH, HEIGHT);
setPreferredSize(size);
setMinimumSize(size);
setMaximumSize(size);
setSize(size);
setResizable(false);
setVisible(true);
}
public static Screen getInstance() {
if(instance == null){
instance = new Screen();
}
return instance;
}
public void seeWindow(GameState state){
switch(state){
case TITLE -> setContentPane(getTitleWindow());
case MENU -> setContentPane(getMenuWindow());
case SETTINGS -> setContentPane(getSettingsWindow());
}
pack();
}
public void requestFocus(GameState state){
switch (state){
case TITLE -> getTitleWindow().requestFocus();
case MENU -> getMenuWindow().requestFocus();
case SETTINGS -> getSettingsWindow().requestFocus();
}
}
private JComponent getTitleWindow(){
if(titleWindow == null){
titleWindow = new TitleWindow();
}
return titleWindow;
}
private JComponent getMenuWindow(){
if(menuWindow == null){
menuWindow = new MenuWindow();
}
return menuWindow;
}
private JComponent getSettingsWindow(){
if(settingsWindow == null){
settingsWindow = new SettingsWindow();
}
return settingsWindow;
}
}
Window摘要class:
package view.wins;
import utilz.GFXManager;
import view.Screen;
import javax.swing.*;
import java.awt.*;
import java.util.Observer;
public abstract class Window extends JComponent implements Observer {
private Image background;
public Window(String background){
setLayout(new BorderLayout());
setPreferredSize(Screen.getInstance().getPreferredSize());
setBackground(background);
setFocusable(true);
}
protected void setBackground(String backgroundName){
this.background = GFXManager.getInstance().getImage("backgrounds/" + backgroundName + ".png");
}
@Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
g.drawImage(background, 0, 0, null);
}
}
A Window 背景设置正确:
package view.wins;
import game.GameState;
import game.Game;
import jdk.swing.interop.SwingInterOpUtils;
import logic.TitleLogic;
import utilz.GFXManager;
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
import java.util.Observable;
import java.util.Observer;
public class TitleWindow extends Window implements Observer {
private final String BELOW_TITLE_TEXT;
private final ImageIcon TITLE_ICON;
private JLabel lblTitleIcon, lblBelowTitle;
private KeyController keyController;
public TitleWindow(){
super("title_background");
BELOW_TITLE_TEXT = "Press enter to start";
TITLE_ICON = new ImageIcon(GFXManager.getInstance().getImage("texts/title.png"));
TitleLogic.getInstance().addObserver(this);
setLayout(new BorderLayout());
addKeyListener(new KeyController());
add(getLblTitleIcon(), BorderLayout.CENTER);
add(getLblBelowTitle(), BorderLayout.SOUTH);
}
private JLabel getLblTitleIcon(){
if(lblTitleIcon == null){
lblTitleIcon = new JLabel(TITLE_ICON);
}
return lblTitleIcon;
}
private JLabel getLblBelowTitle(){
if(lblBelowTitle == null){
lblBelowTitle = new JLabel(BELOW_TITLE_TEXT, SwingConstants.CENTER);
lblBelowTitle.setFont(new Font("MS Gothic", Font.PLAIN, 24));
lblBelowTitle.setForeground(new Color(30,230,120));
}
return lblBelowTitle;
}
private KeyController getKeyController(){
if(keyController == null){
keyController = new KeyController();
}
return keyController;
}
@Override
public void update(Observable o, Object arg) {
if(TitleLogic.getInstance().isTickColorChange()){
getLblBelowTitle().setForeground(new Color(120, 30, 230));
}else{
getLblBelowTitle().setForeground(new Color(30,230,120));
}
}
private class KeyController extends KeyAdapter {
@Override
public void keyTyped(KeyEvent e) {
if(e.getKeyChar() == '\n'){
Game.getInstance().setState(GameState.MENU);
}else if(e.getKeyChar() == 'c'){
TitleLogic.getInstance().tickColorChange();
}
}
}
}
未设置背景的Window:
package view.wins;
import game.GameState;
import game.Game;
import logic.MenuLogic;
import view.objs.Button;
import javax.swing.*;
import java.awt.*;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.util.Observable;
import java.util.Observer;
public class MenuWindow extends Window implements Observer {
private JPanel btnPanel;
private Button btnStartNewGame, btnLoadGame, btnSettings, btnExit;
private Controller controller;
public MenuWindow(){
super("title_background");
MenuLogic.getInstance().addObserver(this);
setLayout(new BorderLayout());
add(getBtnPanel(), BorderLayout.CENTER);
}
private JPanel getBtnPanel(){
if(btnPanel == null){
btnPanel = new JPanel(new GridLayout(4,1));
btnPanel.add(getBtnStartNewGame());
btnPanel.add(getBtnLoadGame());
btnPanel.add(getBtnSettings());
btnPanel.add(getBtnExit());
}
return btnPanel;
}
private Button getBtnStartNewGame(){
if(btnStartNewGame == null){
btnStartNewGame = new Button("mediumLong", "Start new game", getController());
}
return btnStartNewGame;
}
private Button getBtnLoadGame(){
if(btnLoadGame == null){
btnLoadGame = new Button("mediumLong", "Load game", getController());
}
return btnLoadGame;
}
private Button getBtnSettings(){
if(btnSettings == null){
btnSettings = new Button("mediumLong", "Settings", getController());
}
return btnSettings;
}
private Button getBtnExit(){
if(btnExit == null){
btnExit = new Button("mediumLong", "Exit", getController());
}
return btnExit;
}
private Controller getController(){
if(controller == null){
controller = new Controller();
}
return controller;
}
@Override
public void update(Observable o, Object arg) {
}
private class Controller extends MouseAdapter {
@Override
public void mouseClicked(MouseEvent e) {
if(e.getSource().equals(getBtnStartNewGame())){
}else if(e.getSource().equals(getBtnLoadGame())){
}else if(e.getSource().equals(getBtnSettings())){
Game.getInstance().setState(GameState.SETTINGS);
}else if(e.getSource().equals(getBtnExit())){
System.exit(0);
}
}
@Override
public void mouseEntered(MouseEvent e) {
((Button)e.getSource()).changeHighlight();
}
@Override
public void mouseExited(MouseEvent e) {
((Button)e.getSource()).changeHighlight();
}
}
}
按钮是我自己的class。
如果有人想自己测试它或查看更多代码 here 是 github 存储库。
我尝试了一切,甚至更多。我注意到 MenuWindow 的 Graphics 没有被初始化,所以问题可能是因为 window 没有被渲染。我不知道。
这个...
private void run(){
while(true){
if(GameState.changed){
Screen.getInstance().seeWindow(state);
GameState.changed = false;
}else {
Screen.getInstance().requestFocus(state);
}
}
}
是个坏主意。除了“野循环”本身通常不是一个好主意这一事实之外,Swing 也不是线程安全的并且是单线程的。
这意味着如果这是 运行 在事件调度线程的上下文中,它将阻止它并阻止它处理任何新事件。如果它不在 EDT 中 运行,您就有可能导致任何数量的图形故障或其他“脏线程”问题。
再加上像这样的“狂野循环”也会消耗 CPU 个周期这一事实,您会增加巨大的性能开销,但收效甚微。
先看看 Swing 中的并发
Screen.getInstance().requestFocus(state);
也是对 KeyListener
已知局限性的破解,通过使用 key bindings API
可以更好地解决这些局限性
考虑到所有这些因素,Kavaliro
应该看起来更像...
package game;
import view.Screen;
public class Kavaliro {
private static Kavaliro instance = null;
private GameState state;
private Kavaliro() {
state = GameState.TITLE;
Screen.getInstance().seeWindow(state);
}
public static Kavaliro getInstance() {
if (instance == null) {
instance = new Kavaliro();
}
return instance;
}
public static void main(String[] args) {
Kavaliro game = Kavaliro.getInstance();
}
public void setState(GameState state) {
Screen.getInstance().seeWindow(state);
}
}
这也不是在 Java 中创建单例的好方法。
public static Kavaliro getInstance() {
if (instance == null) {
instance = new Kavaliro();
}
return instance;
}
你可以看看Java Singleton Design Pattern Best Practices with Examples and What is an efficient way to implement a singleton pattern in Java? OR you could just make use of Dependency Injection (and probably some research into dependency injection vs singleton)
现在,正如我所说,KeyListener
通常是监视用户键盘输入的一个糟糕选择,这意味着 TitleWindow
应该改变,更像是...
package view.wins;
import game.GameState;
import game.Kavaliro;
import game.Utilities;
import logic.TitleLogic;
import utilz.GFXManager;
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
import java.util.Observable;
import java.util.Observer;
public class TitleWindow extends Window implements Observer {
private final String BELOW_TITLE_TEXT;
private final ImageIcon TITLE_ICON;
private JLabel lblTitleIcon, lblBelowTitle;
public TitleWindow() {
super("title_background");
BELOW_TITLE_TEXT = "Press enter to start";
TITLE_ICON = new ImageIcon(GFXManager.getInstance().getImage("texts/title.png"));
TitleLogic.getInstance().addObserver(this);
setLayout(new BorderLayout());
Utilities.addKeyBinding(this, Utilities.keyStrokeFor(Utilities.Input.ENTER), new AbstractAction() {
@Override
public void actionPerformed(ActionEvent e) {
Kavaliro.getInstance().setState(GameState.MENU);
}
});
Utilities.addKeyBinding(this, Utilities.keyStrokeFor(Utilities.Input.TITLE_TOGGLE), new AbstractAction() {
@Override
public void actionPerformed(ActionEvent e) {
TitleLogic.getInstance().tickColorChange();
}
});
add(getLblTitleIcon(), BorderLayout.CENTER);
add(getLblBelowTitle(), BorderLayout.SOUTH);
}
private JLabel getLblTitleIcon() {
if (lblTitleIcon == null) {
lblTitleIcon = new JLabel(TITLE_ICON);
}
return lblTitleIcon;
}
private JLabel getLblBelowTitle() {
if (lblBelowTitle == null) {
lblBelowTitle = new JLabel(BELOW_TITLE_TEXT, SwingConstants.CENTER);
lblBelowTitle.setFont(new Font("MS Gothic", Font.PLAIN, 24));
lblBelowTitle.setForeground(new Color(30, 230, 120));
}
return lblBelowTitle;
}
@Override
public void update(Observable o, Object arg) {
if (TitleLogic.getInstance().isTickColorChange()) {
getLblBelowTitle().setForeground(new Color(120, 30, 230));
} else {
getLblBelowTitle().setForeground(new Color(30, 230, 120));
}
}
}
现在,为了让生活更轻松,我写了一个快速的“实用程序”class...
package game;
import java.awt.event.KeyEvent;
import javax.swing.Action;
import javax.swing.ActionMap;
import javax.swing.InputMap;
import javax.swing.JComponent;
import static javax.swing.JComponent.WHEN_IN_FOCUSED_WINDOW;
import javax.swing.KeyStroke;
public class Utilities {
public static enum KeyState {
PRESSED, RELEASED
}
public static enum Input {
ENTER(KeyEvent.VK_ENTER), TITLE_TOGGLE(KeyEvent.VK_C);
private int keyEvent;
private Input(int keyEvent) {
this.keyEvent = keyEvent;
}
protected KeyStroke getKeyStroke(int modifiers, KeyState keyState) {
return KeyStroke.getKeyStroke(keyEvent, 0, keyState == KeyState.RELEASED ? true : false);
}
}
public static void addKeyBinding(JComponent parent, KeyStroke keyStroke, Action action) {
addKeyBinding(parent, keyStroke.toString(), keyStroke, action);
}
public static void addKeyBinding(JComponent parent, String name, KeyStroke keyStroke, Action action) {
InputMap inputMap = parent.getInputMap(WHEN_IN_FOCUSED_WINDOW);
ActionMap actionMap = parent.getActionMap();
inputMap.put(keyStroke, name);
actionMap.put(name, action);
}
public static KeyStroke keyStrokeFor(Input key) {
return keyStrokeFor(key, 0, KeyState.PRESSED);
}
public static KeyStroke keyStrokeFor(Input key, int modifiers) {
return keyStrokeFor(key, modifiers, KeyState.PRESSED);
}
public static KeyStroke keyStrokeFor(Input key, int modifiers, KeyState keyState) {
return key.getKeyStroke(modifiers, keyState);
}
public static KeyStroke keyStrokeFor(int key) {
return keyStrokeFor(key, 0, KeyState.PRESSED);
}
public static KeyStroke keyStrokeFor(int key, int modifiers) {
return keyStrokeFor(key, modifiers, KeyState.PRESSED);
}
public static KeyStroke keyStrokeFor(int key, int modifiers, KeyState keyState) {
return KeyStroke.getKeyStroke(key, 0, keyState == KeyState.RELEASED ? true : false);
}
}
这处理了很多锅炉 plate/repeating 代码,但也提供了一种方法来“限制”对 API 的可能输入。请注意 Input
enum
允许 ENTER
和 TITLE_TOGGLE
作为 keyStrokeFor
方法的可行选项。
请注意,TITLE_TOGGLE
“隐藏”了所使用的击键,但同时它也是非常自我记录的。您可以通过许多其他方式来构建这些概念,这只是一个示例。
还有...
public class Button extends JLabel {
会从我这里得到一个非常大的,不,。 API 中已经有一个按钮组件,您应该使用它。它甚至支持诸如翻滚之类的功能, and example,以及许多其他功能,您将花费大量时间 re-building。
参见 How to Use Buttons, Check Boxes, and Radio Buttons。而且,是的,如果您确实需要,您可以删除外观填充背景和边框。
我不了解你,我也不使用 Intellij,但是 return ImageIO.read(new File(GFX_PATH + name));
对我来说很糟糕。
像这样的资源应该真正嵌入到应用程序的运行时上下文中(“包含在 Jar 中”的技术术语)。当“工作目录”上下文与 res
文件夹的位置不同时,这将防止在运行时尝试定位资源时出现问题。
你应该使用...
return ImageIO.read(getClass().getResource(GFX_PATH + name));
但是我不知道如何配置 Intellji 以将 res
文件夹的上下文包含到您的应用程序上下文中。
public void seeWindow(GameState state){
// Use a CardLayout
switch(state){
case TITLE -> setContentPane(getTitleWindow());
case MENU -> setContentPane(getMenuWindow());
case SETTINGS -> setContentPane(getSettingsWindow());
}
pack();
}
好吧,目光短浅。相反,您应该使用 CardLayout
来为您执行此操作,并且通常可以可靠地工作。
参见如何使用 CardLayout
了解更多详情
我正在尝试为不同的 JPanel 添加背景(以后它们将被称为 Window)。这些 Windows 是我创建的 classes 并使它们继承 JPanel。然后根据程序的状态,将 Window 设置为程序的 JFrame 的内容面板。当在某些 Windows 中设置了背景而在其他人中没有设置时,问题就来了。背景设置是使用paintComponent(Graphics g)方法进行的,但是尽管我尝试修复了这个错误,但我没有成功。
这是我认为可能对那些想要帮助的人有用的代码:
主循环:
public class Game{
//here comes other stuff (constructor, main, other methods...)
private void run(){
while(true){
if(GameState.changed){
Screen.getInstance().seeWindow(state);
GameState.changed = false;
}else {
Screen.getInstance().requestFocus(state);
}
}
}
}
屏幕class:
package view;
import game.GameState;
import view.wins.*;
import javax.swing.*;
import java.awt.*;
public class Screen extends JFrame {
private final int WIDTH;
private final int HEIGHT;
private static Screen instance = null;
private JComponent titleWindow, menuWindow, settingsWindow;
private Screen(){
WIDTH = 1152;
HEIGHT = 768;
setTitle("Game");
setDefaultCloseOperation(EXIT_ON_CLOSE);
Dimension dim = Toolkit.getDefaultToolkit().getScreenSize();
this.setLocation(dim.width/2-WIDTH/2, (dim.height - 50)/2-HEIGHT/2);
Dimension size = new Dimension(WIDTH, HEIGHT);
setPreferredSize(size);
setMinimumSize(size);
setMaximumSize(size);
setSize(size);
setResizable(false);
setVisible(true);
}
public static Screen getInstance() {
if(instance == null){
instance = new Screen();
}
return instance;
}
public void seeWindow(GameState state){
switch(state){
case TITLE -> setContentPane(getTitleWindow());
case MENU -> setContentPane(getMenuWindow());
case SETTINGS -> setContentPane(getSettingsWindow());
}
pack();
}
public void requestFocus(GameState state){
switch (state){
case TITLE -> getTitleWindow().requestFocus();
case MENU -> getMenuWindow().requestFocus();
case SETTINGS -> getSettingsWindow().requestFocus();
}
}
private JComponent getTitleWindow(){
if(titleWindow == null){
titleWindow = new TitleWindow();
}
return titleWindow;
}
private JComponent getMenuWindow(){
if(menuWindow == null){
menuWindow = new MenuWindow();
}
return menuWindow;
}
private JComponent getSettingsWindow(){
if(settingsWindow == null){
settingsWindow = new SettingsWindow();
}
return settingsWindow;
}
}
Window摘要class:
package view.wins;
import utilz.GFXManager;
import view.Screen;
import javax.swing.*;
import java.awt.*;
import java.util.Observer;
public abstract class Window extends JComponent implements Observer {
private Image background;
public Window(String background){
setLayout(new BorderLayout());
setPreferredSize(Screen.getInstance().getPreferredSize());
setBackground(background);
setFocusable(true);
}
protected void setBackground(String backgroundName){
this.background = GFXManager.getInstance().getImage("backgrounds/" + backgroundName + ".png");
}
@Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
g.drawImage(background, 0, 0, null);
}
}
A Window 背景设置正确:
package view.wins;
import game.GameState;
import game.Game;
import jdk.swing.interop.SwingInterOpUtils;
import logic.TitleLogic;
import utilz.GFXManager;
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
import java.util.Observable;
import java.util.Observer;
public class TitleWindow extends Window implements Observer {
private final String BELOW_TITLE_TEXT;
private final ImageIcon TITLE_ICON;
private JLabel lblTitleIcon, lblBelowTitle;
private KeyController keyController;
public TitleWindow(){
super("title_background");
BELOW_TITLE_TEXT = "Press enter to start";
TITLE_ICON = new ImageIcon(GFXManager.getInstance().getImage("texts/title.png"));
TitleLogic.getInstance().addObserver(this);
setLayout(new BorderLayout());
addKeyListener(new KeyController());
add(getLblTitleIcon(), BorderLayout.CENTER);
add(getLblBelowTitle(), BorderLayout.SOUTH);
}
private JLabel getLblTitleIcon(){
if(lblTitleIcon == null){
lblTitleIcon = new JLabel(TITLE_ICON);
}
return lblTitleIcon;
}
private JLabel getLblBelowTitle(){
if(lblBelowTitle == null){
lblBelowTitle = new JLabel(BELOW_TITLE_TEXT, SwingConstants.CENTER);
lblBelowTitle.setFont(new Font("MS Gothic", Font.PLAIN, 24));
lblBelowTitle.setForeground(new Color(30,230,120));
}
return lblBelowTitle;
}
private KeyController getKeyController(){
if(keyController == null){
keyController = new KeyController();
}
return keyController;
}
@Override
public void update(Observable o, Object arg) {
if(TitleLogic.getInstance().isTickColorChange()){
getLblBelowTitle().setForeground(new Color(120, 30, 230));
}else{
getLblBelowTitle().setForeground(new Color(30,230,120));
}
}
private class KeyController extends KeyAdapter {
@Override
public void keyTyped(KeyEvent e) {
if(e.getKeyChar() == '\n'){
Game.getInstance().setState(GameState.MENU);
}else if(e.getKeyChar() == 'c'){
TitleLogic.getInstance().tickColorChange();
}
}
}
}
未设置背景的Window:
package view.wins;
import game.GameState;
import game.Game;
import logic.MenuLogic;
import view.objs.Button;
import javax.swing.*;
import java.awt.*;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.util.Observable;
import java.util.Observer;
public class MenuWindow extends Window implements Observer {
private JPanel btnPanel;
private Button btnStartNewGame, btnLoadGame, btnSettings, btnExit;
private Controller controller;
public MenuWindow(){
super("title_background");
MenuLogic.getInstance().addObserver(this);
setLayout(new BorderLayout());
add(getBtnPanel(), BorderLayout.CENTER);
}
private JPanel getBtnPanel(){
if(btnPanel == null){
btnPanel = new JPanel(new GridLayout(4,1));
btnPanel.add(getBtnStartNewGame());
btnPanel.add(getBtnLoadGame());
btnPanel.add(getBtnSettings());
btnPanel.add(getBtnExit());
}
return btnPanel;
}
private Button getBtnStartNewGame(){
if(btnStartNewGame == null){
btnStartNewGame = new Button("mediumLong", "Start new game", getController());
}
return btnStartNewGame;
}
private Button getBtnLoadGame(){
if(btnLoadGame == null){
btnLoadGame = new Button("mediumLong", "Load game", getController());
}
return btnLoadGame;
}
private Button getBtnSettings(){
if(btnSettings == null){
btnSettings = new Button("mediumLong", "Settings", getController());
}
return btnSettings;
}
private Button getBtnExit(){
if(btnExit == null){
btnExit = new Button("mediumLong", "Exit", getController());
}
return btnExit;
}
private Controller getController(){
if(controller == null){
controller = new Controller();
}
return controller;
}
@Override
public void update(Observable o, Object arg) {
}
private class Controller extends MouseAdapter {
@Override
public void mouseClicked(MouseEvent e) {
if(e.getSource().equals(getBtnStartNewGame())){
}else if(e.getSource().equals(getBtnLoadGame())){
}else if(e.getSource().equals(getBtnSettings())){
Game.getInstance().setState(GameState.SETTINGS);
}else if(e.getSource().equals(getBtnExit())){
System.exit(0);
}
}
@Override
public void mouseEntered(MouseEvent e) {
((Button)e.getSource()).changeHighlight();
}
@Override
public void mouseExited(MouseEvent e) {
((Button)e.getSource()).changeHighlight();
}
}
}
按钮是我自己的class。
如果有人想自己测试它或查看更多代码 here 是 github 存储库。
我尝试了一切,甚至更多。我注意到 MenuWindow 的 Graphics 没有被初始化,所以问题可能是因为 window 没有被渲染。我不知道。
这个...
private void run(){
while(true){
if(GameState.changed){
Screen.getInstance().seeWindow(state);
GameState.changed = false;
}else {
Screen.getInstance().requestFocus(state);
}
}
}
是个坏主意。除了“野循环”本身通常不是一个好主意这一事实之外,Swing 也不是线程安全的并且是单线程的。
这意味着如果这是 运行 在事件调度线程的上下文中,它将阻止它并阻止它处理任何新事件。如果它不在 EDT 中 运行,您就有可能导致任何数量的图形故障或其他“脏线程”问题。
再加上像这样的“狂野循环”也会消耗 CPU 个周期这一事实,您会增加巨大的性能开销,但收效甚微。
先看看 Swing 中的并发
Screen.getInstance().requestFocus(state);
也是对 KeyListener
已知局限性的破解,通过使用 key bindings API
考虑到所有这些因素,Kavaliro
应该看起来更像...
package game;
import view.Screen;
public class Kavaliro {
private static Kavaliro instance = null;
private GameState state;
private Kavaliro() {
state = GameState.TITLE;
Screen.getInstance().seeWindow(state);
}
public static Kavaliro getInstance() {
if (instance == null) {
instance = new Kavaliro();
}
return instance;
}
public static void main(String[] args) {
Kavaliro game = Kavaliro.getInstance();
}
public void setState(GameState state) {
Screen.getInstance().seeWindow(state);
}
}
这也不是在 Java 中创建单例的好方法。
public static Kavaliro getInstance() {
if (instance == null) {
instance = new Kavaliro();
}
return instance;
}
你可以看看Java Singleton Design Pattern Best Practices with Examples and What is an efficient way to implement a singleton pattern in Java? OR you could just make use of Dependency Injection (and probably some research into dependency injection vs singleton)
现在,正如我所说,KeyListener
通常是监视用户键盘输入的一个糟糕选择,这意味着 TitleWindow
应该改变,更像是...
package view.wins;
import game.GameState;
import game.Kavaliro;
import game.Utilities;
import logic.TitleLogic;
import utilz.GFXManager;
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
import java.util.Observable;
import java.util.Observer;
public class TitleWindow extends Window implements Observer {
private final String BELOW_TITLE_TEXT;
private final ImageIcon TITLE_ICON;
private JLabel lblTitleIcon, lblBelowTitle;
public TitleWindow() {
super("title_background");
BELOW_TITLE_TEXT = "Press enter to start";
TITLE_ICON = new ImageIcon(GFXManager.getInstance().getImage("texts/title.png"));
TitleLogic.getInstance().addObserver(this);
setLayout(new BorderLayout());
Utilities.addKeyBinding(this, Utilities.keyStrokeFor(Utilities.Input.ENTER), new AbstractAction() {
@Override
public void actionPerformed(ActionEvent e) {
Kavaliro.getInstance().setState(GameState.MENU);
}
});
Utilities.addKeyBinding(this, Utilities.keyStrokeFor(Utilities.Input.TITLE_TOGGLE), new AbstractAction() {
@Override
public void actionPerformed(ActionEvent e) {
TitleLogic.getInstance().tickColorChange();
}
});
add(getLblTitleIcon(), BorderLayout.CENTER);
add(getLblBelowTitle(), BorderLayout.SOUTH);
}
private JLabel getLblTitleIcon() {
if (lblTitleIcon == null) {
lblTitleIcon = new JLabel(TITLE_ICON);
}
return lblTitleIcon;
}
private JLabel getLblBelowTitle() {
if (lblBelowTitle == null) {
lblBelowTitle = new JLabel(BELOW_TITLE_TEXT, SwingConstants.CENTER);
lblBelowTitle.setFont(new Font("MS Gothic", Font.PLAIN, 24));
lblBelowTitle.setForeground(new Color(30, 230, 120));
}
return lblBelowTitle;
}
@Override
public void update(Observable o, Object arg) {
if (TitleLogic.getInstance().isTickColorChange()) {
getLblBelowTitle().setForeground(new Color(120, 30, 230));
} else {
getLblBelowTitle().setForeground(new Color(30, 230, 120));
}
}
}
现在,为了让生活更轻松,我写了一个快速的“实用程序”class...
package game;
import java.awt.event.KeyEvent;
import javax.swing.Action;
import javax.swing.ActionMap;
import javax.swing.InputMap;
import javax.swing.JComponent;
import static javax.swing.JComponent.WHEN_IN_FOCUSED_WINDOW;
import javax.swing.KeyStroke;
public class Utilities {
public static enum KeyState {
PRESSED, RELEASED
}
public static enum Input {
ENTER(KeyEvent.VK_ENTER), TITLE_TOGGLE(KeyEvent.VK_C);
private int keyEvent;
private Input(int keyEvent) {
this.keyEvent = keyEvent;
}
protected KeyStroke getKeyStroke(int modifiers, KeyState keyState) {
return KeyStroke.getKeyStroke(keyEvent, 0, keyState == KeyState.RELEASED ? true : false);
}
}
public static void addKeyBinding(JComponent parent, KeyStroke keyStroke, Action action) {
addKeyBinding(parent, keyStroke.toString(), keyStroke, action);
}
public static void addKeyBinding(JComponent parent, String name, KeyStroke keyStroke, Action action) {
InputMap inputMap = parent.getInputMap(WHEN_IN_FOCUSED_WINDOW);
ActionMap actionMap = parent.getActionMap();
inputMap.put(keyStroke, name);
actionMap.put(name, action);
}
public static KeyStroke keyStrokeFor(Input key) {
return keyStrokeFor(key, 0, KeyState.PRESSED);
}
public static KeyStroke keyStrokeFor(Input key, int modifiers) {
return keyStrokeFor(key, modifiers, KeyState.PRESSED);
}
public static KeyStroke keyStrokeFor(Input key, int modifiers, KeyState keyState) {
return key.getKeyStroke(modifiers, keyState);
}
public static KeyStroke keyStrokeFor(int key) {
return keyStrokeFor(key, 0, KeyState.PRESSED);
}
public static KeyStroke keyStrokeFor(int key, int modifiers) {
return keyStrokeFor(key, modifiers, KeyState.PRESSED);
}
public static KeyStroke keyStrokeFor(int key, int modifiers, KeyState keyState) {
return KeyStroke.getKeyStroke(key, 0, keyState == KeyState.RELEASED ? true : false);
}
}
这处理了很多锅炉 plate/repeating 代码,但也提供了一种方法来“限制”对 API 的可能输入。请注意 Input
enum
允许 ENTER
和 TITLE_TOGGLE
作为 keyStrokeFor
方法的可行选项。
请注意,TITLE_TOGGLE
“隐藏”了所使用的击键,但同时它也是非常自我记录的。您可以通过许多其他方式来构建这些概念,这只是一个示例。
还有...
public class Button extends JLabel {
会从我这里得到一个非常大的,不,。 API 中已经有一个按钮组件,您应该使用它。它甚至支持诸如翻滚之类的功能,
参见 How to Use Buttons, Check Boxes, and Radio Buttons。而且,是的,如果您确实需要,您可以删除外观填充背景和边框。
我不了解你,我也不使用 Intellij,但是 return ImageIO.read(new File(GFX_PATH + name));
对我来说很糟糕。
像这样的资源应该真正嵌入到应用程序的运行时上下文中(“包含在 Jar 中”的技术术语)。当“工作目录”上下文与 res
文件夹的位置不同时,这将防止在运行时尝试定位资源时出现问题。
你应该使用...
return ImageIO.read(getClass().getResource(GFX_PATH + name));
但是我不知道如何配置 Intellji 以将 res
文件夹的上下文包含到您的应用程序上下文中。
public void seeWindow(GameState state){
// Use a CardLayout
switch(state){
case TITLE -> setContentPane(getTitleWindow());
case MENU -> setContentPane(getMenuWindow());
case SETTINGS -> setContentPane(getSettingsWindow());
}
pack();
}
好吧,目光短浅。相反,您应该使用 CardLayout
来为您执行此操作,并且通常可以可靠地工作。
参见如何使用 CardLayout 了解更多详情