I could really use a tri-stated checkbox in Java. It sounds like a simple thing, but I've only seen really ugly implementations [note: link now broken].
Three radio buttons just take up too much real estate and will probably be confusing for the users in my case. It's basically for a search dialog. I need true, false or "don't care" options. Is there a different technique that people use?
Use a drop-down.
I found a way to make a tri-state checkbox by simply adding a listener:
public class TriStateActionListener implements ActionListener{
final protected Icon icon;
public TriStateActionListener(Icon icon){
this.icon=icon;
}
public static Boolean getState(javax.swing.JCheckBox cb){
if (cb.getIcon()==null) return null;
if (cb.isSelected()) return true;
else return false;
}
public void actionPerformed(ActionEvent e) {
javax.swing.JCheckBox cb=(javax.swing.JCheckBox)e.getSource();
if (!cb.isSelected()){
cb.setIcon(icon);
}
else if (cb.getIcon()!=null){
cb.setIcon(null);
cb.setSelected(false);
}
}
}
Then in the application code, it's just a single line:
jCheckBox1.addActionListener(new TriStateActionListener(getResourceMap().getIcon("TriStateIcon")));
After all the feedback, I'm thinking a drop-down may be a better choice. But, I wanted to share my code here for everyone else.
In this implementation the three state can be only set via programmatically. To be Look and Feel portable it use images, that must be placed inside the the same java package.
public class TristateCheckBox extends JCheckBox {
private static final long serialVersionUID = 1L;
private boolean halfState;
private static Icon selected = new javax.swing.ImageIcon(TristateCheckBox.class.getResource("selected.png"));
private static Icon unselected = new javax.swing.ImageIcon(TristateCheckBox.class.getResource("unselected.png"));
private static Icon halfselected = new javax.swing.ImageIcon(TristateCheckBox.class.getResource("halfselected.png"));
#Override
public void paint(Graphics g) {
if (isSelected()) {
halfState = false;
}
setIcon(halfState ? halfselected : isSelected() ? selected : unselected);
super.paint(g);
}
public boolean isHalfSelected() {
return halfState;
}
public void setHalfSelected(boolean halfState) {
this.halfState = halfState;
if (halfState) {
setSelected(false);
repaint();
}
}
}
Sample frame:
public class NewJFrame19 extends javax.swing.JFrame {
private final TristateCheckBox myCheckBox;
public NewJFrame19() {
myCheckBox = new TristateCheckBox();
myCheckBox.setText("123123");
add(myCheckBox);
jButton1 = new javax.swing.JButton();
setDefaultCloseOperation(javax.swing.WindowConstants.EXIT_ON_CLOSE);
getContentPane().setLayout(new java.awt.FlowLayout());
jButton1.setText("jButton1");
jButton1.addActionListener(new java.awt.event.ActionListener() {
public void actionPerformed(java.awt.event.ActionEvent evt) {
jButton1ActionPerformed(evt);
}
});
getContentPane().add(jButton1);
pack();
}
private void jButton1ActionPerformed(java.awt.event.ActionEvent evt) {
myCheckBox.setHalfSelected(true);
}
public static void main(String args[]) {
try {
for (javax.swing.UIManager.LookAndFeelInfo info : javax.swing.UIManager.getInstalledLookAndFeels()) {
if ("Windows".equals(info.getName())) {
javax.swing.UIManager.setLookAndFeel(info.getClassName());
break;
}
}
} catch (ClassNotFoundException ex) {
java.util.logging.Logger.getLogger(NewJFrame19.class.getName()).log(java.util.logging.Level.SEVERE, null, ex);
} catch (InstantiationException ex) {
java.util.logging.Logger.getLogger(NewJFrame19.class.getName()).log(java.util.logging.Level.SEVERE, null, ex);
} catch (IllegalAccessException ex) {
java.util.logging.Logger.getLogger(NewJFrame19.class.getName()).log(java.util.logging.Level.SEVERE, null, ex);
} catch (javax.swing.UnsupportedLookAndFeelException ex) {
java.util.logging.Logger.getLogger(NewJFrame19.class.getName()).log(java.util.logging.Level.SEVERE, null, ex);
}
/* Create and display the form */
java.awt.EventQueue.invokeLater(new Runnable() {
public void run() {
new NewJFrame19().setVisible(true);
}
});
}
private javax.swing.JButton jButton1;
}
JIDE have open sourced some very nice functionality in their Common Layer, one of which happens to be a tristate checkbox
I would suggest that you go run the webstart demo to see if it meets your needs
That "ugly implementations" is an old link. One of the suggestions on that page was updated a couple of years ago. I haven't tested the old implementation, so I don't know if the new one is any better or worse.
TristateCheckBox Revisited
Tristate check-boxes are standard UI idiom for Treeviews with partially checked children nodes. They are widely used in layer management in complex hierarchial views such as Google Earth.
I dont know why anyone would give the solutions with additional icon png files while java api gives great funcionality for overriding paintIcon(..) method.
The best lightweight solution to remember CheckBox state is IMO ClientProperty attribute.
/*
* Tri-state checkbox example
* #s1w_
*/
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
class TCheckBox extends JCheckBox implements Icon, ActionListener {
final static boolean MIDasSELECTED = true; //consider mid-state as selected ?
public TCheckBox() { this(""); }
public TCheckBox(String text) {
super(text);
putClientProperty("SelectionState", 0);
setIcon(this);
addActionListener(this);
}
public TCheckBox(String text, int sel) {
/* tri-state checkbox has 3 selection states:
* 0 unselected
* 1 mid-state selection
* 2 fully selected
*/
super(text, sel > 1 ? true : false);
switch (sel) {
case 2: setSelected(true);
case 1:
case 0:
putClientProperty("SelectionState", sel);
break;
default:
throw new IllegalArgumentException();
}
addActionListener(this);
setIcon(this);
}
#Override
public boolean isSelected() {
if (MIDasSELECTED && (getSelectionState() > 0)) return true;
else return super.isSelected();
}
public int getSelectionState() {
return (getClientProperty("SelectionState") != null ? (int)getClientProperty("SelectionState") :
super.isSelected() ? 2 :
0);
}
public void setSelectionState(int sel) {
switch (sel) {
case 2: setSelected(true);
break;
case 1:
case 0: setSelected(false);
break;
default: throw new IllegalArgumentException();
}
putClientProperty("SelectionState", sel);
}
final static Icon icon = UIManager.getIcon("CheckBox.icon");
#Override
public void paintIcon(Component c, Graphics g, int x, int y) {
icon.paintIcon(c, g, x, y);
if (getSelectionState() != 1) return;
int w = getIconWidth();
int h = getIconHeight();
g.setColor(c.isEnabled() ? new Color(51, 51, 51) : new Color(122, 138, 153));
g.fillRect(x+4, y+4, w-8, h-8);
if (!c.isEnabled()) return;
g.setColor(new Color(81, 81, 81));
g.drawRect(x+4, y+4, w-9, h-9);
}
#Override
public int getIconWidth() {
return icon.getIconWidth();
}
#Override
public int getIconHeight() {
return icon.getIconHeight();
}
public void actionPerformed(ActionEvent e) {
TCheckBox tcb = (TCheckBox)e.getSource();
if (tcb.getSelectionState() == 0)
tcb.setSelected(false);
tcb.putClientProperty("SelectionState", tcb.getSelectionState() == 2 ? 0 :
tcb.getSelectionState() + 1);
// test
System.out.println(">>>>IS SELECTED: "+tcb.isSelected());
System.out.println(">>>>IN MID STATE: "+(tcb.getSelectionState()==1));
}
}
usage: TCheckBox tcb = new TCheckBox("My CheckBox");
I'd just use the one you posted.
As long as your complexity is in another class (that works) and it acts just like any other control, who cares? (That seems to be the assumption behind all of swing, most swing classes seem to be about this complicated.)
Change the UI. Tristate check-box is unusual and can really confuse users. The drop down is good option but for more then one occurrence within dialog it will also bring a lot of confusion to user.
Related
I'm using WorldWind and trying to "pick" multiple surface images in the same layer and not understanding why it isn't working.
I was under the impression that calling this:
this.getWwd().getSceneController().setDeepPickEnabled(true);
Would enable me to pick multiple renderables in the same layer. This seems to work for all other cases other than SurfaceImage. I also noticed if I force the loaded SurfaceImage into different layers it works as expected.
This is the code I'm using to test this out:
public class SurfaceImageViewer extends ApplicationTemplate
{
public static class AppFrame extends ApplicationTemplate.AppFrame
{
private JFileChooser fileChooser = new JFileChooser();
private JSlider opacitySlider;
private SurfaceImageLayer layer;
private JLabel statusLabel = new JLabel("status: ready");
public AppFrame()
{
super(true, true, false);
this.getWwd().getSceneController().setDeepPickEnabled(true);
try
{
this.layer = new SurfaceImageLayer();
this.layer.setOpacity(1);
this.layer.setPickEnabled(true);
this.layer.setName("Surface Images");
insertBeforeCompass(this.getWwd(), layer);
this.getControlPanel().add(makeControlPanel(), BorderLayout.SOUTH);
}
catch (Exception e)
{
e.printStackTrace();
}
this.getWwd().addSelectListener(new SelectListener() {
#Override
public void selected(SelectEvent event) {
PickedObjectList pol = AppFrame.this.getWwd().getObjectsAtCurrentPosition();
if(event.isLeftClick()){
System.out.println("POL SIZE "+pol.size());
}
}
});
}
Action openElevationsAction = new AbstractAction("Open Elevation File...")
{
public void actionPerformed(ActionEvent e)
{
int status = fileChooser.showOpenDialog(AppFrame.this);
if (status != JFileChooser.APPROVE_OPTION)
return;
final File imageFile = fileChooser.getSelectedFile();
if (imageFile == null)
return;
Thread t = new Thread(new Runnable()
{
public void run()
{
try
{
CompoundElevationModel cem
= (CompoundElevationModel) getWwd().getModel().getGlobe().getElevationModel();
LocalElevationModel em = new LocalElevationModel();
em.addElevations(imageFile.getPath());
cem.addElevationModel(em);
}
catch (IOException e1)
{
e1.printStackTrace();
}
}
});
t.setPriority(Thread.MIN_PRIORITY);
t.start();
}
};
Action openImageAction = new AbstractAction("Open Image File...")
{
public void actionPerformed(ActionEvent actionEvent)
{
int status = fileChooser.showOpenDialog(AppFrame.this);
if (status != JFileChooser.APPROVE_OPTION)
return;
final File imageFile = fileChooser.getSelectedFile();
if (imageFile == null)
return;
Thread t = new Thread(new Runnable()
{
public void run()
{
try
{
statusLabel.setText("status: Loading image");
// TODO: proper threading
layer.addImage(imageFile.getAbsolutePath());
getWwd().redraw();
statusLabel.setText("status: ready");
}
catch (IOException e)
{
e.printStackTrace();
}
}
});
t.setPriority(Thread.MIN_PRIORITY);
t.start();
}
};
private JPanel makeControlPanel()
{
JPanel controlPanel = new JPanel(new GridLayout(0, 1, 5, 5));
JButton openImageButton = new JButton(openImageAction);
controlPanel.add(openImageButton);
this.opacitySlider = new JSlider();
this.opacitySlider.setMaximum(100);
this.opacitySlider.setValue((int) (layer.getOpacity() * 100));
this.opacitySlider.setEnabled(true);
this.opacitySlider.addChangeListener(new ChangeListener()
{
public void stateChanged(ChangeEvent e)
{
int value = opacitySlider.getValue();
layer.setOpacity(value / 100d);
getWwd().redraw();
}
});
JPanel opacityPanel = new JPanel(new BorderLayout(5, 5));
opacityPanel.setBorder(new EmptyBorder(0, 10, 0, 0));
opacityPanel.add(new JLabel("Opacity"), BorderLayout.WEST);
opacityPanel.add(this.opacitySlider, BorderLayout.CENTER);
controlPanel.add(opacityPanel);
JButton openElevationsButton = new JButton(openElevationsAction);
controlPanel.add(openElevationsButton);
controlPanel.add(statusLabel);
controlPanel.setBorder(new EmptyBorder(15, 15, 15, 15));
return controlPanel;
}
}
public static void main(String[] args)
{
ApplicationTemplate.start("World Wind Surface Images", SurfaceImageViewer.AppFrame.class);
}
}
These are 2 geotiffs that are layered on top of each other that I've been using to test this out. I would expect my println on the SelectListener to print out "3" when I single left click on both geotiffs. (I've uploaded the geotiffs into a zip available here)
The area where you will see these is in San Francisco, see screenshot:
Update:
It was discovered that the examples for Batch Picking were oriented around AbstractSurfaceObject instances, which did not apply in this case. For the handling of SurfaceImage instances the property for setAlwaysOnTop should be configured to false which appears to let the selection event process all elements under the cursor.
Reading through the examples for DeepPicking, there are actually 2 things that need to be done.
setDeepPickEnabled(true); //This is done.
Disable Batch picking on the desired elements
https://github.com/nasa/World-Wind-Java/blob/master/WorldWind/src/gov/nasa/worldwindx/examples/DeepPicking.java
In order to enable deep picking, any batch picking for the desired elements must be disabled and the
SceneController's deep picking property must be enabled. See {#link gov.nasa.worldwind.SceneController#setDeepPickEnabled(boolean)
Took me a little while to understand the second one, but it appears to be tied to the AbstractSurfaceObject class.
I am assuming that the things that you're drawing on the layer are a subclass of AbstractSurfaceObject
I believe that in this situation, I would subclass the SurfaceImageLayer, and override the addRenderable methods. I would check the renderable if it was an instance of an AbstractSurfaceObject, and disable batch picking on it before forwarding it to the super class.
This code may not be the best long-term solution, but it may provide quick results to determine if this is the underlying issue.
import gov.nasa.worldwind.layers.SurfaceImageLayer;
import gov.nasa.worldwind.render.AbstractSurfaceObject;
import gov.nasa.worldwind.render.Renderable;
/**
* Very Rough extension of SurfaceImageLayer which disables batch picking on all AbstractSurfaceobjects.
* #author http://stackoverflow.com/users/5407189/jeremiah
* #since Nov 26, 2016
*
*/
public class MySurfaceImageLayer extends SurfaceImageLayer {
#Override
public void addRenderable(Renderable renderable) {
if (renderable instanceof AbstractSurfaceObject) {
((AbstractSurfaceObject)renderable).setEnableBatchPicking(false);
}
super.addRenderable(renderable);
}
#Override
public void addRenderables(Iterable<? extends Renderable> renderables) {
for (Renderable r : renderables) {
addRenderable(r);
}
}
}
IF the thing you want to have picked is the image directly, that appears to not be supported out-of-the-box. You would need to do something to get the SurfaceImage references from the SurfaceImageLayer to be visible to the RenderableLayer on doPick. That may come with a new set of problems to watch out for.
As a side-note, if you're rendering Icons then all you need to do is set the IconRenderer.setAllowBatchPicking(false)
I hope that's at least somewhat helpful.
Best of Luck.
I have a base class mainframe and i have keeping the JButton as final static Which its BGcolor going to be changed by a extended class of mainframe namely dataframe. Initially i need to set the BGColor of the JButton to red. Then I need to change it to some other colors from the dataframe. I can able to set the BGColor from the mainframe but not from the dataframe(extended class). I've used mainframe.Button_name.setBackground(color.yellow); but still its not changing
`enter code here`
public class mainframe {
final static JButton Button_name = new JButton("Hi");
public static void main(String[] args)
{
public void run()
{
Button_name.setBackground(color.Red); //This is working
}
}
}
class dataframe extends mainframe implements Runnable
{
public void run()
{
//doing other things
while(some condition)
{
if (another_condition)
{
//from here i need to change that Buttons color
// i've tried this
mainframe.Button_name.setBackground(color.yellow); //Not working
}
}
}
}
Kindly anyone help with this issue
So you want to change the state of a UI component from a different thread in a different class. There are multiple ways you might be able to do this, but first, I would start by defining away for those classes to be able to only effect the change you want them to.
Exposing the entire frame, component or even button is not a good idea, people have a habit of changing things you don't want them to, so instead, we define a simple contract which states what they are allowed to do, for example...
public interface Colorable {
public void setColor(Color color);
}
This immediately decouples your code, meaning that any code that wants to change the state of your UI (or change the color of something else) can do so, without relying on the physical implementation.
Thread
First, we're going to have a look at using a Thread to change the UI...
public class ColorChanger {
private Colorable colorable;
public ColorChanger(Colorable colorable) {
this.colorable = colorable;
}
public void start() {
Thread t = new Thread(new Runnable() {
#Override
public void run() {
for (int index = 0; index < 1000; index++) {
if (index % 100 == 0) {
if ((index / 100) % 2 == 0) {
colorable.setColor(Color.GREEN);
} else {
colorable.setColor(Color.RED);
}
}
try {
// This is so you can see the colors changing
Thread.sleep(5);
} catch (InterruptedException ex) {
}
}
System.out.println("Done");
}
});
t.start();
}
}
This is a pretty basic class, it requires an instance of Colorable and will change the state of the color for every 100 counts, based on if it's an even or odd hundred
We use a simple JPanel as our base test class, when you click the button, the ColorChanger is created and started.
public class TestPane extends JPanel implements Colorable {
private JButton btn;
private ColorChanger changer;
public TestPane() {
setLayout(new GridBagLayout());
setBorder(new EmptyBorder(20, 20, 20, 20));
btn = new JButton("I am your button");
add(btn);
btn.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
if (changer == null) {
changer = new ColorChanger(TestPane.this);
changer.start();
}
}
});
}
#Override
public void setColor(Color color) {
if (EventQueue.isDispatchThread()) {
btn.setBackground(color);
} else {
System.out.println("Not in the EDT");
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
setColor(color);
}
});
}
}
}
You will note that the setColor method has a bunch of code in it, this is to ensure that the updates to the UI are executed only from within the context of the Event Dispatching Thread.
SwingWorker
An alternative is to use a SwingWorker, which operates very similarly to a Thread, expect it has the ability to publish content to the EDT
public class ColorChangerWorker extends SwingWorker<Void, Color> {
private Colorable colorable;
public ColorChangerWorker(Colorable colorable) {
this.colorable = colorable;
}
#Override
protected void process(List<Color> chunks) {
colorable.setColor(chunks.get(chunks.size() - 1));
}
#Override
protected Void doInBackground() throws Exception {
for (int index = 0; index < 1000; index++) {
if (index % 100 == 0) {
if ((index / 100) % 2 == 0) {
publish(Color.GREEN);
} else {
publish(Color.RED);
}
}
try {
// This is so you can see the colors changing
Thread.sleep(5);
} catch (InterruptedException ex) {
}
}
System.out.println("Done");
return null;
}
}
You will note here, that when we want to change the color we call publish. The process method is called to let us know that there is more data to be processed, but here, we're only interested in the last change.
And out TestPane...
public class TestPane extends JPanel implements Colorable {
private JButton btn;
private ColorChangerWorker changer;
public TestPane() {
setLayout(new GridBagLayout());
setBorder(new EmptyBorder(20, 20, 20, 20));
btn = new JButton("I am your button");
add(btn);
btn.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
if (changer == null) {
changer = new ColorChangerWorker(TestPane.this);
changer.execute();
}
}
});
}
#Override
public void setColor(Color color) {
if (EventQueue.isDispatchThread()) {
btn.setBackground(color);
} else {
System.out.println("Not in the EDT");
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
setColor(color);
}
});
}
}
}
You will note that the setColor method remains unchanged, this is deliberate, when you test this class, you will note that "Not in the EDT" is never printed, basically meaning we could do away with all that code and just call btn.setBackground(color);, but I want you to see the difference.
The Button...
Now, when I run this code, I get the following output...
Wait a minute, that buttons background is filled?! Actually it is, but many button implementations have a secondary "content area" filling
You can turn this off using something like...
btn.setContentAreaFilled(false);
btn.setOpaque(true);
Which will result in something like...
Thanks to the very helpful post by Bart Kiers in someone else's post, I have managed to get my input hints working. Hurray! Unfortunately I can't seem to get a small glitch to stop occurring.
During the normal course of operation, the HintTextFields are disabled and enabled to allow or disallow edits. The input hint should only show when the field is empty and not selected, but when I left click the boxes, even when disabled and containing text, the text disappears in favor of the hint. Then, when I click on something else, I lose the contents entirely.
I have added an extra bit to the if then statements regarding gaining and losing focus to prevent it from running that code if the component is disabled, but the problem still occurs. It must be some small interaction with the JTextField class (or maybe FocusListener?). I have also tried getParent() in favor of this in that clause, in case I was misunderstanding the 'this' keyword, but that didn't help. The class below:
class HintTextField extends JTextField implements FocusListener {
private final String hint;
private boolean showingHint;
public HintTextField(final String hint) {
super(hint);
this.hint = hint;
this.showingHint = true;
super.addFocusListener(this);
}
#Override
public void focusGained(FocusEvent e) {
if(this.getText().isEmpty() && this.isEnabled()) {
super.setText("");
showingHint = false;
}
}
#Override
public void focusLost(FocusEvent e) {
if(this.getText().isEmpty() && this.isEnabled()) {
super.setText(hint);
showingHint = true;
}
}
#Override
public String getText() {
return showingHint ? "" : super.getText();
}
}
Original post if anyone wants to go upvote the wonderful Bart Kiers.
Java JTextField with input hint
Also, here is the initialization. This started as a JDeveloper gui.
startDateField.setHorizontalAlignment(javax.swing.JTextField.CENTER);
startDateField.setToolTipText(notes);
startDateField.setBorder(javax.swing.BorderFactory.createLineBorder(new java.awt.Color(0, 0, 0)));
startDateField.setMaximumSize(new java.awt.Dimension(2, 16));
startDateField.addMouseListener(new java.awt.event.MouseAdapter() {
public void mouseClicked(java.awt.event.MouseEvent evt) {
startDateFieldMouseClicked(evt);
}
});
private void startDateFieldMouseClicked(java.awt.event.MouseEvent evt) {//GEN-FIRST:event_startDateFieldMouseClicked
if (SwingUtilities.isRightMouseButton(evt)) {
editNote();
}
}
public void editNote() {
int selected = -1;
for (int a = 0; a < spinner.bitem.bldg.stallArray[1].length; a ++) {
if (spinner.bitem.bldg.stallArray[1][a].equals(stallName)) {
selected = a;
}
}
String oldNote = notes;
JTextField xField = new JTextField(oldNote);
xField.setPreferredSize(new Dimension(200,30));
JPanel myPanel = new JPanel();
myPanel.add(new JLabel("Note:"));
myPanel.add(xField);
int result =
JOptionPane.showConfirmDialog(this, myPanel, "Please enter note.",
JOptionPane.OK_CANCEL_OPTION);
if (result == JOptionPane.OK_OPTION) {
String note = xField.getText();
if (!note.equals(oldNote)) {
notes = note;
spinner.bitem.bldg.stallArray[9][selected] = notes;
spinner.bitem.master.sendNoteDataToSQL(spinner.bitem.bldg.buildingName, spinner.bitem.bldg.stallArray[1][selected], notes);
spinner.updateScreen();
}
}
}
I am trying to get a group of JRadioButtons to be navigable using the arrow keys. I was going to implement this manually with KeyListeners, but apparently this behavior is already supposed to work for at least the last 8 years (http://bugs.sun.com/view_bug.do?bug_id=4104452). However, it's not working for me: pressing the arrow keys does nothing. Java version is 7u45 on Windows.
A standalone test case to see what I'm talking about:
import java.awt.*;
import javax.swing.*;
public class Test {
public static void main(final String[] args) {
if (!EventQueue.isDispatchThread()) {
try {
EventQueue.invokeAndWait(new Runnable() {
public void run() {
main(args);
}
});
} catch (Exception e) {
throw new RuntimeException(e);
}
return;
}
try {
//UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
//UIManager.setLookAndFeel("javax.swing.plaf.nimbus.NimbusLookAndFeel");
} catch (Throwable t) {
throw new RuntimeException(t);
}
JFrame frame = new JFrame();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
ButtonGroup group = new ButtonGroup();
JPanel panel = new JPanel();
panel.setLayout(new BoxLayout(panel, BoxLayout.PAGE_AXIS));
JRadioButton rb;
rb = new JRadioButton("Option A");
panel.add(rb);
group.add(rb);
rb = new JRadioButton("Option B");
panel.add(rb);
group.add(rb);
rb = new JRadioButton("Option C");
panel.add(rb);
group.add(rb);
frame.add(panel);
frame.pack();
frame.setVisible(true);
}
}
I have tried using different look & feels, different containers, and different layout managers, but it still does not work.
You need to add the right/left (up/down?) keys to the focus traversal policy of each radio button. For example to add the right/left arrow keys:
Set set = new HashSet( rb.getFocusTraversalKeys(KeyboardFocusManager.FORWARD_TRAVERSAL_KEYS ) );
set.add( KeyStroke.getKeyStroke( "RIGHT" ) );
rb.setFocusTraversalKeys(KeyboardFocusManager.FORWARD_TRAVERSAL_KEYS, set );
set = new HashSet( rb.getFocusTraversalKeys(KeyboardFocusManager.BACKWARD_TRAVERSAL_KEYS ) );
set.add( KeyStroke.getKeyStroke( "LEFT" ) );
rb.setFocusTraversalKeys(KeyboardFocusManager.BACKWARD_TRAVERSAL_KEYS, set );
Read the section from the Swing tutorial on How to Use the Focus Subsystem for more information.
I believe you can achieve your goal using KeyBindings instead of KeyListeners. In many cases bindings are actually recommended over KeyListeners, as the second ones can generate many problems (frame catching the key activity must be active one etc.)
Thank you everyone for the answers.
I discovered the reason for my confusion. Apparently, when the Sun bug report system says that a bug's status is "Closed" and its "Resolved Date" is "2005-07-19", that doesn't mean the bug is fixed at all. Apparently, it's just logged as a duplicate of some other (newer?) bug. Nearly 16 years since it was first reported it still isn't fixed. Whatever.
The needed behavior is much more subtle than I realized. I experimented in native Windows dialogs in various programs:
Most button-like components: buttons, checkboxes, and radio buttons, implement the arrow keys for focus navigation. In Java this corresponds to the AbstractButton class. (JMenuItem is also a subclass of that, but that has its own distinct arrow key behavior.)
Only radio buttons get selected/checked during this navigation.
Unfocusable (including disabled or invisible) components must be skipped.
Attempting to navigate before the first button in a group or after the last one is inconsistent: on some dialogs it loops from end to end; on others it moves irreversibly onto non-button components; and on yet others it does nothing. I experimented with all these different behaviors and none of them was particularly better than the others.
I implemented a looping behavior below as it felt slightly more fluent. The navigation silently skips past non-AbstractButton components, forming a sort-of separate focus cycle private to buttons. This is dubious but sometimes needed when a set of related checkboxes or radio buttons are mixed with other components. Testing for a common parent component to identify groups would also be a reasonable behavior, but that didn't work in one dialog where I'd used separate components purely for layout reasons (to implement a line break in a FlowLayout).
As suggested I studied up on InputMaps and ActionMaps instead of using a KeyListener. I've always avoided the maps as they seem overcomplicated but I guess I see the advantage of being able to easily override the binding.
This code uses an auxialiary look and feel to install the desired behavior for all AbstractButton components application-wide (which is a nice technique I found out about here). I've tested it with several different dialog boxes and windows and it seems to be okay. If it causes issues I'll update this post.
Call:
ButtonArrowKeyNavigation.install();
once at application startup to install it.
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
public class ButtonArrowKeyNavigation {
private ButtonArrowKeyNavigation() {}
public static void install() {
UIManager.addAuxiliaryLookAndFeel(lookAndFeel);
}
private static final LookAndFeel lookAndFeel = new LookAndFeel() {
private final UIDefaults defaults = new UIDefaults() {
#Override
public javax.swing.plaf.ComponentUI getUI(JComponent c) {
if (c instanceof AbstractButton && !(c instanceof JMenuItem)) {
if (c.getClientProperty(this) == null) {
c.putClientProperty(this, Boolean.TRUE);
configure(c);
}
}
return null;
}
};
#Override public UIDefaults getDefaults() { return defaults; };
#Override public String getID() { return "ButtonArrowKeyNavigation"; }
#Override public String getName() { return getID(); }
#Override public String getDescription() { return getID(); }
#Override public boolean isNativeLookAndFeel() { return false; }
#Override public boolean isSupportedLookAndFeel() { return true; }
};
private static void configure(JComponent c) {
InputMap im = c.getInputMap(JComponent.WHEN_FOCUSED);
ActionMap am = c.getActionMap();
im.put(KeyStroke.getKeyStroke(KeyEvent.VK_LEFT, 0), "focusPreviousButton");
im.put(KeyStroke.getKeyStroke(KeyEvent.VK_UP, 0), "focusPreviousButton");
im.put(KeyStroke.getKeyStroke(KeyEvent.VK_RIGHT, 0), "focusNextButton");
im.put(KeyStroke.getKeyStroke(KeyEvent.VK_DOWN, 0), "focusNextButton");
am.put("focusPreviousButton", focusPreviousButton);
am.put("focusNextButton", focusNextButton);
}
private static final Action focusPreviousButton = new AbstractAction() {
public void actionPerformed(ActionEvent e) {
move((AbstractButton)e.getSource(), -1);
}
};
private static final Action focusNextButton = new AbstractAction() {
public void actionPerformed(ActionEvent e) {
move((AbstractButton)e.getSource(), +1);
}
};
private static void move(AbstractButton ab, int direction) {
Container focusRoot = ab.getFocusCycleRootAncestor();
FocusTraversalPolicy focusPolicy = focusRoot.getFocusTraversalPolicy();
Component toFocus = ab, loop = null;
for (;;) {
toFocus = direction > 0
? focusPolicy.getComponentAfter(focusRoot, toFocus)
: focusPolicy.getComponentBefore(focusRoot, toFocus);
if (toFocus instanceof AbstractButton) break;
if (toFocus == null) return;
// infinite loop protection; should not be necessary, but just in
// case all buttons are somehow unfocusable at the moment this
// method is called:
if (loop == null) loop = toFocus; else if (loop == toFocus) return;
}
if (toFocus.requestFocusInWindow()) {
if (toFocus instanceof JRadioButton) {
((JRadioButton)toFocus).setSelected(true);
}
}
}
}
Here is my example of JRadioButtons can be navigable using the arrow keys(UP and Down) and modified few codes for you.
public class JRadioButton extends JPanel {
private JRadioButton[] buttons;
public JRadioButtonTest(int row) {
ButtonGroup group = new ButtonGroup();
buttons = new JRadioButton[row];
for (int i = 0; i < buttons.length; i++) {
final int curRow = i;
buttons[i] = new JRadioButton("Option " + i);
buttons[i].addKeyListener(enter);
buttons[i].addKeyListener(new KeyAdapter() {
#Override
public void keyPressed(KeyEvent e) {
switch (e.getKeyCode()) {
case KeyEvent.VK_UP:
if (curRow > 0)
buttons[curRow - 1].requestFocus();
break;
case KeyEvent.VK_DOWN:
if (curRow < buttons.length - 1)
buttons[curRow + 1].requestFocus();
break;
default:
break;
}
}
});
group.add(buttons[i]);
add(buttons[i]);
}
}
private KeyListener enter = new KeyAdapter() {
#Override
public void keyTyped(KeyEvent e) {
if (e.getKeyChar() == KeyEvent.VK_ENTER) {
((JButton) e.getComponent()).doClick();
}
}
};
public static void main(String[] args) {
JFrame frame = new JFrame();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(new JRadioButton(3));
frame.pack();
frame.setVisible(true);
}
}
The core implement method is calling requestFocus() on the correct JRadioButton when an arrow key is called. Extra KeyListener for when the Enter key is pressed.
You can use this KeyListener to your program and add more key.
Good luck!
I need a listener that will constantly check if a static boolean value has been changed so that I can repaint a component on a frame. Can someone please help me I really don't know much about listeners and haven't worked with them much? Help will be greatly appreciated.
edit(more clarity): I have two separate classes in which on class is the "main frame" the second class is an extension of JLabel and implements MouseListner for a "clickable photo". The "main frame" creates instances of the photo and when the photo is clicked the "main frame" is supposed to paint on the panel a description of the photo. This is "main frame"
MenuBar menuBar;
static AnnotationVisual a;
Picture pic;
Picture pic2;
GalleryScreen(int rows, int columns){
this.setBorder(BorderFactory.createEmptyBorder(500,500,0,0));
pic = new Picture("pic1", "Z:/My Documents/Downloads/Ball.jpg", new Coordinate(0,0));
pic2 = new Picture("pic2", "Z:/My Documents/Downloads/hoop.jpg" , new Coordinate(1,0));
this.add(pic);
this.add(pic2);
a = new AnnotationVisual();
}
public void paintComponent(Graphics g){
super.paintComponent(g);
if(a.shouldAnnotate()){
FontMetrics size= g.getFontMetrics();
if(getWidth()>=(a.dispX()+size.stringWidth(a.annotationText()))){
g.setColor(Color.white);
g.fillRect(a.dispX()-3,a.dispY()-12,size.stringWidth(a.annotationText())+5,15);
g.setColor(Color.black);
g.drawRect(a.dispX()-3,a.dispY()-12,size.stringWidth(a.annotationText())+5,15);
g.drawString(a.annotationText(), a.dispX(), a.dispY());
}else{
String sub="";
int letters=0;
g.setColor(Color.white);
g.fillRect(a.dispX()-3,a.dispY()-12,getWidth(),15);
g.setColor(Color.black);
for(int i=0;i<a.annotationText().length();i++){
if(a.dispX()+letters+16<=getWidth()){
sub+=a.annotationText().substring(i,i+1);
letters=size.stringWidth(sub);
}else{
sub=sub+"...";
i=a.annotationText().length();
}
}
g.drawRect(a.dispX()-3,a.dispY()-12,size.stringWidth(sub)+3,15);
g.drawString(sub,a.dispX(),a.dispY());
}
}
}
public static AnnotationVisual getA()
{
return a;
}
This is "clickable photo"
import java.awt.Dimension;
import java.awt.Toolkit;
import java.awt.event.*;
import javax.swing.*;
public class Picture extends JLabel implements MouseListener
{
String myAnnotation;
String filePath;
Coordinate imageCoord;
private boolean wasDoubleClick;
private Timer timer;
EditAnnotation newEdit;
AnnotationVisual newVisual;
public Picture(String annotation, String filePath, Coordinate coord)
{
super(new ImageIcon(filePath));
this.addMouseListener(this);
myAnnotation=annotation;
this.filePath = filePath;
imageCoord = coord;
newEdit = new EditAnnotation(annotation);
newVisual = new AnnotationVisual();
}
public Picture(String filePath)
{
super(new ImageIcon(filePath));
this.addMouseListener(this);
this.filePath = filePath;
newEdit = new EditAnnotation();
newVisual = new AnnotationVisual();
}
public String getAnnotation()
{
return myAnnotation;
}
public AnnotationVisual getAnnotationVisual()
{
return newVisual;
}
public void setAnnotation(String annotation)
{
myAnnotation=annotation;
}
public Coordinate getCoordinate()
{
return imageCoord;
}
public void setCoordinate(Coordinate coord)
{
imageCoord = coord;
}
public Dimension getSize()
{
return new Dimension(super.getIcon().getIconWidth(), super.getIcon().getIconHeight());
}
public void mouseClicked(MouseEvent e)
{
final int scrLocX = (int)e.getLocationOnScreen().getX();
final int scrLocY = (int)e.getLocationOnScreen().getY();
if (e.getClickCount() == 2)
{
wasDoubleClick = true;
}
else if(e.getClickCount() == 1)
{
Integer timerinterval = (Integer) Toolkit.getDefaultToolkit().getDesktopProperty("awt.multiClickInterval");
timer = new Timer(timerinterval.intValue(), new ActionListener()
{
public void actionPerformed(ActionEvent evt)
{
if (wasDoubleClick)
{
GalleryScreen.getA().deleteAnnotation();
myAnnotation = newEdit.getAnnotation();
newEdit.show(myAnnotation);
wasDoubleClick = false;
}
else
{
GalleryScreen.getA().deleteAnnotation();
GalleryScreen.getA().showAnnotation(scrLocX, scrLocY , myAnnotation);
}
}
});
timer.setRepeats(false);
timer.start();
}
}
public void mouseEntered(MouseEvent e)
{
}
public void mouseExited(MouseEvent e)
{
}
public void mousePressed(MouseEvent e)
{
}
public void mouseReleased(MouseEvent e)
{
}
}
AnnotationVisual is the thing that supposed to pop up when single clicked
You're probably better off making the boolean private, and only allowing it to be changed through a setter method. The setter method, when called, should then repaint the component.
The point of listeners is to invert the logic. You don't constantly check if a value is changed. You notify the listener when you change the value.
So, instead of Foo.bar = 5, you invoke Foo.setBar(5), where in addition to the assignment, you call barListener.valueChanged(value)
As a sidenote - avoid storing state in static variables.
You don't set a listener on a field in Java, you set it on a property. While properties (according to the JavaBeans spec) can be fields, they're usually done as pairs of methods (one getter, one setter; the latter being not needed for read-only fields) as that lets you hook extra logic in to be called when the property is accessed. Such as firing a listener callback to say that the value has changed. (You could use a thread to monitor for that sort of thing, but that's really nasty and error-prone. Wasteful too.)
One thing to be aware of though: you don't know what thread the value will have been modified from. Take care when invoking back into Swing…