Scrolling on a JComboBox popup hide it - java

My client is complaining that JComboBox popups often close when the scroll is being used over a JComboBox popup with no vertical scrollbar. (He seems to accidently use scrolling over it because he is using an Apple Magic Mouse.)
Any way to prevent this to happen ?
I know it has to do with the ComboBoxUI, but I would like a few pointer where to start. BasicComboPopup.handler is private (not reusable) and I don't see any code relative to any a MouseWhellListener in BasicComboPopup.

As seen in the source, BasicPopupMenuUI contains a nested class, MouseGrabber, that implements the AWTEventListener interface. The receipt of MouseEvent.MOUSE_WHEEL in eventDispatched() cancels the popup as a function of isInPopup(). I know of no simple way to defeat the behavior.
Empirically, this example invokes show() from the actionPerformed() handler of a JButton; mouse wheel events are ignored. This might be a reasonable alternative for your user, perhaps combined with a suitable ActionEvent modifier mask.
In contrast, this example invokes show() in response to isPopupTrigger() in a MouseAdapter; as expected, mouse wheel events cancel the popup.

Thanks to your suggestion, I've got an idea an found a solution by hacking AWTEventListeners.
Toolkit.getDefaultToolkit().addAWTEventListener(new AWTEventListener()
{
public void eventDispatched(AWTEvent event)
{
if (event instanceof MouseWheelEvent)
{
Object source = event.getSource();
if ((source instanceof JScrollPane) &&
(((JScrollPane) source).getParent().getClass().
getName().equals("com.apple.laf.AquaComboBoxPopup")))
{
JViewport viewport = ((JScrollPane) source).getViewport();
if (viewport.getViewSize().height <= viewport.getHeight())
// prevent consuming if there is a vertical scrollbar
((MouseWheelEvent) event).consume();
}
}
}
}, AWTEvent.MOUSE_WHEEL_EVENT_MASK);
Thanks guys !

I have tested default behaviour of a combobox. And when I am scrolling over the popup it is fine it will not close it. But when I scroll outside it or even over the combobox itself then it disappears.
I do not know if you are after something like this but I have added the mouse wheel listener to the combobox this way if I detect the movement over the combobox there I am reshowing the popup. -- This bit only partially solves the issue that the mouse wheeling will not show the combo box when scrolling over the combobox.
import java.awt.HeadlessException;
import java.awt.event.MouseWheelEvent;
import java.awt.event.MouseWheelListener;
import javax.swing.JComboBox;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
public class ComboBoxMouseWheel
{
public static void main(String[] args)
{
SwingUtilities.invokeLater(new Runnable()
{
#Override
public void run()
{
createGUI();
}
});
}
private static void createGUI() throws HeadlessException
{
String[] items = new String[]
{
"oasoas", "saas", "saasas"
};
final JComboBox jcb = new JComboBox(items);
jcb.addMouseWheelListener(new MouseWheelListener()
{
#Override
public void mouseWheelMoved(MouseWheelEvent e)
{
System.out.println("ohjasajs");
e.consume();
jcb.showPopup();
}
});
JPanel p = new JPanel();
p.add(jcb);
JPanel contentPane = new JPanel();
contentPane.add(p);
JFrame f = new JFrame();
f.setContentPane(contentPane);
f.setSize(300, 300);
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
f.setVisible(true);
}
}
I hope this is helpful even a bit. If you manage to solve other way please do share it with us.
The solution provided by #trashgod seems doable but it looks so elaborated :), thus I propose mine approach an alternative.
Good luck, Boro.

Here is a solution that will work in most cases
Toolkit.getDefaultToolkit().addAWTEventListener(new AWTEventListener() {
public void eventDispatched(AWTEvent event) {
if (event instanceof MouseWheelEvent) {
Object source = event.getSource();
if (source instanceof JScrollPane) {
JScrollPane scroll = (JScrollPane) source;
if (scroll.getName().equals("ComboBox.scrollPane")) {
MouseWheelEvent sourceEvent = ((MouseWheelEvent) event);
for (MouseWheelListener listener : scroll.getListeners(MouseWheelListener.class)) {
listener.mouseWheelMoved(sourceEvent);
}
sourceEvent.consume();
}
}
}
}
}, AWTEvent.MOUSE_WHEEL_EVENT_MASK);

Related

.add() in java AWT not working after actionPerformed

I am fairly new to coding and have encountered this issue within my code.
I create a button using the Java AWT import. I then check for a response using a while loop and wish to create another button after, however .add() seems to no longer function.
import java.awt.*;
import java.awt.event.*;
public class Main1
{
public static void main(String[] args)
{
Frame f = new Frame();
f.setSize(500, 500);
f.setVisible(true);
ButtonPanel bp = new ButtonPanel(f);
bp.x = null;
while (bp.x == null)
{
}
System.out.println(bp.x);
//THE ISSUE- THIS WILL NOT APPEAR AFTER BUTTON PRESS
f.add("South", new Button("REEE"));
}
}
class ButtonPanel extends Panel implements ActionListener
{
volatile String x;
public ButtonPanel(Frame f)
{
Button b = new Button("Hi");
b.addActionListener(this);
f.add("North", b);
}
public void actionPerformed(ActionEvent e)
{
x = e.getActionCommand();
}
}
I have been trying solutions for this for the last day or so and nothing seems to be working. I've seen in other posts people have said to use Wait/Notify however I am not too sure how those work and I would like to know explicitly what is going wrong in my program (though I am still open to using Wait/Notify in my solution).
Any help would be appreciated, thank you very much
So, they're a number of issues at play here.
The first is the fact that layout managers are generally lazy. This means that you can add and/or remove a number of components quickly and then do a single layout and paint pass.
To do this, you need to revalidate the Container which was updated.
Next, AWT (and Swing by extension) is based on Model-View-Controller concept, one aspect of this is the "observer pattern". This is basically a callback concept that allows you to be notified when something of interest happens.
Button makes use of an ActionListener to generate events when the button is "actioned". This is the "observer pattern" in action.
Why is this important? You really want to think about what information is needed to be passed where and who's actually responsible for doing what.
For example, is it really the ButtonPanel's responsibility to update the frame? Is giving ButtonPanel unfettered control over the frame really a good idea?
Instead, ButtonPanel "should" be providing some kind of notification when some action has occurred and then any interested parties should be able to do what ever they need to.
As a "basic" example...
import java.awt.Button;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Frame;
import java.awt.Panel;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.EventListener;
import javax.swing.event.EventListenerList;
public class Test {
public static void main(String[] args) {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
new Test();
}
});
}
public Test() {
Frame f = new Frame();
f.setPreferredSize(new Dimension(500, 500));
f.pack();
ButtonPanel bp = new ButtonPanel(f);
bp.addObsever(new Observer() {
#Override
public void hiWasPerformed() {
f.add("South", new Button("REEE"));
f.revalidate();
}
});
f.setVisible(true);
}
public interface Observer extends EventListener {
public void hiWasPerformed();
}
class ButtonPanel extends Panel {
private EventListenerList eventListener = new EventListenerList();
public ButtonPanel(Frame f) {
Button b = new Button("Hi");
b.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
Observer[] listeners = eventListener.getListeners(Observer.class);
for (Observer observer : listeners) {
observer.hiWasPerformed();
}
}
});
f.add("North", b);
}
public void addObsever(Observer observer) {
eventListener.add(Observer.class, observer);
}
public void removeObsever(Observer observer) {
eventListener.remove(Observer.class, observer);
}
}
}
It looks like nothing is happening because the buttons are being added below the bottom of the window. You should consider using a layout manager to solve this issue.
However, in the meantime the simple solution is to move this line f.add("South", new Button("REEE")); inside the action event and to make use of Frame.pack();:
public class Main1
{
public static void main(String[] args)
{
Frame f = new Frame();
//set minimums rather than a fixed size
f.setMinimumSize(new Dimension(500, 500));
f.setVisible(true);
CustomButton b = new CustomButton(f);
//Add this line to update/size/show the UI
f.pack();
//Don't place any more code inside the main method. Future events should be triggered by interacting with the UI/buttons
}
}
Then for the button we don't need to extend Panel, we can do something like this:
class CustomButton implements ActionListener
{
Frame parentFrame;
public CustomButton(Frame f)
{
parentFrame = f;
Button b = new Button("Hi");
b.addActionListener(this);
f.add("North", b);
}
public void actionPerformed(ActionEvent e)
{
//Add button here instead of the main class
parentFrame.add("South", new Button("REEE"));
//The buttons are being added below the bottom of your window, this will force them to be shown.
//Using a layout manager will solve this ploblem and you will not need to do this:
parentFrame.pack();
}
}
Note: clicking on the "Hi" button multiple times will have interesting results of the "REEE" buttons overlapping or doing odd things if you resize the window.

JOptionPane.showMessageDialog - Why breaking down checkboxes?

I'm very interested, how to explain this, it's sth like hack? Or there is nothing to be excited about?
Maybe, you had some other, more intresting, experiences like this, when you were a beginer?
Warning! Its not a problem (It was realy helpful!), I just looking for more sense in my code :)
Sooo, in brief: Last if statment bans one checkbox...
import javax.swing.*;
import java.awt.event.ItemEvent;
import java.awt.event.ItemListener;
class NotMain {
public static void main(String[] args) {
X x = new X();
}
}
class X implements ItemListener{
X() {
JCheckBox jCheckBox[] = new JCheckBox[2];
JPanel panel = new JPanel();
JFrame frame = new JFrame();
jCheckBox[0] = new JCheckBox("1");
jCheckBox[1] = new JCheckBox("2");
jCheckBox[0].addItemListener(this);
jCheckBox[1].addItemListener(this);
panel.add(jCheckBox[0]);
panel.add(jCheckBox[1]);
frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
frame.setVisible(true);
}
private int i;
#Override
public void itemStateChanged(ItemEvent e) {
if (e.getStateChange() == ItemEvent.SELECTED) {
i++;
} else i--;
if (i == 2) {
//if you comment next line, you'll be able to select two checkboxes
JOptionPane.showMessageDialog(null, "nope.");
}
}
}
The state of the check box changes when a mousePressed and mouseReleased event is generated for the check box. This is easy to demonstrate. Just do a mousePressed on the check box and then drag the mouse off the check box before releasing the mouse. The state doesn't change.
In your example the check box knows that a mousePressed and mouseReleased has been done so it generates and itemStateChanged event which causes the JOptionPane to be displayed.
The problem is that the ItemStateChange code executes before the mouseReleased code. Now when the mouseReleased code is executed the option pane has the focus so the state of the check box is not changed.
Change your code to:
if (i == 2)
{
SwingUtilities.invokeLater(new Runnable()
{
public void run()
{
JOptionPane.showMessageDialog(null, "nope.");
}
});
}
Now the display of the option pane is added to the end of the Event Dispatch Thread (EDT) which means both the mousePressed and mouseReleased events are handled by the check box before the JOPtionPane is displayed, so the state of the check box changes to selected.

How to cancel a mouseclicker event in Java

I have a mouseclicker event added to some JLabels and, after one of them will be clicked, I want to remove the link between that JLabel and the mouseclicker event.
To add the mouseclicker event to JLabel I use this code:
JLabel.addMouseListener(this);
There is a way to remove the JLabel from being clicked after the effect is solved? How can I do this?
I searched something but I'm not sure to how I can describe the problem and search about it, so i didn't found results.
This may seem trivial, but you could simply do:
myLabel.removeMouseListener(this);
Option two is to leave the MouseListener in place, but make it smarter -- i.e., give it logic that allows it to ignore input if need be. This could be a simple if block such as
#Override
public void mousePressed(MouseEvent me) {
if (someBoolean) {
return;
}
// here have your usual code
}
and then in your code, when you want to de-activate the MouseListener, simply change the listener's someBoolean field to false. This use of a boolean switch or flag is useful for when you need to turn the listener on and off repeatedly.
As a side note, you're usually better off not using this for your listeners as that is giving the main GUI class a bit too much responsibility. Instead use anonymous inner classes for simple few line code or named class for more involved listener code.
For example:
import java.awt.Color;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import javax.swing.*;
import javax.swing.border.Border;
public class TurnListenerOnAndOff extends JPanel {
private JLabel myLabel = new JLabel("My Label");
private JCheckBox listenerEnabledCheckBox = new JCheckBox("Listener Enabled", true);
public TurnListenerOnAndOff() {
// make label bigger with a border
Border outsideBorder = BorderFactory.createLineBorder(Color.black);
Border insideBorder = BorderFactory.createEmptyBorder(5, 5, 5, 5);
myLabel.setBorder(BorderFactory.createCompoundBorder(outsideBorder, insideBorder));
// create and add MyMouseListener to my label
myLabel.addMouseListener(new MyMouseListener());
// add components to the GUI's main JPanel
add(myLabel);
add(listenerEnabledCheckBox);
}
private class MyMouseListener extends MouseAdapter {
#Override
public void mousePressed(MouseEvent e) {
// if the JCheckBox isn't checked...
if (!listenerEnabledCheckBox.isSelected()) {
return; // let's get out of here
}
// otherwise if the check box is checked, do following code
System.out.println("myLabel pressed!");
}
}
private static void createAndShowGui() {
TurnListenerOnAndOff mainPanel = new TurnListenerOnAndOff();
JFrame frame = new JFrame("On and Off");
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();
}
});
}
}

Listen to the paste events JTextArea

I want to call a function when the user pastes text in my JTextArea. Is there any event generated when the text is pasted to the JTextArea and which listener can I use to trigger my function on this event?
One possible solution (and I hope some one has a better one) would be to replace the key binding Action responsible for actually performing the paste operation.
Now, before you do this, the default paste operation is not trivial, instead, I would replace the default paste Action with a proxy, which could call the original, but would allow you to intercept the operation, but not have to re-implement the functionality yourself, for example...
public class ProxyAction extends AbstractAction {
private Action action;
public ProxyAction(Action action) {
this.action = action;
}
#Override
public void actionPerformed(ActionEvent e) {
action.actionPerformed(e);
System.out.println("Paste Occured...");
}
}
Then you would simply need to look up the default Action and replace it...
JTextArea ta = new JTextArea(10, 10);
Action action = ta.getActionMap().get("paste-from-clipboard");
ta.getActionMap().put("paste-from-clipboard", new ProxyAction(action));
The problem here is, this won't tell you if the operation failed or succeeded or what was actually pasted. For that, you could use a DocumentListener, registered before you call the default Action which could record the changes to the document. Obviously, you'd want to deregister this after the default action ;)...
Now, equally, you could just override the paste method of the JTextArea, which equates to about the same thing, but, the first option would be more portable...
As an idea...
Take a look at How to Use Actions and How to Use Key Bindings for more details
you can have something like below, whenever you paste something in the textarea, then 'Pasted!' is printed out on your console. It prints only on paste !
import java.awt.*;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import javax.swing.*;
public class TextAreaDemo extends JFrame {
JTextArea _resultArea = new JTextArea(6, 20);
public TextAreaDemo() {
_resultArea.setText("");
JScrollPane scrollingArea = new JScrollPane(_resultArea);
JPanel content = new JPanel();
content.setLayout(new BorderLayout());
content.add(scrollingArea, BorderLayout.CENTER);
this.setContentPane(content);
this.setTitle("TextAreaDemo B");
this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
this.pack();
_resultArea.addKeyListener(new KeyListener() {
#Override
public void keyTyped(KeyEvent e) {
}
#Override
public void keyPressed(KeyEvent e) {
if ((e.getKeyCode() == KeyEvent.VK_V) && ((e.getModifiers() & KeyEvent.CTRL_MASK) != 0)) {
System.out.println("Pasted!");
}
}
#Override
public void keyReleased(KeyEvent e) {
}
});
}
public static void main(String[] args) {
JFrame win = new TextAreaDemo();
win.setVisible(true);
}
}
You can also check out Wrapping Actions which is basically the same suggestion as MadProgrammer except that the WrapperAction will delegate all the methods of the Action to the original Action. This will allow you to pick up the text and Icons associated with the original Action in case you ever want to add your custom Action to a JMenuItem or JButton.

How can I create a "Drop-Down" menu in a Java Swing toolbar?

I've created a drop-down menu on my Swing JToolBar. But it doesn't create behave the way I want. I'm aiming for it to work like Firefox's "Smart Bookmarks" button.
It disappears when the user selects a menu item: CORRECT!
It disappears when the user presses ESC: CORRECT!
It disappears when the user clicks somewhere in the main frame outside of the menu: CORRECT!
But it doesn't disappear when the user clicks a second time on the button that shows the drop-down menu: INCORRECT... :-(
My question is how can I add this behaviour, that it does disappear when the clicks on the button that shows the menu a second time.
Here's my current code, from Java 6 on the Mac:
import javax.swing.*;
import javax.swing.event.PopupMenuEvent;
import javax.swing.event.PopupMenuListener;
import java.awt.*;
import java.awt.event.ItemEvent;
import java.awt.event.ItemListener;
public class ScratchSpace {
public static void main(String[] arguments) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
JFrame frame = new JFrame("Toolbar with Popup Menu demo");
final JToolBar toolBar = new JToolBar();
toolBar.add(createMoreButton());
final JPanel panel = new JPanel(new BorderLayout());
panel.add(toolBar, BorderLayout.NORTH);
panel.setPreferredSize(new Dimension(600, 400));
frame.getContentPane().add(panel);
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
private static AbstractButton createMoreButton() {
final JToggleButton moreButton = new JToggleButton("More...");
moreButton.addItemListener(new ItemListener() {
public void itemStateChanged(ItemEvent e) {
if (e.getStateChange() == ItemEvent.SELECTED) {
createAndShowMenu((JComponent) e.getSource(), moreButton);
}
}
});
moreButton.setFocusable(false);
moreButton.setHorizontalTextPosition(SwingConstants.LEADING);
return moreButton;
}
private static void createAndShowMenu(final JComponent component, final AbstractButton moreButton) {
JPopupMenu menu = new JPopupMenu();
menu.add(new JMenuItem("Black"));
menu.add(new JMenuItem("Red"));
menu.addPopupMenuListener(new PopupMenuListener() {
public void popupMenuWillBecomeVisible(PopupMenuEvent e) {
}
public void popupMenuWillBecomeInvisible(PopupMenuEvent e) {
moreButton.setSelected(false);
}
public void popupMenuCanceled(PopupMenuEvent e) {
moreButton.setSelected(false);
}
});
menu.show(component, 0, component.getHeight());
}
}
Well, here is a potential solution that is not without it's drawbacks. Only you can decide if this is acceptable for your application. The issue is that the popup closing occurs before other mouse-handling events are fired so clicking on your More.. button again causes the popup to hide, thus resetting the buttons state to deselected BEFORE the button even gets told it was pressed.
The easy workaround is to add the following call within your main program:
UIManager.put("PopupMenu.consumeEventOnClose", Boolean.TRUE);
The result of this is that whenever a popup menu is closed because of a mouse-pressed event, that mouse event will be consumed at the time the menu is closed and won't be passed on to any other components under the mouse. If you can live with limitation, this is an easy solution.
What's happening is that when you click off the menu, it cancels the popup menu, so you deselect the button, but the next immediate event is clicking the button, and now its deselected so it shows the menu again.
I don't have the exact solution yet, but give me a little bit ...
I don't use Firefox so I don't know what the Smart Bookmarks button looks like, but maybe use a JMenu as the "button". You could try using the Border of a JButton to make it look more like a button.
Well, the listener on the button reacts only when it is pushed down, because you listen for ItemEvent.SELECTED events only. How about adding another if clause to listen for ItemEvent.DESELECTED events here:
moreButton.addItemListener(new ItemListener() {
public void itemStateChanged(ItemEvent e) {
if (e.getStateChange() == ItemEvent.SELECTED) {
createAndShowMenu((JComponent) e.getSource(), moreButton);
}
}
});
You could either store a reference to the menu somewhere, or you could make the menu itself add another listener to the button. The latter solution could be more straightforward, since you already seem to send a button reference to the menu.

Categories