Solved

Focus traversal issue with a custom TableCellEditor

Posted on 2004-09-22
7
996 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
[X]
Welcome to Experts Exchange

Add your voice to the tech community where 5M+ people just like you are talking about what matters.

  • Help others & share knowledge
  • Earn cash & points
  • Learn & ask questions
  • 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

Ready to get started with anonymous questions?

It's easy! Check out this step-by-step guide for asking an anonymous question on Experts Exchange.

Question has a verified solution.

If you are experiencing a similar issue, please ask a related question

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…
Introduction This article is the last of three articles that explain why and how the Experts Exchange QA Team does test automation for our web site. This article covers our test design approach and then goes through a simple test case example, how …
This tutorial covers a practical example of lazy loading technique and early loading technique in a Singleton Design Pattern.
This video teaches viewers about errors in exception handling.

624 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