Home > Net >  Updating custom cursor in java swing window
Updating custom cursor in java swing window

Time:01-23

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) {

    }
}

CodePudding user response:

Without more context, it's difficult to spot the error. Potential reasons:

  • The frame instance your window class and your other class are not the same
  • You cursor is not set on frame but another component

Here's a working example:

Main App:

public class MyApp extends JFrame {

    public static void main(String[] args) {
        SwingUtilities.invokeLater(new Runnable() {
            public void run() {
                MyApp app = new MyApp();
                app.setVisible(true);
            }
        });
    }

    private MyApp() {
        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        setSize(600, 600);

        // Create cursors
        Cursor c1 = Util.getCursor("c1.png");
        Cursor c2 = Util.getCursor("c2.png");

        setCursor(c1);

        JButton button1 = new JButton("Change to Cursor1");
        button1.setActionCommand("c1");
        button1.addActionListener(new MyActionListener(this));

        JButton button2 = new JButton("Change to Cursor2");
        button2.setActionCommand("c2");
        button2.addActionListener(new MyActionListener(this));

        add(button1, BorderLayout.NORTH);
        add(button2, BorderLayout.SOUTH);
    }

}

ActionListener (handles the button clicks):

public class MyActionListener implements ActionListener {

    private JFrame jFrame;

    public MyActionListener(JFrame jFrame) {
        this.jFrame = jFrame;
    }

    @Override
    public void actionPerformed(ActionEvent e) {
        switch (e.getActionCommand()){
            case "c1":
                jFrame.setCursor(Util.getCursor("c1.png"));
                System.out.println("switch to c1");
                break;
            case "c2":
                jFrame.setCursor(Util.getCursor("c2.png"));
                System.out.println("switch to c2");
                break;
        }
    }

}

Util (read cursor image):

public class Util {
    public static Cursor getCursor(String fileName) {
        BufferedImage img = null;
        try {
            img = ImageIO.read(Util.class.getResourceAsStream(fileName));
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
        return Toolkit.getDefaultToolkit().createCustomCursor(img, new Point(0, 0), fileName);
    }

}

CodePudding user response:

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.

  • We're generally discouraged from extending from top level containers like JFrame, as stated, JFrame is not a simple component and you're not actually adding any new functionality to the class and in the process, locking yourself into a single use, there by reducing re-usability. Better to start with a JPanel as your base component and simply create an instance of JFrame or `JDialog or what ever top level container you want to use, when you need it
  • KeyListener is a poor choice for monitoring keyboard input (seriously, just do a search for "my key listener won't work". Instead, take a look at How to Use Key Bindings
  • Related