Pressing the Control key I want to show a Jtooltip of a Jbutton.
How can I show a tooltip to the desired keystrokes?
Not sure if I understand the question, but you can use Control+F1 to display the tooltip of the currently focused component.
There is a private method in the TooltipManager called show which takes a JComponent as a parameter, which is used to show the tooltip. This is actually used by the TooltipManager when CTRL+F1 is pressed...
So, my first recommendation would be, use CTRL+F1 because it's built in. My second recommendation is to use CTRL+F1 because people press CTRL for a lot of reasons (like copy/paste, menu short cuts, etc), which could be rather annoying if you keep popping up tool tips all the time. My third recommendation is to use CTRL+F1 because the show method is private
However, because I'm simply curious (and completely crazy), you "could" (but I wouldn't recommend it) use a dirty, dirty hack (I fell like Phillip Fry), which is just as likely to blow up in your face as solve the problem (but I was curious about how to bind an action to the CTRL key)
import java.awt.Component;
import java.awt.EventQueue;
import java.awt.KeyboardFocusManager;
import java.awt.event.ActionEvent;
import java.awt.event.InputEvent;
import java.awt.event.KeyEvent;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import javax.swing.AbstractAction;
import javax.swing.Action;
import javax.swing.ActionMap;
import javax.swing.InputMap;
import javax.swing.JButton;
import javax.swing.JComponent;
import static javax.swing.JComponent.WHEN_IN_FOCUSED_WINDOW;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.KeyStroke;
import javax.swing.ToolTipManager;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
public class Test {
public static void main(String[] args) {
new Test();
}
public Test() {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
ex.printStackTrace();
}
JFrame frame = new JFrame("Testing");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(new TestPane());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
public class TestPane extends JPanel {
public TestPane() {
JButton btn = new JButton("Banana");
btn.setToolTipText("Hello");
add(btn);
TooltipPopup.register(this);
}
}
public static class TooltipPopup {
public static void register(JComponent comp) {
new TooltipPopup(comp);
}
private JComponent parent;
private boolean showing = false;
private TooltipPopup(JComponent parent) {
this.parent = parent;
bindKeyStrokeTo(parent,
JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT,
"help.press",
KeyStroke.getKeyStroke(KeyEvent.VK_CONTROL, InputEvent.CTRL_DOWN_MASK),
new AbstractAction() {
#Override
public void actionPerformed(ActionEvent e) {
if (!showing) {
Component comp = KeyboardFocusManager.getCurrentKeyboardFocusManager().getFocusOwner();
if (comp != null && comp instanceof JComponent) {
JComponent focused = (JComponent) comp;
try {
Class clazz = ToolTipManager.sharedInstance().getClass();
Method method = clazz.getDeclaredMethod("show", JComponent.class);
method.setAccessible(true);
method.invoke(ToolTipManager.sharedInstance(), focused);
showing = true;
} catch (NoSuchMethodException | SecurityException | IllegalAccessException | IllegalArgumentException | InvocationTargetException ex) {
ex.printStackTrace();
}
}
}
}
});
bindKeyStrokeTo(parent,
JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT,
"help.release",
KeyStroke.getKeyStroke(KeyEvent.VK_CONTROL, 0, true),
new AbstractAction() {
#Override
public void actionPerformed(ActionEvent e) {
showing = false;
}
});
}
public void bindKeyStrokeTo(JComponent parent, int condition, String name, KeyStroke keyStroke, Action action) {
InputMap im = parent.getInputMap(condition);
ActionMap am = parent.getActionMap();
im.put(keyStroke, name);
am.put(name, action);
}
}
}
All this does is binds a press and release Action to the CTRL for a given component (parent container), which will find the currently focused component and show it's tool tip.
It uses a reflection "tick" (hack) to call the private show method of the ToolTipManager.
You need the "press" and "release" actions, because otherwise you will get a repeated key event, which will make the tool tip "flash"
camickr solution is the better (and correct) choice, this was a simply "I wonder how" hack
Related
I am using a scripting API for making a script for a bot for a game I play, however whenever I override a bot manager, a JOptionPane appears that blocks execution until I close it, however I would like to run this script without human intervention, so I would like to bypass this pane.
I have tried to overwrite the JOptionPane class at runtime to no avail, and I have tried contacting the developer of the bot's API to add an override however he doesn't want to add an override.
I am open to any methods of resolving this (reflection, etc.), I was thinking of detecting the pane's title and force-closing it however that it way beyond my skill level.
Pane title: Override Warning!
I would add the pane text however it reveals personal information.
Here is the possibility to suppress all JOptionPane dialogs. If you want to do it with some of them you probably need to provide additional check for dialog title.
import java.awt.AWTEvent;
import java.awt.BorderLayout;
import java.awt.Component;
import java.awt.FlowLayout;
import java.awt.Toolkit;
import java.awt.event.AWTEventListener;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.WindowEvent;
import javax.swing.JButton;
import javax.swing.JCheckBox;
import javax.swing.JDialog;
import javax.swing.JFrame;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
import javax.swing.WindowConstants;
public class CloseDialog implements Runnable {
private final AWTEventListener hideOptionPaneListener = new AWTEventListener() {
#Override
public void eventDispatched(AWTEvent event) {
if (event instanceof WindowEvent) {
processWindowEvent((WindowEvent) event);
}
}
};
private void processWindowEvent(WindowEvent we) {
if (we.getID() == WindowEvent.WINDOW_OPENED && we.getWindow() instanceof JDialog) {
final JDialog dlg = (JDialog) we.getWindow();
Component[] comps = dlg.getContentPane().getComponents();
if (comps.length > 0 && comps[0] instanceof JOptionPane) { // probably also check dialog title
dlg.setVisible(false);
}
}
}
#Override
public void run() {
final JFrame frm = new JFrame("Test frame");
JButton button = new JButton("Show option pane");
button.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
JOptionPane.showMessageDialog(frm, "Simple message");
}
});
final JCheckBox checkBox = new JCheckBox("Suppress option pane");
checkBox.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
if (checkBox.isSelected()) {
Toolkit.getDefaultToolkit().addAWTEventListener(hideOptionPaneListener, AWTEvent.WINDOW_EVENT_MASK);
} else {
Toolkit.getDefaultToolkit().removeAWTEventListener(hideOptionPaneListener);
}
}
});
JPanel top = new JPanel(new FlowLayout());
top.add(button);
frm.add(top, BorderLayout.NORTH);
frm.add(checkBox, BorderLayout.SOUTH);
frm.setSize(400, 200);
frm.setLocationRelativeTo(null);
frm.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
frm.setVisible(true);
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new CloseDialog());
}
}
A JButton has a different appearance when rolled over. That appearance is different from the "selected" appearance.
I want to display my button "as if" it was rolled over, so that user understands that if he hits the Return key, that button will be triggered.
The problem is not the same as setting the default button, because I am in a situation where I really want to get the user to understand that although he wouldn't expect it, if he hits enter that button will be activated. More details below for those who want some. Setting button as default would make button the default one, but wouldn't be significantly signaling to the user.
In my case the strong enough signal is the appearance that the button has when it is rolled over.
How to do that ?
More details on the situation, for those who want some :
I have a list of buttons representing options, and a text box at the top, which acts as a filter on the buttons
when filter is such that only one option remains, hitting return directly clicks that option's button
in reality user would have had to select the button with tab or arrow, and then hit enter.
since that shortcut is not obvious I want to signal it to user
Based on your question, what you "really" want, is the JRootPane#setDefaultButton, which will highlight the button, in a OS specific manner and if the user presses the default "action" key (Enter in most cases) will call it's ActionListener
For example...
The "normal" button is just a plain old JButton, the Hacked sets the rollOver to enabled and Default has been set as the default button for the JRootPane
As you can see, you're suggest fix does nothing on MacOS, don't know what it might do on other platforms
I suggest you have a look at How to Use Root Panes for more details
import java.awt.EventQueue;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.Action;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JRootPane;
import javax.swing.SwingUtilities;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
public class Test {
public static void main(String[] args) {
new Test();
}
public Test() {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
ex.printStackTrace();
}
JFrame frame = new JFrame("Testing");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(new TestPane());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
public class TestPane extends JPanel {
private JButton asDefault = new JButton("Default");
public TestPane() {
JButton hack = new JButton("Hacked");
hack.getModel().setRollover(true);
hack.setRolloverEnabled(true);
asDefault.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
System.out.println("Defaulted");
}
});
add(new JButton("Normal"));
add(hack);
add(asDefault);
}
#Override
public void addNotify() {
super.addNotify();
JRootPane rootPane = SwingUtilities.getRootPane(this);
if (rootPane != null) {
rootPane.setDefaultButton(asDefault);
}
}
}
}
So using button.getModel().setRollover(true); doesn't work on all platforms and on those platforms it does work on, I suspect the user will simply need to move the mouse through it to return it to normal
button.getModel().setRollover(true);
I have a customized JButton in a Java Swing application.
it changes it appearances according to mouse event.
MouseEvent.MOUSE_ENTERED - will trigger the hover image for the button. MouseEvent.MOUSE_PRESSED - will trigger the pressed image.
MouseEvent.MOUSE_RELEASED - will change the foreground to gray and render the button disabled.
This is working fine with actual mouse clicks.
I want to add a support for pressing the ENTER key.
simply calling button.doClick() did not go through the hover-press-release cycle, but simply jumped to the release event.
So I have this short and effective code for doing this.
InputMap im = workspacePnl.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW);
ActionMap am = workspacePnl.getActionMap();
im.put(KeyStroke.getKeyStroke("ENTER"), "connect");
am.put("connect", new ConectAction());
private class ConectAction extends AbstractAction {
#Override
public void actionPerformed(ActionEvent ev) {
simulateClick();
}
and the problematic code:
public void simulateClick() {
MouseEvent evt;
evt = new MouseEvent(connectBtn,
MouseEvent.MOUSE_ENTERED, 1, 0, 0, 0, 1, false);
connectBtn.dispatchEvent((AWTEvent) evt);
//CommonUtil.sleep(300);
evt = new MouseEvent(connectBtn,
MouseEvent.MOUSE_PRESSED, 8, 0, 0, 0, 1, false);
connectBtn.dispatchEvent((AWTEvent) evt);
//CommonUtil.sleep(300);
evt = new MouseEvent(connectBtn,
MouseEvent.MOUSE_RELEASED, 20, 0, 0, 0, 1, false);
connectBtn.dispatchEvent((AWTEvent) evt);
}
I am trying to make the ENTER press go through the same route: trigger a MOUSE_ENTERED event which will alter the button's appearance for hover, followed by MOUSE_PRESSED and MOUSE_RELEASED.
But I only see the last event effect. it is as if i'm only firing the last event alone which lacks the liveliness for an interactive software.
I tried (as can be seen commented out) to have the thread go to sleep after each event firing, but it has no effect.
If I try to fire each of the other two events they are noticeable on the screen by themselves. it's the batching together that messes things up.
How can I fire a series of dispatchEvents one by one which will all be noticed by the user? how can i make the program wait for the current dispatchEvent to work it's magic before striding on to the next on?
Any help or insights would be greatly appreciated.
How can I fire a series of dispatchEvents one by one which will all be
noticed by the user? how can i make the program wait for the current
dispatchEvent to work it's magic before striding on to the next on?
Mouse and Key Event are correctly implemented in ButtonComponents, don't use MouseListener, to use events from ButtonModel, by using ChangeListener, for example
MouseEvent.MOUSE_ENTERED - will trigger the hover image for the button.
Make use of the roll over support supplied by the button, see JButton#setRolloverEnabled & JButton#setRolloverIcon
MouseEvent.MOUSE_PRESSED - will trigger the pressed image.
Is a little more difficult, but you can use listener to the ButtonModel for changes and update the icon based on you requirements
MouseEvent.MOUSE_RELEASED - will change the foreground to gray and render the button disabled.
Should probably be achieved through the use a ActionListener
I am trying to make the ENTER press go through the same route:
JButton#doClick will go through the isArmed and isPressed states of the model automatically, which will trigger the state changes provided by the previous comments...
With the mouse...
With the keyboard...
import java.awt.EventQueue;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.IOException;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.imageio.ImageIO;
import javax.swing.AbstractAction;
import javax.swing.ActionMap;
import javax.swing.ImageIcon;
import javax.swing.InputMap;
import javax.swing.JButton;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JTextField;
import javax.swing.KeyStroke;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
public class Test {
public static void main(String[] args) {
new Test();
}
public Test() {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
ex.printStackTrace();
}
JFrame frame = new JFrame("Testing");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(new TestPane());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
public class TestPane extends JPanel {
public TestPane() {
setLayout(new GridBagLayout());
JButton btn = new JButton();
try {
btn.setIcon(new ImageIcon(ImageIO.read(getClass().getResource("/Trash01.png"))));
btn.setRolloverIcon(new ImageIcon(ImageIO.read(getClass().getResource("/Trash02.png"))));
btn.setRolloverEnabled(true);
// btn.setSelectedIcon(new ImageIcon(ImageIO.read(getClass().getResource("/Trash03.png"))));
} catch (IOException ex) {
Logger.getLogger(Test.class.getName()).log(Level.SEVERE, null, ex);
}
btn.getModel().addChangeListener(new ChangeListener() {
#Override
public void stateChanged(ChangeEvent e) {
if (btn.getModel().isPressed()) {
try {
btn.setIcon(new ImageIcon(ImageIO.read(getClass().getResource("/Trash03.png"))));
} catch (IOException ex) {
ex.printStackTrace();
}
} else {
try {
btn.setIcon(new ImageIcon(ImageIO.read(getClass().getResource("/Trash01.png"))));
} catch (IOException ex) {
ex.printStackTrace();
}
}
System.out.println("Armed: " + btn.getModel().isArmed());
System.out.println("Enabled: " + btn.getModel().isEnabled());
System.out.println("Pressed: " + btn.getModel().isPressed());
System.out.println("Rollover: " + btn.getModel().isRollover());
System.out.println("Selected: " + btn.getModel().isSelected());
}
});
GridBagConstraints gbc = new GridBagConstraints();
gbc.gridwidth = GridBagConstraints.REMAINDER;
add(btn, gbc);
add(new JTextField("Stealer of focus"), gbc);
btn.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
btn.setEnabled(false);
}
});
InputMap im = getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW);
ActionMap am = getActionMap();
im.put(KeyStroke.getKeyStroke("ENTER"), "connect");
am.put("connect", new AbstractAction() {
#Override
public void actionPerformed(ActionEvent ev) {
System.out.println("click");
btn.doClick();
}
});
}
}
}
It's batching together because your whole code is running on the EventDispatchingThread (EDT).
When you call simulateClick() from actionPerformed() call it on a new thread instead of the same thread (which will be EDT).
I'm using a ListCellRenderer to edit the appearance of entries in a JList. Once they are selected (by clicking them, this code is within a click event) I call the ListCellRenderer to change the color of the text. If they are selected again I want the text to return to the normal color. The problem I am having is that once I select a second entry the first entry goes back to its normal color. How can I keep selected entries the selected color until they are actually deselected? Here is the section where I actually call the function:
for(int i = 0; i < selectedEntries.size() - 1; i++){
System.out.println("Inside the for loop at entry:" + i);
if(selectedEntries.get(i).equals(selectedEntry)){
selectedEntries.remove(i);
removed = true;
renderer.getListCellRendererComponent(logEntries, value, index, false, false);
System.out.println("Duplicate Entry Removed From List");
}
}
if(!removed){
selectedEntries.add(selectedEntry);
renderer.getListCellRendererComponent(logEntries, value, index, true, false);
}
Just for ease of interpretation selectedEntries is an ArrayList containing each selected entry's text.
Once they are selected (by clicking them, this code is within a click event) I call the ListCellRenderer to change the color of the text
No, that's not how it should work, the ListCellRenderer will be called again (by the JList) and the isSelected parameter will be true, to which you would render the values differently
The ListCellRenderer is responsible for rendering the entire state, selected or not selected.
Have a look at Writing a Custom Cell Renderer for more details
For example
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Font;
import javax.swing.DefaultListCellRenderer;
import javax.swing.DefaultListModel;
import javax.swing.JFrame;
import javax.swing.JList;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
public class Test {
public static void main(String[] args) {
new Test();
}
public Test() {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
ex.printStackTrace();
}
JFrame frame = new JFrame("Testing");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(new TestPane());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
public class TestPane extends JPanel {
public TestPane() {
setLayout(new BorderLayout());
DefaultListModel<String> model = new DefaultListModel<>();
model.addElement("Apples");
model.addElement("Bananas");
model.addElement("Peachs");
model.addElement("Pears");
JList<String> listOfStrings = new JList<>(model);
listOfStrings.setCellRenderer(new FancyPancyListCellRenderer());
add(new JScrollPane(listOfStrings));
}
#Override
public Dimension getPreferredSize() {
return new Dimension(200, 200);
}
}
public static class FancyPancyListCellRenderer extends DefaultListCellRenderer {
protected static final Font SELECTED_FONT = new Font("Comic Sans MS", Font.PLAIN, 12);
#Override
public Component getListCellRendererComponent(JList<?> list, Object value, int index, boolean isSelected, boolean cellHasFocus) {
super.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus);
if (isSelected) {
setForeground(Color.YELLOW);
setFont(SELECTED_FONT);
} else {
setFont(UIManager.getFont("Label.font"));
}
return this;
}
}
}
Also, MouseListener really isn't a suitable means by which to detect changes in the selection, what happens if the user selects rows using the keyboard? You should be using a ListSelectionListener instead (but not to correct this issue).
Have a look at How to Write a List Selection Listener and How to Use Lists for more details
Right now when a user right clicks on a selected JList item in my program the resulting JPopupMenu clears the selection (at least visually) until the popup menu is closed. This isn't consistent with the native look and feel of any platform I know of. The item should stay visually selected or have a selected-color border around it. But I can't find anythin in the API about popup menus changing selection appearance. Is there any way I can control this behavior?
How are you implementing your Mouse Listener that shows the popup? I have created a test application to demonstrate the behaviour of List selections and popup menus that I would typically expect. On Windows with Java 1.5/6 this behaves correctly.
Maybe this will help you with your particular problem.
package jlist;
import java.awt.BorderLayout;
import java.awt.event.ActionEvent;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import javax.swing.AbstractAction;
import javax.swing.Action;
import javax.swing.JFrame;
import javax.swing.JList;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JPopupMenu;
import javax.swing.JScrollPane;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener;
public class Test extends JPanel implements ListSelectionListener {
private static final String ACTION_FEED = "Feed";
private JList list;
private JPopupMenu menu;
// Initialise a JList and add to JPanel.
public Test() {
super(new BorderLayout());
list = new JList(new Object[]{"Badger", "Ferret", "Stoat", "Weasel"});
initActions();
list.addListSelectionListener(this);
// Add mouse listener
list.addMouseListener(new MouseAdapter() {
#Override
public void mousePressed(MouseEvent e) {
if (e.isPopupTrigger()) showPopup(e);
}
#Override
public void mouseReleased(MouseEvent e) {
if (e.isPopupTrigger()) showPopup(e);
}
private void showPopup(MouseEvent e) {
menu.show(e.getComponent(), e.getX(), e.getY());
}
});
add(new JScrollPane(list), BorderLayout.CENTER);
valueChanged(null);
}
// Triggered when List Selection changes. Used to control Actions enabled state.
public void valueChanged(ListSelectionEvent e) {
boolean selected = list.getSelectedValue() != null;
getActionMap().get(ACTION_FEED).setEnabled(selected);
}
// Initialise Actions and Popup Menu
private void initActions() {
menu = new JPopupMenu();
Action feed = new AbstractAction(ACTION_FEED) {
public void actionPerformed(ActionEvent e) {
String value = (String) list.getSelectedValue();
JOptionPane.showMessageDialog(Test.this, "Fed " + value);
}
};
getActionMap().put(ACTION_FEED, feed);
menu.add(feed);
}
public static void main(String [] args) {
JFrame frame = new JFrame("Test");
frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
frame.add(new Test());
frame.pack();
frame.setLocationByPlatform(true);
frame.setVisible(true);
}
}