JTable with a complex editor

2019-03-10 18:57发布

I have many custom editors for a JTable and it's an understatement to say that the usability, particularly in regard to editing with the keyboard, is lacking.

The main reason for this is that my editors are always created with a similar (though often more complex) situation to this:

@Override
public Component getTableCellEditorComponent(JTable table, Object value, boolean isSelected, int row, int column) {
  JPanel container = new JPanel();
  container.setLayout(new BorderLayout());
  container.add(field, BorderLayout.CENTER);
  field.setText((String) value);
  container.add(new JButton("..."), BorderLayout.EAST);
  return container;
}

I.E a panel with more than one component inside. The actual text editor is a descendant of the component being returned as the editor. So, rendering issues aside, from what I can tell, the JTable is focusing the component that is returned by the getTableCellEditorComponent method so when you press a key with a cell highlighted it passes focus and the key press to the panel, thinking that's the editor.
Is there anyway I can inform JTable that the "real" editor is the JTextfield? Adding a hacky requestFocusInWindow on the correct component is insufficient as the key press won't get passed on.

5条回答
啃猪蹄的小仙女
2楼-- · 2019-03-10 19:18

I think that I solved it.
To tell you the truth, I don't know what solved the problem, since I'm using a custom editor, a custom renderer and stuff...

When a cell is highlighted and I press "abc", the 3 letters go on screen (cell, in this case).

grid.addKeyListener(new KeyAdapter() {
    public void keyTyped(KeyEvent ke) {
        int l = grid.getSelectedRow();
        int c = grid.getSelectedColumn();
        grid.editCellAt(l, c);
    }
});

Well... I tried... =)
(I don't know if it's the same because my JTable uses JTextField and JComboBox as editors).

查看更多
戒情不戒烟
3楼-- · 2019-03-10 19:23

Check some related articles here and here.

Another good article about JTable editing in general.

查看更多
Anthone
4楼-- · 2019-03-10 19:27

I had very similar problem. In my case I had complex TableCellEditor which consists of JSpinner and some other components. The problem was that when cell editor started I wanted to transfer focus to its internal component. I fixed this by calling panel.transferFocusDownCycle() but this in turn caused keyboard events to stop working - when my internal component had focus and I pressed key up, I was expecting that component will intercept this event and change its value. Instead table changed row focus to one above... I fixed this by adding KeyListener and dispatching all key events directly to the internal component.

This is wrapper class based on JPanel I wrote to make my life easier.

public class ContainerPanel extends JPanel implements KeyListener, FocusListener {

    private JComponent component = null;

    public ContainerPanel(JComponent component) {
        this.component = component;
        addKeyListener(this);
        addFocusListener(this);
        setFocusCycleRoot(true);
        setFocusTraversalPolicy(new ContainerOrderFocusTraversalPolicy());
        add(component);
    }

    @Override
    public void keyTyped(KeyEvent e) {
        component.dispatchEvent(e);
    }

    @Override
    public void keyPressed(KeyEvent e) {
        component.dispatchEvent(e);
    }

    @Override
    public void keyReleased(KeyEvent e) {
        component.dispatchEvent(e);
    }

    @Override
    public void focusGained(FocusEvent e) {
        component.transferFocusDownCycle();
    }

    @Override
    public void focusLost(FocusEvent e) {
    }
}
查看更多
我只想做你的唯一
5楼-- · 2019-03-10 19:29

I fixed something similar in 2 steps

First override the editCellAt from your JTable and call requestFocus after preparing the editor:

public boolean editCellAt( int row, int column, EventObject e )
{
  if ( cellEditor != null && !cellEditor.stopCellEditing() )
    {
    return false;
    }

  if ( row < 0 || row >= getRowCount() ||
      column < 0 || column >= getColumnCount() )
    {
    return false;
    }

  if ( !isCellEditable(row, column) )
    return false;

  TableCellEditor editor=getCellEditor(row, column);
  if ( editor != null && editor.isCellEditable(e) )
    {
    editorComp=prepareEditor(editor, row, column);
    if ( editorComp == null )
      {
      removeEditor();
      return false;
      }
    //aangepast
    Rectangle rect=getCellRect(row, column, false);
    if ( datamodel_.useAdaptedEditorRect() )
      rect=datamodel_.changeRectangle(rect, editorComp);
    editorComp.setBounds(rect);
    add(editorComp);
    editorComp.validate();

    setCellEditor(editor);
    setEditingRow(row);
    setEditingColumn(column);
    editor.addCellEditorListener(this);
    //NEXT LINE ADDED 
    editorComp.requestFocus();
    return true;
    }
  return false;
}

Then overload the requestFocus from your JPanel and make sure your textfield is put as editorComponent:

public class EditorPanel extends JPanel {
   JComponent editorComponent;

   public boolean isRequestFocusEnabled()
   {
     return true;
   }

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

You can always grab the keyEvent and set it yourself:

AWTEvent event = EventQueue.getCurrentEvent();
if ( event instanceof KeyEvent )
  {
  char newSelection = ( (KeyEvent) event).getKeyChar();
  int keyCode = ( (KeyEvent) event ).getKeyCode();
  editorComponent.requestFocus();
  if ( editorComponent instanceof JTextField )
    {
    if ( ( newSelection >= (char) FIRST_ALLOWED_CHAR ) && ( newSelection != (char) LAST_ALLOWED_CHAR ) ) //comes from DefaultKeyTypedAction
       ( (JTextField) editorComponent ).setText(Character.toString(newSelection));
    if ( keyCode == KeyEvent.VK_BACK_SPACE || keyCode == KeyEvent.VK_DELETE )
      ( (JTextField) editorComponent ).setText("");          
    }
  }
else
  editorComponent.requestFocus();
查看更多
爱情/是我丢掉的垃圾
6楼-- · 2019-03-10 19:30

If I read your question correctly, you want the user to be able to type into a cell immediately, without activating the cell editor first, i.e., you want whatever keystroke activated the cell to be the first character entered into the text field.

My first attempt was to add a propertyChangeListener on the focusOwner property of the KeyboardFocusManager, only to notice that the focus never leaves the JTable. You probably ran into that as well. Time for plan B.

I got that "first keypress" thing to work by adding a KeyListener to the table that records the last KeyEvent for the keyPressed() method in an instance field. The getTableCellEditorComponent() method reads the character from there. I also needed that hacky requestFocusInWindow() call you mention if the user is to keep typing any characters after the first one.

For my sample app, I created a subclass of JTable that adds a KeyListener to itself. It's a much better idea to make your CellEditor instance implement KeyListener and add that to the regular JTable instead, but I'll leave that to you.

Here's your code snippet as I modified it:

@Override
public Component getTableCellEditorComponent(JTable table, Object value, boolean isSelected, int row, int column) {
    JPanel container = new JPanel();
    container.setLayout(new BorderLayout());
    container.add(field, BorderLayout.CENTER);

    // Will want to add an instanceof check as well as a check on Character.isLetterOrDigit(char).
    char keypressed = ((StickyKeypressTable)table).getLastKeyPressed();
    field.setText(String.valueOf(keypressed));

    container.add(new JButton("..."), BorderLayout.EAST);

    SwingUtilities.invokeLater(new Runnable() {
        public void run() {
            // This needs to be in an invokeLater() to work properly
            field.requestFocusInWindow();
        }
    });
    return container;
}

As far as nastiness goes this sits somewhere up there with Vogon Poetry, but it should solve your immediate problem.

查看更多
登录 后发表回答