JTree select node by clicking anywhere on the row - java

I have code taken from here that would allow selection of a JTree Row by clicking anywhere on the row. it works fine in single row selection mode. However, I am not sure how to modify it in order to handle multiple row selections. how do I distinguish the case when user is make a multiple selection(eg. by holding down the shift or control button while making a left mouse click on a row)?
import java.awt.Rectangle;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import javax.swing.JFrame;
import javax.swing.JTree;
import javax.swing.SwingUtilities;
import javax.swing.tree.DefaultMutableTreeNode;
import javax.swing.tree.TreeNode;
#SuppressWarnings("serial")
public class NavTree extends JTree {
private boolean fWholeRowSelectionEnabled;
private MouseListener fRowSelectionListener;
final NavTree fThis;
public NavTree(TreeNode rootNode) {
super(rootNode);
fThis = this;
init();
}
public NavTree() {
fThis = this;
init();
}
private void init() {
//setCellRenderer(new NavTreeCellRenderer());
fRowSelectionListener = new MouseAdapter() {
public void mousePressed(MouseEvent e) {
if (SwingUtilities.isLeftMouseButton(e)) {
int closestRow = fThis.getClosestRowForLocation(
e.getX(), e.getY());
Rectangle closestRowBounds = fThis.getRowBounds(closestRow);
if(e.getY() >= closestRowBounds.getY() &&
e.getY() < closestRowBounds.getY() +
closestRowBounds.getHeight()) {
if(e.getX() > closestRowBounds.getX() &&
closestRow < fThis.getRowCount()){
fThis.setSelectionRow(closestRow);
}
} else
fThis.setSelectionRow(-1);
}
}
};
setWholeRowSelectionEnabled(true);
}
public void setWholeRowSelectionEnabled(boolean wholeRowSelectionEnabled) {
fWholeRowSelectionEnabled = wholeRowSelectionEnabled;
if (fWholeRowSelectionEnabled)
addMouseListener(fRowSelectionListener);
else
removeMouseListener(fRowSelectionListener);
}
public boolean isWholeRowSelectionEnabled() {
return fWholeRowSelectionEnabled;
}
public static void main(String[] args) {
JFrame frame = new JFrame();
DefaultMutableTreeNode root = new DefaultMutableTreeNode("Root");
root.add(new DefaultMutableTreeNode("Child 1"));
root.add(new DefaultMutableTreeNode("Child 2"));
root.add(new DefaultMutableTreeNode("Child 3"));
NavTree tree = new NavTree(root);
frame.add(tree);
frame.setSize(200, 300);
frame.setVisible(true);
}
}

Use the modifier key information of the MouseEvent. See MouseEvent#getModifiersEx for more information

PS: the listener registration contains a bug
public void setWholeRowSelectionEnabled(boolean wholeRowSelectionEnabled) {
fWholeRowSelectionEnabled = wholeRowSelectionEnabled;
if (fWholeRowSelectionEnabled)
addMouseListener(fRowSelectionListener);
else
removeMouseListener(fRowSelectionListener);
}
Setting the property wholeRowSelectionEnabled to true should register the listener only one time. Your code would add the listener again and again if the property is set to true multiple times. What I mean is that the property setter should be idempotent.
A quickfix could be to remove it first and add it if enabled
public void setWholeRowSelectionEnabled(boolean wholeRowSelectionEnabled) {
removeMouseListener(fRowSelectionListener);
fWholeRowSelectionEnabled = wholeRowSelectionEnabled;
if (fWholeRowSelectionEnabled)
addMouseListener(fRowSelectionListener);
}

Related

How to across JTextField to next JTextField by hit Enter

When select check box to set enable(false) on second text field and hit enter need to focus at third text field
if not select any check box then hit ENTER it can be focus on text field as flow.
I should add any code or how can I make it work
My GUI:
private void chk1ActionPerformed(java.awt.event.ActionEvent evt) {
// Set action perform for check box:
if(chk1.isSelected()){
p1.setEnabled(false);
} else{
p1.setEnabled(true);
}
}
private void chk2ActionPerformed(java.awt.event.ActionEvent evt) {
// Set action perform for check box:
if(chk2.isSelected()){
p2.setEnabled(false);
} else{
p2.setEnabled(true);
}
}
private void chk3ActionPerformed(java.awt.event.ActionEvent evt) {
// Set action perform for check box:
if(chk3.isSelected()){
p3.setEnabled(false);
} else{
p3.setEnabled(true);
}
}
private void p_numKeyPressed(java.awt.event.KeyEvent evt) {
// TODO add your handling code here:
if(evt.getKeyCode()==KeyEvent.VK_ENTER){
p1.requestFocus();
}
}
private void p1KeyPressed(java.awt.event.KeyEvent evt) {
// TODO add your handling code here:
if(evt.getKeyCode()==KeyEvent.VK_ENTER){
p2.requestFocus();
}
}
private void p2KeyPressed(java.awt.event.KeyEvent evt) {
// TODO add your handling code here:
if(evt.getKeyCode()==KeyEvent.VK_ENTER){
p3.requestFocus();
}
}
I think what you are looking for is the Focus Subsystem. This is responsible for the order in which components are getting focus when you press a focus traversal key. For example you can define what is the order of the components to get focus, and also add the check to transfer focus only to enabled components. What you also seem to want to do is to add the ENTER key as a focus traversal key (ie when pressing it, the focus will be transfered to another component - according to the current focus traversal policy). All this can be done via the focus subsystem.
The following code seems to do what you are asking, by utilizing the focus subsystem:
import java.awt.Component;
import java.awt.Container;
import java.awt.DefaultFocusTraversalPolicy;
import java.awt.GridLayout;
import java.awt.KeyboardFocusManager;
import java.awt.event.KeyEvent;
import java.util.HashSet;
import java.util.Set;
import javax.swing.JButton;
import javax.swing.JCheckBox;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JTextField;
import javax.swing.KeyStroke;
import javax.swing.SwingUtilities;
public class FocusHopSystem {
public static class AbsoluteFocusTraversalPolicy extends DefaultFocusTraversalPolicy {
private final Component[] comps;
public AbsoluteFocusTraversalPolicy(final Component... comps) {
this.comps = comps.clone();
}
private int indexOf(final Component comp) {
for (int i = 0; i < comps.length; ++i)
if (comps[i] == comp)
return i;
return -1;
}
#Override
public Component getComponentAfter(final Container aContainer, final Component aComponent) {
final int aIndex = indexOf(aComponent);
if (aIndex < 0)
return null;
for (int i = 1; i < comps.length; ++i) {
final Component after = comps[(aIndex + i) % comps.length];
if (after != null && after.isDisplayable() && after.isVisible() && after.isEnabled())
return after;
}
return null;
}
#Override
public Component getComponentBefore(final Container aContainer, final Component aComponent) {
final int aIndex = indexOf(aComponent);
if (aIndex < 0)
return null;
for (int i = 1; i < comps.length; ++i) {
final Component before = comps[(aIndex - i + comps.length) % comps.length];
if (before != null && before.isDisplayable() && before.isVisible() && before.isEnabled())
return before;
}
return null;
}
#Override
public Component getFirstComponent(final Container aContainer) {
for (int i = 0; i < comps.length; ++i) {
final Component first = comps[i];
if (first != null && first.isDisplayable() && first.isVisible() && first.isEnabled())
return first;
}
return null;
}
#Override
public Component getLastComponent(final Container aContainer) {
for (int i = comps.length - 1; i >= 0; --i) {
final Component last = comps[i];
if (last != null && last.isDisplayable() && last.isVisible() && last.isEnabled())
return last;
}
return null;
}
#Override
public Component getDefaultComponent(final Container aContainer) {
return getFirstComponent(aContainer);
}
}
private static void createAndShowGUI() {
final String text = "0";
final int width = 12;
final JTextField tf1 = new JTextField(text, width),
tf2 = new JTextField(text, width),
tf3 = new JTextField(text, width),
tf4 = new JTextField(text, width);
final JCheckBox ck2 = new JCheckBox("jTextField2", true),
ck3 = new JCheckBox("jTextField3", true),
ck4 = new JCheckBox("jTextField4", true);
final JButton save = new JButton("Save");
ck2.addActionListener(e -> tf2.setEnabled(ck2.isSelected()));
ck3.addActionListener(e -> tf3.setEnabled(ck3.isSelected()));
ck4.addActionListener(e -> tf4.setEnabled(ck4.isSelected()));
final JPanel frameContents = new JPanel(new GridLayout(0, 5, 5, 5));
//First row:
frameContents.add(new JPanel());
frameContents.add(ck2);
frameContents.add(ck3);
frameContents.add(ck4);
frameContents.add(new JPanel());
//Second row:
frameContents.add(tf1);
frameContents.add(tf2);
frameContents.add(tf3);
frameContents.add(tf4);
frameContents.add(save);
//Make the other components unfocusable:
for (final Component comp: new Component[]{ck2, ck3, ck4, save})
comp.setFocusable(false);
//Make frameContents the focus cycle root:
frameContents.setFocusCycleRoot(true);
//Install our FocusTraversalPolicy to the cycle root:
frameContents.setFocusTraversalPolicy(new AbsoluteFocusTraversalPolicy(tf1, tf2, tf3, tf4));
//Add KeyEvent.VK_ENTER key to the focus traversal keys of the frameContents:
final Set forwardKeys = frameContents.getFocusTraversalKeys(KeyboardFocusManager.FORWARD_TRAVERSAL_KEYS);
final Set newForwardKeys = new HashSet(forwardKeys);
newForwardKeys.add(KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, 0));
frameContents.setFocusTraversalKeys(KeyboardFocusManager.FORWARD_TRAVERSAL_KEYS, newForwardKeys);
final JFrame frame = new JFrame("Focus traversal test.");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.getContentPane().add(frameContents);
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
//Start focus on the first text field:
tf1.requestFocusInWindow();
}
public static void main(final String[] args) {
SwingUtilities.invokeLater(FocusHopSystem::createAndShowGUI);
}
}
In the above example, I am adding the key ENTER as an alternative focus traversal key, aside from TAB.
Edit 1: It's even simpler...
As I found out, it's even simpler than the first sample code, and that's because the default focus traversal policy does what you are asking, ie allows only focusable-and-enabled components to gain focus and in the desired order. So we only need to make the unneeded components to non-focusable (ie the check-boxes and the button) and leave enabled the text-fields. Just add/set the ENTER key as a focus traversal key and you are good to go:
import java.awt.Component;
import java.awt.GridLayout;
import java.awt.KeyboardFocusManager;
import java.awt.event.KeyEvent;
import java.util.HashSet;
import java.util.Set;
import javax.swing.JButton;
import javax.swing.JCheckBox;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JTextField;
import javax.swing.KeyStroke;
import javax.swing.SwingUtilities;
public class FocusHopSystem2 {
private static void createAndShowGUI() {
final String text = "0";
final int width = 12;
final JTextField tf1 = new JTextField(text, width),
tf2 = new JTextField(text, width),
tf3 = new JTextField(text, width),
tf4 = new JTextField(text, width);
final JCheckBox ck2 = new JCheckBox("jTextField2", true),
ck3 = new JCheckBox("jTextField3", true),
ck4 = new JCheckBox("jTextField4", true);
final JButton save = new JButton("Save");
ck2.addActionListener(e -> tf2.setEnabled(ck2.isSelected()));
ck3.addActionListener(e -> tf3.setEnabled(ck3.isSelected()));
ck4.addActionListener(e -> tf4.setEnabled(ck4.isSelected()));
final JPanel frameContents = new JPanel(new GridLayout(0, 5, 5, 5));
//First row:
frameContents.add(new JPanel());
frameContents.add(ck2);
frameContents.add(ck3);
frameContents.add(ck4);
frameContents.add(new JPanel());
//Second row:
frameContents.add(tf1);
frameContents.add(tf2);
frameContents.add(tf3);
frameContents.add(tf4);
frameContents.add(save);
//Make the other components unfocusable:
for (final Component comp: new Component[]{ck2, ck3, ck4, save})
comp.setFocusable(false);
//Make frameContents the focus cycle root:
frameContents.setFocusCycleRoot(true);
////Install the DefaultFocusTraversalPolicy to the cycle root:
//frameContents.setFocusTraversalPolicy(new DefaultFocusTraversalPolicy()); //This is not necessary in this case.
//Add KeyEvent.VK_ENTER key to the focus traversal keys of the frameContents:
final Set forwardKeys = frameContents.getFocusTraversalKeys(KeyboardFocusManager.FORWARD_TRAVERSAL_KEYS);
final Set newForwardKeys = new HashSet(forwardKeys);
newForwardKeys.add(KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, 0));
frameContents.setFocusTraversalKeys(KeyboardFocusManager.FORWARD_TRAVERSAL_KEYS, newForwardKeys);
final JFrame frame = new JFrame("Focus traversal test.");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.getContentPane().add(frameContents);
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
//Start focus on the first text field:
tf1.requestFocusInWindow();
}
public static void main(final String[] args) {
SwingUtilities.invokeLater(FocusHopSystem2::createAndShowGUI);
}
}
You may also read The AWT Focus Subsystem.
This is the way the Tab key works by default. When you press Tab focus will move to the next enabled component.
If you want to duplicate this functionality for the Enter key then first you can create a custom Action
Action transfer = new AbstractAction()
{
#Override
public void actionPerformed(ActionEvent e)
{
Component c = (Component)e.getSource();
c.transferFocus();
}
};
By default the "Enter" key will invoke the ActionListener added to the text field. So, now you can add this Action to specific text fields:
...
textField1.addActionListener( transfer );
textField2.addActionListener( transfer );
Or you can replace the default Enter Action of all the text fields by adding your custom Action to the ActionMap:
...
ActionMap am = (ActionMap)UIManager.get("TextField.actionMap");
am.put("notify-field-accept", transfer);

Set JButton to the location of another JButton

I'm trying to move a JButton to the location of another one but the button I want to move moves to a wrong point. My idea is that this happens because I use multiple JPanels. I tried: getLocationOnScreen, getBounds and getLocation, but none of them worked, how to solve it? When an user selects a card on the table or from a player by clicking this card the the target is set, the sender is set by clicking a card from the top panel. playerCardSpotTargetand playerCardSpotSender are both of type Card. When I try to move the for example eight of diamonds this card moves to a point behind the eight and nine of clubs.
Code:
This events belong to the blue cards on the table and the cards for the players(I have to change the name of the event, I know).
private void PlayerOneMouseClicked(java.awt.event.MouseEvent evt){
playerCardSpotTarget=(Card)evt.getSource();
if(playerCardSpotTarget.isBorderPainted()){
playerCardSpotTarget.setBorderPainted(false);
}
else{
playerCardSpotTarget.setBorderPainted(true);
}
}
This event belongs to the cards in the top panel.
private void MouseClicked(java.awt.event.MouseEvent evt) {
playerCardSpotSender=(Card)evt.getSource();
System.out.println(playerCardSpotSender.suit+" "+playerCardSpotSender.kind);
if (playerCardSpotTarget != null && playerCardSpotTarget.isBorderPainted()) {
playerCardSpotSender.setLocation(playerCardSpotTarget.getLocation());
System.out.println(playerCardSpotTarget.getLocationOnScreen());
System.out.println(playerCardSpotSender.getLocationOnScreen());
}
}
Layout for the center panel in the JFrame (BorderLayout.CENTER)
JPanel centerPanelNorth;
JPanel centerPanelCenter;
JPanel centerPanelEast;
JPanel centerPanelSouth;
JPanel centerPanelWest;
JLabel tablePicture;
JPanel centerPanel;
centerPanel=new JPanel(new BorderLayout());
tablePicture = new JLabel(new ImageIcon(this.getClass().getResource(Constants.POKERTABLE_ICON)));
centerPanelNorth=new JPanel();
centerPanelEast=new JPanel();
centerPanelSouth=new JPanel();
centerPanelWest=new JPanel();
centerPanelCenter=new JPanel();
centerPanel.add(centerPanelCenter,BorderLayout.CENTER);
centerPanelCenter.add(tablePicture);
//add
tablePicture.add(boardCard1);
tablePicture.add(boardCard2);
tablePicture.add(boardCard3);
tablePicture.setLayout(new GridBagLayout());
//PLAYER NORTH
centerPanel.add(centerPanelNorth,BorderLayout.NORTH);
centerPanelNorth.add(playerOneCardOne);
centerPanelNorth.add(playerOneCardTwo);
//PLAYER EAST
centerPanel.add(centerPanelEast,BorderLayout.EAST);
centerPanelEast.setLayout(new BoxLayout(centerPanelEast,BoxLayout.X_AXIS));
centerPanelEast.add(playerTwoCardOne);
centerPanelEast.add(playerTwoCardTwo);
//PLAYER SOUTH
centerPanel.add(centerPanelSouth,BorderLayout.SOUTH);
centerPanelSouth.add(playerThreeCardOne);
centerPanelSouth.add(playerThreeCardTwo);
//PLAYER WEST
centerPanel.add(centerPanelWest,BorderLayout.WEST);
centerPanelWest.setLayout(new BoxLayout(centerPanelWest,BoxLayout.X_AXIS));
centerPanelWest.add(playerFourCardOne);
centerPanelWest.add(playerFourCardTwo);
Card.java
public class Card extends JButton{
int suit;
int kind;
boolean known;
String iconPath;
Integer boardPosition;
}
Animating the button movement isn't actually the hardest problem, the hardest problem is trying to move the data about in away in which you can manage it and how to connect the source component with the target...
To start with, you need a means by which you can move a component across container boundaries. While there are probably a few ways to do this, the simplest is to probably use the glass pane of the frame
public class AnimationPane extends JPanel {
public AnimationPane() {
setOpaque(false);
setLayout(null);
}
}
This is nothing special, it's just a JPanel which is transparent and has no layout manager, normally, not recommended, but in the case, we're going to take control..
Now, we need some way to animate the movement...
public enum Animator {
INSTANCE;
private List<IAnimatable> animatables;
private Timer timer;
private Animator() {
animatables = new ArrayList<>(25);
timer = new Timer(40, new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
IAnimatable[] anins = animatables.toArray(new IAnimatable[animatables.size()]);
for (IAnimatable animatable : anins) {
animatable.update();
}
}
});
timer.start();
}
public void addAnimatable(IAnimatable animatable) {
animatables.add(animatable);
}
public void removeAnimatable(IAnimatable animatable) {
animatables.remove(animatable);
}
}
public interface IAnimatable {
public void update();
}
public interface IMoveAnimatable extends IAnimatable{
public JComponent getSourceComponent();
public IImportable getImportable();
}
So the Animator is the core "engine", it's basically a Swing Timer which simply calls update on any IAnimatables it might be managing. The intention with this approach is you can have a number of animations running, but it won't degrade the system (greatly) as you only have a single update/timer point.
Now, normally I'd just use something like the Timing Framework or the Trident Framework or even the Universal Tween Engine
The IAnimatable interfaces just define the basic contracts that provide functionality for the animation.
We need to define some kind of contract the defines objects which can take part in the animation process and receive information, or the "target"
public interface IImportable {
public JComponent getView();
public void importValue(String value);
}
public abstract class AbstractImportable extends JPanel implements IImportable {
#Override
public JComponent getView() {
return this;
}
}
Now it occurs to me that we could tap into the pre-existing Transferable API, which would allow you to also implement drag-n-drop (and even copy/cut and paste), this would be used to define a lookup mechanism where you match a given data type with potential targets based on the DataFlavor ... but I'll leave you to investigate how that might work...
The core mechanism basically removes the source component from it's current container, adds it to the AnimationPane, moves the source component across the AnimationPane and then imports the data into the target...
The problem is, you need to translate the location of component from it's current context to the AnimationPane.
A components location is relative to it's parents context. It's relatively easy to do with SwingUtilities.convertPoint(Component, Point, Component)
We calculate the origin point of the source component and the target point, relative to the AnimationPane. We then, on each call to update, calculate the progress of the animation. Instead of using a "delta" movement, we calculate the different between the time we started and a predefined duration (1 second in this case), this generally produces a more flexible animation
public class DefaultAnimatable implements IMoveAnimatable {
public static final double PLAY_TIME = 1000d;
private Long startTime;
private JComponent sourceComponent;
private IImportable importable;
private JComponent animationSurface;
private Point originPoint, destinationPoint;
private String value;
public DefaultAnimatable(JComponent animationSurface, JComponent sourceComponent, IImportable importable, String value) {
this.sourceComponent = sourceComponent;
this.importable = importable;
this.animationSurface = animationSurface;
this.value = value;
}
public String getValue() {
return value;
}
public JComponent getAnimationSurface() {
return animationSurface;
}
#Override
public JComponent getSourceComponent() {
return sourceComponent;
}
#Override
public IImportable getImportable() {
return importable;
}
#Override
public void update() {
if (startTime == null) {
System.out.println("Start");
IImportable importable = getImportable();
JComponent target = importable.getView();
originPoint = SwingUtilities.convertPoint(getSourceComponent().getParent(), getSourceComponent().getLocation(), getAnimationSurface());
destinationPoint = SwingUtilities.convertPoint(target.getParent(), target.getLocation(), getAnimationSurface());
destinationPoint.x = destinationPoint.x + ((target.getWidth() - getSourceComponent().getWidth()) / 2);
destinationPoint.y = destinationPoint.y + ((target.getHeight() - getSourceComponent().getHeight()) / 2);
Container parent = getSourceComponent().getParent();
getAnimationSurface().add(getSourceComponent());
getSourceComponent().setLocation(originPoint);
parent.invalidate();
parent.validate();
parent.repaint();
startTime = System.currentTimeMillis();
}
long duration = System.currentTimeMillis() - startTime;
double progress = Math.min(duration / PLAY_TIME, 1d);
Point location = new Point();
location.x = progress(originPoint.x, destinationPoint.x, progress);
location.y = progress(originPoint.y, destinationPoint.y, progress);
getSourceComponent().setLocation(location);
getAnimationSurface().repaint();
if (progress == 1d) {
getAnimationSurface().remove(getSourceComponent());
Animator.INSTANCE.removeAnimatable(this);
animationCompleted();
}
}
public int progress(int startValue, int endValue, double fraction) {
int value = 0;
int distance = endValue - startValue;
value = (int) Math.round((double) distance * fraction);
value += startValue;
return value;
}
protected void animationCompleted() {
getImportable().importValue(getValue());
}
}
Okay, now this produces a linear animation, which is pretty boring, now if you have plenty of time, you could create an easement like this or just use one of the animation frameworks...
Now, we need to put it together...
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Container;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.GridBagLayout;
import java.awt.GridLayout;
import java.awt.Point;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.ArrayList;
import java.util.List;
import javax.swing.JButton;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
import javax.swing.Timer;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
import javax.swing.border.LineBorder;
public class AnimationTest {
public static void main(String[] args) {
new AnimationTest();
}
public AnimationTest() {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
ex.printStackTrace();
}
AnimationPane animationPane = new AnimationPane();
LeftPane leftPane = new LeftPane(animationPane);
RightPane rightPane = new RightPane();
leftPane.setImportabale(rightPane);
rightPane.setImportabale(leftPane);
JFrame frame = new JFrame("Testing");
frame.setLayout(new GridLayout(1, 2));
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(leftPane, BorderLayout.WEST);
frame.add(rightPane, BorderLayout.WEST);
frame.setGlassPane(animationPane);
animationPane.setVisible(true);
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
public class RightPane extends AbstractImportable {
private IImportable source;
private JButton imported;
private String importedValue;
public RightPane() {
setLayout(new GridBagLayout());
setBorder(new LineBorder(Color.DARK_GRAY));
}
public void setImportabale(IImportable source) {
this.source = source;
}
#Override
public void importValue(String value) {
if (imported != null) {
// May re-animate the movement back...
remove(imported);
}
importedValue = value;
imported = new JButton(">> " + value + "<<");
add(imported);
revalidate();
repaint();
}
#Override
public Dimension getPreferredSize() {
return new Dimension(200, 200);
}
}
public class LeftPane extends AbstractImportable {
private IImportable importable;
public LeftPane(AnimationPane animationPane) {
setLayout(new GridBagLayout());
JButton btn = new JButton("Lefty");
btn.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
DefaultAnimatable animatable = new DefaultAnimatable(animationPane, btn, importable, "Lefty");
Animator.INSTANCE.addAnimatable(animatable);
}
});
add(btn);
setBorder(new LineBorder(Color.DARK_GRAY));
}
public void setImportabale(IImportable target) {
this.importable = target;
}
#Override
public void importValue(String value) {
}
#Override
public Dimension getPreferredSize() {
return new Dimension(200, 200);
}
}
}
Maybe use mousePressed(),when you move the card,you press it until the target.And in the process, you get the information about JButton.getLocation() by the event,and than you need to solve the collision problem between two cards.So it's good!Of course, this is my advice ,you should have better idea!

Why does a fireContentsChanged call of a JList freezes the entire GUI?

I had some problems with freezing SWING GUIs when re-rendering a JTable with a custom cell renderer in Java. So I asked the question "Why does a JTable view update block the entire GUI?". The answers pointed to the fact, that a JList without modifying JTable and overwriting doLayout might be a better choice. So I implemented the example with a JList and ran into the same problem: while generating data, everything works fine and the progress bar moves. But when the view is updated, the program freezes and the progress bar stops moving.
Please note, that the sleep statement is there only to let the generation take a longer, more realistic time (reading thousands of data sets via JDBC and create objects out of them takes a lot time). One could remove it and increment the number of generated items. But you can clearly see, that the HTML rendering is quite slow. But I need this colors and the two lines (if not necessarily so many different colors).
So could you please tell me, where my mistake is? I think, that EDT and other work is separated through separate threads and I cannot see any mistke.
Update: I looked around at SO and found this question "https://stackoverflow.com/a/20813122/2429611". There is said:
The more interesting question would be how to avoid that UI blocking, but I don't think that's possible with just Swing, you'll have to implement some lazy loading, or rendering in batches.
This would mean, that I cannot solve my problem. Is this correct?
package example;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Component;
import java.awt.FlowLayout;
import java.awt.Graphics;
import java.awt.event.ActionEvent;
import java.security.SecureRandom;
import java.util.ArrayList;
import java.util.List;
import javax.swing.AbstractAction;
import javax.swing.AbstractListModel;
import javax.swing.Box;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JList;
import javax.swing.JPanel;
import javax.swing.JProgressBar;
import javax.swing.JScrollPane;
import javax.swing.ListCellRenderer;
import javax.swing.SwingUtilities;
public class ListExample extends AbstractListModel {
static List<DemoObject> internalList = new ArrayList<>();
#Override
public int getSize() {
return internalList.size();
}
#Override
public DemoObject getElementAt(int index) {
return internalList.get(index);
}
public void fireContentsChanged() {
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
fireContentsChanged(this, 0, -1);
}
});
}
static class MyCellRenderer extends JLabel implements ListCellRenderer<ListExample.DemoObject> {
public MyCellRenderer() {
setOpaque(true);
}
#Override
public Component getListCellRendererComponent(JList<? extends ListExample.DemoObject> list,
ListExample.DemoObject value,
int index,
boolean isSelected,
boolean cellHasFocus) {
setText("<html>" + value.toString()
+ "<br/>"
+ "<span bgcolor=\"#ff0000\">Line 2; Color = " + value.c + "</span>");
Color background;
Color foreground;
// check if this cell represents the current DnD drop location
JList.DropLocation dropLocation = list.getDropLocation();
if (dropLocation != null
&& !dropLocation.isInsert()
&& dropLocation.getIndex() == index) {
background = Color.BLUE;
foreground = Color.WHITE;
// check if this cell is selected
} else if (isSelected) {
background = Color.RED;
foreground = Color.WHITE;
// unselected, and not the DnD drop location
} else {
background = value.c; //Color.WHITE;
foreground = Color.BLACK;
};
setBackground(background);
setForeground(foreground);
return this;
}
}
static class DemoObject {
String str;
Color c;
public DemoObject(String str, int color) {
this.str = str;
this.c = new Color(color);
}
#Override
public String toString() {
return str;
}
}
static JPanel overlay;
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
JFrame frame = new JFrame("Example");
frame.setLayout(new BorderLayout(4, 4));
// Add JTable
final ListExample model = new ListExample();
JList list = new JList(model);
list.setCellRenderer(new MyCellRenderer());
frame.add(new JScrollPane(list), BorderLayout.CENTER);
// Add button
Box hBox = Box.createHorizontalBox();
hBox.add(new JButton(new AbstractAction("Load data") {
#Override
public void actionPerformed(ActionEvent e) {
new Thread(new Runnable() {
#Override
public void run() {
overlay.setVisible(true);
internalList.clear();
System.out.println("Generating data ...");
SecureRandom sr = new SecureRandom();
for (int i = 0; i < 10000; i++) {
internalList.add(
new DemoObject(
"String: " + i + " (" + sr.nextFloat() + ")",
sr.nextInt(0xffffff)
)
);
// To create the illusion, that data are
// fetched via JDBC (which takes a little
// while), this sleep statement is embedded
// here. In a real world scenario, this wait
// time is caused by talking to the database
// via network
if (i%10 == 0) {
try {
Thread.sleep(1);
} catch (Exception e) {
}
}
}
System.out.println("Updating view ...");
model.fireContentsChanged();
overlay.setVisible(false);
System.out.println("Finished.");
}
}).start();
}
}));
hBox.add(Box.createHorizontalGlue());
frame.add(hBox, BorderLayout.NORTH);
// Create loading overlay
overlay = new JPanel(new FlowLayout(FlowLayout.CENTER)) {
#Override
protected void paintComponent(Graphics g) {
g.setColor(new Color(0, 0, 0, 125));
g.fillRect(0, 0, getWidth(), getHeight());
super.paintComponent(g);
}
};
overlay.setOpaque(false);
overlay.setBackground(new Color(0, 0, 0, 125));
JProgressBar bar = new JProgressBar();
bar.setIndeterminate(true);
overlay.add(bar);
frame.setGlassPane(overlay);
frame.getGlassPane().setVisible(false);
// Create frame
frame.setSize(600, 400);
frame.setVisible(true);
}
});
}
}
there are three problems (recreating, reseting the model, and custom Renderer stoped to works)
JList (JComboBox hasn't) has an issue by removing more than 999 items, you have to set a new model to JList
see important for ComboBoxModel extends AbstractListModel implements MutableComboBoxModel for setElementAt(to hold current selection)
usage of public void fireContentsChanged() { is wrong, don't see reason to use this way, again is about to replace current, reset the model
. e.g. with success atr runtime and by recrusive testing for/if event (fired)
setModel(new DefaultListModel(list.toArray()) {
protected void fireContentsChanged(Object obj, int i, int j) {
if (!isFired)
super.fireContentsChanged(obj, i, j);
}
});

Different behavior of JDialog when is created by program and by user action

I have 3 clasess : Loader, MyDialog and TEST(with main method). (for code see below)
Everything I want to achieve is create simple dialog with JLabel and JProgressBar, which will notify user about how much time remains to show MyDialog. MyDialog is Jdialog with time consuming operation in constructor (loading data from database etc.).
In code below is model situation. When "MyDialog" is created by main (constant BY_USER is false), everything working exactly i want to. But when i make dialog with button , and instance of MyDialog is created after button press (constant BY_USER is true), Loader is blank white form. It looks like is not completed.
Loader is extending Thread, so i suppose that problem will be in threading (event dispatch thread)? I dont know, what is wrong and how fix it. Please help.
Thanks and sorry for my English.
CLASS TEST :
package test;
import java.awt.BorderLayout;
import java.awt.Dimension;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.WindowConstants;
public class TEST {
public static final boolean BY_USER = false;
public static void main(String[] args) {
if (BY_USER) {
JFrame mainDialog = new JFrame("Main");
JButton show = new JButton("Show MyDialog");
show.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
MyDialog dialog = new MyDialog();
}
});
mainDialog.add(show);
mainDialog.setLocationRelativeTo(null);
mainDialog.setMinimumSize(new Dimension(160, 80));
mainDialog.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
mainDialog.setVisible(true);
} else {
MyDialog dialog = new MyDialog();
}
}
}
CLASS MyDialog :
package test;
import javax.swing.JFrame;
import javax.swing.WindowConstants;
public class MyDialog extends JFrame{
public MyDialog() {
super();
// making loader with title, first message and count of steps of operation
Loader loader = new Loader("Loader", "First showed message", 100);
loader.ShowLoader();
// time-consuming operation (loading data from database etc.).
// for clarity replaced with for statement
int j=0;
for(int i=0; i<Integer.MAX_VALUE; i++)
{
j++;
if(j==Integer.MAX_VALUE/100){
// updating loader message and progress bar value
loader.NewAction(Integer.MAX_VALUE - i+"");
j=0;
}
}
// closing loader
loader.DestroyLoader();
this.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
this.setSize(300, 300);
this.setLocationRelativeTo(null);
this.setVisible(true);
}
}
CLASS Loader:
package test;
import java.awt.BorderLayout;
import java.awt.Dialog;
import java.awt.Dimension;
import javax.swing.JDialog;
import javax.swing.JLabel;
import javax.swing.JProgressBar;
import javax.swing.SwingConstants;
public class Loader extends Thread{
private JDialog dialog;
private JLabel message = new JLabel("", SwingConstants.CENTER);
private JProgressBar progressBar = new JProgressBar(0, 100);
private String newMessage;
private double percentForStep;
private int remainingSteps;
public Loader(String taskName, String firstMessage, int steps) {
this.remainingSteps = steps-1;
dialog = new JDialog((Dialog) null, taskName);
dialog.setLayout(new BorderLayout(15, 15));
dialog.add(message, BorderLayout.CENTER);
dialog.add(progressBar, BorderLayout.SOUTH);
message.setText(firstMessage);
percentForStep = 100 / steps;
}
public void ShowLoader()
{
dialog.setMinimumSize(new Dimension(400,120));
dialog.setLocationRelativeTo(null);
dialog.setVisible(true);
this.start();
}
public void DestroyLoader(){
dialog.dispose();
this.interrupt();
}
public void NewAction(String newMessage){
this.newMessage = newMessage;
this.remainingSteps--;
Lock.changed = true;
}
public int RemainingStepsCount()
{
return remainingSteps;
}
#Override
#SuppressWarnings({"CallToThreadYield", "SleepWhileInLoop"})
public void run() {
do{
synchronized (Lock.class) {
if (Lock.changed) {
Lock.changed = false;
this.message.setText(newMessage);
this.progressBar.setValue((int)(100-(remainingSteps*percentForStep)));
dialog.repaint();
}
dialog.repaint();
}
}while(true);
}
}
class Lock{
static boolean changed = false;
}
Look to SwingWorker and his use; I think it can help you to solve the problem.

Java: prevent beep sound on key press

I have a Java program which is launched from a browser, running with various java.awt.Button buttons. When clicked, it's fine. But when a key is pressed that corresponds to that button, Windows makes the "Default Beep" sound.
The buttons are created like this:
import java.awt.GridLayout;
import java.awt.BorderLayout;
import java.io.*;
import java.awt.Panel;
import java.awt.Button;
import java.awt.Font;
import java.awt.event.*;
import java.lang.Integer;
public class buttonPne extends Panel implements ActionListener, constants {
private pClient parent = null;
private labelPne buttonLabel[] = new labelPne[8];
private Panel buttonPanel[] = new Panel[8];
public Button theButtons[] = new Button[8];
private boolean buttonStatus[] = new boolean[8];
public buttonPne(pClient c) {
parent = c;
this.setLayout(new GridLayout(1, 8, 1, 0));
for (int i = 0; i < 8; i++) {
buttonLabel[i] = new labelPne(parent);
buttonLabel[i].setSize(9, 9);
theButtons[i] = new Button();
theButtons[i].setFont(ButtonFont);
theButtons[i].setActionCommand(Integer.toString(i));
theButtons[i].addActionListener(this);
theButtons[i].addKeyListener(parent);
buttonPanel[i] = new Panel();
buttonPanel[i].setBackground(backgroundColor);
buttonPanel[i].setLayout(new BorderLayout());
buttonPanel[i].add("North", theButtons[i]);
buttonPanel[i].add("South", buttonLabel[i]);
this.add(buttonPanel[i]);
}
Deactivate();
}
The key presses are handled like this:
import java.awt.event.KeyEvent;
...
public class pClient extends Applet implements KeyListener, constants {
...
f.addKeyListener(this);
...
public void keyPressed(KeyEvent evt) {
int theKey;
theKey = evt.getKeyCode();
switch(theKey) {
case KeyEvent.VK_1:
case KeyEvent.VK_F1:
buttons.DoButton(0);
break;
...
public void DoButton(int theNumber) {
if (buttonStatus[theNumber]) {
if (parent.pollSendFlag(BUTTONS)) {
parent.chat.takeFocus();
parent.compass.Deactivate();
Deactivate();
}
}
return;
}
How can I stop the default beep from happening on key presses?
Use Swing components instead of AWT components.

Categories