javaswingwindowcustom-cursor

Updating custom cursor in java swing window


I want to set custom cursor in my java swing app, and then edit it.

I set a custom cusrsor after showing window (in "Window" class). Later in code (in the other class), I want to chainge it again, so i call this updateCursor() funcion (in "Window" class again), and it and it won't work. There is no errors or warnings, but the cursor isn't changing - just stays the same. I tried, and I can't find answer anywhere. I appreciate any help.

This is full code - Window.java:

import MainMenu;

import javax.imageio.ImageIO;
import javax.swing.*;
import java.awt.*;
import java.io.File;
import java.io.IOException;

public class Window {
    public static final int WIDTH = 817, HEIGHT = 640;

    JFrame frame = new JFrame("");

    public void open() {
        frame.pack();
        frame.setVisible(true);
        frame.setResizable(false);
        frame.setSize(WIDTH - 33, HEIGHT - 25);
        frame.setLocationRelativeTo(null);
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

        frame.setFocusable(true);
        frame.requestFocus();
        frame.setFocusTraversalKeysEnabled(true);

        frame.addKeyListener(new InputManager());
        frame.addMouseListener(new InputManager());
        frame.add(new MainMenu());
        frame.add(new Game());

        loadCursors();

        updateCursor(0);
    }

    public static final int NORMAL = 0, ACTIVE = 1, INACTIVE = 2;

    Cursor cursor_normal, cursor_active, cursor_inactive;

    public void loadCursors() {
        try {
            cursor_normal = Toolkit.getDefaultToolkit().createCustomCursor(ImageIO.read(new File(new SpritesManager().cursor_normal)), new Point(0, 0), "custom cursor (normal)");
            cursor_active = Toolkit.getDefaultToolkit().createCustomCursor(ImageIO.read(new File(new SpritesManager().cursor_active)), new Point(0, 0), "custom cursor (active)");
            cursor_inactive = Toolkit.getDefaultToolkit().createCustomCursor(ImageIO.read(new File(new SpritesManager().cursor_inactive)), new Point(0, 0), "custom cursor (inactive)");
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    public void updateCursor(int cursorType) {
        switch (cursorType) {
            case NORMAL -> frame.setCursor(cursor_normal);
            case ACTIVE -> frame.setCursor(cursor_active);
            case INACTIVE -> frame.setCursor(cursor_inactive);
        }
    }
}

MainMenu.java:

import Window;

import javax.swing.*;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;

public class MainMenu extends JPanel implements KeyListener {

    @Override
    public void keyTyped(KeyEvent e) {

    }

    @Override
    public void keyPressed(KeyEvent e) {
        // testing
        new Window().updateCursor(Window.ACTIVE);
    }

    @Override
    public void keyReleased(KeyEvent e) {

    }
}

Solution

  • You can't do ...

    new Window().updateCursor(Window.ACTIVE);
    

    and magically expect the other instance of Window to be updated, in fact, you don't need to do this at all.

    This is going to create another instance/copy of Window, which is not present on the screen and it will have no effect on the instance which is been displayed.

    You could call setCursor directly on the instance MainMenu.

    Now, if you want to "centralise" the functionality, I would start by creating a "manager" class, for example...

    public class CursorManager {
    
        public enum CusorType {
            NORMAL, ACTIVE, INACTIVE;
        }
    
        private Cursor cursorNormal, cursorActive, cursorInactive;
    
        public CursorManager() throws IOException {
            cursorNormal = Toolkit.getDefaultToolkit().createCustomCursor(ImageIO.read(new File(new SpritesManager().cursor_normal)), new Point(0, 0), "custom cursor (normal)");
            cursorActive = Toolkit.getDefaultToolkit().createCustomCursor(ImageIO.read(new File(new SpritesManager().cursor_active)), new Point(0, 0), "custom cursor (active)");
            cursorInactive = Toolkit.getDefaultToolkit().createCustomCursor(ImageIO.read(new File(new SpritesManager().cursor_inactive)), new Point(0, 0), "custom cursor (inactive)");
        }
    
        public void setCursor(CusorType cursorType, Component comp) {
            switch (cursorType) {
                case NORMAL ->
                    comp.setCursor(cursorNormal);
                case ACTIVE ->
                    comp.setCursor(cursorActive);
                case INACTIVE ->
                    comp.setCursor(cursorInactive);
            }
        }
    }
    

    I would then create this instance of the manager during the initialisation phase of your code

    public static void main(String[] args) {
        EventQueue.invokeLater(new Runnable() {
            @Override
            public void run() {
                try {
                    CursorManager cursorManager = new CursorManager();
                    //.. Every thing else...
                } catch (IOException ex) {
                    Logger.getLogger(Main.class.getName()).log(Level.SEVERE, null, ex);
                }
            }
        });
    }
    

    And then pass this instance to every class that might need it...

    // You'll need to update Window to accept this parameter
    new Window(cursorManager).open();
    

    And...

    public class MainMenu extends JPanel implements KeyListener {
    
        private CursorManager cursorManager;
        
        private MainMenu(CursorManager cursorManager) {
            this.cursorManager = cursorManager;
        }
    
        @Override
        public void keyPressed(KeyEvent e) {
            // testing
            cursorManager.setCursor(CursorManager.CusorType.ACTIVE, this);
        }
    
        //...
    }
    

    This is commonly known as "dependency injection" and is VERY powerful

    Feedback

    Just as a side note, if I was doing something like this, it would be very different, but I tried to keep it simple.