I have a JList where I want to be able to navigate to different cells, type text, then press "enter" to commit the change. The problem is when I change a few cells and then navigate via the up and down keys and try typing in the currently selected cell, the selection somehow jumps to a previously filled in cell. I've pared down my code to what I think is the minimum to replicate the problem:
package listtest;
import java.awt.Dimension;
import java.awt.Rectangle;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import javax.swing.DefaultListModel;
import javax.swing.JDialog;
import javax.swing.JList;
import javax.swing.JPanel;
import javax.swing.JPopupMenu;
import javax.swing.JTextField;
import javax.swing.ListSelectionModel;
import javax.swing.UIManager;
import javax.swing.border.EmptyBorder;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener;
public class JListSelection{
static int selectedIndex;
static JList<String> serials;
static DefaultListModel<String> model;
static private JPopupMenu editPopup;
static private JTextField editTextField;
public static void main(String[] args) {
JPanel pan = new JPanel(null);
selectedIndex = 0;
serials = new JList<String>();
model = new DefaultListModel<String>();
serials = new JList<String>(model);
for(int i = 0; i < 19; i++) {
model.addElement(" ");
}
serials.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
serials.addListSelectionListener(new ListSelectionListener() {
#Override
public void valueChanged(ListSelectionEvent e) {
selectedIndex = serials.getSelectedIndex();
System.out.println("in listener: " + serials.getSelectedIndex());
}
});
serials.addKeyListener(new KeyAdapter() {
public void keyPressed(KeyEvent e) {
System.out.println("In keypressed: " + e.getKeyCode() + " " + serials.getSelectedIndex());
}
public void keyReleased(KeyEvent e) {
int code = e.getKeyCode();
switch( code ){
case KeyEvent.VK_UP:
System.out.println("UP " + serials.getSelectedIndex());
break;
case KeyEvent.VK_DOWN:
System.out.println("DOWN " + serials.getSelectedIndex());
break;
}
if(e.getKeyCode() >= KeyEvent.VK_A && e.getKeyCode() <= KeyEvent.VK_Z
|| e.getKeyCode() >= KeyEvent.VK_0 && e.getKeyCode() <= KeyEvent.VK_9) {
System.out.println(selectedIndex + " " + serials.getSelectedIndex());
Rectangle r = serials.getCellBounds(selectedIndex, selectedIndex);
if (editPopup == null) {
createEditPopup();
}
editPopup.setPreferredSize(new Dimension(r.width, r.height));
editPopup.show(serials, r.x, r.y);
editTextField.setText(
serials.getSelectedValue().toString().equals(" ") ?
e.getKeyChar()+"" : serials.getSelectedValue().toString());
editTextField.requestFocusInWindow();
}
}
});
serials.setBounds(0, 0, 200, 800);
pan.add(serials);
JDialog di = new JDialog();
di.setContentPane(pan);
di.pack();
di.setLocationRelativeTo(null);
di.setSize(300, 400);
di.setVisible(true);
}
private static void createEditPopup(){
editTextField = new JTextField();
editTextField.setBorder(
UIManager.getBorder("List.focusCellHighlightBorder"));
editTextField.addActionListener(new ActionListener(){
public void actionPerformed(ActionEvent e){
DefaultListModel<String> model = (DefaultListModel<String>)
serials.getModel();
model.set(selectedIndex, editTextField.getText());
editPopup.setVisible(false);
}
});
editPopup = new JPopupMenu();
editPopup.setBorder(new EmptyBorder(0, 0, 0, 0));
editPopup.add(editTextField);
}
}
If you run the code and start by selecting a cell and typing something then pressing enter it works how it should. If you then use the down arrow keys to type a few other cells at some point the selection will jump to a previously selected cell and I can't figure out any way to see what is causing this jump, let alone prevent it.
You have encountered a feature of JList. What is happening is as you type each character, the JList is attempting to scroll to the entry that begins with the letter you typed.
So, if you entered "Joe" and "Dave" and then tried to type "Jerry" the JList will select the "Joe" row.
See: Deactivate selection by letter in JList
Following the technique from that question:
// Add these lines just before your first "addKeyListener"
for (KeyListener lsnr : serials.getKeyListeners()) {
if(lsnr.getClass().getSimpleName().equals("Handler")){
serials.removeKeyListener(lsnr);
}
}
These lines go before line 47 in your code example.
It's a bit of a rough way to kill the autoselect nature of JList.
The listener we are removing is added to the JList by BasicListUI in the installListeners() method, as list.addKeyListener(getHandler()). Consult the source for BasicListUI.
The class returned by getHandler() is a catch-all listener that implements several different listener interfaces, including KeyListener, and this is where the autoselect behavior is implemented.
The odd usage of getSimpleName() to determine class name is necessary because Handler is a private class in BasicListUI, so we can't use instanceof.
Needless to say, these kinds of shenanigans make for somewhat brittle code. If you wish to use this approach, make sure you document it well and prepare to fix it when migrating to future Java versions.
If you find yourself fighting the design of a component like this, you might be using the wrong component. Perhaps you would be better off using a single-column JTable.
Well I fixed the problem by adding an int that keeps track of what the selected index is when I press a key and added
if(Math.abs(selectedIndex- keyPressedIndex) != 1) {
serials.setSelectedIndex(keyPressedIndex);
selectedIndex = serials.getSelectedIndex();
}
to the ListSelectionListener. Still don't know why it happens though
Related
I have a JTable in which I alter between having row or column selection enabled. This feature works well but I want to be able to determine the number of cells which are highlighted adjacent to the currently selected cell. Currently, either the entire row or column is selected. I have tried to add a ListSelectionModel to implement this functionality but it only allows for either an entire row or column to be selected. Here are example images:
and
ADJACENTSELECTION is by default set to two so I'd like to highlight the 2 cells to the right and left of the selected cell when rowSelection is enabled or 2 cells above and below the selected cell when rowSelection is false. Any help would be greatly appreciated. Here is my code:
package selectionTest;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.EventQueue;
import java.awt.event.ActionEvent;
import javax.swing.AbstractAction;
import javax.swing.Action;
import javax.swing.ActionMap;
import javax.swing.BorderFactory;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JTable;
import javax.swing.KeyStroke;
import javax.swing.border.Border;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener;
import javax.swing.table.DefaultTableModel;
public class App {
private void display() {
JFrame f = new JFrame("Demo");
JPanel gui = new JPanel(new BorderLayout());
GridTable gridTable = new GridTable(25, 25);
gui.add(gridTable, BorderLayout.CENTER);
f.add(gui);
f.setBackground(Color.BLUE);
gui.setBackground(Color.WHITE);
f.setLocationByPlatform(true);
f.pack();
f.setSize(500, 600);
f.setVisible(true);
}
public static void main(String[] args) {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
new App().display();
}
});
}
}
class GridTable extends JTable {
static int ADJACENTSELECTION = 2;
boolean rowSelection = false;
int rows, cols;
public GridTable(int rows, int cols) {
this.rows = rows;
this.cols = cols;
this.setModel(new DefaultTableModel(rows, cols));
this.setShowGrid(true);
Border blackline = BorderFactory.createLineBorder(Color.black);
this.setBorder(blackline);
this.setGridColor(Color.black);
ActionMap map = this.getActionMap();
Action action = switchOrientation();
String keyStrokeAndKey = "control O";
KeyStroke keyStroke = KeyStroke.getKeyStroke(keyStrokeAndKey);
this.getInputMap().put(keyStroke, keyStrokeAndKey);
this.getActionMap().put(keyStrokeAndKey, action);
this.getSelectionModel().addListSelectionListener(new ListSelectionListener() {
public void valueChanged(ListSelectionEvent event) {
int row = getSelectedRow();
int column = getSelectedColumn();
if (rowSelection) {
setRowSelectionInterval(Math.max(0, row - ADJACENTSELECTION), Math.min(row, row + ADJACENTSELECTION));
} else {
setColumnSelectionInterval(Math.max(0, column - ADJACENTSELECTION), Math.min(column, column + ADJACENTSELECTION));
}
}
});
}
private AbstractAction switchOrientation() {
AbstractAction switchOrientation = new AbstractAction() {
#Override
public void actionPerformed(ActionEvent e) {
rowSelection = !rowSelection;
setColumnSelectionAllowed(rowSelection);
setRowSelectionAllowed(!rowSelection);
}
};
return switchOrientation;
}
}
You are almost there, just a couple of extra steps and you can update the selection listener to only highlight specific cells:
Allow for selection of specific cells by adding this.setCellSelectionEnabled(true); to your GridTable method:
public GridTable(int rows, int cols) {
this.setCellSelectionEnabled(true);
//...
make sure that the SelectionInterval ranges/values are correct:
if (rowSelection == true){
//Correct using `getRowCount()` for selection range
setRowSelectionInterval(Math.max(0, row - ADJACENTSELECTION), Math.min(getRowCount()-1, row + ADJACENTSELECTION));
}
else{
//Correct using `getColumnCount()` for selection range
setColumnSelectionInterval(Math.max(0, column - ADJACENTSELECTION), Math.min(getColumnCount()-1, column + ADJACENTSELECTION));
}
Make sure that when you update the selection that it does not trigger the selection event to happen again. You can do this by using a boolean to track the selection event:
this.getSelectionModel().addListSelectionListener(new ListSelectionListener(){
//Use this to track changes
boolean initial = true;
//...
public void valueChanged(ListSelectionEvent e){
if (initial){
int column = getSelectedColumn();
int row = getSelectedRow();
//...
Finally, putting it all together, this is the full selection listener (And don't forget to add this.setCellSelectionEnabled(true); to GridTable):
this.getSelectionModel().addListSelectionListener(new ListSelectionListener(){
//Use this to track changes
boolean initial = true;
public void valueChanged(ListSelectionEvent e){
//Only change value on first select (This prevents the below from triggering itself)
if (initial){
int column = getSelectedColumn();
int row = getSelectedRow();
initial = false;
if (rowSelection == true){
setRowSelectionInterval(Math.max(0, row - ADJACENTSELECTION), Math.min(getRowCount()-1, row + ADJACENTSELECTION));
}
else{
setColumnSelectionInterval(Math.max(0, column - ADJACENTSELECTION), Math.min(getColumnCount()-1, column + ADJACENTSELECTION));
}
}
//Set the value back once a selection is complete
else{
initial = true;
}
}
});
This will highlight the selected cell, and two cells either side of the selection
In a project I've been working on, I noticed that all the JList items in my JScrollPane are hidden until the JScrollPane/JList has been clicked. The weird part is it's not completely covered. There's this white box with a transparent border that spreads out over the whole thing, covering all but a few pixels on all edges.
Pictures:
As you can see, there is this white block in the middle - notice the pink "border":
Now, once I click that white box, it goes away:
I know the magenta looks horrible, but I'm using it for contrast.
Which leads me to my question: how do I get rid of that obnoxious white box?
Here is my code:
public static void listJars(File f)
{
JCheckBox firstBox = null;
DefaultListModel<JCheckBox> model = new DefaultListModel<>();
if(mainGUI.checkList != null)
{
//System.out.println("Already exists lol: " + mainGUI.checkList.getName());
mainGUI.pluginList.remove(mainGUI.checkList);
}
//mainGUI.pluginList.repaint();
File[] files = new File(f.getPath()).listFiles();
if (files != null)
{
for (File file : files)
{
if (file.getName().endsWith(".jar") || file.getName().endsWith("._jar"))
{
JCheckBox cb = new JCheckBox(file.getName());
if(firstBox == null)
{
firstBox = cb;
}
cb.setSelected(file.getName().endsWith(".jar"));
cb.setVisible(true);
cb.setText(file.getName());
model.addElement(cb);
cb.repaint();
}
}
}
JCheckBoxList jCheckBoxList = new JCheckBoxList(model, mainGUI.textField1.getText());
jCheckBoxList.setName("pluginCheckboxList");
jCheckBoxList.setSize(mainGUI.pluginList.getSize());
mainGUI.pluginList.add(jCheckBoxList);
mainGUI.checkList = jCheckBoxList;
jCheckBoxList.setVisible(true);
jCheckBoxList.setVisibleRowCount(10);
}
And ten there's my JCheckBoxList class.
package Components;
import javax.swing.*;
import javax.swing.border.Border;
import javax.swing.border.EmptyBorder;
import java.awt.*;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.io.File;
#SuppressWarnings("serial")
public class JCheckBoxList extends JList<JCheckBox>
{
protected static Border noFocusBorder = new EmptyBorder(1, 1, 1, 1);
protected String lastPath;
public JCheckBoxList(final String lastPath)
{
this.lastPath = lastPath;
setCellRenderer(new CellRenderer());
setBackground(Color.magenta);
addMouseListener(new MouseAdapter()
{
public void mousePressed(MouseEvent e)
{
int index = locationToIndex(e.getPoint());
if (index != -1)
{
JCheckBox checkBox = getModel().getElementAt(index);
checkBox.setSelected(!checkBox.isSelected());
repaint();
final String oldname = checkBox.getText();
if (!checkBox.isSelected())
{
checkBox.setName(checkBox.getText().substring(0, checkBox.getText().length() - 4) + "._jar");
}
else
{
checkBox.setName(checkBox.getText().substring(0, checkBox.getText().length() - 5) + ".jar");
}
System.out.println("Changed! Sel: " + checkBox.isSelected() + ", Name: " + checkBox.getName());
checkBox.setText(checkBox.getName());
String base = new File(lastPath).getParent() + "/plugins/";
boolean rename = new File(base + oldname).renameTo(new File(base + checkBox.getText()));
}
}
});
setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
}
public JCheckBoxList(ListModel<JCheckBox> model, String lastPath)
{
this(lastPath);
setModel(model);
}
protected class CellRenderer implements ListCellRenderer<JCheckBox>
{
public Component getListCellRendererComponent(
JList<? extends JCheckBox> list, JCheckBox value, int index,
boolean isSelected, boolean cellHasFocus)
{
//Drawing checkbox, change the appearance here
value.setBackground(isSelected ? getSelectionBackground()
: getBackground());
value.setForeground(isSelected ? getSelectionForeground()
: getForeground());
value.setEnabled(isEnabled());
value.setFont(getFont());
value.setFocusPainted(false);
value.setBorderPainted(true);
value.setBorder(BorderFactory.createEmptyBorder(0, 10, 5, 0));
return value;
}
}
}
And then there's my scroll pane, which has these settings (using the Intelliji IDEA UI designer):
Any ideas?
mainGUI.pluginList.add(jCheckBoxList);
mainGUI.checkList = jCheckBoxList;
jCheckBoxList.setVisible(true);
jCheckBoxList.setVisibleRowCount(10);
Looks to me like you are dynamically adding components to a visible GUI.
When you do this the basic code is:
panel.add(...);
panel.revalidate();
panel.repaint();
You should set the visibleRowCount() before the above code is executed.
Also:
Swing components are visible by default so you don't need the setVisible(true).
You may want to consider using a one column JTable since it already supports a checkbox renderer and editor.
Edit:
The solution I gave you above is the general solution. A scroll pane is different, you should only ever add a component to the viewport.
Based on your incorrect solution you should be using:
//mainGUI.pluginList.add(jCheckBoxList);
mainGUI.pluginList.setViewportView(jCheckBoxList);
The problem with posting only a few random lines of code is that we don't know the full context of the code. I did not realize "pluginList" was actually a scrollpane. Usually the variable name will have scroll or scrollpane in the name.
mainGUI.pluginList.setViewportView(mainGUI.checkList); // pluginList is the JScrollPane.
Do that, and it fixes everything! Put it in with my listJars method.
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);
}
});
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);
}
What I want to do is when user finish editing of data in table cell to move focus onto another cell depending of what user entered, and to turn that cell into editing mode so user can start typing immediately with no additional action. This way user can focus on his work and software will do the 'thinking' about which cell should be edited next.
Simple task which does not look so simple in real life ... anyone some idea?
Please try this example.
It should let you navigate through the table by entering the values u, d, l, r for Up, Down, Left, Right.
Hope that this will give you an idea about how to do it.
import java.awt.event.ActionEvent;
import javax.swing.AbstractAction;
import javax.swing.Action;
import javax.swing.JFrame;
import javax.swing.JTable;
import javax.swing.KeyStroke;
import javax.swing.table.DefaultTableModel;
import javax.swing.table.TableModel;
public class Test extends JFrame {
private JTable table;
private TableModel tableModel;
public Test() {
tableModel = new DefaultTableModel(5, 5);
table = new JTable(tableModel);
table.setColumnSelectionAllowed(true);
getContentPane().add(table);
Action handleEnter = new AbstractAction() {
public void actionPerformed(ActionEvent e) {
table.getCellEditor().stopCellEditing(); // store user input
int row = table.getSelectedRow();
int col = table.getSelectedColumn();
String val = String.valueOf(table.getValueAt(row, col)).toLowerCase();
if (val.equals("u"))
--row;
else if (val.equals("d"))
++row;
else if (val.equals("l"))
--col;
else if (val.equals("r"))
++col;
if ( row >= 0 && row < tableModel.getRowCount()
&& col >= 0 && col < tableModel.getColumnCount()) {
table.changeSelection(row, col, false, false);
table.editCellAt(row, col);
}
}
};
// replace action for ENTER, since next row would be selected automatically
table.getInputMap().put(KeyStroke.getKeyStroke("ENTER"), "handleEnter");
table.getActionMap().put("handleEnter", handleEnter);
}
public static void main(String[] args) {
Test test = new Test();
test.setSize(800, 600);
test.setVisible(true);
}
}
You should add a KeyListener to the JTable to get all typed Characters.
After the user presses Enter, you should check the word the user has typed.
Write your own FocusTraversalPolicy to set it to the Table
table.setFocusTraversalPolicy(policy)
The FocusTraversalPolicy describes which component gets the next focus.
After this you can call
FocusManager.getCurrentManager().focusNextComponent();
EDIT: I did not test this, it is just an idea.