代码之家  ›  专栏  ›  技术社区  ›  Frank

如何在Java中创建大尺寸的自定义游标?

  •  1
  • Frank  · 技术社区  · 6 年前

    我正在开发一个JAVA Swing应用程序来获得一个获奖的密码保护系统,我需要一个大的自定义光标[80×80 ],你可能会问为什么这么大,有一个在线的Web演示,你可以看看为什么它需要这么大: http://gatecybertech.net

    上面链接中的登录页面上使用了这个大光标。当然,您需要先创建一个测试密码,然后才能尝试登录过程。

    但无论如何,在我的Swing应用程序中,对于最大的自定义光标,我达到了32 x 32的限制,我的代码如下所示:

    Image cursorImage = toolkit.getImage("Cursor_Crosshair.PNG");
    Tabular_Panel.setCursor(Toolkit.getDefaultToolkit().createCustomCursor(cursorImage,new Point(0,0),"custom cursor"));
    

    cursor_crosshair.png的图像大小为:80 x 80

    但是在屏幕上显示的是它的缩小版本:32 x 32

    所以我的问题是:如何绕过客户光标图像的大小限制,使光标显示在80 x 80的大小?

    我知道操作系统可能是限制的原因,有没有办法克服它?

    2 回复  |  直到 6 年前
        1
  •  1
  •   Radiodef    6 年前

    这是我对玻璃窗绘画方法的看法。这与设置自定义光标的行为非常相似。当显示自定义光标时,默认的“箭头”光标将被隐藏;当组件设置了其他光标(如文本框)时,自定义光标将被隐藏。

    不幸的是,它最终似乎需要相当多的摇摆黑魔法,所以我不太喜欢它,但它似乎工作正常。我以前做过这样的光标,但它是为了更简单的事情,所以我没有遇到这些问题。

    我遇到的一些问题是:

    • 玻璃窗格截取光标更改(如 here )我唯一能找到的解决方案是重写 Component.contains(int,int) 返回 false (描述 here ,显示 here 但为什么这样做有效,而且似乎不会破坏其他任何东西,这是个谜。

    • 鼠标退出事件有时会返回组件边界内的一个位置,所以我认为除了使用计时器之外,没有可靠的方法来知道鼠标何时离开窗口。

    package mcve;
    
    import javax.swing.*;
    import java.awt.*;
    import java.awt.event.*;
    import java.awt.image.*;
    import javax.imageio.*;
    import java.net.*;
    import java.io.*;
    
    public class LargeCursor {
        public static void main(String[] args) {
            SwingUtilities.invokeLater(() -> {
                JFrame frame = new JFrame();
    
                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);
    
                JPanel content = createTestPanel();
                content.setCursor(BlankCursor.INSTANCE);
    
                frame.setContentPane(content);
                frame.pack();
                frame.setLocationRelativeTo(null);
                frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
                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 = createTransparentImage(
                        ImageIO.read(new URL("https://i.stack.imgur.com/9h2oI.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) {
                            if (deepest.getCursor() != BlankCursor.INSTANCE) {
                                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);
                    int y = mouseLocation.y - (cursorImage.getHeight() / 2);
    
                    g.drawImage(cursorImage, x, y, this);
                }
            }
        }
    
        static final class BlankCursor {
            static final Cursor INSTANCE =
                Toolkit.getDefaultToolkit().createCustomCursor(
                    new BufferedImage(1, 1, BufferedImage.TYPE_INT_ARGB),
                    new Point(),
                    "BLANK");
        }
    
        static JPanel createTestPanel() {
            JPanel panel = new JPanel(new GridLayout(3, 3));
            panel.setBorder(BorderFactory.createEmptyBorder(100, 100, 100, 100));
    
            for (int i = 0; i < 9; ++i) {
                if ((i % 2) == 0) {
                    JTextField field = new JTextField("Text Field");
                    field.setHorizontalAlignment(JTextField.CENTER);
                    panel.add(field);
                } else {
                    panel.add(new JButton("Button"));
                }
            }
    
            return panel;
        }
    
        static BufferedImage createTransparentImage(BufferedImage img) {
            BufferedImage copy =
                GraphicsEnvironment.getLocalGraphicsEnvironment()
                                   .getDefaultScreenDevice()
                                   .getDefaultConfiguration()
                                   .createCompatibleImage(img.getWidth(),
                                                          img.getHeight(),
                                                          Transparency.TRANSLUCENT);
            for (int x = 0; x < img.getWidth(); ++x) {
                for (int y = 0; y < img.getHeight(); ++y) {
                    int rgb = img.getRGB(x, y) & 0x00FFFFFF;
                    int bright = (((rgb >> 16) & 0xFF) + ((rgb >> 8) & 0xFF) + (rgb & 0xFF)) / 3;
                    int alpha = 255 - bright;
                    copy.setRGB(x, y, (alpha << 24) | rgb);
                }
            }
    
            return copy;
        }
    }
    
        2
  •  0
  •   Frank    6 年前

    好的,经过一些研究和修改,我找到了答案:

    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
    {
      static private MyGlassPane myGlassPane;
    
      // Create the GUI and show it. For thread safety, this method should be invoked from the event-dispatching thread.
      private static void createAndShowGUI()
      {
        //Create and set up the window.
        JFrame frame=new JFrame("Demo_Large_Custom_Cursor");
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    
        //Start creating and adding components.
        JCheckBox changeButton=new JCheckBox("Custom Cursor \"visible\"");
        changeButton.setSelected(false);
    
        //Set up the content pane, where the "main GUI" lives.
        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);
    
        //Set up the menu bar, which appears above the content pane.
        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(changeButton,menuBar,frame.getContentPane());
        changeButton.addItemListener(myGlassPane);
        frame.setGlassPane(myGlassPane);
    
        //Show the window.
        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)
      {
        //Schedule a job for the event-dispatching thread:
        //creating and showing this application's GUI.
        javax.swing.SwingUtilities.invokeLater(new Runnable()
        {
          public void run()
          {
            createAndShowGUI();
          }
        });
      }
    }
    
    /**
     We have to provide our own glass pane so that it can paint.
     */
    class MyGlassPane extends JComponent implements ItemListener
    {
      Point point;
    
      //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;
      }
    
      public MyGlassPane(AbstractButton aButton,JMenuBar menuBar,Container contentPane)
      {
        CBListener listener=new CBListener(aButton,menuBar,this,contentPane);
        addMouseListener(listener);
        addMouseMotionListener(listener);
      }
    }
    
    /**
     Listen for all events that our check box is likely to be interested in. Redispatch them to the check box.
     */
    class CBListener extends MouseInputAdapter
    {
      Toolkit toolkit;
      Component liveButton;
      JMenuBar menuBar;
      MyGlassPane glassPane;
      Container contentPane;
    
      public CBListener(Component liveButton,JMenuBar menuBar,MyGlassPane 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();
        }
      }
    }
    

    光标“crosshair.png”如下: enter image description here