how to control focus in JTable - java

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.

Related

Alter selection behavior for a JTable to only select a variable number of cells in a row or column

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

JList selection randomly jumps to previous index

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

Trying to use 1 action listener for multiple buttons

So I am trying to code a jeopardy game, but the catch is that I am only trying to assign my buttons to 1 action listener so that all buttons function on their own yet work from 1 action listener.
I've tried a lot, nothing works!
package jep;
import java.awt.Color;
import java.awt.GridLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.File;
import javax.sound.sampled.AudioSystem;
import javax.sound.sampled.Clip;
import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JOptionPane;
public class jep implements ActionListener{
public JButton[][] t = new JButton[6][6];
public static void main(String[] args) {
new jep();
}
static int n = 100;
public jep() {
JFrame frame = new JFrame("Jeopardy");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setSize(1920,1080);
frame.setLayout(new GridLayout(6, 5));
frame.setVisible(true);
for (int r = 0; r < 6; r++) {
for (int c = 0; c < 5; c++) {
String vakue = String.valueOf(n);
t[r][c] = new JButton(vakue);
t[r][c].setBackground(Color.BLUE);
t[r][c].setForeground(Color.YELLOW);
t[r][c].addActionListener(this);
frame.add(t[r][c]);
}
n = n +300;
}
#Override
public void actionPerformed(ActionEvent arg0) {
// TODO Auto-generated method stub
}
I am trying to get it so that i can click multiple buttons using only 1 action listener but all i can get is a grid
Here is the corrected code with print to console on button press. Please, check the comments in the code:
import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
public class Jep implements ActionListener { // class name has to start with a capital letter
int i = 6;
int j = 5;
public JButton[][] t = new JButton[i][j];
public static void main(String[] args) {
new Jep();
}
static int n = 100;
public Jep() {
JFrame frame = new JFrame("Jeopardy");
JPanel[][] panelHolder = new JPanel[i][j]; // use panels to add you buttons, check this for details:
// https://stackoverflow.com/questions/2510159/can-i-add-a-component-to-a-specific-grid-cell-when-a-gridlayout-is-used
frame.setLayout(new GridLayout(i, j));
frame.setVisible(true);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setSize(1920, 1080);
frame.setVisible(true);
for (int r = 0; r < 6; r++) {
for (int c = 0; c < 5; c++) {
String vakue = String.valueOf(n);
t[r][c] = new JButton(vakue);
t[r][c].setBackground(Color.BLUE);
t[r][c].setForeground(Color.BLACK);
t[r][c].addActionListener(this);
panelHolder[r][c] = new JPanel();
panelHolder[r][c].add(t[r][c]);
frame.add(panelHolder[r][c]);
n = n + 300;
}
}
}
#Override
public void actionPerformed(ActionEvent arg0) {
System.out.println("press, value = " + arg0.getActionCommand()); // here is a simple system out log statement
}
}
Output(when you press several buttons):
press, value = 100
press, value = 400
press, value = 700
press, value = 1000
press, value = 1300
press, value = 1600
App Winodow:
Hope this will help.
You can use AbstractButton (superclass of JButton).setActionCommand, and then in your listener using action command you can figure out your row and column
Instead of creating a 6 x 6 array of JButtons, you ought to use JTable, set the renderer to render your cells values as buttons (or something else) and as recommended here add a listSelectionListener to the tablemodel to get the row and the column values on click. As recommended here: Get position of Click JTable while using Cell renderer
Learning JTable and how to use renders etc can take a little time. But I assume that for this question you are in a learning environment anyway, that this is not something being done for business, or? So I would recommend you take the time to learn JTable. You will end up being much happier with your final product I promise you.

Instead of the picture it shows me the x.png inside the cell, how can i make it SHOW the actual picture or put some background color when value=="1"

package dmaze2;
import java.awt.Dimension;
import javax.swing.ImageIcon;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTable;
public class Dmaze2 extends JPanel
{
JTable jt;
public Dmaze2()
{
String[] columns = {"1","2","3","4","5","6","7","8"};
Object[][] table={{"f","f","f","f","f","f","f","f"},
//if this table is string makes problem to add picture
{"f","f","f","f","f","f","f","f"},
{"f","f","f","f","f","f","f","f"},
{"f","f","f","f","f","f","f","f"},
{"f","f","f","o","f","f","f","f"},
{"f","f","f","f","f","f","f","f"},
{"f","f","f","f","f","f","f","f"},
{"f","f","f","f","f","f","f","f"}};
int num=0;
ImageIcon Icon = new ImageIcon("x.png");
//i have the image in all files of the project to be sure it finds it
for (int i = 0; i < 8 ; i++)
{
int a=1;
for (int j = 0; j<7 && a<8; j++,a++)
{
if(table[i][j]=="f" && table[i][a]=="f")
{
num=num+1;
table[i][j]=Icon;
//if i try to enter the image here it will show it as x.png (as string) instead of the actual picture
table[i][a]="u";
}
}
//int b=1;
for (int j = 0; j<8 && i<7; j++)
{
if(table[i][j]=="f" && table[i+1][j]=="f")
{
num=num+1;
table[i][j]="u";//we put the block used
table[i+1][j]="u";
}
}
System.out.println("");
}
jt = new JTable(table,columns);
{
}
jt.setPreferredScrollableViewportSize(new Dimension(350,363));
jt.setFillsViewportHeight(true);
JScrollPane jps = new JScrollPane(jt);
add(jps);
}
public static void main(String[] args)
{
JFrame jf = new JFrame();
Dmaze2 t = new Dmaze2();
jf.setTitle("Depth First Search");
jf.setSize(500, 500);
jf.setVisible(true);
jf.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
jf.add(t);
}
}
You need to #Override the getColumnClass() of the table's XxxTableModel. If you don't the renderer will render the column as the Object.toString(). See more at Concepts: Editors and Renderers
DefaultTableModel model = new DefaultTableModel(tableData, columns) {
#Override
public Class<?> getColumnClass(int column) {
switch(column) {
case 1: return ImageIcon.class; // or whichever column you want
default: return String.class;
}
}
};
JTable table = new JTable(model);
Side Notes:
Have a look at How Do I Compare Strings in Java
Set your frame visible after adding all your components
Swing apps should be run on the Event Dispatch Thread. See more at Initial Threads
You may want to read your image files from the class path if the images are resources of your application. Passing a String path to the ImageIcon signifies a read from the local file system. At time of deployment, the path you use will no longer be valid. See the answers from this question and this question for more details on how you can accomplish this task of reading from the class path and embedding your resources.

Create info panel on mouseover in JTable? Tooltip might not be sufficient

I want to display an info box on mousing over a JTable cell using Java Swing, so there are multi-parts
How can I capture the mouse-over event in a table cell? I have to be able to set the cell content, then get data on it.
How can I display a panel/box with dynamic server data on mousing over that cell?
How can I cache the info panel/box so I don't have to query the server on every mouse over?
Example:
In table cell I enter: 94903. After tabbing or entering, the cell is set to the number. On mouse-over, it displays a box with Name, Address, Phone number, email, etc.
Thanks!
You could format the tooltip text using HTML, this would allow you to provide a complex structure of information to the tooltip without the need or expense of writing your own solution. The only problem is that the tooltip will be automatically discarded.
If this still doesn't suit, you could try:
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.event.*;
import javax.swing.JPopupMenu;
import javax.swing.JTable;
import javax.swing.Timer;
public class TestTable {
private Timer showTimer;
private Timer disposeTimer;
private JTable table;
private Point hintCell;
private MyPopup popup; // Inherites from JPopupMenu
public TestTable() {
showTimer = new Timer(1500, new ShowPopupActionHandler());
showTimer.setRepeats(false);
showTimer.setCoalesce(true);
disposeTimer = new Timer(5000, new DisposePopupActionHandler());
disposeTimer.setRepeats(false);
disposeTimer.setCoalesce(true);
table.addMouseMotionListener(new MouseMotionAdapter() {
#Override
public void mouseMoved(MouseEvent e) {
Point p = e.getPoint();
int row = table.rowAtPoint(p);
int col = table.columnAtPoint(p);
if ((row > -1 && row < table.getRowCount()) && (col > -1 && col < table.getColumnCount())) {
if (hintCell == null || (hintCell.x != col || hintCell.y != row)) {
hintCell = new Point(col, row);
Object value = table.getValueAt(row, col);
// Depending on how the data is stored, you may need to load more data
// here...
// You will probably want to maintain a reference to the object hint data
showTimer.restart();
}
}
}
});
}
protected MyPopup getHintPopup() {
if (popup == null) {
// Construct the popup...
}
return popup;
}
public class ShowPopupActionHandler implements ActionListener {
#Override
public void actionPerformed(ActionEvent e) {
if (hintCell != null) {
disposeTimer.stop(); // don't want it going off while we're setting up
MyPopup popup = getHintPopup();
popup.setVisible(false);
// You might want to check that the object hint data is update and valid...
Rectangle bounds = table.getCellRect(hintCell.y, hintCell.x, true);
int x = bounds.x;
int y = bounds.y + bounds.height;
popup.show(table, x, y);
disposeTimer.start();
}
}
}
public class DisposePopupActionHandler implements ActionListener {
#Override
public void actionPerformed(ActionEvent e) {
MyPopup popup = getHintPopup();
popup.setVisible(false);
}
}
}
Now, I've not constructed the popup, I'd use the popup menu from Bob Sinclar's answer as well
Heres a good way to get the mouse over working I woud do this first then just output some text when your over it to check.
This shows a way to get a pop up menu to appear
And in regards to the cacheing i might store the last 10 values in memory and do a request from the server each time a new entry point is pinged ie you dont have it locally. And then maybe every minute update the last 10 hit incase their info changes.
another useful mouseover guide

Categories