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.
Related
EDIT : I found my problem but still don't have a clue for why this happen, I'm still not finished Online Lectures from Professor Mehran
Sahami (Stanford), maybe I'll find an answer if I push on on the
lecture videos.
The problem is I remove my other components methods before my button
method for efficient posting space, so I should put my JToggleButton
method after my main JFrame method for it to work, but what if my
other components inherit other class too? Which method should I put first to make all of components works? That I'll found out with
practicing java more.
Thank you #Dan and #SebVb for answers and suggestions, sorry if this
just a beginners mistake :)
I am learning java for a month now and already had simple project for learning but now I have problems with JToggleButton, ItemEvent, and actionPerformed included in If-statement.
I've searching for a week for examples on using actionPerformed within if-statement that have ItemEvent from another class but i can't find a same problem to produce a working result.
I'm trying to make a window scanner that will scan only if toggle button is selected then paint JPanel using buffered image (repaint every 100 millisecond) and disposed it if toggle button is deselected, but I think my approach to do it is wrong. I have one main class and two sub-classes like these:
Main class:
public class WindowScanner {
public static void main(String[] args) {
new Window().setVisible(true);
}
}
Window class:
class Window extends JFrame {
static JToggleButton captureButton = new JToggleButton("CAPTURE");
#SuppressWarnings("Convert2Lambda")
public Window() {
// JFrame looks codes
/** EDIT: these components method should be written after button method
* JPanel looks codes
* JLabel looks codes
* END EDIT
*/
add(captureButton);
// capture button default looks code
ItemListener captureListener = new ItemListener(){
#Override
public void itemStateChanged(ItemEvent captureButtonEvent) {
int captureState = captureButtonEvent.getStateChange();
if(captureState == ItemEvent.SELECTED){
// capture button SELECTED looks code
System.out.println("capture button is selected");
} else if(captureState == ItemEvent.DESELECTED){
// capture button DESELECTED looks code
System.out.println("capture button is deselected");
}
}
}; captureButton.addItemListener(captureListener);
}
}
Scanner class:
public class Scanner extends Window {
private static BufferedImage boardCaptured;
static int delay = 100;
protected BufferedImage boardScanned(){
return boardCaptured;
}
#SuppressWarnings("Convert2Lambda")
public static void Scan() {
if (captureButton.isSelected()) {
ActionListener taskPerformer = new ActionListener() {
#Override
public void actionPerformed(ActionEvent captureEvent) {
try {
// capturing method
} catch (AWTException error) {
// AWTException error method
}
// is this the right place to put JPanel code?
JPanel panel = new JPanel();
boardCaptured = new BufferedImage(500, 500, BufferedImage.TYPE_INT_ARGB);
Graphics2D graphic = boardCaptured.createGraphics();
panel.setSize(500,500);
panel.paint(graphic);
panel.revalidate();
panel.repaint();
}
}; new Timer(delay, taskPerformer).start();
} else {
// this suppose to end capturing if capture button isSelected() == false
}
}
}
So here is my questions:
Do I really have to make Main class separated from Window class?
What the reason?
How to make my if statement in Scan method recognize state of my
JToggleButton from Window class? Is it impossible and I had a wrong
approach to do it?
In Scanner class, i can't make a get/set for my actionPerformed
(Netbeans always checked it as an error), but why I can make one for
BufferdImage?
If I can't get question number 3 happen, how can I make If statement
to stop capturing using Timer.stop()? Or am I in wrong approach again?
Do my JPanel in Scanner class would be produced and make a viewer
for my buffered image?
P.S. I'm sorry it cramped with questions, I tried not to make multiple post, so I make single post with multiple questions. Please notice me if there's answer before, I'm honestly can't find it or had search it with wrong tags.
Here is a simple version of what I think you want to do. This can be edited to include your variables, such as boardCaptured. This code mainly portrays how to get a component from a different class.
Main.java (Contains all the classes in one java file)
import javax.swing.JLabel;
import javax.swing.JToggleButton;
import javax.swing.JFrame;
import java.awt.Color;
import java.awt.FlowLayout;
import java.awt.event.ActionListener;
import java.awt.event.ActionEvent;
import java.util.Random;
import javax.swing.Timer;
class WindowScanner extends JFrame {
private JLabel label;
private JToggleButton captureButton = new JToggleButton("CAPTURE");
WindowScanner() {
super("Fist Window");
setSize(150, 100);
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setLayout(new FlowLayout());
add(captureButton);
setVisible(true);
new Scanner(this);
}
public JToggleButton getCaptureButton() {
return captureButton;
}
}
class Scanner extends JFrame {
private WindowScanner wS;
private int delay = 1000;
private Timer t = new Timer(delay, new taskPerformer());
Scanner(WindowScanner wS) {
super("Second Window");
this.wS = wS;
setBounds(200,0,500,500);
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setVisible(true);
wS.getCaptureButton().addActionListener(new taskPerformer());
}
private Color randomColor() {
Random rand = new Random();
float r = rand.nextFloat() / 2f ;
float g = rand.nextFloat() / 2f;
float b = rand.nextFloat() / 2f;
Color randomColor = new Color(r, g, b);
return randomColor;
}
private class taskPerformer implements ActionListener {
#Override
public void actionPerformed(ActionEvent captureEvent) {
if(captureEvent.getSource() == wS.getCaptureButton()) {
if (wS.getCaptureButton().isSelected()) {
t.start();
} else {
t.stop();
}
}
if(captureEvent.getSource() == t) {
getContentPane().setBackground(randomColor());
revalidate();
repaint();
}
}
}
}
public class Main {
public static void main (String[] args) {
new WindowScanner();
}
}
This particular piece of code changes the color of the background in the second JFrame to a random color every second using a timer from javax.swing.timer. This code portrays how to get a component, or a variable if you change it, from a different class.
It is mainly these code fragments which allow it.
1
public JToggleButton getCaptureButton() {
return captureButton;
}
This allows other classes to get the component.
2
private WindowScanner wS;
Scanner(WindowScanner wS) {
...
this.wS = wS;
...
}
This makes the current instance of WindowScanner and the instance of WindowScanner declared in Scanner the same instance.
Note: Look into using public getters and setters.
As for your 5 listed questions.
1) Do I really have to make Main class separated from Window class? What the reason?
In most cases yes you do. As SebVb said it is good practice. However you can do something like this if you wish to have them in the same class.
import javax.swing.JToggleButton;
import javax.swing.JFrame;
import java.awt.FlowLayout;
public class Test extends JFrame {
private JToggleButton captureButton = new JToggleButton("CAPTURE");
Test() {
super("Fist Window");
setSize(150, 100);
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setLayout(new FlowLayout());
add(captureButton);
setVisible(true);
}
public JToggleButton getCaptureButton() {
return captureButton;
}
public static void main(String[] args) {
java.awt.EventQueue.invokeLater(new Runnable() {
public void run() {
Test frame = new Test();
}
});
}
}
2) How to make my if statement in Scan method recognize state of my JToggleButton from Window class? Is it impossible and I had a wrong approach to do it?
You were using the wrong approach to do this. See the code and code fragments I have put above for how to do it correctly. (Using public getters.)
3) In Scanner class, i can't make a get/set for my actionPerformed (Netbeans always checked it as an error), but why I can make one for BufferdImage?
I can't entirely say I'm sure what you are asking but see my code above to see if that helps. If it doesn't leave a comment trying to fully explain what you mean.
4) If I can't get question number 3 happen, how can I make If statement to stop capturing using Timer.stop()? Or am I in wrong approach again?
In my code I show you how this can be related to the JToggleButton. See code fragment below
private class taskPerformer implements ActionListener {
#Override
public void actionPerformed(ActionEvent captureEvent) {
if(captureEvent.getSource() == wS.getCaptureButton()) {
if (wS.getCaptureButton().isSelected()) {
t.start();
} else {
t.stop();
}
}
if(captureEvent.getSource() == t) {
getContentPane().setBackground(randomColor());
revalidate();
repaint();
}
}
}
This code says when the JToggleButton fires an ActionEvent if it is selected then start the timer, t.start(), or if it is not selected stop the timer, t.stop().
5) Do my JPanel in Scanner class would be produced and make a viewer for my buffered image?
Again I'm not entirely sure what you are asking but here is my best guess. You have two options.
1
Put boardCaptured directly on the frame.
paint(graphic);
repaint();
revaildate();
2
Create a JPanel like you did but outside the ActionListener
JPanel panel = new JPanel()
boardCaptured = new BufferedImage(500, 500, BufferedImage.TYPE_INT_ARGB);
Graphics2D graphic = boardCaptured.createGraphics();
panel.setSize(500,500);
panel.paint(graphic);
add(panel);
private class taskPerformer implements ActionListener {
if(captureEvent.getSource() == t) {
panel.paint(graphic);
panel.revalidate();
panel.repaint();
}
}
I think there's an easier way do do what do you want. Taking your questions by order
Having the main class separated from your Window class allow you to re-use your Windows class everywhere you want. It's a good pratice to only init your GUI objects on your main class
Why don't you have your JToggleButton private and a mehtod wwhich will access to his status ? also, with a static field, all your instaces of Windows will share the same JToggleButton.
It's an anonymous class that contains your actionPerformed method. If you want to see it, you have to create an inner class.
I think your approch is wrong. Using a thread, which will launch your repaint with a specific delay is better. If you create a class which extends Runnable, you can check the state of your button and then do the appropriate action
Your JPanel is inside an ActionListener, i've never seen that and i don't think that it can works.
In a shorter version
Put in your Window class your JPanel, BufferedImage and JToggleButton
Create a specific thread to do your repainting when the JToggleButton is selected
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();
}
});
}
}
I wrote a program for an applet that is supposed to display different text in a text box when you push a button. My program has no errors when I compiled it, but the text box doesn't display correctly. I don't know what is wrong with it. Here is my code
import java.awt.*;
import java.awt.event.*;
public class colors{
Button button1;
Button button2;
Button button3;
Label label1;
TextField objTextField1;
public static void main (String args[]){
colors c = new colors();
}
public colors() {
Frame f = new Frame ("Colors");
Button button1 = new Button("Blue");
button1.setBounds(10,305,120,75);
button1.addMouseListener(new MyMouseListener1());
Button button2 = new Button("Red");
button2.setBounds(140,305,120,75);
button2.addMouseListener(new MyMouseListener2());
Button button3 = new Button("Yellow");
button3.setBounds(270,305,120,75);
button3.addMouseListener(new MyMouseListener3());
f.add(button1);
f.add(button2);
f.add(button3);
label1 = new Label("Click a Button to Reveal Text");
label1.setBounds(20,105,200,25);
f.add(label1);
objTextField1 = new TextField("Which Color?", 15);
objTextField1.setBounds(20,75,125,50);
f.add(objTextField1);
f.add(label1);
f.add(objTextField1);
f.addWindowListener(new WindowAdapter()
{
public void windowClosing(WindowEvent we){
System.exit(0);
}
});
f.setSize(400,400);
f.setVisible(true);
}
public class MyMouseListener1 extends MouseAdapter{
public void mouseClicked(MouseEvent me){
objTextField1.setText("Blue");
}
}
public class MyMouseListener2 extends MouseAdapter{
public void mouseClicked(MouseEvent me){
objTextField1.setText("Red");
}
}
public class MyMouseListener3 extends MouseAdapter{
public void mouseClicked(MouseEvent me){
objTextField1.setText("Yellow");
}
}
}
When a Button is clicked it fires an ActionEvent.
You should use an ActionListener instead of a MouseListener.
public void actionPerformed(ActionEvent e) {
...//code that reacts to the action...
}
AND don't forget to add
button.addActionListener(instance);
I have tested your code and it's working as expected but I have noticed some of the points in your code as mentioned below:
Use setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE) instead of System.exit(0) and adding WindowListener to close the window.
Use frame.pack() instead of frame.setSize() that fits the components as per component's preferred size.
Don't use null layout and never use absolute positioning via calling setBounds() instead use a proper Layout Manager that suits as per your application design.
Read more How to Use Various Layout Managers
Use SwingUtilities.invokeLater() or EventQueue.invokeLater() to make sure that EDT is initialized properly.
Read more
Why to use SwingUtilities.invokeLater in main method?
SwingUtilities.invokeLater
Should we use EventQueue.invokeLater for any GUI update in a Java desktop application?
Follow Java Naming convention
Keep the instance members private
This is already addressed by #TAsk that you should be using ActionListener instead of MouseListener if you are interested in only mouseClicked() method.
You should be using Swing components instead of AWT components as already mentioned by #peeskillet
To make an applet you should extend javax.swing.JApplet class & override init() method.
To change the color, you must write your logics in actionPerformed() of ActionListener. But it's an interface. So, you can make use of Anonymous Inner class & implement actionPerformed() in it.
So, when you call addActionListener() on a JButton, I recommend you to do that by using Anonymous Inner class. It would be more clear through following code.
My Suggestion: Whenever you write code, always keep OOD principles in your mind. This isn't right place to discuss that, but your code has a Code smell which is Duplication in code.
Below is the best way to do what you want & we're also using DRY Principle.
import java.awt.Color;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JButton;
import javax.swing.JPanel;
public class ColorChanger extends javax.swing.JApplet {
private JPanel mainPanel;
private JButton btnRed;
private JButton btnGreen;
private JButton btnBlue;
#Override
public void init() {
super.init();
mainPanel = new JPanel();
btnRed = new JButton("Red");
btnGreen = new JButton("Green");
btnBlue = new JButton("Blue");
this.add(mainPanel);
mainPanel.add(btnRed);
mainPanel.add(btnGreen);
mainPanel.add(btnBlue);
bindActionEvent(btnRed, Color.RED);
bindActionEvent(btnGreen, Color.GREEN);
bindActionEvent(btnBlue, Color.BLUE);
}
private void bindActionEvent(JButton b1, Color color) {
b1.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
mainPanel.setBackground(color);
//Write setText() for your TextField here.
}
});
} //END Of Helper Method
}
I'm a beginner at java and want to make a JFrame with tabs containing a seperate JPanel. One panel has a list where it displays things that you select in a different panel, so I want this panel to always display a list of stuff that you have selected in a different panel (I hope that makes sense). To do this, I need to make a method to refresh the JList. This is the Farthest that I've gotten on that:
public class PanelClass extends JPanel {
private JList list;
private DefaultListModel listModel = new DefaultListModel();
private ArrayList<SomeOtherClass> objectArray = new ArrayList<SomeOtherClass>();
public PanelClass() {
list.setModel(listModel);
}
public void refresh() {
updateListModel();
list.setModel(listModel);
}
public void updateListModel() {
if (objectArray.isEmpty()) {
System.out.println("No Objects In Array!");
} else {
listModel.clear();
for (SomeOtherClass SOC : objectArray) {
// SOC.getName() just returns a string
listModel.addElement(SOC.getName());
}
}
}
public void addObjectToArray(SomeOtherClass SOC) {
objectArray.add(SOC);
}
}
Could someone please tell me how to make a "refresh" method to constantly keep the JList up to date?
The AWT/Swing event model is based upon the widgets being event sources (in the MVC paradigm, they are both view and controller). Different widgets source different event types.
Look at the java.awt.event (primarily), and javax.swing.event packages for the listener interfaces you'll need to implement and register in order to produce your desired effect.
Basically, you write a Listener implementation, and register it with Widget1. When Widget1 detects an event, it will notify you, and you can then use the information it provides to update Widget2.
For instance, if a button being clicked would add an object to your list, you might have something like below (I usually put this code in the encompassing JFrame class, and make it implement the listener interfaces; but you can choose to use inner classes or separate listener classes):
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
public class MyFrame extends JFrame implements ActionListener {
private JButton button = new JButton("Click me!");
private DefaultListModel<String> listModel = new DefaultListModel<String>();
private JList<String> list = new JList<String>(listModel);
private int counter = 1;
public MyFrame() {
setTitle("Test Updates");
JTabbedPane tabs = new JTabbedPane();
add(tabs, BorderLayout.CENTER);
JPanel panel = new JPanel();
panel.add(list);
tabs.add("Selections", panel);
panel = new JPanel();
button.addActionListener(this);
panel.add(button);
tabs.add("Options", panel);
pack();
}
#Override
public void actionPerformed(final ActionEvent event) {
if (button.equals(event.getSource())) {
listModel.addElement("Item " + counter++);
}
}
/* Test it! */
public static void main(String[] args) {
final MyFrame frame = new MyFrame();
frame.addWindowListener(new WindowAdapter() {
#Override public void windowClosing(final WindowEvent e) {
frame.setVisible(false);
frame.dispose();
System.exit(0);
}
});
frame.setVisible(true);
}
}
This code sample is minimal, but it should give you an idea of how to go about implementing what you want.
You can do it in two way. First : Write it in infinite thread loop so that it will constantly update JList. Second : You can call refresh() method whenever new SOC objects are added in your ArrayList. It means you can call refresh() method from addObjectToArray() method which ultimately call the refresh method only when you have some change in your ArrayList.
FYI : I did it in my project and I went for second option.
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);