How to set an accelerator for a JMenu sub menu? - java

I have a user request to add an accelerator to a sub menu (JMenu) which would allow the user to press the short cut and have the corresponding sub menu "fold out", showing its contained menu items.
I don't recall every having seen something like this (either in Java or any other language). Our application is written in Java using Swing. We have a number of JMenuItems with accelerators that work well, but when I attempted to add an accelerator to JMenu I get the following exception:
java.lang.Error: setAccelerator() is not defined for JMenu. Use setMnemonic() instead.
I've tried to use the MenuDemo! code to experiment with this a bit further.
This is what I tried:
//a submenu
menu.addSeparator();
submenu = new JMenu("A submenu");
submenu.setMnemonic(KeyEvent.VK_S);
submenu.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_U, InputEvent.CTRL_MASK));
The last line is the one added by me, which causes the exception.
I've tried extensive googling but all I can find is articles on how to add accelerators to JMenuItem.
It seems the JMenu does not support this natively. Is there any workaround to achieve this behaviour?

Another option is to override the accelerator get/set and reproduce the JMenuItem behaviour. Then the UI will do the rest of the job.
The important thing is to fire the property change and have a consistent get/set for the accelerator. The advantage of this solution is that it also provides a visual indication of the shortcut/accelerator.
Here is a small demo code:
import java.awt.event.KeyEvent;
import javax.swing.JFrame;
import javax.swing.JMenu;
import javax.swing.JMenuBar;
import javax.swing.JMenuItem;
import javax.swing.KeyStroke;
import javax.swing.SwingUtilities;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
public class TestMenu {
protected void initUI() {
JFrame frame = new JFrame(TestMenu.class.getSimpleName());
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
JMenuBar bar = new JMenuBar();
JMenu topMenu = new JMenu("Top Menu");
JMenu subMenu = new JMenu("Sub menu") {
private KeyStroke accelerator;
#Override
public KeyStroke getAccelerator() {
return accelerator;
}
#Override
public void setAccelerator(KeyStroke keyStroke) {
KeyStroke oldAccelerator = accelerator;
this.accelerator = keyStroke;
repaint();
revalidate();
firePropertyChange("accelerator", oldAccelerator, accelerator);
}
};
subMenu.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_F4, KeyEvent.CTRL_MASK));
JMenuItem item1 = new JMenuItem("Item 1");
JMenuItem item2 = new JMenuItem("Item 2");
subMenu.add(item1);
subMenu.addSeparator();
subMenu.add(item2);
topMenu.add(subMenu);
bar.add(topMenu);
frame.setJMenuBar(bar);
frame.setSize(400, 300);
frame.setVisible(true);
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (ClassNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (InstantiationException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IllegalAccessException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (UnsupportedLookAndFeelException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
new TestMenu().initUI();
}
});
}
}

I don't think that is possible just like that.
But what you could do is adding an AbstractAction, which simulates a click.
submenu.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(KeyStroke.getKeyStroke("control U"), "expand");
submenu.getActionMap().put("expand", new AbstractAction("expand") {
private static final long serialVersionUID = 1L;
#Override
public void actionPerformed(ActionEvent evt) {
submenu.doClick();
}
}
);
I hope this also works for you.

Related

Java Mouse Listener error with UIManager SetLookAndFeel

The below code displays a JFrame with a JMenu and a [custom] JPanel.
I set the look-and-feel (LAF) to Windows LAF by calling method UIManager.setLookAndFeel
I click on the JMenu with the mouse and then I move the mouse onto the [custom] JPanel – while the menu is still visible. Then I click the mouse again and the mousePressed method of the MouseListener on my [custom] JPanel does not get called, even though the mouseReleased method does.
If I do not explicitly set the LAF, i.e. if I use the default LAF, it works fine.
How can I fix this?
Below are two classes: MListen and Panel. Class MListen declares a main method, so you can launch that class. Then you can perform the actions described above to reproduce the behavior. Class Panel is my custom JPanel.
I want the mousePressed method of the MouseListener, in class Panel to be called every time that the mouse is pressed if it is on the Panel.
MListen.java
package drawer.Main;
import javax.swing.*;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.event.*;
public class MListen extends JFrame{
public MListen(){
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setSize(800, 600);
//setExtendedState(MAXIMIZED_BOTH);
setLayout(new BorderLayout());
getContentPane().setBackground(Color.WHITE);
Panel panel = new Panel();
JMenuBar bar = new JMenuBar();
JMenu file = new JMenu("file");
JMenuItem open = new JMenuItem("open");
JButton b = new JButton("AB");
file.add(b);
bar.add(file);
file.add(open);
setJMenuBar(bar);
add(panel, BorderLayout.CENTER);
setVisible(true);
}
public static void main(String[] args){
javax.swing.SwingUtilities.invokeLater(new Runnable() {
public void run() {
try {
UIManager.setLookAndFeel("com.sun.java.swing.plaf.windows.WindowsLookAndFeel");
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException
| UnsupportedLookAndFeelException e) {
e.printStackTrace();
}
new MListen();
}
});
}
}
Panel.java
package drawer.Main;
import java.awt.*;
import java.awt.event.*;
import javax.swing.JPanel;
public class Panel extends JPanel implements MouseListener, MouseMotionListener{
public float size;
public Color color;
public boolean DoDraw;
public Panel(){
addMouseListener(this);
setFocusable(true);
addMouseMotionListener(this);
setBackground(Color.RED);
}
#Override
public void mouseDragged(MouseEvent arg0) {
//System.out.println("Drag");
}
#Override
public void mouseMoved(MouseEvent arg0) {
// TODO Auto-generated method stub
}
#Override
public void mouseClicked(MouseEvent arg0) {
// TODO Auto-generated method stub
}
#Override
public void mouseEntered(MouseEvent arg0) {
// TODO Auto-generated method stub
}
#Override
public void mouseExited(MouseEvent arg0) {
// TODO Auto-generated method stub
}
#Override
public void mousePressed(MouseEvent arg0) {
System.out.println("Press");
// TODO Auto-generated method stub
}
#Override
public void mouseReleased(MouseEvent arg0) {
System.out.println("relese");
// TODO Auto-generated method stub
}
}
Try running the above code clicking on the red JPanel. The console will display "Press". Now click on the JMenu and then on the JPanel and "Press" will not be displayed in the console.
Thanks!
Edit
I need the UIManager.setLookAndFeel("com.sun.java.swing.plaf.windows.WindowsLookAndFeel");
Can I fix this without removing it?
Also I need the MousePressed on the first time
Here is a bad answer, but it should work. You can capture all of the events before they're delegated.
This can be added to your JPanel constructor.
long eventMask = AWTEvent.MOUSE_EVENT_MASK;
Toolkit.getDefaultToolkit().addAWTEventListener( new AWTEventListener() {
public void eventDispatched(AWTEvent e){
MouseEvent evt = (MouseEvent)e;
if( evt.getSource() == this && evt.getId()==MouseEvent.MOUSE_PRESSED ){
this.mousePressed(evt);
evt.consume();
}
}
}, eventMask );
This captures all mouse events check that they original from the correct panel, checks if it is a mousePressed event and calls the method.
This is good code for debugging, but not good to find in your project.

Java bringing window to front doesnt let me type in textfield

I want to be able to type in textfield when my frame brought foreground. Program is set to bring my Frame to the foreground after 5 seconds and set focus on textfield. I start the program then click another window. My frame shows up after 5 seconds with cursor blinking. But when i type something it doesnt actually taken as input by textfield. I also implemented FocusListener to confirm that focus set to textfield when frame brought to foreground. My operating system is Windows 10.
import java.awt.BorderLayout;
import java.awt.Dimension;
import java.awt.FlowLayout;
import java.awt.event.FocusEvent;
import java.awt.event.FocusListener;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JTextField;
public class TestMain implements FocusListener {
/**
* Create the GUI and show it. For thread safety, this method should be
* invoked from the event-dispatching thread.
*/
public JFrame frame = null;
private void createAndShowGUI() {
// Create and set up the window.
frame = new JFrame();
frame.addFocusListener(this);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
JPanel panel = new JPanel();
panel.setLayout(new FlowLayout());
JLabel label1 = new JLabel("Label");
panel.add(label1);
JTextField textField1 = new JTextField();
textField1.setPreferredSize(new Dimension(300, 40));
panel.add(textField1);
textField1.addFocusListener(this);
label1.addFocusListener(this);
frame.addWindowListener(new WindowAdapter() {
// Program Closing Alert
public void windowActivated(WindowEvent e) {
System.out.println("window activated");
textField1.requestFocusInWindow();
}
});
frame.getContentPane().add(panel, BorderLayout.CENTER);
// Display the window.
frame.pack();
frame.setVisible(true);
}
public static void main(String[] args) {
TestMain mn = null;
// Schedule a job for the event-dispatching thread:
// creating and showing this application's GUI.
mn = new TestMain();
mn.createAndShowGUI();
System.out.println("START");
try {
Thread.sleep(5000);
mn.frame.setAlwaysOnTop(true);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
#Override
public void focusGained(FocusEvent e) {
System.out.println("Focus Gained by " + e.getComponent().getClass().getName());
}
#Override
public void focusLost(FocusEvent e) {
System.out.println("Focus Lost by " + e.getComponent().getClass().getName());
}
}
UPDATE : When i add
frame.setLocationByPlatform( true );
after
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
it works only for the first time. if i change main function as
try {
Thread.sleep(5000);
mn.frame.setAlwaysOnTop(true);
mn.frame.setAlwaysOnTop(false);
Thread.sleep(5000);
mn.frame.setAlwaysOnTop(true);
mn.frame.setAlwaysOnTop(false);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
it doesnt work for the second time
You do bring focus to the JTextField but you dont b ring focus to the frame itself. Bringing the frame to the front and bringing the focus to the frame is different. A solution similar can be found here: How to set focus the already running application?
Moving your frame doesn't focus your textfield. You should add toFront() after set always on top so if the Window is visible, brings the Window to the front and may make it the focused Window.
try {
Thread.sleep(5000);
mn.frame.setVisible ( true );
mn.frame.toFront ( );
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}

Why can't I get KeyEvent.VK_TAB when I use Key Binding for a JPanel

I will print related info if users focus on current window and press a key. However, it works for some keys like 'a' but not for 'tab'. Here is a simple demo:
import java.awt.BorderLayout;
import java.awt.Dimension;
import java.awt.event.ActionEvent;
import java.awt.event.KeyEvent;
import javax.swing.AbstractAction;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.KeyStroke;
public class KeyBindingTest {
public static void main(String[] args) {
KeyBindingTest test = new KeyBindingTest();
test.createUI();
}
public void createUI(){
JFrame frame = new JFrame("KeyBinding Test");
MainPanel mainPanel = new MainPanel();
frame.add(mainPanel,BorderLayout.CENTER);
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
#SuppressWarnings("serial")
class MainPanel extends JPanel{
public MainPanel(){
setPreferredSize(new Dimension(200, 200));
//========================key binding============================
requestFocusInWindow();
String aString = "aStr";
getInputMap().put(KeyStroke.getKeyStroke(KeyEvent.VK_A, 0), aString);
getActionMap().put(aString, new AbstractAction() {
#Override
public void actionPerformed(ActionEvent e) {
// TODO Auto-generated method stub
System.out.println("a is typed");
}
});
String tabString = "tabStr";
getInputMap().put(KeyStroke.getKeyStroke(KeyEvent.VK_TAB, 0), tabString);
getActionMap().put(tabString, new AbstractAction() {
#Override
public void actionPerformed(ActionEvent e) {
// TODO Auto-generated method stub
System.out.println("tab is typed");
}
});
}
}
}
What can I do to reach it? Thanks in advance.
Quote from How to Use the Focus Subsystem (The Java™ Tutorials > Creating a GUI With JFC/Swing > Using Other Swing Features) (suggested by #alex2410(link to #camickr post) and #mKorbel):
In most Look and Feel models, components are navigated using the Tab and Shift-Tab keys. These keys are the default focus traversal keys and can be changed programmatically.
...
Tab shifts the focus in the forward direction. Shift-Tab moves the focus in the backward direction. Tabbing moves the focus through the buttons into the text area. Additional tabbing moves the cursor within the text area but not out of the text area because, inside a text area, Tab is not a focus traversal key. However, Control-Tab moves the focus out of the text area and into the first text field. Likewise, Control-Shift-Tab moves the focus out of the text area and into the previous component.
...
The Control key is used by convention to move the focus out of any component that treats Tab in a special way, such as JTable.
You have just received a brief introduction to the focus architecture. If you want more details, see the specification for the Focus Subsystem.
So if you want to make the Tab KeyBinding action work in the panel, you need to remove the Tab key focus navigation from the panel.
import java.awt.*;
import java.awt.event.*;
import java.util.*;
import javax.swing.*;
//http://stackoverflow.com/q/24800417/714968
public class KeyBindingTest3 {
public static void main(String[] args) {
EventQueue.invokeLater(new Runnable() {
#Override public void run() {
createAndShowGUI();
}
});
}
public static void createAndShowGUI() {
JFrame f = new JFrame("KeyBinding Test");
f.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
f.getContentPane().add(new MainPanel());
f.setSize(320, 240);
f.setLocationRelativeTo(null);
f.setVisible(true);
}
}
class MainPanel extends JPanel {
public MainPanel() {
super();
//#see JTable constructor
Set<KeyStroke> forwardKeys = new HashSet<KeyStroke>(1);
forwardKeys.add(KeyStroke.getKeyStroke(
KeyEvent.VK_TAB, InputEvent.CTRL_MASK));
setFocusTraversalKeys(
KeyboardFocusManager.FORWARD_TRAVERSAL_KEYS, forwardKeys);
Set<KeyStroke> backwardKeys = new HashSet<KeyStroke>(1);
backwardKeys.add(KeyStroke.getKeyStroke(
KeyEvent.VK_TAB, InputEvent.SHIFT_MASK | InputEvent.CTRL_MASK));
setFocusTraversalKeys(
KeyboardFocusManager.BACKWARD_TRAVERSAL_KEYS, backwardKeys);
setPreferredSize(new Dimension(200, 200));
String aString = "aStr";
getInputMap().put(KeyStroke.getKeyStroke(KeyEvent.VK_A, 0), aString);
getActionMap().put(aString, new AbstractAction() {
#Override public void actionPerformed(ActionEvent e) {
System.out.println("a is typed");
}
});
String tabString = "TAB";
getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(
KeyStroke.getKeyStroke(KeyEvent.VK_TAB, 0), tabString);
getActionMap().put(tabString, new AbstractAction() {
#Override public void actionPerformed(ActionEvent e) {
System.out.println("tab is typed");
}
});
}
}
interesting issue with TAB, looks like as bug, because isn't possible to get, capture the KeyChar from TAB without using Shift_TAB before, event from TAB is somehow consumed elsewhere, no idea whats happened
my view - there is an issue with Focus because key TAB is used by Native OS and as built_in KeyBindings in Swing,
opposite issue with TAB and Shift_TAB in question Java Swing: how to stop unwanted shift-tab keystroke action
maybe someone has explanation how to catch a TAB event
TAB is used as KeyBindings (built_in in API) for many JComponents or navigations inside Container contains more than one JComponent
funny output from AWTEventListener (win8_64b/Java7_xxx)
is typed //tab is pressed
is typed
is typed
is typed
is typed
is typed
is typed
is typed
is typed
is typed //shift is pressed
is typed
is typed
is typed
is typed
is typed
is typed
is typed
is typed
is typed
is typed
is typed
is typed
is typed
is typed
is typed
is typed
is typed
is typed
is typed
is typed
is typed
is typed
is typed
is typed
is typed
is typed
is typed
is typed
is typed // tab is pressed again
is typed
is typed
is typed
is typed //:-) nobody knows something from milky way
is typed
is typed
shift tab is typed //now is tab event unlocked for Container in Swing
shift tab is typed
shift tab is typed
ctrl tab is typed
ctrl tab is typed
ctrl tab is typed
tab is typed // now is possible, finally TAB is unlocked and firing an event
tab is typed
tab is typed
from code
import java.awt.AWTEvent;
import java.awt.BorderLayout;
import java.awt.Dimension;
import java.awt.Toolkit;
import java.awt.event.AWTEventListener;
import java.awt.event.ActionEvent;
import java.awt.event.KeyEvent;
import javax.swing.AbstractAction;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.KeyStroke;
//https://stackoverflow.com/q/24800417/714968
public class KeyBindingTest {
public static void main(String[] args) {
KeyBindingTest test = new KeyBindingTest();
test.createUI();
}
public void createUI() {
JFrame frame = new JFrame("KeyBinding Test");
MainPanel mainPanel = new MainPanel();
frame.add(mainPanel, BorderLayout.CENTER);
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
Toolkit.getDefaultToolkit().addAWTEventListener(new AWTEventListener() {
#Override
public void eventDispatched(AWTEvent event) {
if (event instanceof KeyEvent) {
KeyEvent ke = (KeyEvent) event;
System.out.println(ke.getKeyChar() + " is typed");
}
}
}, AWTEvent.KEY_EVENT_MASK);
}
#SuppressWarnings("serial")
class MainPanel extends JPanel {
public MainPanel() {
setPreferredSize(new Dimension(200, 200));
//========================key binding============================
//requestFocusInWindow();
String aString = "aStr";
getInputMap().put(KeyStroke.getKeyStroke(KeyEvent.VK_A, 0), aString);
getActionMap().put(aString, new AbstractAction() {
#Override
public void actionPerformed(ActionEvent e) {
// TODO Auto-generated method stub
System.out.println("a is typed");
}
});
String tabString = "TAB";
this.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(KeyStroke.getKeyStroke(tabString), tabString);
//getInputMap().put(KeyStroke.getKeyStroke(KeyEvent.VK_TAB, 0), tabString);
this.getActionMap().put(tabString, new AbstractAction() {
#Override
public void actionPerformed(ActionEvent e) {
// TODO Auto-generated method stub
System.out.println("tab is typed");
}
});
String tabShiftString = "shift TAB";
this.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(KeyStroke.getKeyStroke(tabShiftString), tabShiftString);
//getInputMap().put(KeyStroke.getKeyStroke(KeyEvent.VK_TAB, 0), tabString);
this.getActionMap().put(tabShiftString, new AbstractAction() {
#Override
public void actionPerformed(ActionEvent e) {
// TODO Auto-generated method stub
System.out.println("shift tab is typed");
}
});
String ctrlShiftString = "ctrl TAB";
this.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(KeyStroke.getKeyStroke(ctrlShiftString), ctrlShiftString);
//getInputMap().put(KeyStroke.getKeyStroke(KeyEvent.VK_TAB, 0), tabString);
this.getActionMap().put(ctrlShiftString, new AbstractAction() {
#Override
public void actionPerformed(ActionEvent e) {
// TODO Auto-generated method stub
System.out.println("ctrl tab is typed");
}
});
}
}
}
I think it should work if you put
setFocusTraversalKeysEnabled(false);
in your MainPanel constructor. At least it works for e.g. addKeyListener(...);

KeyListener won't work?

I am trying to make a very simple 2-frame program called Duck Simulator.
This has a JFrame and 2 pictures. If you want to know what it does, it just is a JFrame with a starting picture of a duck sitting in a pond. It has a JLabel saying "Press D to drink water!" And when you press D, it is supposed to set the image to the duck drinking.
It shows the opening image of the duck sitting in the pond in the JFrame, but when I press D, it doesn't do anything.
Here is the code:
package net.ducksimulator.classes;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.io.File;
import java.io.IOException;
import javax.imageio.ImageIO;
import javax.swing.ImageIcon;
import javax.swing.JFrame;
import javax.swing.JLabel;
public class a implements KeyListener {
static JFrame f;
public static void main (String[] args) {
f = new JFrame("Duck Simulator ALPHA");
try {
f.setContentPane(new JLabel(new ImageIcon(ImageIO.read(new File("images/duck-sitting.png")))));
} catch (IOException e) {
System.out.println("Image doesn't exist.");
}
f.setResizable(false);
f.setVisible(true);
JLabel l = new JLabel("Press D to drink water!");
l.setBounds(250,20,100,10);
f.add(l);
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
f.pack();
f.setLocationRelativeTo(null);
}
public void drink() throws IOException {
f.setContentPane(new JLabel(new ImageIcon(ImageIO.read(new File("images/duck-drinking.png")))));
}
public void sit() throws IOException {
f.setContentPane(new JLabel(new ImageIcon(ImageIO.read(new File("images/duck-sitting.png")))));
}
#Override
public void keyPressed(KeyEvent e) {
int key = e.getKeyCode();
if (key == KeyEvent.VK_D) {
try {
drink();
} catch (IOException e1) {
e1.printStackTrace();
}
}
}
#Override
public void keyReleased(KeyEvent e) {
int key = e.getKeyCode();
if (key == KeyEvent.VK_D) {
try {
System.out.println("Bagels");
sit();
} catch (IOException e1) {
e1.printStackTrace();
}
}
}
#Override
public void keyTyped(KeyEvent e) {
}
}
Simply implementing KeyListener isn't enough, you need to specify which component you want to receive key events.
This is where the problems start. KeyListener will only raise events for components that have focus AND are focusable.
A better solution would be to use the key bindings API which provides you with better control over the level of focus a component needs before it will trigger key events
You should avoid using setBounds for, at least, two reasons.
Firstly, the container you adding the component to is under the control of a layout manager (BorderLayout in this instance), which makes the use of setBounds pointless and secondly, you don't control the factors which will alter the required amount of space a component might need when presented on different platforms, such as font metrics and rendering pipelines. Let the layout managers do there job

How to force JPopupMenu to show title even if Look and Feel UI dictates otherwise?

Javadoc for JPopupMenu constructor says the following:
JPopupMenu
public JPopupMenu(String label)
Constructs a JPopupMenu with the specified title.
Parameters:
label - the string that a UI **may** use to display as a title for the popup menu.
Key word being "may". Evidently in the default UI, such titles are ignored when creating a popup menu. I very much want such titles in some of my popup menus to be used regardless of whether or not the L&F thinks I should. I can't find the hook to make it so. Evidently, this is buried deep in the UI code somewhere. Is there a way to override this default?
Failing that, I have tried adding a disabled menu item as the first item of the menu. Trouble with that is then I lose control of its rendering, and it renders in the "greyed out" style instead of appearing as the important title. If I don't disable it, then it renders as I want, but is selectable, which a title would not be.
So bottom line, how do I either force the UI to display my title, or failing that, how do I add a non-selectable menu item at the top of the menu that I have full rendering control over?
I had similar problem (I mean the original question). I solved it this way:
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JMenuItem;
import javax.swing.JPopupMenu;
import javax.swing.SwingConstants;
public class LabeledPopupMenu extends JPopupMenu
{
private String originalLabelText = null;
private final JLabel label;
private static String replaceHTMLEntities(String text)
{
if (-1 != text.indexOf("<") ||
-1 != text.indexOf(">") ||
-1 != text.indexOf("&"))
{
text = text.replaceAll("&", "&");
text = text.replaceAll("<", "<");
text = text.replaceAll(">", ">");
}
return text;
}
public LabeledPopupMenu()
{
super();
this.label = null;
}
public LabeledPopupMenu(String label)
{
super();
originalLabelText = label;
this.label = new JLabel("<html><b>" +
replaceHTMLEntities(label) + "</b></html>");
this.label.setHorizontalAlignment(SwingConstants.CENTER);
add(this.label);
addSeparator();
}
#Override public void setLabel(String text)
{
if (null == label) return;
originalLabelText = text;
label.setText("<html><b>" +
replaceHTMLEntities(text) +
"</b></html>");
}
#Override public String getLabel()
{
return originalLabelText;
}
}
I have tested it ony on Mac with default L&F, but it worked for me:
public static void main(String[] args)
{
JFrame frame = new JFrame();
frame.setSize(100, 100);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setLocation(100, 100);
frame.setVisible(true);
LabeledPopupMenu myPopup = new LabeledPopupMenu("Say & <something>");
myPopup.add(new JMenuItem("Sample item"));
myPopup.show(frame, 50, 50);
}
JPopup is very strange Container, didn't work me
1) public JPopupMenu(String label)
2) didn't work me alignment for JLabel, please maybe somebody can test text justify by using Html
3) not possible shows JComboBox dropdown popup is same time with JPopup (doesn't matter if is JPopup light or heavyweight)
4) and another (not important for basic Swing) tested Java5 and 6 with various LaF
import java.awt.*;
import javax.swing.*;
import javax.swing.UIManager.LookAndFeelInfo;
import javax.swing.event.*;
import javax.swing.plaf.*;
import org.pushingpixels.substance.api.skin.SubstanceOfficeSilver2007LookAndFeel;
class MyPopupMenuListener implements PopupMenuListener {
public void popupMenuCanceled(PopupMenuEvent popupMenuEvent) {
System.out.println("Canceled");
}
public void popupMenuWillBecomeInvisible(PopupMenuEvent popupMenuEvent) {
System.out.println("Becoming Invisible");
}
public void popupMenuWillBecomeVisible(PopupMenuEvent popupMenuEvent) {
System.out.println("Becoming Visible");
}
}
public class PopupMenuListenerDemo {
public static void main(final String args[]) {
final JFrame frame = new JFrame("PopupSample Example");
/*SwingUtilities.invokeLater(new Runnable() {
public void run() {
try {
UIManager.setLookAndFeel(new SubstanceOfficeSilver2007LookAndFeel());
SwingUtilities.updateComponentTreeUI(frame);
} catch (UnsupportedLookAndFeelException e) {
throw new RuntimeException(e);
}
}
});
try {
for (LookAndFeelInfo info : UIManager.getInstalledLookAndFeels()) {
System.out.println(info.getName());
if ("Nimbus".equals(info.getName())) {
UIManager.setLookAndFeel(info.getClassName());
break;
}
}
} catch (UnsupportedLookAndFeelException e) {
// handle exception
} catch (ClassNotFoundException e) {
// handle exception
} catch (InstantiationException e) {
// handle exception
} catch (IllegalAccessException e) {
// handle exception
}
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (Exception e) {
e.printStackTrace();
}*/
UIResource res = new BorderUIResource.LineBorderUIResource(Color.red);
UIManager.put("PopupMenu.border", res);
JPopupMenu popupMenu = new JPopupMenu("Title");
//force to the Heavyweight Component or able for AWT Components
popupMenu.setLightWeightPopupEnabled(false);
PopupMenuListener popupMenuListener = new MyPopupMenuListener();
popupMenu.addPopupMenuListener(popupMenuListener);
JLabel lbl1 = new JLabel("My Title");
lbl1.setHorizontalAlignment(SwingConstants.CENTER);
popupMenu.add(lbl1);
JTextField text = new JTextField("My Title");
text.setHorizontalAlignment(SwingConstants.CENTER);
popupMenu.add(text);
String[] list = {"1", "2", "3", "4",};
JComboBox comb = new JComboBox(list);
popupMenu.add(comb);
JMenuItem pasteMenuItem = new JMenuItem("Paste");
pasteMenuItem.setEnabled(false);
popupMenu.add(pasteMenuItem);
popupMenu.addSeparator();
JMenuItem findMenuItem = new JMenuItem("Find");
popupMenu.add(findMenuItem);
JButton btn = new JButton();
JLabel lbl = new JLabel("My Title");
lbl.setHorizontalAlignment(SwingConstants.CENTER);
btn.setComponentPopupMenu(popupMenu);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(btn, BorderLayout.CENTER);
frame.add(lbl, BorderLayout.NORTH);
frame.setSize(350, 150);
SwingUtilities.invokeLater(new Runnable() {
public void run() {
frame.setVisible(true);
}
});
}
}
5 and terrible alignment for JLabel if there is added JComboBox :-) brrrrr !!!!
Wasn't even aware of that feature :-)
Digging a bit, it turns out that MotifLookAndFeel is the only of the core LAFs which support a title in the popup. It's realized by a custom border. Which you can do as well:
JPopupMenu popup = new JPopupMenu("My Label");
popup.add("dummy menu item");
Border titleUnderline = BorderFactory.createMatteBorder(1, 0, 0, 0, popup.getForeground());
TitledBorder labelBorder = BorderFactory.createTitledBorder(
titleUnderline, popup.getLabel(),
TitledBorder.CENTER, TitledBorder.ABOVE_TOP, popup.getFont(), popup.getForeground());
popup.setBorder(BorderFactory.createCompoundBorder(popup.getBorder(),
labelBorder));
JComponent comp = new JPanel();
comp.setComponentPopupMenu(popup);
Note: as far as I can see, there is no safe way to detect whether or not the LAF handles the title itself (which would result in doubling it)

Categories

Resources