I have the JPopupMenu shown on right mouse click. I want JPopupMenu' top right corner to be at the click location (not top left one, as default). To perform this, I need to set the X coordinate as mouseEvent.getX() - popupMenu.getWidth(). The problem is, before popup is shown first time, its width equals 0.
SSCCE:
public class PopupTest2 {
public static void main(String[] a) {
final JFrame frame = new JFrame();
frame.setSize(500, 500);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
final JPanel panel = new JPanel(new BorderLayout());
panel.setBorder(BorderFactory.createLineBorder(Color.RED));
final JPopupMenu menu = new JPopupMenu();
for (int i = 0; i < 10; i++) {
JMenuItem item = new JMenuItem("Item #"+String.valueOf(i));
menu.add(item);
}
panel.addMouseListener(new MouseAdapter() {
#Override
public void mouseClicked(MouseEvent e) {
if (e.getButton() == MouseEvent.BUTTON3) {
// first time works wrong
menu.show(panel, e.getX() - menu.getWidth(), e.getY());
}
}
});
frame.setContentPane(panel);
frame.setUndecorated(true);
frame.setBackground(new Color(50, 50, 50, 200));
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
frame.setVisible(true);
}
});
}
}
Using the preferredSize (as already mentioned) is the way to go for getting the location.
But (being on a quest here :-): manually showing the popup is not the recommended approach. A really clean implementation will use the componentPopupMenu property and implement getPopupLocation as needed, something like
JPanel panel = new JPanel(new BorderLayout()) {
#Override
public Point getPopupLocation(MouseEvent event) {
JPopupMenu menu = getComponentPopupMenu();
if (menu == null || event == null) return null;
return new Point(event.getX() - menu.getPreferredSize().width, event.getY());
}
};
JPopupMenu menu = new JPopupMenu();
panel.setComponentPopupMenu(menu);
What's preferred size of the JPopupMenu before showing?
Probably you can determine the width of context menu as the highest preferred width of ist elements?
Using popup.getPreferredSize().width and popup.getPreferredSize().height you can get popup size before it is shown.
This is an example locating the popup menu at the top of a button:
but_menu = new JButton("");
but_menu.addMouseListener(new MouseAdapter() {
public void mousePressed(MouseEvent e) {
Point p = but_menu.getLocation();
Point dest = new Point();
dest.x = 0 - popup.getPreferredSize().width + but_menu.getWidth();
dest.y = 0 - popup.getPreferredSize().height;
popup.show(but_menu,dest.x,dest.y);
}
});
Related
I have been having difficulty with making a method of Listeners to use repetitively in my code. Also, I am new to this, so I am very sorry for anything I leave out on this. Though, now here is my method for the Listeners:
public static void keysEvents(Optional<String> output)
{
MouseListener mouseEvents = new MouseAdapter()
{
public void mouseClick (MouseEvent mouseEvent)
{
Integer mouseModifiers = mouseEvent.getModifiers();
if ((mouseModifiers & InputEvent.BUTTON1_MASK) ==
InputEvent.BUTTON1_MASK)
{
if (output == null)
{
System.out.println("click");
}
//more options...
}
}
public void mouseRelease (MouseEvent mouseEvent)
{
}
};
//More listeners...
}
EDITED VERSION:
public static MouseListener keysEvents(Optional<String> output)
{
MouseListener mouseEvents = new MouseAdapter()
{
public void mouseClick (MouseEvent mouseEvent)
{
Integer mouseModifiers = mouseEvent.getModifiers();
if ((mouseModifiers & InputEvent.BUTTON1_MASK) ==
InputEvent.BUTTON1_MASK)
{
if (output == null)
{
System.out.println("click");
}
//more options...
}
}
public void mouseRelease (MouseEvent mouseEvent)
{
}
};
//More listeners...
return mouseEvents;
//How would I have it return different listeners?
}
Here is the code for the program's window:
JPanel MainP = new JPanel();
MainP.setLayout(new GridLayout(4, 2, 100, 30));
frame.setLayout(new GridLayout(1, 2));
frame.setBackground(Color.blue);
JPanel _B1_ = new JPanel();
JPanel _B2_ = new JPanel();
JPanel _B3_ = new JPanel();
JPanel _B4_ = new JPanel();
Button _Continue_ = new Button("Continue");
Button _Load_Game_ = new Button("Load Game");
Button _Settings_ = new Button("Settings");
Button _Exit_ = new Button("Exit");
_Continue_.addMouseListener(keysEvents(Optional.of(""))); //<edited here.
_Continue_.setBackground(Color.lightGray);
_Load_Game_.setBackground(Color.lightGray);
_Settings_.setBackground(Color.lightGray);
_Exit_.setBackground(Color.lightGray);
MainP.setBackground(Color.gray);
_B1_.setBackground(Color.gray);
_B2_.setBackground(Color.gray);
_B3_.setBackground(Color.gray);
_B4_.setBackground(Color.gray);
MainP.add(_B1_);
MainP.add(_Continue_);
MainP.add(_B2_);
MainP.add(_Load_Game_);
MainP.add(_B3_);
MainP.add(_Settings_);
MainP.add(_B4_);
MainP.add(_Exit_);
frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
frame.setUndecorated(!frameBorder);
frame.pack();
frame.setMinimumSize(new Dimension(_minX, _minY));
frame.setSize(windowX, windowY);
frame.add(MainP);
frame.setVisible(true);
Finally, if I asked wrongly in any way, please tell me; so I can do better, later on, when asking questions. Hope it does not suck hours out of your life like it did me.
You need to use ActionListener. This is a method used with JButtons, and I've used it many times. JButtons are more popular and are easier to use than Buttons. JButtons are part of the swing component. Here is an example of the usage of ActionListener:
JButton button = new JButton("Example"); //Create a JButton with the text "Example"
button.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
//Code to be executed upon press goes here
}
});
I'd also like to add to #camickr's comment; Java variables should always start with a lower case letter, and never start with _. For example, JButton continue = new JButton("Continue"); would be the proper way to declare and initialize a button. Any word after the beginning word in a varable name should be capitalized. For example, JButton continueButton = new JButton("Continue"); would be the proper naming.
I have JSplitPane that has oneTouchExpandable set to true.
JSplitPane splitPane = new JSplitPane(JSplitPane.VERTICAL_SPLIT);
splitPane.setDividerSize(10);
splitPane.setOneTouchExpandable(true);
The problem is that I do not know how to attach key bindings to up and down arrows on JSplitPane's divider. For up arrow I want Ctrl+U and for down - Ctrl + D.
Thanks!
Implementation of the arrow button shown by OneTouchExpandable is UI label and will take extra work unnecessarily to bind them. You can easily use Key Binding on JSplitPane itself to control the JSplitPane divider location using setDividerLocation(int). Increase on Ctrl + U and Decrease on Ctrl + D. For example:
Action incrDividerLoc = new AbstractAction() {
public void actionPerformed(ActionEvent e) {
JSplitPane srcSplitPan = (JSplitPane) e.getSource();
(srcSplitPan).setDividerLocation(srcSplitPan.getDividerLocation()+10);
}
};
Action decrDividerLoc = new AbstractAction() {
public void actionPerformed(ActionEvent e) {
JSplitPane srcSplitPan = (JSplitPane) e.getSource();
(srcSplitPan).setDividerLocation(srcSplitPan.getDividerLocation()-10);
}
};
jSplitPane1.getInputMap().put(KeyStroke.getKeyStroke(KeyEvent.VK_U, KeyEvent.CTRL_DOWN_MASK),
"increaseDivider");
jSplitPane1.getInputMap().put(KeyStroke.getKeyStroke(KeyEvent.VK_D, KeyEvent.CTRL_DOWN_MASK),
"decreaseDivider");
jSplitPane1.getActionMap().put("increaseDivider", incrDividerLoc);
jSplitPane1.getActionMap().put("decreaseDivider", decrDividerLoc);
Note: method A value less than 0 passed to setDividerLocation(int) implies the divider should be reset to a value that attempts to honor the preferred size of the left/top component. After notifying the listeners, the last divider location is updated, via setLastDividerLocation.
The problem is that I do not know how to attach key bindings to up and down arrows on JSplitPane's divider.
Normally you would try to access the Action of the button. In many cases the component will already define an Action that you can use. See Key Bindings for a list of the default bindings for a JSplitPane. Unfortunately there is no Action to support the one touch clicking options.
So we need to access the buttons directly from the UI:
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import javax.swing.plaf.basic.*;
public class SplitPaneDividerAction extends AbstractAction
{
private boolean leading;
public SplitPaneDividerAction(boolean leading)
{
this.leading = leading;
}
#Override
public void actionPerformed(ActionEvent e)
{
JSplitPane splitPane = (JSplitPane)e.getSource();
BasicSplitPaneUI ui = (BasicSplitPaneUI)splitPane.getUI();
BasicSplitPaneDivider divider = ui.getDivider();
if (leading)
((JButton)divider.getComponent(0)).doClick();
else
((JButton)divider.getComponent(1)).doClick();
}
private static void createAndShowUI()
{
JPanel leading = new JPanel();
leading.setPreferredSize( new Dimension(200, 100) );
leading.setBackground( Color.BLUE );
leading.setFocusable(true);
JPanel trailing = new JPanel();
trailing.setPreferredSize( new Dimension(200, 100) );
trailing.setBackground( Color.RED );
trailing.setFocusable(true);
JSplitPane splitPane = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, leading, trailing);
splitPane.setOneTouchExpandable(true);
splitPane.setDividerLocation(100);
InputMap im = splitPane.getInputMap(JSplitPane.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT);
ActionMap am = splitPane.getActionMap();
im.put(KeyStroke.getKeyStroke("control U"), "leading");
im.put(KeyStroke.getKeyStroke("control D"), "trailing");
am.put("leading", new SplitPaneDividerAction(true));
am.put("trailing", new SplitPaneDividerAction(false));
JFrame frame = new JFrame("SSCCE");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add( splitPane );
frame.setSize(200, 200);
frame.setLocationByPlatform( true );
frame.setVisible( true );
}
public static void main(String[] args)
{
EventQueue.invokeLater(new Runnable()
{
public void run()
{
createAndShowUI();
}
});
}
}
Of course this approach will only work if your LAF extends from the BasicSplitPaneUI.
I have a JFrame in which I remove and add components and each time I do this, I resize the frame accordingly. I added a Component Listener but of course it gets triggered both by the user resizing the window and also by my setSize() methods.
Is there any way to see from the ComponentEvent whether the user resized the window or I did through setSize()?
The posible solutions I found are:
1. Use a flag - boolean resizing - which I set to true before setSize() and to false after that.
2. Add a mouseDragged listener and compare sizes before and after the drag.
The second one is definitely not a good choice. The first one would work but I would like to know if I can find in a simple way whether the user is the one who resized the window or not.
I resize the frame accordingly
Whats wrong with using pack()?
I remove and add components and each time I do this,
Then this is where you should set your Boolean value:
programResize == true:
panel.add(...);
frame.setSize(...); // this should cause the ComponentListener to fire
// the ComponentListener would then use programResize == false;
Or a better option option could be:
component.removeComponentListener(...);
panel.add(...);
frame.setSize(...);
component.addComponentListener(...);
I like this approach better because all the logic based on the manual update is self contained in one place and there is no need to define a Boolean variable.
Is there any way to see from the ComponentEvent whether the user
resized the window or I did through setSize()?
yes it is, use boolean flag reseted by Swing Timer
in the case that user resized window then ComponentListener firing a new event per every pixel, bunch of events
by determine the contianer set(Xxx)Size is this event fired only once time
example about events from ComponentListner
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
import java.util.Vector;
public class ComponentEventDemo extends JPanel
implements ComponentListener, HierarchyListener,
ItemListener {
private JFrame frame;
private static final long serialVersionUID = 1L;
private JTextArea display;
private JLabel label;
private JButton button = new JButton("Change Size");
private String newline = "\n";
public ComponentEventDemo() {
display = new JTextArea();
display.setEditable(false);
JScrollPane scrollPane = new JScrollPane(display);
scrollPane.setPreferredSize(new Dimension(350, 200));
label = new JLabel("This is a label", JLabel.CENTER);
label.addComponentListener(this);
JCheckBox checkbox = new JCheckBox("Label visible", true);
checkbox.addItemListener(this);
checkbox.addComponentListener(this);
button.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
Dimension dim = frame.getPreferredSize();
if (!dim.equals(new Dimension(800, 600))) {
frame.setPreferredSize(new Dimension(800, 600));
frame.pack();
} else {
frame.setPreferredSize(new Dimension(400, 300));
frame.pack();
}
}
});
JPanel panel = new JPanel(new GridLayout(1, 3));
panel.add(label);
panel.add(checkbox);
panel.add(button);
panel.addComponentListener(this);
frame = new JFrame("ComponentEventDemo");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(scrollPane, BorderLayout.CENTER);
frame.add(panel, BorderLayout.PAGE_END);
frame.pack();
frame.setVisible(true);
}
#Override
public void itemStateChanged(ItemEvent e) {
if (e.getStateChange() == ItemEvent.SELECTED) {
label.setVisible(true);
label.revalidate();
label.repaint();
} else {
label.setVisible(false);
}
}
protected void displayMessage(String message) {
//If the text area is not yet realized, and
//we tell it to draw text, it could cause
//a text/AWT tree deadlock. Our solution is
//to ensure that the text area is realized
//before attempting to draw text.
// if (display.isShowing()) {
display.append(message + newline);
display.setCaretPosition(display.getDocument().getLength());
//}
}
#Override
public void componentHidden(ComponentEvent e) {
//displayMessage(e.getComponent().getClass().getName() + " --- Hidden");
}
#Override
public void componentMoved(ComponentEvent e) {
//displayMessage(e.getComponent().getClass().getName() + " --- Moved");
}
#Override
public void componentResized(ComponentEvent e) {
displayMessage(e.getComponent().getClass().getName() + " --- Resized ");
}
#Override
public void componentShown(ComponentEvent e) {
//displayMessage(e.getComponent().getClass().getName() + " --- Shown");
}
public static void main(String[] args) {
javax.swing.SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
ComponentEventDemo componentEventDemo = new ComponentEventDemo();
}
});
}
#Override
public void hierarchyChanged(HierarchyEvent e) {
displayMessage(e.getComponent().getClass().getName() + " --- Hierarchy changed");
}
}
I'm creating a custom button, and I'm having trouble getting it to look right on most of the built-in PLAFs.
Here's my code
public MyButton(String text, Icon icon) {
if (icon == null) icon = createDefaultIcon();
mainButton = new JButton(text);
popupButton = new JButton(icon);
removeBorder(mainButton);
removeBorder(popupButton);
setModel(new DefaultButtonModel());
setBorder(UIManager.getBorder("Button.border"));
int popupButtonWidth = popupButton.getPreferredSize().width;
int popupButtonHeight = mainButton.getPreferredSize().height;
Dimension popupButtonSize = new Dimension(popupButtonWidth, popupButtonHeight);
popupButton.setMinimumSize(popupButtonSize);
popupButton.setPreferredSize(popupButtonSize);
popupButton.setMaximumSize(popupButtonSize);
setLayout(new BoxLayout(this, BoxLayout.LINE_AXIS));
add(mainButton);
add(new JSeparator(VERTICAL));
add(popupButton);
}
private void removeBorder(JButton button) {
Border border = button.getBorder();
if (border instanceof CompoundBorder) {
button.setBorder(((CompoundBorder) border).getInsideBorder());
} else {
button.setBorder(BorderFactory.createEmptyBorder());
}
}
Here is how the button looks in the PLAFs installed on my computer
Metal
Nimbus
CDE/Motif
Mac OS X
CDE/Motif is the only one that works properly. I looked at the source for some of the ButtonUIs, and it seems they can ignore the background color and the borders. Unfortunately the background color and the borders are what I need to set. How do I get my custom button to support the built-in PLAFs correctly?
Edit:
As requested, here's the code I used to produce the images
public class MyButtonDemo implements Runnable {
public void run() {
// Change the array index to get a different PLAF
try {
UIManager.setLookAndFeel(UIManager.getInstalledLookAndFeels()[0].getClassName());
} catch (Exception ignored) { }
JFrame frame = new JFrame();
frame.getContentPane().setLayout(new FlowLayout());
frame.getContentPane().add(new MyButton("My Button", null);
frame.getContentPane().add(new JButton("Normal Button"));
frame.pack();
frame.setLocationRelativeTo(null);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setVisible(true);
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new MyButtonDemo());
}
}
Welcome to the wonderful world of pluggable look & feel. The only real choices you have, that I can think of, is provide. UI delegate for each platform or have a look at at the Syththetic UI
Not sure what I did, but it's working now for all but Nimbus. Here's the code
public MyButton(String text, Icon icon) {
arrowIcon = createDefaultArrowIcon();
mainButton = new JButton(text, icon);
popupButton = new JButton(arrowIcon);
mainButton.setBorder(BorderFactory.createEmptyBorder());
popupButton.setBorder(BorderFactory.createEmptyBorder());
setLayout(new BoxLayout(this, BoxLayout.LINE_AXIS));
add(mainButton);
add(new JSeparator(VERTICAL));
add(popupButton);
setModel(new DefaultButtonModel());
init(null, null);
}
#Override
public void updateUI() {
setUI((ButtonUI)UIManager.getUI(this));
}
#Override
public String getUIClassID() {
return "ButtonUI";
}
I need to create a combo box with multi-selection, how to achieve that?
I know, that the question is rather old, but for those, who still looks for solution of this problem, try the following code:
public class ComboSelections {
public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException, InstantiationException, UnsupportedLookAndFeelException {
UIManager.setLookAndFeel((LookAndFeel) Class.forName("com.sun.java.swing.plaf.nimbus.NimbusLookAndFeel").newInstance());
final JPopupMenu menu = new JPopupMenu();
JMenuItem one = new JCheckBoxMenuItem("One");
JMenuItem two = new JCheckBoxMenuItem("Two");
JMenuItem three = new JCheckBoxMenuItem("Three");
JMenuItem four = new JCheckBoxMenuItem("Four");
menu.add(one);
menu.add(two);
menu.add(three);
menu.add(four);
final JButton button = new JButton("Click me");
button.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
if (!menu.isVisible()) {
Point p = button.getLocationOnScreen();
menu.setInvoker(button);
menu.setLocation((int) p.getX(),
(int) p.getY() + button.getHeight());
menu.setVisible(true);
} else {
menu.setVisible(false);
}
}
});
one.addActionListener(new OpenAction(menu, button));
two.addActionListener(new OpenAction(menu, button));
three.addActionListener(new OpenAction(menu, button));
four.addActionListener(new OpenAction(menu, button));
JFrame frame = new JFrame();
JPanel panel = new JPanel();
panel.add(button);
frame.getContentPane().add(panel);
frame.pack();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setVisible(true);
}
private static class OpenAction implements ActionListener {
private JPopupMenu menu;
private JButton button;
private OpenAction(JPopupMenu menu, JButton button) {
this.menu = menu;
this.button = button;
}
#Override
public void actionPerformed(ActionEvent e) {
menu.show(button, 0, button.getHeight());
}
}
}
There are a few basic problems with creating custom combobox popup content (like a list with multiselection):
1. Default UI suggests JList usage as the content so to change that behavior you will have to change the whole ComboBoxUI
2. You cannot simply change the default combobox list into multiselection one due to the fact that only one value gets "selected" at the end and list has default rollover selection mouse listener, that will make you unable to choose more than one element
So i'd reccomend you to use simple JList instead of combobox or look into using some extended components libraries like JideSoft - they have this component and lots more which you won't be able to quickly create using Swing features.