Eric
Eric

Reputation: 2076

Using a Keybind and MouseInfo to get Label

This is a follow up question from my previous question.

I have two JPanels that have 5 JLabels each. When I hove the mouse over one of the JLabels and press the delete key, I want to print the label's text. Here is what I have tried:

import java.awt.BorderLayout;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.FlowLayout;
import java.awt.MouseInfo;
import java.awt.Point;
import java.awt.PointerInfo;
import java.awt.event.ActionEvent;
import java.awt.event.KeyEvent;

import javax.swing.*;

@SuppressWarnings("serial")
public class KeybindExample extends JPanel {

   public KeybindExample() {
      setLayout(new BorderLayout());
      add(firstRow(), BorderLayout.NORTH);
      add(secondRow(),BorderLayout.SOUTH);
      int condition = JComponent.WHEN_IN_FOCUSED_WINDOW;
      InputMap inputMap = getInputMap(condition);
      ActionMap actionMap = getActionMap();

      KeyStroke delKeyStroke = KeyStroke.getKeyStroke(KeyEvent.VK_DELETE, 0);
      String delete = "delete";

      inputMap.put(delKeyStroke, delete);
      actionMap.put(delete, new DeleteAction());
   }

   private class DeleteAction extends AbstractAction {
      @Override
      public void actionPerformed(ActionEvent evt) {
         PointerInfo pInfo = MouseInfo.getPointerInfo();
         Point ptOnScrn = pInfo.getLocation();
         int xPanel = getLocationOnScreen().x;
         int yPanel = getLocationOnScreen().y;
         int x = ptOnScrn.x - xPanel;
         int y = ptOnScrn.y - yPanel;

         Component component = getComponentAt(x, y);
         if (component != null) {
             JLabel selectedLabel = (JLabel) component;
             System.out.println("Selected Label: " + selectedLabel.getText());
          }
      }
   }
   private static JPanel firstRow(){
        JPanel panel = new JPanel(new FlowLayout(FlowLayout.LEFT));
        String [] x = {"1","2","3","4","5"};
        for(int i = 0; i < 5; i++){
            JLabel label = new JLabel(x[i]);
            label.setPreferredSize(new Dimension(50,50));
            panel.add(label);
        }

        return panel;
   }
   private static JPanel secondRow(){
        JPanel panel = new JPanel(new FlowLayout(FlowLayout.LEFT));
        String [] x = {"6","7","8","9","10"};
        for(int i = 0; i < 5; i++){
            JLabel label = new JLabel(x[i]);
            label.setPreferredSize(new Dimension(50,50));
            panel.add(label);
        }

        return panel;
   }

   private static void createAndShowGui() {
      KeybindExample mainPanel = new KeybindExample();

      JFrame frame = new JFrame("Key Bindings Example");
      frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
      frame.getContentPane().add(mainPanel);
      frame.pack();
      frame.setLocationByPlatform(true);
      frame.setVisible(true);
   }

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

I get the following Error:

Exception in thread "AWT-EventQueue-0" java.lang.ClassCastException: javax.swing.JPanel cannot be cast to javax.swing.JLabel

If I'm understanding this correctly, I have a JPanel(KeybindExample) that contains two JPanels, and each of those two JPanels contain JLabels. Therefore, when I do getComponentAt(x,y) the component I'm getting is another JPanel which is causing the issue with the typecasting.

What I believe I need to do is to get the Component At (x,y)'s component, which would be a JLabel. In other words I need to go one step further. I tried the following, but it only worked for the top panel, and sometimes still returned the same error.

     Component component = getComponentAt(x, y);
     if (component != null) {
        JPanel selectPanel = (JPanel) component;
        Component comp = selectPanel.getComponentAt(x,y);

        if(comp != null){
           JLabel selectLabel = (JLabel) comp;
           System.out.println("Selected Label: " + selectLabel.getText());
        }
     }
  }

}

How can I have multiple JPanels that contain multiple JLabels but be able to have the JLabel that has the mouse hovered over's text.

Upvotes: 1

Views: 192

Answers (2)

dic19
dic19

Reputation: 17971

Despite the approach described in this answer is really good, it quickly falls in a pitfall just because Component#getComponentAt(int x, int y) has its limitations as you already are experiencing:

Determines if this component or one of its immediate subcomponents contains the (x, y) location, and if so, returns the containing component. This method only looks one level deep. If the point (x, y) is inside a subcomponent that itself has subcomponents, it does not go looking down the subcomponent tree.

Note: remarks are mine.

IMHO you shoud follow KISS (without the last S of course) principle and go with a more simple, robust and scalable approach like described in this other answer. In a nutshell:

  • Keep a JLabel class member (lets say hoveredLabel)

  • Implement just one MouseListener to update this class member on mouseEntered() using getSource() and set this to null on mouseExited().

  • Attach this mouse listener to all labels.

  • Use key binding to remove hoveredLabel only if hoveredLabel != null

Upvotes: 3

Hovercraft Full Of Eels
Hovercraft Full Of Eels

Reputation: 285405

The key question is -- how to get the reference to the desired JLabel at a certain point in time, here when you press the delete key. Your current solution, the one that I proposed previously, is not working because the code finds the current component relative to the KeybindExample JPanel since this is the component that you're calling the getComponentAt() method on. One possible solution is to call getComponentAt() on the JPanels that actually hold the JLabels. Another possible solution is to calculate the mouse location relative to each JLabel, and use the contains(...) method to see if the mouse is over one of the JLabels. To do this, you'd need to have access to all the JLabels, perhaps by putting them in a List<JLabel>. For example...

import java.awt.BorderLayout;
import java.awt.Dimension;
import java.awt.FlowLayout;
import java.awt.MouseInfo;
import java.awt.Point;
import java.awt.PointerInfo;
import java.awt.event.ActionEvent;
import java.awt.event.KeyEvent;
import java.util.ArrayList;
import java.util.List;

import javax.swing.*;

@SuppressWarnings("serial")
public class KeybindExample extends JPanel {
   private List<JLabel> labelList = new ArrayList<>();

   public KeybindExample() {
      setLayout(new BorderLayout());
      add(firstRow(), BorderLayout.NORTH);
      add(secondRow(), BorderLayout.SOUTH);
      int condition = JComponent.WHEN_IN_FOCUSED_WINDOW;
      InputMap inputMap = getInputMap(condition);
      ActionMap actionMap = getActionMap();

      KeyStroke delKeyStroke = KeyStroke.getKeyStroke(KeyEvent.VK_DELETE, 0);
      String delete = "delete";

      inputMap.put(delKeyStroke, delete);
      actionMap.put(delete, new DeleteAction());
   }

   private class DeleteAction extends AbstractAction {
      @Override
      public void actionPerformed(ActionEvent evt) {
         PointerInfo pInfo = MouseInfo.getPointerInfo();
         Point ptOnScrn = pInfo.getLocation();

         for (JLabel label : labelList) {
            int x = ptOnScrn.x - label.getLocationOnScreen().x;
            int y = ptOnScrn.y - label.getLocationOnScreen().y;

            if (label.contains(x, y)) {
               System.out.println("Selected Label: " + label.getText());
            }

         }
         // int xPanel = getLocationOnScreen().x;
         // int yPanel = getLocationOnScreen().y;
         // int x = ptOnScrn.x - xPanel;
         // int y = ptOnScrn.y - yPanel;
         //
         // Component component = getComponentAt(x, y);
         // if (component != null) {
         // JLabel selectedLabel = (JLabel) component;
         // System.out.println("Selected Label: " + selectedLabel.getText());
         // }
      }
   }

   private JPanel firstRow() {
      JPanel panel = new JPanel(new FlowLayout(FlowLayout.LEFT));
      String[] x = { "1", "2", "3", "4", "5" };
      for (int i = 0; i < 5; i++) {
         JLabel label = new JLabel(x[i]);
         labelList.add(label);
         label.setPreferredSize(new Dimension(50, 50));
         panel.add(label);
      }

      return panel;
   }

   private JPanel secondRow() {
      JPanel panel = new JPanel(new FlowLayout(FlowLayout.LEFT));
      String[] x = { "6", "7", "8", "9", "10" };
      for (int i = 0; i < 5; i++) {
         JLabel label = new JLabel(x[i]);
         labelList.add(label);
         label.setPreferredSize(new Dimension(50, 50));
         panel.add(label);
      }

      return panel;
   }

   private static void createAndShowGui() {
      KeybindExample mainPanel = new KeybindExample();

      JFrame frame = new JFrame("Key Bindings Example");
      frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
      frame.getContentPane().add(mainPanel);
      frame.pack();
      frame.setLocationByPlatform(true);
      frame.setVisible(true);
   }

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

Upvotes: 3

Related Questions