4

Imagine I'm building an IRC client with Java and I'd like rich text in the chat view to show IRC colors and colored nicks. I'd like to build this with a JTable. I can do that, but the text is then not selectable. Making the table editable doesn't make sense.

I've also investigated:

  • TextArea - no rich text formatting
  • JEditPane - can't append, only replace which is bad performance wise
  • JList - can't select text

So I got a table working I just need the text to be selectable without making it editable. I'd also would only like the text contents, and none of the HTML to be copied into the clipboard upon copying the text selection.

I have tried various iterations of setRowSelectionAllowed(), setColumnSelectionEnabled() and setCellSelectionEnabled() and setSelectionMode the table model returns false for isCellEditable(). Nothing has made the text selectable.

EDIT: as per answer 1 I was wrong about text editor panes so I'm trying those solutions.

Bjorn
  • 69,215
  • 39
  • 136
  • 164
  • Jus to know If that would get in the way: Has the table any kind of selection enabled (row/column/cell)? – DSquare Mar 16 '14 at 18:19
  • I've tried variations of enabling selections. I've updated the question with more details. – Bjorn Mar 16 '14 at 18:25
  • Mmm, i think you can do something, if you override `changeSelection()` from JTable, then there you can call the current cellEditor (You have to set to some JTextComponent i guess) and then there you call `textComponent.selectAll()` – nachokk Mar 16 '14 at 18:53
  • @nachokk thanks, but that doesn't sound like a good user experience. You could only highlight the entire cell, and not just like a subsection of it. I'm guessing tables are also not the answer. – Bjorn Mar 16 '14 at 19:22
  • @BjornTipling yes you can.. see my example, it's may be a strange example im not expert in swing but.. it's editable then you can select, but the cellEditor is non-editable! ;) see my answer, and im agree with you, perhaps a jtable is not what you are looking for. I override changeselection cause i think you need the `selectAll` feature. – nachokk Mar 16 '14 at 19:24

4 Answers4

2

I don't know why you don't want to use a JTextPane or JEditorPane. You insert text by its document. Examples here --> How to use Editor Panes and Text Panes.

But for your purpose you can for example do something like this. I override changeSelection to selectAll text when is clicking, the cells are editable but its cellEditors are not editable.

public class JTableTest {


        private final DefaultCellEditor cellEditor;
        private final JTextField textfield;
        private JPanel panel;
        private MyTableModel tableModel = new MyTableModel();
        private JTable table = new JTable() {

            @Override
            public TableCellEditor getCellEditor(int row, int column) {               
                    return JTableTest.this.cellEditor;                
            }

            @Override
            public void changeSelection(
                    final int row, final int column, final boolean toggle, final boolean extend) {
                super.changeSelection(row, column, toggle, extend);
                SwingUtilities.invokeLater(new Runnable() {
                    @Override
                    public void run() {
                        if ((getCellEditor(row, column) != null && !editCellAt(row, column))) {                        
                            JTextField textfield=(JTextField)JTableTest.this.cellEditor.getComponent();
                            textfield.selectAll();                          
                        }
                    }
                });
            }
        };

        public JTableTest() {
            JScrollPane scroll = new JScrollPane(table);
            table.setModel(tableModel);
            panel = new JPanel(new BorderLayout());
            panel.add(scroll, BorderLayout.CENTER);
            textfield = new JTextField();
            textfield.setEditable(Boolean.FALSE);
            textfield.setBorder(null);
            cellEditor = new DefaultCellEditor(textfield);
            tableModel.insertValue(new ItemRow("nonEditable", "Editable"));
        }



        private class ItemRow {

            private String column1;
            private String column2;

            public ItemRow(String column1, String column2) {
                this.column1 = column1;
                this.column2 = column2;
            }

            public String getColumn1() {
                return column1;
            }

            public void setColumn1(String column1) {
                this.column1 = column1;
            }

            public String getColumn2() {
                return column2;
            }

            public void setColumn2(String column2) {
                this.column2 = column2;
            }
        }

        private class MyTableModel extends AbstractTableModel {

            public static final int COLUMN1_INDEX = 0;
            public static final int COLUMN2_INDEX = 1;
            private final List<ItemRow> data = new ArrayList<>();

            private final String[] columnsNames = {
                "Column1",
                "Column2",};

            private final Class<?>[] columnsTypes = {
                String.class,
                String.class
            };


            public MyTableModel() {
                super();
            }

            @Override
            public Object getValueAt(int inRow, int inCol) {
                ItemRow row = data.get(inRow);
                Object outReturn = null;

                switch (inCol) {
                    case COLUMN1_INDEX:
                        outReturn = row.getColumn1();
                        break;
                    case COLUMN2_INDEX:
                        outReturn = row.getColumn2();
                        break;
                    default:
                        throw new RuntimeException("invalid column");
                }

                return outReturn;
            }

            @Override
            public void setValueAt(Object inValue, int inRow, int inCol) {
                System.out.println("Gets called ");
                if (inRow < 0 || inCol < 0 || inRow >= data.size()) {
                    return;
                }

                ItemRow row = data.get(inRow);
                switch (inCol) {
                    case COLUMN1_INDEX:
                        row.setColumn1(inValue.toString());
                        break;
                    case COLUMN2_INDEX:
                        row.setColumn2(inValue.toString());
                        break;
                }
                fireTableCellUpdated(inRow, inCol);
            }

            @Override
            public int getRowCount() {
                return data.size();
            }

            @Override
            public int getColumnCount() {
                return columnsTypes.length;
            }

            @Override
            public String getColumnName(int inCol) {
                return this.columnsNames[inCol];
            }

            @Override
            public Class<?> getColumnClass(int columnIndex) {
                return this.columnsTypes[columnIndex];
            }

            /**
             *
             * @param row
             */
            public void insertValue(ItemRow row) {
                data.add(row);
                fireTableRowsInserted(data.size() - 1, data.size() - 1);
            }

            @Override
            public boolean isCellEditable(int rowIndex, int columnIndex) {
                return true;
            }



        }


        private static void createAndShowGUI(final Container container, final String title) {
            //Create and set up the window.
            JFrame frame = new JFrame(title);
            frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
            frame.setLocationByPlatform(Boolean.TRUE);
            frame.add(container);
            //Display the window.
            frame.pack();
            frame.setVisible(true);
        }

        public static void main(String args[]) {
            SwingUtilities.invokeLater(new Runnable() {

                @Override
                public void run() {
                    createAndShowGUI(new JTableTest().panel, "Test");
                }

            });
        }

}
nachokk
  • 14,363
  • 4
  • 24
  • 53
  • Note: Override `changeselection` cause i think you need the `selectAll` feature. – nachokk Mar 16 '14 at 19:25
  • Ok I will try this and see if it works and report back. Thank you. – Bjorn Mar 16 '14 at 19:46
  • Unfortunately since this is actually an IntelliJ Plugin I'm limited to using the 1.6 JDK. – Bjorn Mar 16 '14 at 19:54
  • @BjornTipling Another solution based n my previous [question](http://stackoverflow.com/questions/22418159/celleditor-with-documentfilter-never-get-called) you can define a `documentFilter` then you consume inputs ;) – nachokk Mar 16 '14 at 20:46
1

I accomplished this by enabling the editing and then making the component responsible for the edition ignore any changes. For this I created a TableCellEditor and intercepted the key types to the JTextField, the component used for editing.

import java.awt.BorderLayout;
import java.awt.Component;
import java.awt.GridLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;

import javax.swing.AbstractCellEditor;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.JTextField;
import javax.swing.SwingUtilities;
import javax.swing.table.DefaultTableCellRenderer;
import javax.swing.table.DefaultTableModel;
import javax.swing.table.TableCellEditor;

public class TableCellSelectionTest
{
    public static void main(String[] args)
    {
        SwingUtilities.invokeLater(new Runnable() {
            public void run()
            {
                new TableCellSelectionTest().initUI();
            }
        });
    }

    public void initUI()
    {
        JFrame frame = new JFrame();
        int N = 5;
        int M = 3;
        Object[][] data = new Object[N][M];
        for (int i = 0; i < N; ++i)
        {
            for (int j = 0; j < M; ++j)
            {
                data[i][j] = "This is the cell (" + i + ", " + j +")";
            }
        }
        String[] columnNames = { "Column 1", "Column 2", "Column 3" };
        DefaultTableModel model = new DefaultTableModel(data, columnNames);
        final MyTableCellEditor editor = new MyTableCellEditor();
        JTable table = new JTable(model) {
            @Override
            public TableCellEditor getCellEditor(int row, int column)
            {
                return editor;
            }
        };

        frame.add(new JScrollPane(table), BorderLayout.CENTER);
        frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
        frame.pack();
        frame.setLocationRelativeTo(null);
        frame.setVisible(true);
    }

    class MyTableCellEditor extends AbstractCellEditor implements
            TableCellEditor
    {
        Object _value;

        @Override
        public Object getCellEditorValue()
        {
            return _value;
        }

        @Override
        public Component getTableCellEditorComponent(JTable table,
                Object value, boolean isSelected, int row, int column)
        {
            _value = value;
            JTextField textField = new JTextField(_value.toString());
            textField.addKeyListener(new KeyAdapter()
            {
                 public void keyTyped(KeyEvent e) {
                         e.consume();  //ignores the key
                   }


            @Override
            public void keyPressed(KeyEvent e)
            {
                e.consume();
            }});
            textField.setEditable(false); //this is functionally irrelevent, makes slight visual changes
            return textField;
        }
    }

}
DSquare
  • 2,458
  • 17
  • 19
  • Can you still see rich text in this case? If I do this and add HTML wouldn't it show the HTML with the angular brackets and not the styling/rendered HTML? – Bjorn Mar 16 '14 at 19:45
  • @BjornTipling You can change the Editing component to suit your needs. I used a JTextField, you can replace it with a more powerful one if necessary. – DSquare Mar 16 '14 at 19:48
  • In this way, you can't copy with `CTRL+C` for example. – nachokk Mar 16 '14 at 20:44
  • @nachokk You certainly can. – DSquare Mar 16 '14 at 20:45
  • how? if apparently the events are consumed – nachokk Mar 16 '14 at 20:47
  • @nachokk Key types into the Editing component are consumed. The clipboard is another system that lives outside of this component. To clarify, the key type is an event, and event can have multiple "listeners" this just consumes the event so the listener of the text component does not see it, but does not consume it globally. – DSquare Mar 16 '14 at 20:48
  • I see, but then for example he can with backspace delete content..i think that better implements a `DocumentFilter` or something like that to prevent that or make the textfield.setEditable(false) as i did in my example. – nachokk Mar 16 '14 at 21:05
  • Changed `keyTyped` for `keyPressed` to consume *all* events and fix the visual deletion with backspace. Anyway it is impossible to modify the contents of any cell. Also I added the line `textField.setEditable(false);` which make the cursor disappear. Has pros and cons, cleaner visually, but can't use the keyboard to select words. – DSquare Mar 16 '14 at 21:27
1

I tried both the answers here... but one problem at least is that you can tell when you've entered the "editing" mode.

This might be of interest... uses a combination of Editor magic and cheeky rendering to make it look like no editing is going on: editor's click-count-to-start is set to 1, and the component (JTextPane) delivered by the editor's method does setEditable( false ).

If this tickles your fancy, you might be interested at looking at my implementation of a JTable which adjusts (perfectly, harnessing the JTextPane's powerful wrapping power) the row height to the text, for individual rows, including when you change the columns: How to wrap lines in a jtable cell?

public class SelectableNonEditableTableTest {
    public static void main(String[] args) {

        EventQueue.invokeLater(new Runnable() {
            @Override
            public void run() {
                JFrame main_frame = new JFrame();
                main_frame.setPreferredSize(new Dimension(1200, 300));
                main_frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

                ArrayList<String> nonsense = new ArrayList<String>(
                        Arrays.asList(
                                "Lorem ipsum dolor sit amet, sed dolore vivendum ut",
                                "pri an soleat causae doctus.",
                                "Alienum abhorreant mea ea",
                                "cum malorum diceret ei. Pri oratio invidunt consequat ne.",
                                "Ius tritani detraxit scribentur et",
                                "has detraxit legendos intellegat at",
                                "quo oporteat constituam ex"));

                JTable example_table = new JTable(10, 4);

                example_table.setRowHeight( example_table.getRowHeight() * 2 );

                DefaultCellEditor cell_editor = new SelectableNonEditableCellEditor(
                        new JTextField());
                cell_editor.setClickCountToStart(1);
                example_table.setDefaultEditor(Object.class, cell_editor);

                TableCellRenderer renderer = new SelectableNonEditableTableRenderer();
                example_table.setDefaultRenderer(Object.class, renderer);

                for (int i = 0; i < 10; i++) {
                    example_table.setValueAt(nonsense.get(i % nonsense.size()),
                            i, i % 4);
                }

                main_frame.getContentPane().add(new JScrollPane(example_table));

                main_frame.pack();
                main_frame.setVisible(true);

            }
        });
    }

}

class SelectableNonEditableCellEditor extends DefaultCellEditor {

    public SelectableNonEditableCellEditor(JTextField textField) {
        super(textField);
    }

    public Component getTableCellEditorComponent(JTable table, Object value,
            boolean isSelected, int row, int col) {
        Component comp = super.getTableCellEditorComponent(table, value,
                isSelected, row, col);
        if (value instanceof java.lang.String) {
            DefaultStyledDocument sty_doc = new DefaultStyledDocument();
            try {
                sty_doc.insertString(0, (String) value, null);
            } catch (BadLocationException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
            JTextPane jtp_comp = new JTextPane(sty_doc);
            jtp_comp.setEditable(false);
            return jtp_comp;
        }
        return comp;
    }

}

class SelectableNonEditableTableRenderer extends JTextPane implements
        TableCellRenderer {
    @Override
    public Component getTableCellRendererComponent(JTable table, Object value,
            boolean isSelected, boolean hasFocus, int row, int column) {
        if (value instanceof DefaultStyledDocument) {
            setDocument((DefaultStyledDocument) value);
        } else {
            setText((String) value);
        }
        return this;
    }
}
Community
  • 1
  • 1
mike rodent
  • 14,126
  • 11
  • 103
  • 157
0

Maybe you can implement your own TableCellRenderer that extends JTextField in your table.

Ezekiel Baniaga
  • 853
  • 1
  • 12
  • 26