mouse double click is not working - java

I have written this java code to detect the double click of left button on mouse, but this code is not working please help.
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import javax.swing.JFrame;
import javax.swing.JScrollPane;
import javax.swing.JTable;
public class B extends MouseAdapter {
JFrame frame = new JFrame();
Object rows[][] = new Object[5][3];
String colums[] = {"A","B","C"};
JTable table = new JTable(rows,colums);
JScrollPane scroll = new JScrollPane(table);
public static void main(String arg[]) {
new B();
}
B() {
frame.setExtendedState(JFrame.MAXIMIZED_BOTH);
table.addMouseListener(this);
frame.add(scroll);
frame.setVisible(true);
}
public void mouseClicked(MouseEvent clicked) {
if(clicked.getSource()==table && clicked.getButton()==1 && clicked.getClickCount()==2)
System.out.println("Left Double Click");
}
}

Your example won't compile
You should be using SwingUtilities.isLeftMouseButton(clicked) instead of clicked.getButton()==1
The table may be consuming the MouseEvent and installing the cell editor before your MouseListener is notified.
If you use table.setFillsViewportHeight(true); you can double click outside of the rows/columns successfully
You can change the table's CellEditors to ignore the MouseEvent (or change the number of clicks required), this will allow you MouseListener to pick up the double clicks, but will also increase your work load, as you will need to supply a CellEditor for each column Class type
TableCellEditor editor = new DefaultCellEditor(new JTextField(10)) {
#Override
public boolean isCellEditable(EventObject anEvent) {
boolean editable = false;
if (!(anEvent instanceof MouseEvent)) {
editable = super.isCellEditable(anEvent);
}
return editable;
}
};
table.setDefaultEditor(Object.class, editor);
Without more context, is difficult to know what else to suggest

Related

Deselect node from JTree when click any place outside the tree

I am using a JTree, which is using a DefaultTreeModel. This tree model has some nodes inside, and when I click on a node, I get the information of the node and I change the background color to show that this node is selected.
It is possible to call the tree to clear the selection when I click on any place out of the tree? By clearing the selection I will be able to change the background color again, but I don't know how or where to use the clearSelection() method of the tree when I click out of the tree.
Here is the code I am using:
The example:
import javax.swing.*;
import javax.swing.tree.*;
import java.awt.*;
public class JTreeSelectDeselect {
public static void main(String[] args) {
JFrame frame = new JFrame();
frame.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);
JPanel panel = new JPanel(new BorderLayout());
JTree tree = new JTree();
tree.setCellRenderer(new DeselectTreeCellRenderer());
panel.add(tree, BorderLayout.LINE_START);
panel.add(new JScrollPane(new JTextArea(10, 30)));
frame.add(panel);
frame.pack();
frame.setVisible(true);
}
}
class DeselectTreeCellRenderer extends DefaultTreeCellRenderer {
#Override
public Color getBackgroundSelectionColor() {
return new Color(86, 92, 160);
}
#Override
public Color getBackground() {
return (null);
}
#Override
public Color getBackgroundNonSelectionColor() {
return new Color(23, 27, 36);
}
#Override
public Component getTreeCellRendererComponent(JTree tree, Object value,
boolean sel, boolean exp, boolean leaf, int row, boolean hasFocus) {
super.getTreeCellRendererComponent(tree, value, sel, exp, leaf, row, hasFocus);
setForeground(new Color(225, 225, 221, 255));
setOpaque(false);
return this;
}
}
I am showing here how I create the nodes and add it to the tree using a tree model and how I set my custom TreeCellRenderer.
In the cell renderer I paint the selected node with a specific color, and if the node is deselected, I paint it using another color. When I change the selection of the nodes, their background is painting correctly, but when I click outside the tree, the selected node is not deselected, so it is not painted with the specific color established in the cell renderer.
There is a way to deselect the node when I click outside the tree?
And just if someone knows, there is a way to change some of the leafs by check boxes from the TreeCellRenderer? To have some children as labels and some others as check boxes. Because when I try to add check boxes it says (as I expected) that check boxes are not DefaultMutableTreeNode objects and I can't add them to the tree model.
First of all you don't need to subclass the DefaultTreeCellRenderer if you just want to change some colors. You can create a new one, set the colors as you wish and set it to the tree. In the below code sample I've done this in the getDefaultTreeCellRenderer().
Your panel contains two elements the tree and the text area.
To achieve what you needed I added a mouse listener and a focus listener to the tree:
The Mouse Listener - on mouseClicked() is triggered both when you click inside the tree or outside it (but not in the TextArea, for that we have the focus listener).
To check whether you clicked in the boundaries of a cell we use the tree.getRowForLocation(e.getX(),e.getY()) and if it returns -1 this means we clicked outside any cell so we can clear the selection
The Focus Listener - when you lose focus from the JTree and click on the text area this will be triggered and we just clear the selection
We needed both listeners because the first one gets triggered only when you click on the tree and around it but not it the Text area and the second one gets triggered when you focus out of the tree area and focus on the text area.
import javax.swing.*;
import javax.swing.tree.DefaultTreeCellRenderer;
import java.awt.*;
import java.awt.event.FocusEvent;
import java.awt.event.FocusListener;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
public class JTreeSelectDeselect {
public static void main(String[] args) {
JFrame frame = new JFrame();
frame.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);
JPanel panel = new JPanel(new BorderLayout());
JTree tree = new JTree();
tree.setCellRenderer(getDefaultTreeCellRenderer());
tree.addMouseListener(getMouseListener(tree));
tree.addFocusListener(getFocusListener(tree));
panel.add(tree, BorderLayout.LINE_START);
panel.add(new JScrollPane(new JTextArea(10, 30)));
frame.add(panel);
frame.pack();
frame.setVisible(true);
}
private static DefaultTreeCellRenderer getDefaultTreeCellRenderer() {
DefaultTreeCellRenderer defaultTreeCellRenderer = new DefaultTreeCellRenderer();
defaultTreeCellRenderer.setBackgroundSelectionColor(new Color(86, 92, 160));
defaultTreeCellRenderer.setBackgroundNonSelectionColor(new Color(135, 151, 53));
defaultTreeCellRenderer.setBackground(new Color(225, 225, 221, 255));
defaultTreeCellRenderer.setForeground(new Color(225, 225, 221, 255));
return defaultTreeCellRenderer;
}
private static FocusListener getFocusListener(final JTree tree) {
return new FocusListener() {
#Override
public void focusGained(FocusEvent e) {
}
#Override
public void focusLost(FocusEvent e) {
System.out.println("focus lost");
tree.clearSelection();
}
};
}
private static MouseListener getMouseListener(final JTree tree) {
return new MouseListener() {
#Override
public void mouseClicked(MouseEvent e) {
System.out.println("mouse clicked");
if(tree.getRowForLocation(e.getX(),e.getY()) == -1) {
System.out.println("clicked outside a specific cell");
tree.clearSelection();
}
}
#Override
public void mousePressed(MouseEvent e) {
}
#Override
public void mouseReleased(MouseEvent e) {
}
#Override
public void mouseEntered(MouseEvent e) {
}
#Override
public void mouseExited(MouseEvent e) {
}
};
}
}
If you want to deselect all selected nodes in the tree on mouse click somewhere else, you need to get notified when user clicks somewhere else. To get it you can use the global event listener (Toolkit.getDefaultToolkit().addAWTEventListener()).
If you want to display some checkboxes in your tree you need a custom cell renderer, which for a condition returns a checkbox. Also you need a data container to hold whether the node is selected and finally you need a routine to update your data model, when the node is clicked. The last thing is usually provided by cell editors (more about in the article about editors and renderes), but in your case it's easier to do using the global mouse listener, that we've installed before.
Of course my example contains a little bit "Swing magic", so when you don't understand something, feel free to ask me about ;)
import java.awt.AWTEvent;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Component;
import java.awt.Toolkit;
import java.awt.event.AWTEventListener;
import java.awt.event.MouseEvent;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import javax.swing.JCheckBox;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTextArea;
import javax.swing.JTree;
import javax.swing.SwingUtilities;
import javax.swing.WindowConstants;
import javax.swing.tree.DefaultTreeCellRenderer;
import javax.swing.tree.DefaultTreeModel;
import javax.swing.tree.TreePath;
/**
* <code>JTreeDeselected</code>.
*/
public class JTreeSelectDeselect {
private final JTree tree = new JTree();
// model to hold nodes that must be presented as check boxes
// and whether the check boxes are selected
private final Map<Object, Boolean> checkMap = new HashMap<>();
// listener as method reference
private AWTEventListener awtListener = this::mouseClicked;
public static void main(String[] args) {
SwingUtilities.invokeLater(new JTreeSelectDeselect()::start);
}
private void start() {
checkMap.put("football", true);
checkMap.put("soccer", false);
checkMap.put("violet", true);
checkMap.put("yellow", false);
checkMap.put("pizza", true);
checkMap.put("ravioli", false);
JFrame frame = new JFrame();
frame.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);
JPanel panel = new JPanel(new BorderLayout());
// JTree tree = new JTree();
tree.setCellRenderer(new DeselectTreeCellRenderer(checkMap));
panel.add(new JScrollPane(tree), BorderLayout.LINE_START);
panel.add(new JScrollPane(new JTextArea(10, 30)));
frame.add(panel);
// register global listener
Toolkit.getDefaultToolkit().addAWTEventListener(awtListener, AWTEvent.MOUSE_EVENT_MASK);
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
private void mouseClicked(AWTEvent evt) {
if (evt instanceof MouseEvent) {
MouseEvent me = (MouseEvent) evt;
if (me.getID() == MouseEvent.MOUSE_PRESSED) {
if (me.getComponent().equals(tree)) {
TreePath path = tree.getPathForLocation(me.getX(), me.getY());
if (path == null) {
tree.clearSelection();
} else {
// update check box value
String pathString = Objects.toString(path.getLastPathComponent(), "");
Boolean val = checkMap.get(pathString);
if (val != null) {
checkMap.put(pathString, !val);
((DefaultTreeModel) tree.getModel()).valueForPathChanged(path, pathString);
}
}
} else {
tree.clearSelection();
}
}
}
}
}
class DeselectTreeCellRenderer extends DefaultTreeCellRenderer {
private final JCheckBox checkBox = new JCheckBox();
private final Map<Object, Boolean> checkMap;
public DeselectTreeCellRenderer(Map<Object, Boolean> checkMap) {
this.checkMap = checkMap;
}
#Override
public Color getBackgroundSelectionColor() {
return new Color(86, 92, 160);
}
#Override
public Color getBackground() {
return (null);
}
#Override
public Color getBackgroundNonSelectionColor() {
return new Color(23, 27, 36);
}
#Override
public Component getTreeCellRendererComponent(JTree tree, Object value, boolean sel, boolean exp, boolean leaf, int row,
boolean hasFocus) {
super.getTreeCellRendererComponent(tree, value, sel, exp, leaf, row, hasFocus);
setForeground(new Color(225, 225, 221, 255));
setOpaque(false);
// if our "check model" contains an entry for the node,
// present the node as check box
if (checkMap.containsKey(Objects.toString(value))) {
checkBox.setOpaque(true);
checkBox.setSelected(Boolean.TRUE == checkMap.get(Objects.toString(value)));
checkBox.setBackground(sel ? getBackgroundSelectionColor() : getBackgroundNonSelectionColor());
checkBox.setForeground(getForeground());
checkBox.setFont(getFont());
checkBox.setBorder(getBorder());
checkBox.setText(getText());
return checkBox;
}
return this;
}
}
The Robot class in the Java AWT package is used to generate native system input events for the purposes of test automation, self-running demos, and other applications where control of the mouse and keyboard is needed. The primary purpose of Robot is to facilitate automated testing of Java platform implementations. In simple terms, the class provides control over the mouse and keyboard devices. In the below snippet "Robot" has been used to capture the event and clear selection from the tree.
import java.awt.AWTException;
import java.awt.Robot;
import java.awt.event.*;
import javax.swing.*;
import javax.swing.tree.TreePath;
public class TreeDeselectionTest {
JTree createdTreeInstance = new JTree();
TreePath pathSelectionInstance;
Robot robotInstance;
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
new TreeDeselectionTest().createTreeAndCaptureEvents();
}
});
}
public void createTreeAndCaptureEvents() {
try {
robotInstance = new Robot();
} catch (AWTException exceptionInstance) {
exceptionInstance.printStackTrace();
}
createdTreeInstance.setShowsRootHandles(false);
createdTreeInstance.addMouseListener(new MouseAdapter() {
#Override
public void mousePressed(MouseEvent eventMousePressInstance) {
if (robotInstance != null && pathSelectionInstance != null && eventMousePressInstance.getButton() == MouseEvent.BUTTON1) {
createdTreeInstance.clearSelection();
robotInstance.mousePress(InputEvent.BUTTON1_MASK);
robotInstance.mouseRelease(InputEvent.BUTTON1_MASK);
}
pathSelectionInstance = createdTreeInstance.getSelectionPath();
}
});
JFrame frameConetnds = new JFrame();
frameConetnds.setContentPane(new JScrollPane(createdTreeInstance));
frameConetnds.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frameConetnds.setSize(400, 400);
frameConetnds.setLocationRelativeTo(null);
frameConetnds.setVisible(true);
}
}

JComboBox popup appears and hide immediately when clicking on its border (Bad User Experience)

When you have a swing JComboBox and click on its border, the popup appears and disappears immediately. When I say click, I mean press the left button of the mouse and release immediately.
It may be considered a bad user experience because no user would expect it to happen. Any user would expect one of the following behaviors when clicking on a border of a combobox:
The popup to open and remain opened,
Or it not to open at all.
Surely no user would expect the popup to be opened and closed immediately.
The user does not click on the border on purpose. But it may happen frequently when the combobox is small and he tries to click on it quickly.
In the year 2000 somebody registered this behavior as a bug in openjdk site: https://bugs.openjdk.java.net/browse/JDK-4346918
They've recognized it as a bug, but closed it with the resolution: "Won't fix", with the following observation:
I've been able to reproduce the problem but it's not significant so
I'm not going to fix it. The problem is that the drop down portion of
the combo box will hide when the mouse is released after clicking on
the border. This bug doesn't have a very major impact.
I agree with them, that it doesn't have a very major impact. But I still think that it leads to a bad user experience and I would like to know if there is a simple workaround to make the popup either to remain opened or not to open at all when the user clicks on its border.
The described behavior can be reproduced by clicking the left mouse button on the border of any JComboBox. See below a simple code where it can be reproduced:
import java.awt.FlowLayout;
import javax.swing.*;
public class JComboBoxUX{
public static void main(String[] args){
SwingUtilities.invokeLater(new Runnable(){
#Override
public void run(){
JComboBox<String> combobox = new JComboBox<String>(
new String[]{"aaaaaaaaaa","bbbbbbbb","ccccccccc"});
JPanel panel = new JPanel(new FlowLayout(FlowLayout.CENTER, 10, 10));
panel.add(combobox);
JFrame frame = new JFrame("JComboBox UX");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setContentPane(panel);
frame.setSize(300, 150);
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
}
The problem seems to be in:
class BasicComboPopup extends ... {
private Handler getHandler() {
if (handler == null) {
handler = new Handler();
}
return handler;
}
private class Handler implements ... MouseListener ... {
public void mouseReleased(MouseEvent e) {
//...
Component source = (Component)e.getSource();
Dimension size = source.getSize();
Rectangle bounds = new Rectangle( 0, 0, size.width - 1, size.height - 1 );
if ( !bounds.contains( e.getPoint() ) ) {
//...
comboBox.setPopupVisible(false);
}
}
}
}
By subtracting one from size.width and size.height, the mouse falls outside of the bounds of the arrow button, and the popup menu is hidden.
Fixing the issue is problematic. The Handler class is private, so we can't extend it, the getHandler() is private, so we can't override that in BasicComboPopup either.
One could extend MetalComboBoxUI and override createPopup() to return a custom ComboPopup, such as one extending BasicComboPopup but extending createMouseListener() to return a similar class to the Handler above, but without the subtract ones.
Oh, and do the same thing for each LAF you wish to support. Yuk.
Attacking the problem from the other direction, one could extend the MetalComboBoxButton (which is returned by e.getSource()) and override the getSize() method to return a dimension one pixel larger in both directions, when the menu is displayed. Of course, you'd still need to extend and override the MetalComboBoxUI to create and install this custom button.
And again, you'd need to do the same thing for each LAF you wish to support. Again, yuk.
Unfortunately, it does not appear that Swing has the needed hooks to easily override the needed functionalities, and has marked various classes as private internal implementation details, preventing their reuse (in order to prevent breakage later if they want to change the internals).
AJNeufeld's suggestion worked perfectly. Thank you!
Below is the code, if someone needs it.
JComboBoxGoodBorder.java:
import java.awt.Component;
import java.awt.Dimension;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.util.Vector;
import javax.swing.ComboBoxModel;
import javax.swing.JComboBox;
import javax.swing.JComponent;
import javax.swing.SwingUtilities;
import javax.swing.plaf.ComponentUI;
import javax.swing.plaf.basic.BasicComboPopup;
import javax.swing.plaf.basic.ComboPopup;
import javax.swing.plaf.metal.MetalComboBoxUI;
public class JComboBoxGoodBorder<T> extends JComboBox<T> {
public JComboBoxGoodBorder(){
super();
}
public JComboBoxGoodBorder(ComboBoxModel<T> aModel){
super(aModel);
}
public JComboBoxGoodBorder(T[] items){
super(items);
}
public JComboBoxGoodBorder(Vector<T> items){
super(items);
}
#Override
public void updateUI(){
setUI(MetalComboBoxUIGoodBorder.createUI(this));
}
private static class MetalComboBoxUIGoodBorder extends MetalComboBoxUI {
public static ComponentUI createUI(JComponent c) {
return new MetalComboBoxUIGoodBorder();
}
#Override
protected ComboPopup createPopup() {
return new BasicComboPopup(comboBox) {
#Override
protected MouseListener createMouseListener(){
return new MouseAdapter(){
#Override
public void mousePressed(MouseEvent e) {
if (e.getSource() == list) {
return;
}
if (!SwingUtilities.isLeftMouseButton(e) || !comboBox.isEnabled())
return;
if ( comboBox.isEditable() ) {
Component comp = comboBox.getEditor().getEditorComponent();
if ((!(comp instanceof JComponent)) || ((JComponent)comp).isRequestFocusEnabled()) {
comp.requestFocus();
}
}
else if (comboBox.isRequestFocusEnabled()) {
comboBox.requestFocus();
}
togglePopup();
}
#Override
public void mouseReleased(MouseEvent e) {
if (e.getSource() == list) {
if (list.getModel().getSize() > 0) {
// JList mouse listener
if (comboBox.getSelectedIndex() != list.getSelectedIndex()) {
comboBox.setSelectedIndex( list.getSelectedIndex() );
} else {
comboBox.getEditor().setItem( list.getSelectedValue() );
}
}
comboBox.setPopupVisible(false);
// workaround for cancelling an edited item (bug 4530953)
if (comboBox.isEditable() && comboBox.getEditor() != null) {
comboBox.configureEditor(comboBox.getEditor(),
comboBox.getSelectedItem());
}
return;
}
// JComboBox mouse listener
Component source = (Component)e.getSource();
Dimension size = source.getSize();
Rectangle bounds = new Rectangle( 0, 0, size.width, size.height);
if ( !bounds.contains( e.getPoint() ) ) {
MouseEvent newEvent = convertMouseEvent( e );
Point location = newEvent.getPoint();
Rectangle r = new Rectangle();
list.computeVisibleRect( r );
if ( r.contains( location ) ) {
if (comboBox.getSelectedIndex() != list.getSelectedIndex()) {
comboBox.setSelectedIndex( list.getSelectedIndex() );
} else {
comboBox.getEditor().setItem( list.getSelectedValue() );
}
}
comboBox.setPopupVisible(false);
}
hasEntered = false;
stopAutoScrolling();
}
};
}
};
}
}
}
Test.java:
import java.awt.FlowLayout;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
public class Test{
public static void main(String[] args){
SwingUtilities.invokeLater(new Runnable(){
#Override
public void run(){
JComboBoxGoodBorder<String> combobox = new JComboBoxGoodBorder<String>(
new String[]{"aaaaaaaaaa","bbbbbbbb","ccccccccc"});
JPanel panel = new JPanel(new FlowLayout(FlowLayout.CENTER, 10, 10));
panel.add(combobox);
JFrame frame = new JFrame("JComboBox Good Border");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setContentPane(panel);
frame.setSize(300, 300);
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
}

How to set Combobox value to one of the choices in the list

I need to change the value of the current JComboBox value.
I got this in my Settings gui class used by Main.class:
package com.tominocz.cookieclicker;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.FlowLayout;
import java.awt.Toolkit;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JComboBox;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.border.LineBorder;
#SuppressWarnings({ "serial", "rawtypes", "unchecked" })
public class Settings extends JFrame {
private String[] choiceList = { "Default (Arial)", "Comic Sans MS" };
public class ChoiceComboListener implements ActionListener {
public void actionPerformed(ActionEvent ev) {
JComboBox cb = (JComboBox) ev.getSource();
String currentComboSelection = (String) cb.getSelectedItem();
if (currentComboSelection.equals(choiceList[0])) {
Main.SelectedFont = "Arial";
Main.refreshGame();
Main.opt.setSize(240, 105);
Save.saveGame(Main.save);
}
if (currentComboSelection.equals(choiceList[1])) {
Main.SelectedFont = "Comic Sans MS";
Main.refreshGame();
Main.opt.setSize(240, 107);
Save.saveGame(Main.save);
}
}
}
public Settings() {
super("Settings");
setLayout(new FlowLayout());
Dimension screen = Toolkit.getDefaultToolkit().getScreenSize();
int x = (int) (((screen.getWidth()) - this.getWidth()) / 2 - 200);
int y = (int) (((screen.getHeight()) - this.getHeight()) / 2 - 100);
this.setLocation(x, y);
this.setAlwaysOnTop(true);
this.setIconImage(((ImageIcon) Main.SettingsIcon).getImage());
JPanel northPanel = new JPanel();
northPanel.setIgnoreRepaint(true);
northPanel.setBorder(new LineBorder(Color.GRAY));
this.getContentPane().add(northPanel, BorderLayout.NORTH);
JComboBox choiceCombo = new JComboBox(choiceList);
northPanel.add(Main.ChooseFont);
northPanel.add(choiceCombo);
choiceCombo.addActionListener(new ChoiceComboListener());
// LOOK HEREEEEEEEEEEEEEEEEEEEEEEE
if (Main.SelectedFont.equals("Comic Sans MS")) {
[the current JComboBox value(may be the default one - choiceList[0])] = choiceList[1];
}
// LOOK HEREEEEEEEEEEEEEEEEEEEEEEE
Main.OK.setBorder(null);
Main.OK.setBorderPainted(false);
Main.OK.setContentAreaFilled(false);
add(Main.OK);
Main.OK.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
Object source = e.getSource();
if (source instanceof JButton) {
Main.opt.dispose();
}
}
});
}
}
Now how do I change the currentComboSelection value if i'm not available to use it in:
public Settings() {
...
?
Suggestions:
You can set the JComboBox's selected item one of two ways: via setSelectedIndex(int index) or setSelectedItem(Object item) where the first way, you choose the index of the desired selection and the 2nd way you choose the object held by the JComboBox (here they are Strings) that is supposed to be selected.
Other issues:
you look to be getting data from the Main class by directly accessing a field, possibly a static field at that. Don't do this as it will make your code tightly coupled, a set up for hard to find bugs, and difficult to enhance or change. Use tightly controlled getter methods instead. i.e., give Main a public Font getSelectedFont() method so that this class can call the method in a non-static way without directly fiddling with Main's fields, without making Main fields static that shouldn't be static.
The same goes for your directly changing Main's state in this Settings class -- that's not how to do Java safely for the same reasons above. Good OOPs principles don't get thrown out the window just because you're creating an event-driven GUI.
It looks like this is supposed to be a secondary window that is displayed from a main GUI window. If so, this should be displayed as a dialog, such as a JDialog or JOptionPane and not a JFrame. This will prevent the display another icon on the OS's toolbar, will guarantee that this window is on top of the main window, and will allow this window to be displayed in a modal fashion if need be.
You're setting the layout of your contentPane to FlowLayout, and yet you are trying to add a JPanel to its BorderLayout.NORTH position. This obviously won't work.
For example, simple code similar to yours that uses a JOptionPane for the secondary dialog window:
import java.awt.Component;
import java.awt.Dimension;
import java.awt.event.ActionEvent;
import javax.swing.*;
public class TestSettingsPanel {
private static void createAndShowGui() {
MainPanel mainPanel = new MainPanel();
JFrame frame = new JFrame("Test Settings");
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();
}
});
}
}
class MainPanel extends JPanel {
private static final long serialVersionUID = 1L;
public static final String[] CHOICE_LIST = { "Default (Arial)",
"Comic Sans MS", "Courier", "Times New Roman" };
private static final int PREF_W = 500;
private static final int PREF_H = 400;
private JTextField fontField = new JTextField(10);
private SettingsPanel settingsPanel;
public MainPanel() {
fontField.setFocusable(false);
fontField.setText(CHOICE_LIST[0]);
add(new JLabel("Font: "));
add(fontField);
add(new JButton(new SettingsAction("Change Settings")));
}
#Override // let's make this bigger
public Dimension getPreferredSize() {
if (isPreferredSizeSet()) {
return super.getPreferredSize();
}
return new Dimension(PREF_W, PREF_H);
}
private class SettingsAction extends AbstractAction {
private static final long serialVersionUID = 1L;
public SettingsAction(String name) {
// give our button / Action some text
super(name);
int mnemonic = (int) name.charAt(0);
putValue(MNEMONIC_KEY, mnemonic); // set alt-key combination short-cut
}
#Override
public void actionPerformed(ActionEvent e) {
// create our settingsPanel in a lazy fashion
if (settingsPanel == null) {
settingsPanel = new SettingsPanel();
}
// set the combo selection by calling the setSetting method:
settingsPanel.setSetting(fontField.getText());
// create a JOptionPane and dsiplay the settingsPanle inside of it
Component parentComponent = MainPanel.this;
String title = "Change Font Settings";
int optionType = JOptionPane.OK_CANCEL_OPTION;
int messageType = JOptionPane.PLAIN_MESSAGE;
// display JOptionPane here
int result = JOptionPane.showConfirmDialog(parentComponent,
settingsPanel, title, optionType, messageType);
// find out what button user pressed
if (result == JOptionPane.OK_OPTION) {
// if OK button pressed, extract information from settingsPanel
String fontType = settingsPanel.getFontType();
fontField.setText(fontType);
}
}
}
}
// note that SettingsPanel is completely ignorant about the GUI that displays it.
class SettingsPanel extends JPanel {
private static final long serialVersionUID = 1L;
private JComboBox<String> choiceListCombo = new JComboBox<>(MainPanel.CHOICE_LIST);
public SettingsPanel() {
add(new JLabel("Select Font:"));
add(choiceListCombo);
}
// to allow outside classes to set the combo's selected item
public void setSetting(String text) {
if (text != null && !text.trim().isEmpty()) {
choiceListCombo.setSelectedItem(text);
}
}
// allow outside classes to extract the selected item from combo
public String getFontType() {
return (String) choiceListCombo.getSelectedItem();
}
}
You can get and set the selected Item (Object) of your Combobox by using the methods:
choiceCombo.getSelectedItem();
and
choiceCombo.setSelectedItem(anObject);

CellRenderer Item repaint

I created my own CellRenderer which include some several strings and a JProgressBar in a JList Item ... But the JProgressBar and so the whole JList Item will painted one time and I'm looking for a way to repaint the Items ... I tried to start a thread, that will permanently repaint ... But I don't know what I have to repaint to get the result ...
JList repaint ... no result
CellRenderer repaint ... no result
JFrame repaint ... no result
Does anyone understand my Problem and know a way out?
Thank you very much!
UPDATE: [Update deleted]
NEXT UPDATE:
import java.awt.Component;
import java.awt.Dimension;
import javax.swing.DefaultListModel;
import javax.swing.JFrame;
import javax.swing.JList;
import javax.swing.JPanel;
import javax.swing.JProgressBar;
import javax.swing.ListCellRenderer;
public class Main extends JFrame{
public DefaultListModel contentListModel = new DefaultListModel();
public MyCellRenderer MCR = new MyCellRenderer();
public JList contentList = new JList(contentListModel);
public Main(){
super("Example");
setMinimumSize(new Dimension(300,300));
setDefaultCloseOperation(EXIT_ON_CLOSE);
contentList.setCellRenderer(MCR);
contentListModel.addElement("");
contentListModel.addElement("");
add(contentList);
}
public static void main(String[] args){
new Main().setVisible(true);
}
class MyCellRenderer extends JPanel implements ListCellRenderer{
public MyCellRenderer(){
JProgressBar jpb = new JProgressBar();
jpb.setIndeterminate(true);
add(jpb);
}
#Override
public Component getListCellRendererComponent(JList arg0, Object arg1,
int arg2, boolean arg3, boolean arg4) {
// TODO Auto-generated method stub
return this;
}
}
}
As already mentioned, renderers are not live components, that is not part of component hierarchy. Consequently, "natural" animation effects (like f.i. the movement of the indeterminate progressbar) are lost. A trick that might work - but beware: that's highly LAF dependent! - is to lie to the system and report the bar as being dispayable always. That combined with a timer faking a new value every x ms might show the animation again:
public static class ProgressBarRenderer implements TableCellRenderer {
/** The bar. */
private JProgressBar indeterminate = new JProgressBar() {
// fake displayable to trigger ui animation
#Override
public boolean isDisplayable() {
return true;
};
};
/** The bar. */
private JProgressBar determinate = new JProgressBar() ;
public ProgressBarRenderer() {
indeterminate.setStringPainted(true);
indeterminate.setIndeterminate(true);
indeterminate.setString(null);
}
#Override
public Component getTableCellRendererComponent(JTable table,
Object value, boolean isSelected, boolean hasFocus, int row,
int column) {
int pbi = (Integer) value;
if (pbi < 0) {
return indeterminate;
}
determinate.setValue(pbi);
return determinate;
}
}
// a timer driving the animation
Action update = new AbstractAction() {
int count;
#Override
public void actionPerformed(ActionEvent e) {
table.setValueAt(-1, 0, AncientSwingTeam.INTEGER_COLUMN);
}
};
new Timer(100, update).start();
Cell renderers are static/rubber stamps of components, they are not "real", active components. They are simply "painted" onto the surface of the view that uses them.
This means that it's not (really) possible to repaint them as such. You can encourage the list to update by changing the value of the row you want to change. This will cause the model to trigger an update that will cause the list to repaint.
import java.awt.BorderLayout;
import java.awt.Component;
import java.awt.EventQueue;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.DefaultListModel;
import javax.swing.JFrame;
import javax.swing.JList;
import javax.swing.JProgressBar;
import javax.swing.JScrollPane;
import javax.swing.ListCellRenderer;
import javax.swing.Timer;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
public class TestProgressListCellRenderer {
public static void main(String[] args) {
new TestProgressListCellRenderer();
}
public TestProgressListCellRenderer() {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
}
final DefaultListModel<Float> model = new DefaultListModel<>();
model.addElement(0f);
JList<Float> list = new JList<>(model);
list.setCellRenderer(new ProgressListCellRenderer());
JFrame frame = new JFrame("Testing");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setLayout(new BorderLayout());
frame.add(new JScrollPane(list));
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
Timer timer = new Timer(125, new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
float value = model.getElementAt(0);
value += 0.01;
if (value >= 1f) {
value = 1f;
((Timer)e.getSource()).stop();
}
model.setElementAt(value, 0);
}
});
timer.setRepeats(true);
timer.setCoalesce(true);
timer.start();
}
});
}
public class ProgressListCellRenderer extends JProgressBar implements ListCellRenderer<Float> {
#Override
public Component getListCellRendererComponent(JList<? extends Float> list, Float value, int index, boolean isSelected, boolean cellHasFocus) {
setValue(Math.round(value * 100));
return this;
}
}
}
This is a simple example and I tried using setIndeterminate, but I couldn't get it to update (very annoying), but this might trigger some ideas
+1 for interesting issue,
but Renderer in Swing isn't designated to showing animations, this is only static painting, illusion, snapshot,
you have to animate this Object in your code, but not easy job for JProgressBar.setIndeterminate(true); if is possible without dirty hacks,
required own JProgressBar
required own animation for ListCellRenderer, and the result could be so far in compare with JProgressBar placed in JPanel e.g.
I think JList couldn't be proper JComponents, use JTable with one Column and /or without JTableHeader
simple workaround to use JPanel (layed by GridLayout) with JProgressBars for JProgressBar.setIndeterminate(true);, then output to the Swing GUI will be very similair to the JTable (without JTableHeader or JList)
put this JPanel to JScrollPane
override getPreferredSize for JScrollPane, then you'll simulating JList.setVisibleRowCount properly
change JScrollBar.setUnitIncrement for natural scrolling for JPanel with JComponents and with JList, JTable, JTextArea placed in JScrollPane
You may be able to drive the animation with your own javax.swing.Timer in a custom ProgressBarUI, seen here; incrementAnimationIndex() looks promising, but I've not tried it.
Absent a better solution, you can use a ProgressIcon in a DefaultListCellRenderer; the approach is illustrated here in a JTabbedPane.
It's easy.
public void updateListData(final JList myList) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
ListModel lm = myList.getModel();
DefaultListModel dlm = new DefaultListModel();
for (int i = 0; i < lm.getSize(); i++) {
dlm.addElement(lm.getElementAt(i));
}
myList.setModel(dlm);
}
});
}
The code above can be called from EDT and from another thread. But you also should read how to deal with Swing components and understand models (ListModel, TableModel, etc). To ask an element in JList to repaint you should modify its object in model.

JPopupPanel closed when click on its JPopupMenu

I have a JPopupPanel showing up when a button is clicked.
This JPopupPanel has a JPopupMenu which shows up with the mouse right click, showing different options.
When left button pressed to choose one of this options in the PopupMenu, the PopupPanel closes leaving the PopupMenu by itself for a moment, and when the button is released, the PopupMenu also dissapears (as expected), but the action cannot be seen in the PopupPanel since it is already closed.
How can I avoid the JPopupPanel to close when choosing one of the options of the JPopupMenu?
Thanks
Sorry I do not have experience using JPopupPanel. Thus, I can only offer a simple hack.
I would suspect that you can do something similar as in my example (below) where I 'ignore' hiding popup menu on an option select.
My approach here is to reshow the popup menu on an option selection. So fallowing this maybe you can try and reshow your panel when it hides.
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Point;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import javax.swing.JFrame;
import javax.swing.JMenuItem;
import javax.swing.JPanel;
import javax.swing.JPopupMenu;
import javax.swing.SwingUtilities;
public class JPopupMenuIgnoreOptionCloseOnClick extends JPanel
{
private static final long serialVersionUID = 1L;
private JPopupMenu popup = new JPopupMenu("Oi I am popup");
private MouseListener mL = new MouseAdapter()
{
#Override
public void mousePressed(MouseEvent e)
{
System.out.println("mL mousePressed e.isP="+e.isPopupTrigger());
super.mousePressed(e);
showPopup(e);
}
#Override
public void mouseReleased(MouseEvent e)
{
System.out.println("mL mouseReleased e.isP="+e.isPopupTrigger());
super.mouseReleased(e);
showPopup(e);
}
private void showPopup(MouseEvent e)
{
if(e.isPopupTrigger())
{
prevLocation = e.getPoint();
popup.show(e.getComponent(), e.getX(), e.getY());
}
}
};
private Point prevLocation = null;
private MouseListener optionML = new MouseAdapter()
{
#Override
public void mouseReleased(MouseEvent e)
{
System.out.println("optionML mouseReleased prevLocation="+prevLocation);
e.consume();
popup.show(JPopupMenuIgnoreOptionCloseOnClick.this, prevLocation.x,prevLocation.y);
}
};
public JPopupMenuIgnoreOptionCloseOnClick()
{
addMouseListener(mL);
JMenuItem opt1 =new JMenuItem("Option 1");
opt1.addMouseListener(optionML);
popup.add(opt1);
JMenuItem opt2 =new JMenuItem("Option 2");
opt2.addMouseListener(optionML);
popup.add(opt2);
JMenuItem opt3 =new JMenuItem("Option 3");
opt3.addMouseListener(optionML);
popup.add(opt3);
}
public static void main(String[] args)
{
SwingUtilities.invokeLater(new Runnable()
{
#Override
public void run()
{
JPopupMenuIgnoreOptionCloseOnClick p = new JPopupMenuIgnoreOptionCloseOnClick();
p.setPreferredSize(new Dimension(400, 400));
JPanel contentPane = new JPanel();
contentPane.setBackground(Color.CYAN);
contentPane.add(p);
JFrame f = new JFrame();
f.setContentPane(contentPane);
f.setSize(800, 600);
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
f.setVisible(true);
}
});
}
}
I came across this problem myself when installing a custom JPopupMenu on a JCommandButton.
For your JCommandButton I found this to be helpful in preventing premature disposal of the parent popup:
this.putClientProperty(BasicCommandButtonUI.DONT_DISPOSE_POPUPS, true);
If what you are looking for is instead that upon making a JPopupMenu JMenuItem selection, the parent popup panel will stay open, you have a couple options. The problem stems from JPopupMenu's broken link in the ancestry container chain, on which the UI relies. Instead of getParent(), you need to return getInvoker().
1:
modify the library source in BasicPopupPanelUI.WindowTracker.eventDispatched(). Either change the SwingUtilities.getAncestorOfClass() calls to use SwingXUtilities.getAncestorOfClass() which accounts for this special case. Or implement the logic yourself.
if(parent instanceof JPopupMenu) parent = ((JPopupMenu)parent).getInvoker()
2:
Add this code to the widget (CustomButton?)
final JPopupMenu popper = new JPopupMenu(){ //hack
#Override public Container getParent(){
StackTraceElement ste = Thread.currentThread().getStackTrace()[2];
if(ste.getClassName().equals(SwingUtilities.class.getName()))
return CustomButton.this.getParent();
return super.getParent();
}
};
I chose #2, since I have issues with modifying 3rd party libraries.

Categories

Resources