如何在 Java 中实现大型自定义光标?

How To Implement Large Custom Cursor In Java?

这个问题与我之前的问题有关:

如果您对它的用途感到好奇,可以在以下位置找到名为 GATE [图形访问表格条目] 的屡获殊荣的密码保护系统的实际用例:http://gatecybertech.net/

在上一个问题之后,我找到了一种创建大型自定义光标的方法,工作答案是 post 在我之前的 post 中编辑的。但是为了实现它,我必须先单击一个复选框,现在我希望能够创建一个大的自定义光标而无需首先单击一个复选框,所以我修改了我的代码,使其看起来像下面没有复选框的样子:

import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
import java.awt.image.*;
import java.io.*;
import javax.imageio.*;
import javax.swing.event.MouseInputAdapter;

public class Demo_Large_Custom_Cursor_Simple
{
  static private MyGlassPane_Simple myGlassPane;

  private static void createAndShowGUI()
  {
    JFrame frame=new JFrame("Demo_Large_Custom_Cursor_Simple");
    frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

//    JCheckBox changeButton=new JCheckBox("Custom Cursor \"visible\"");
//    changeButton.setSelected(false);

    Container contentPane=frame.getContentPane();
    contentPane.setLayout(new FlowLayout());
//    contentPane.add(changeButton);

    JButton Button_1=new JButton("<Html><Table Cellpadding=7><Tr><Td>A</Td><Td>B</Td></Tr><Tr><Td>C</Td><Td>D</Td></Tr></Table></Html>");
    Button_1.setPreferredSize(new Dimension(80,80));
    Button_1.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent evt) { Out("Button 1"); } });
    contentPane.add(Button_1);

    JButton Button_2=new JButton("<Html><Table Cellpadding=7><Tr><Td>1</Td><Td>2</Td></Tr><Tr><Td>3</Td><Td>4</Td></Tr></Table></Html>");
    Button_2.setPreferredSize(new Dimension(80,80));
    Button_2.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent evt) { Out("Button 2"); } });
    contentPane.add(Button_2);

    JMenuBar menuBar=new JMenuBar();
    JMenu menu=new JMenu("Menu");
    menu.add(new JMenuItem("Do nothing"));
    menuBar.add(menu);
    frame.setJMenuBar(menuBar);

    // Set up the glass pane, which appears over both menu bar and content pane and is an item listener on the change button.
//    myGlassPane=new MyGlassPane_Simple(changeButton,menuBar,frame.getContentPane());
    myGlassPane=new MyGlassPane_Simple(menuBar,frame.getContentPane());
//    changeButton.addItemListener(myGlassPane);
    frame.setGlassPane(myGlassPane);

    frame.setLocationRelativeTo(null);
    frame.pack();
    frame.setVisible(true);
  }

  private static void out(String message) { System.out.print(message); }

  private static void Out(String message) { System.out.println(message); }

  public static void main(String[] args) { SwingUtilities.invokeLater(new Runnable() { public void run() { createAndShowGUI(); } }); }
}

// We have to provide our own glass pane so that it can paint.
class MyGlassPane_Simple extends JComponent implements ItemListener
{
  Point point;

//  public MyGlassPane_Simple(AbstractButton aButton,JMenuBar menuBar,Container contentPane)
  public MyGlassPane_Simple(JMenuBar menuBar,Container contentPane)
  {
//    CBListener_Simple listener=new CBListener_Simple(aButton,menuBar,this,contentPane);
    CBListener_Simple listener=new CBListener_Simple(menuBar,this,contentPane);
    addMouseListener(listener);
    addMouseMotionListener(listener);
  }

  // React to change button clicks.
  public void itemStateChanged(ItemEvent e) { setVisible(e.getStateChange()==ItemEvent.SELECTED); }

  protected void paintComponent(Graphics g)
  {
    try
    {
      if (point!=null)
      {
//      g.setColor(Color.red);
//      g.fillOval(point.x-10,point.y-10,20,20);

        BufferedImage image=ImageIO.read(new File("C:/Cursor_Crosshair.PNG"));
        g.drawImage(image,point.x-39,point.y-39,null);
      }
    }
    catch (Exception e) { }
  }

  public void setPoint(Point p) { point=p; }
}

// Listen for all events that our check box is likely to be interested in. Redispatch them to the check box.
class CBListener_Simple extends MouseInputAdapter
{
  Toolkit toolkit;
  Component liveButton;
  JMenuBar menuBar;
  MyGlassPane_Simple glassPane;
  Container contentPane;

//  public CBListener_Simple(Component liveButton,JMenuBar menuBar,MyGlassPane_Simple glassPane,Container contentPane)
  public CBListener_Simple(JMenuBar menuBar,MyGlassPane_Simple glassPane,Container contentPane)
  {
    toolkit=Toolkit.getDefaultToolkit();
    this.liveButton=liveButton;
    this.menuBar=menuBar;
    this.glassPane=glassPane;
    this.contentPane=contentPane;
  }

  public void mouseMoved(MouseEvent e)
  {
//    redispatchMouseEvent(e,false);
    redispatchMouseEvent(e,true);
  }

  public void mouseDragged(MouseEvent e) { redispatchMouseEvent(e,false); }
  public void mouseClicked(MouseEvent e) { redispatchMouseEvent(e,false); }
  public void mouseEntered(MouseEvent e) { redispatchMouseEvent(e,false); }
  public void mouseExited(MouseEvent e) { redispatchMouseEvent(e,false); }
  public void mousePressed(MouseEvent e) { redispatchMouseEvent(e,false); }
  public void mouseReleased(MouseEvent e) { redispatchMouseEvent(e,true); }

  // A basic implementation of redispatching events.
  private void redispatchMouseEvent(MouseEvent e,boolean repaint)
  {
    Point glassPanePoint=e.getPoint();
    Container container=contentPane;
    Point containerPoint=SwingUtilities.convertPoint(glassPane,glassPanePoint,contentPane);

    if (containerPoint.y<0)
    { // We're not in the content pane
      if (containerPoint.y+menuBar.getHeight()>=0)
      {
        // The mouse event is over the menu bar. Could handle specially.
      }
      else
      {
        // The mouse event is over non-system window decorations, such as the ones provided by the Java look and feel. Could handle specially.
      }
    }
    else
    {
      // The mouse event is probably over the content pane. Find out exactly which component it's over.  
      Component component=SwingUtilities.getDeepestComponentAt(container,containerPoint.x,containerPoint.y);

//      if ((component!=null) && (component.equals(liveButton)))
      if ((component!=null))
      {
        // Forward events over the check box.
        Point componentPoint=SwingUtilities.convertPoint(glassPane,glassPanePoint,component);
        component.dispatchEvent(new MouseEvent(component,e.getID(),e.getWhen(),e.getModifiers(),componentPoint.x,componentPoint.y,e.getClickCount(),e.isPopupTrigger()));
      }
    }

    // Update the glass pane if requested.
    if (repaint)
    {
      glassPane.setPoint(glassPanePoint);
      glassPane.repaint();
    }
  }
}

此应用中的 3 个 类 是:

Demo_Large_Custom_Cursor_Simple.java
MyGlassPane_Simple.java
CBListener_Simple.java

它们是 运行 此演示应用程序所需的全部 类。

Cursor_Crosshair.PNG 图像如下所示:

但是,它不再显示大的自定义光标,我想知道我做错了什么,基于这个现有的代码我应该怎么做才能在鼠标进入应用程序时显示大的自定义光标window?

感谢@Radiodef 对我上一个问题的鼓舞人心的回答:

这是符合我要求的答案的修改版本:

import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
import java.awt.image.*;
import java.io.*;
import javax.imageio.*;

public class Demo_Large_Custom_Cursor_Simple
{
  static Insets An_Inset=new Insets(0,0,0,0);

  private static void createAndShowGUI()
  {
    JFrame frame=new JFrame("Demo_Large_Custom_Cursor_Simple");
    frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

    Container contentPane=frame.getContentPane();
    contentPane.setLayout(new FlowLayout());
    contentPane.setPreferredSize(new Dimension(2*80+15,2*80+15));

    int Font_Size=6;
    String Color="blue",Font_Face="Monospaced",
           Token_1="<Html>"+
                   "  <Table Border=0 Cellspacing=3 Cellpadding=3>"+
                   "    <Tr><Td Align=Center><Font Size="+Font_Size+" Color="+Color+">1</Font></Td><Td Align=Center><Font Size="+Font_Size+" Color="+Color+">A</Font></Td></Tr>"+
                   "    <Tr><Td Align=Center><Font Size="+Font_Size+" Color="+Color+">\u2664</Font></Td><Td Align=Center><Font Size="+Font_Size+" Color="+Color+">\u203b</Font></Td></Tr>"+
                   "  </Table>"+
                   "</Html>",
           Token_2="<Html>"+
                   "  <Table Border=0 Cellspacing=3 Cellpadding=3>"+
                   "    <Tr><Td Align=Center><Font Size="+Font_Size+" Color="+Color+">2</Font></Td><Td Align=Center><Font Size="+Font_Size+" Color="+Color+">B</Font></Td></Tr>"+
                   "    <Tr><Td Align=Center><Font Size="+Font_Size+" Color="+Color+">\u2660</Font></Td><Td Align=Center><Font Size="+Font_Size+" Color="+Color+">\u2638</Font></Td></Tr>"+
                   "  </Table>"+
                   "</Html>",
           Token_3="<Html>"+
                   "  <Table Border=0 Cellspacing=3 Cellpadding=3>"+
                   "    <Tr><Td Align=Center><Font Size="+Font_Size+" Color="+Color+">3</Font></Td><Td Align=Center><Font Size="+Font_Size+" Color="+Color+">C</Font></Td></Tr>"+
                   "    <Tr><Td Align=Center><Font Size="+Font_Size+" Color="+Color+">\u2667</Font></Td><Td Align=Center><Font Size="+Font_Size+" Color="+Color+">\u2668</Font></Td></Tr>"+
                   "  </Table>"+
                   "</Html>",
           Token_4="<Html>"+
                   "  <Table Border=0 Cellspacing=3 Cellpadding=3>"+
                   "    <Tr><Td Align=Center><Font Size="+Font_Size+" Color="+Color+">4</Font></Td><Td Align=Center><Font Size="+Font_Size+" Color="+Color+">D</Font></Td></Tr>"+
                   "    <Tr><Td Align=Center><Font Size="+Font_Size+" Color="+Color+">\u2663</Font></Td><Td Align=Center><Font Size="+Font_Size+" Color="+Color+">\u262f</Font></Td></Tr>"+
                   "  </Table>"+
                   "</Html>";
    JButton Button_1=new JButton(Token_1);
    Button_1.setPreferredSize(new Dimension(80,80));
    Button_1.setFont(new Font(Font_Face,0,16));
    Button_1.setMargin(An_Inset);
    Button_1.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent evt) { Out(Button_1.getText().replaceAll("<[^>]*>"," ").replaceAll("( )+"," ")); } });
    contentPane.add(Button_1);

    JButton Button_2=new JButton(Token_2);
    Button_2.setPreferredSize(new Dimension(80,80));
    Button_2.setFont(new Font(Font_Face,0,16));
    Button_2.setMargin(An_Inset);
    Button_2.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent evt) { Out(Button_2.getText().replaceAll("<[^>]*>"," ").replaceAll("( )+"," ")); } });
    contentPane.add(Button_2);

    JButton Button_3=new JButton(Token_3);
    Button_3.setPreferredSize(new Dimension(80,80));
    Button_3.setFont(new Font(Font_Face,0,16));
    Button_3.setMargin(An_Inset);
    Button_3.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent evt) { Out(Button_3.getText().replaceAll("<[^>]*>"," ").replaceAll("( )+"," ")); } });
    contentPane.add(Button_3);

    JButton Button_4=new JButton(Token_4);
    Button_4.setPreferredSize(new Dimension(80,80));
    Button_4.setFont(new Font(Font_Face,0,16));
    Button_4.setMargin(An_Inset);
    Button_4.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent evt) { Out(Button_4.getText().replaceAll("<[^>]*>"," ").replaceAll("( )+"," ")); } });
    contentPane.add(Button_4);

    JMenuBar menuBar=new JMenuBar();
    JMenu menu=new JMenu("Menu");
    menu.add(new JMenuItem("Do nothing"));
    menuBar.add(menu);
    frame.setJMenuBar(menuBar);

    JPanel glass=new CustomGlassPane();
    glass.add(new CursorPanel(),BorderLayout.CENTER);
    frame.setGlassPane(glass);
    // This next call is necessary because JFrame.setGlassPane delegates to the root pane:
    // - https://docs.oracle.com/javase/9/docs/api/javax/swing/RootPaneContainer.html#setGlassPane-java.awt.Component-
    // - http://hg.openjdk.java.net/jdk8/jdk8/jdk/file/687fd7c7986d/src/share/classes/javax/swing/JFrame.java#l738
    // And JRootPane.setGlassPane may call setVisible(false):
    // - https://docs.oracle.com/javase/9/docs/api/javax/swing/JRootPane.html#setGlassPane-java.awt.Component-
    // - http://hg.openjdk.java.net/jdk8/jdk8/jdk/file/687fd7c7986d/src/share/classes/javax/swing/JRootPane.java#l663
    glass.setVisible(true);

    frame.setLocationRelativeTo(null);
    frame.pack();
    frame.setVisible(true);
  }

  static class CustomGlassPane extends JPanel
  {
    CustomGlassPane()
    {
      super(new BorderLayout());
      super.setOpaque(false);
    }

    @Override
    public boolean contains(int x,int y) { return false; }
  }

  static class CursorPanel extends JPanel
  {
    final BufferedImage cursorImage;
    Point mouseLocation;

    CursorPanel()
    {
      try { cursorImage=ImageIO.read(new File("C:/Cursor_Crosshair.PNG")); }
      catch (IOException x) { throw new UncheckedIOException(x); }

      setOpaque(false);
      long mask=AWTEvent.MOUSE_EVENT_MASK|AWTEvent.MOUSE_MOTION_EVENT_MASK;
      Toolkit.getDefaultToolkit().addAWTEventListener((AWTEvent e)->
      {
        switch (e.getID())
        {
          case MouseEvent.MOUSE_ENTERED :
          case  MouseEvent.MOUSE_EXITED :
          case   MouseEvent.MOUSE_MOVED :
          case MouseEvent.MOUSE_DRAGGED : capturePoint((MouseEvent)e);
            break;
        }
      },mask);

      // This turned out to be necessary, because the 'mouse exit' events don't always have a Point location which is outside the pane.
      Timer timer=new Timer(100,(ActionEvent e)->
      {
        if (mouseLocation!=null)
        {
          Point p=MouseInfo.getPointerInfo().getLocation();
          SwingUtilities.convertPointFromScreen(p,this);
          if (!contains(p)) setMouseLocation(null);
        }
      });
      timer.setRepeats(true);
      timer.start();
    }

    void capturePoint(MouseEvent e)
    {
      Component comp=e.getComponent();
      Point onThis=SwingUtilities.convertPoint(comp,e.getPoint(),this);
      boolean drawCursor=contains(onThis);

      if (drawCursor)
      {
        Window window=SwingUtilities.windowForComponent(this);
        if (window instanceof JFrame)
        {
          Container content=((JFrame)window).getContentPane();
          Point onContent=SwingUtilities.convertPoint(comp,e.getPoint(),content);
          Component deepest=SwingUtilities.getDeepestComponentAt(content,onContent.x,onContent.y);
          if (deepest==null) drawCursor=false;
        }
      }

      setMouseLocation(drawCursor?onThis:null);
    }

    void setMouseLocation(Point mouseLocation)
    {
      this.mouseLocation=mouseLocation;
      repaint();
    }

    @Override
    protected void paintComponent(Graphics g)
    {
      super.paintComponent(g);

      if (mouseLocation!=null)
      {
        int x=mouseLocation.x-(cursorImage.getWidth()/2)+1;
        int y=mouseLocation.y-(cursorImage.getHeight()/2)+1;

        g.drawImage(cursorImage,x,y,this);
      }
    }
  }

  private static void out(String message) { System.out.print(message); }
  private static void Out(String message) { System.out.println(message); }

  public static void main(String[] args) { SwingUtilities.invokeLater(new Runnable() { public void run() { createAndShowGUI(); } }); }
}

Cursor_Crosshair.PNG 看起来像这样: