Solved

Focus traversal issue with a custom TableCellEditor

Posted on 2004-09-22
7
979 Views
Last Modified: 2007-12-19
I'm having a focus traversal issue using the following three classes that together define a customized TableCellEditor that I wish to use within a JTable.  The class CaseDocument is a subclass of PlainDocument that simply transforms the inserted character into uppercase.

----------------------------- code begins here ---------------------------------------------------

public class SymbolCellEditor extends AbstractCellEditor implements TableCellEditor {
     private SymbolField symbolField;

     public SymbolCellEditor() { symbolField = new SymbolField(); }
      
     public Component getTableCellEditorComponent(JTable table, Object value,
               boolean isSelected, int rowIndex, int vColIndex)
     {      
          symbolField.setSymbolFieldText((String)value, false);
          return symbolField;  
     }

     // So a single click starts editing.
     public boolean isCellEditable(EventObject evt)  { return true; }

     // So we can programmatically start editing.
     public void requestFocus() { symbolField.requestFocus(); }

     public Object getCellEditorValue() { return symbolField.getSymbolFieldText(); }
}

public class SymbolField extends SymbolComponent {

  private JTextField symbolField;
 
  public SymbolField() { this(0); }
 
  public SymbolField(int size) {
    symbolField = (size > 0) ? new JTextField(size) : new JTextField();
    symbolField.setDocument(new CaseDocument());
   
    initialize();
  }

  public JComponent getSymbolComponent() { return symbolField; }
  public Object getEventSource() { return symbolField; }
  public JTextField getTextField() { return symbolField; }
  public String getSymbolFieldText() { return symbolField.getText().trim(); }
 
  public void setSymbolFieldText(String symbol) { symbolField.setText(symbol); }
  public void setSymbolFieldEditable(boolean isEditable) { symbolField.setEditable(isEditable); }

  public void setSymbolFieldText(String symbol, boolean toUpperCase) {
    if (!toUpperCase) {
      boolean isPressed_tmp = isPressed;
      isPressed = false;
      symbolField.setText(symbol);
      isPressed = isPressed_tmp;
    } else {
      symbolField.setText(symbol);
    }      
  }
 
  public void addActionListener(ActionListener al) { symbolField.addActionListener(al); }

  public void requestFocus() { symbolField.requestFocus(); }
}

public abstract class SymbolComponent extends JPanel {
 
  protected boolean isPressed = true;

  public abstract Object getEventSource();
  public abstract JTextField getTextField();
  public abstract JComponent getSymbolComponent();
 
  /**
   * Gets the trimmed symbol name in the correct case. If no symbol is
   * entered/selected then "" is returned.
   */
  public abstract String getSymbolFieldText();
 
  public abstract void addActionListener(ActionListener al);

  private static final String UPPERCASE_TOOLTIP = "Upper Case - click to change.";
  private static final String MIXEDCASE_TOOLTIP = "Mixed Case - click to change.";
 
  protected void initialize() {
    isPressed = true;

    final JLabel caseLabel = new JLabel(" A ");

    caseLabel.setToolTipText(UPPERCASE_TOOLTIP);
    caseLabel.setBorder(BorderFactory.createLoweredBevelBorder());
    caseLabel.addMouseListener(new MouseAdapter() {
      public void mouseClicked(MouseEvent e) {
        if (isPressed) {
          caseLabel.setText(" a ");
          caseLabel.setBorder(BorderFactory.createRaisedBevelBorder());
          caseLabel.setToolTipText(MIXEDCASE_TOOLTIP);
          isPressed = false;
        }
        else {
          caseLabel.setText(" A ");
          caseLabel.setBorder(BorderFactory.createLoweredBevelBorder());
          getTextField().setText(getTextField().getText().toUpperCase());
          caseLabel.setToolTipText(UPPERCASE_TOOLTIP);
          isPressed = true;
        }
      }
    });

    getSymbolComponent().setName("Symbol");
    getSymbolComponent().setToolTipText("Enter the symbol name.");
   
    setLayout(new BorderLayout(2,0));
    add(getSymbolComponent(), BorderLayout.CENTER);
   
    add(caseLabel, BorderLayout.EAST);  
  }
}

----------------------------- code end here ---------------------------------------------------

Now within my application, I have a method that will set the focus to within SymbolCellEditor as

  private boolean startCellEditing(int row, int col) {
    try {
      boolean success = table.editCellAt(row, col);
      if (success) {
           TableCellEditor editor = table.getCellEditor();
           ((SymbolCellEditor) editor).requestFocus();
        table.changeSelection(row, col, false, false);
      }
   
      return success;
    } catch (Exception e) {
      // Not important...
      return false;
    }
  }

This method is called when I add a new row to the JTable as:

     startCellEditing(tableModel.getRowCount() - 1, 0);

(of course, I've already added my data object to the model before this method is called).  The editor seems to work as I had hoped: that to make the focus to jump within the SymbolCellEditor. However there is one VERY annoying thing: when I hit the Tab key to go to the next cell of the same row, the focus jumps out of the table to another component in the dialog!!!  The focus should simply go to the next cell on the same row. I'm assuming that it has something to do with the FocusTraversalPolicy set (or inherited) within the SymbolField, but I don't really understand Java 1.4's new Focus Manager.

Can anyone help me out?  I apologize for this lengthy question, but it is VERY important that I find an answer to this annoying issue.  Thanks in advance.
0
Comment
Question by:mwalker
  • 3
  • 2
7 Comments
 
LVL 92

Expert Comment

by:objects
ID: 12128783
Exactly what is it you are trying to achieve?
0
 

Author Comment

by:mwalker
ID: 12133115
First, when my startCellEditing() method is called the focus is transferred immediately to the 1st cell of the new row; allowing the user to start typing without clicking into the cell.  Secondly when the user hits the tab key, the focus goes to the next cell on the same row.  I neglected to include another TableCellEditor that I wrote the allows for the cells of a table to begin editing upon receiving the focus.  An instance of this class is applied to all of the other columns.

public class OverwriteCellEditor extends AbstractCellEditorimplements TableCellEditor {
  private JTextField textField = new JTextField();

  public OverwriteCellEditor() {
    textField.addFocusListener( new FocusAdapter() {
      public void focusGained(FocusEvent fe) {
        textField.selectAll();
      }
    });
  }

  public boolean isCellEditable(EventObject evt) {
    // So a single click starts editing.
    return true;
  }

  public void requestFocus() {
    // So we can programmatically start editing. See startCellEditing below.
    textField.requestFocus();
  }

  public Component getTableCellEditorComponent(JTable table, Object value,
              boolean isSelected, int row, int column) {
    String valueString = (value == null) ? "" : value.toString();
    textField.setText(valueString);
   
    // So that by default you replace the contents of the field.
    // Selecting the cell with the mouse allow fine grained editing rather
    // than complete replacing.
    textField.selectAll();
 
    return textField;
  }
 
  public Object getCellEditorValue() { return textField.getText(); }

Applying only the OverwriteCellEditor above to the table works the way I want it.  However I tried to only use the SymbolCellEditor on the table and I get the crazy focus issue.
0
 
LVL 92

Expert Comment

by:objects
ID: 12204820
sorry for the delay getting back to you, been a bit swamped here.

Previously I've implemented what you are doing by subclassing JTable (if I understand you correctly)
ie. to handle tab automatically editting next cell, and selecting the text field when editting starts
0
 

Author Comment

by:mwalker
ID: 12216420
Would you please provide a code snippet of the JTable subclass you mentioned? Thanks.
0
 
LVL 92

Accepted Solution

by:
objects earned 500 total points
ID: 12221624
Here you go, hopefully I stripped out all the app specific stuff:

import javax.swing.*;
import javax.swing.event.*;
import javax.swing.table.*;
import java.awt.*;
import java.awt.event.*;

public class MyTable extends JTable
{
      protected JTextField TF = new JTextField(Doc, null, 0);
      private DefaultCellEditor Editor = new DefaultCellEditor(TF);
            
      public MyTable(TableModel model)
      {
            super(model);

            Editor.setClickCountToStart(1);
            setDefaultEditor(Object.class, Editor);

            // Tab actions
            
            getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT).getParent().put(KeyStroke.getKeyStroke(KeyEvent.VK_TAB, 0, false), "tabPressed");
            getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT).getParent().put(KeyStroke.getKeyStroke(KeyEvent.VK_TAB, KeyEvent.SHIFT_MASK, false), "shiftTabPressed");
            getActionMap().put("tabPressed", new TabAction());
            getActionMap().put("shiftTabPressed", new ShiftTabAction());
      }

      public Component prepareEditor(TableCellEditor editor, int row, int col)
      {
            Component result = super.prepareEditor(editor, row, col);
            if (result instanceof JTextField)
            {
                  clearSelection();
                  setRowSelectionInterval(row, row);
                  setColumnSelectionInterval(col, col);
                  ((JTextField)result).selectAll();
                  TF.requestFocus();
            }
            return result;
      }

      public boolean isManagingFocus()
      {
            return true;
      }

      public boolean isFocusCycleRoot()
      {
            return true;
      }

      /**
      * Captures tabs and moves focus to the next or previous cell, depending
      * on whether the <Shift> key was also pressed (i.e. tab or back tab).
      */
      
      public void processComponentKeyEvent(KeyEvent e)
      {
                  int row = getEditingRow();
                  int col = getEditingColumn();
                  boolean inEditingMode = row > 0 && col > 0;
                  if (inEditingMode && e.getID() == KeyEvent.KEY_RELEASED)
                  {
                        getEditorComponent().requestFocus();
                  }
                  
            if ( (e.getID() == KeyEvent.KEY_TYPED)
                  && (e.toString().toLowerCase().indexOf(",tab") >= 0) )
            {
                  if (e.isShiftDown())
                  {
                        tabToNextEditableCell(-1); // backtab
                  }
                  else
                  {
                        tabToNextEditableCell(1); // tab
                  }
                  e.consume();
            }
            else
            {
                  super.processComponentKeyEvent(e);
            }
      }
      
      /**
      * Sets editing focus onto next editable cell.
      * When moving forward, traverses cells from left to right then top to
      * bottom
      * @param direction FORWARD or BACKWARD
      */
      private void tabToNextEditableCell(int direction)
      {
            int iNumRows = getRowCount();
            int iNumCols = getColumnCount();
            int row = getSelectedRow();
            int col = getSelectedColumn();
            boolean exitSearchLoop = false;

            // Skip through cells in turn until find one that is editable...
      
            while (!exitSearchLoop)
            {
                  col += direction;
                  if (col<0)
                  {
                        if (row>0)
                        {
                              col = iNumCols - 1;
                              row--;
                        }
                        else
                        {
                              exitSearchLoop = true;
                        }
                  }
                  else if (col>=iNumCols)
                  {
                        if (row<iNumRows)
                        {
                              col = 0;
                              row++;
                        }
                        else
                        {
                              exitSearchLoop = true;
                        }
                  }
      
      
                  if (isCellEditable(row, col))
                  {
                        clearSelection();
                        setRowSelectionInterval(row, row);
                        setColumnSelectionInterval(col, col);
                        editCellAt(row, col);
                        if (getEditorComponent() instanceof JTextField)
                        {
                              JTextField cellToEdit = (JTextField) getEditorComponent();
                              cellToEdit.selectAll();
                        }
                        getEditorComponent().requestFocus();
                        exitSearchLoop = true;
                  }
            } // end while            
      } // end tabToNextEditableCell()
            
      private class TabAction extends AbstractAction
      {
            public void actionPerformed(ActionEvent ae)
            {
                  int row = getEditingRow();
                  int col = getEditingColumn();
                  boolean inEditingMode = row > 0 && col > 0;        

                  //if user is not editing OR user is editing AND value did pass validation        
                  //send focus to next focusable component        
                  
                  if (( inEditingMode &&  getCellEditor(row, col).stopCellEditing()) || !inEditingMode )
                  {
                        tabToNextEditableCell(1); // tab
                  }
            }
      }

      private class ShiftTabAction extends AbstractAction
      {
            public void actionPerformed(ActionEvent ae)
            {
                  int row = getEditingRow();
                  int col = getEditingColumn();
                  boolean inEditingMode = row > 0 && col > 0;
                  
                  //if user is not editing OR user is editing AND value did pass validation
                  //send focus to previous component
                  
                  if (( inEditingMode &&  getCellEditor(row, col).stopCellEditing()) || !inEditingMode )
                  {
                        tabToNextEditableCell(-1); // backtab
                  }
            }
      }
}
0

Featured Post

Better Security Awareness With Threat Intelligence

See how one of the leading financial services organizations uses Recorded Future as part of a holistic threat intelligence program to promote security awareness and proactively and efficiently identify threats.

Join & Write a Comment

For customizing the look of your lightweight component and making it look lucid like it was made of glass. Or: how to make your component more Apple-ish ;) This tip assumes your component to be of rectangular shape and completely opaque. (COD…
By the end of 1980s, object oriented programming using languages like C++, Simula69 and ObjectPascal gained momentum. It looked like programmers finally found the perfect language. C++ successfully combined the object oriented principles of Simula w…
Viewers will learn about arithmetic and Boolean expressions in Java and the logical operators used to create Boolean expressions. We will cover the symbols used for arithmetic expressions and define each logical operator and how to use them in Boole…
This tutorial explains how to use the VisualVM tool for the Java platform application. This video goes into detail on the Threads, Sampler, and Profiler tabs.

759 members asked questions and received personalized solutions in the past 7 days.

Join the community of 500,000 technology professionals and ask your questions.

Join & Ask a Question

Need Help in Real-Time?

Connect with top rated Experts

19 Experts available now in Live!

Get 1:1 Help Now