Go Premium for a chance to win a PS4. Enter to Win

x
?
Solved

Focus traversal issue with a custom TableCellEditor

Posted on 2004-09-22
7
Medium Priority
?
1,006 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
5 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 2000 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

Independent Software Vendors: We Want Your Opinion

We value your feedback.

Take our survey and automatically be enter to win anyone of the following:
Yeti Cooler, Amazon eGift Card, and Movie eGift Card!

Question has a verified solution.

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

Go is an acronym of golang, is a programming language developed Google in 2007. Go is a new language that is mostly in the C family, with significant input from Pascal/Modula/Oberon family. Hence Go arisen as low-level language with fast compilation…
Introduction This article is the first of three articles that explain why and how the Experts Exchange QA Team does test automation for our web site. This article explains our test automation goals. Then rationale is given for the tools we use to a…
Viewers learn how to read error messages and identify possible mistakes that could cause hours of frustration. Coding is as much about debugging your code as it is about writing it. Define Error Message: Line Numbers: Type of Error: Break Down…
Viewers will learn about basic arrays, how to declare them, and how to use them. Introduction and definition: Declare an array and cover the syntax of declaring them: Initialize every index in the created array: Example/Features of a basic arr…
Suggested Courses

783 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