4

I'm not asking HOW to display a Component in a JTable, as there are several tutorials and examples online. However, I want to know what the BEST way of going about this might be.

For instance, most tutorials I run into have examples that create separate classes (Main class, one that extends JTable, on that extends TableModel, one that extends TableCellRenderer, et cetera). However, I found that you cannot only do it in one class, but one method by simply using the following:

Example Code (SSCCE)


Main

public class Main
{
  public static void main(String[] args)
  {
    javax.swing.JFrame jf = new javax.swing.JFrame("A table with components");
    jf.setLayout(new java.awt.BorderLayout());
    jf.setDefaultCloseOperation(javax.swing.JFrame.EXIT_ON_CLOSE);
    jf.add(new TableWithCompsPanel(), java.awt.BorderLayout.CENTER);
    jf.setVisible(true);
  }
}

TableWithComps

public class TableWithCompsPanel extends java.awt.Container
{
  private Class<?> tableColumnClassArray[];
  private javax.swing.JTable jTableWithComps;
  private Object tableContentsArray[][];

  public TableWithCompsPanel()
  {
    tableContentsArray = new Object[][]
      {
        {"This is plain text",                                            new javax.swing.JButton("This is a button")    },
        {new javax.swing.JLabel("This is an improperly rendered label!"), new javax.swing.JCheckBox("This is a checkbox")}
      };
    tableColumnClassArray = new Class<?>[]{String.class, java.awt.Component.class};
    initGUI();
  }

  private void initGUI()
  {
    setLayout(new java.awt.BorderLayout());
    jTableWithComps = new javax.swing.JTable(new javax.swing.table.AbstractTableModel()
      {
        @Override public int getRowCount()
        {
          return tableContentsArray.length;
        }

        @Override public int getColumnCount()
        {
          return tableContentsArray[0].length;
        }

        @Override public Object getValueAt(int rowIndex, int columnIndex)
        {
          return tableContentsArray[rowIndex][columnIndex];
        }

        @Override public Class<?> getColumnClass(int columnIndex)
        {
          return tableColumnClassArray[columnIndex];
        }
      });
    jTableWithComps.setDefaultRenderer(java.awt.Component.class, new javax.swing.table.TableCellRenderer()
    {
      @Override public java.awt.Component getTableCellRendererComponent(javax.swing.JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column)
      {
        return value instanceof java.awt.Component ? (java.awt.Component)value : new javax.swing.table.DefaultTableCellRenderer();
      }
    });
    add(jTableWithComps, java.awt.BorderLayout.CENTER);
  }
}

Question


What I'm wondering is that, if it can be accomplished in such a short amount of code, why do examples go out of their way to separate it out into three, or sometimes even more, classes? Is my code somehow less efficient at runtime? I can understand separating the main class and the class that has the example GUI, but not why you would separate out the example GUI into several classes.

Edit: I see alot of people giving great reasons why this code is impractical. I would appreciate your answers more if you provided an alternative!

Ky -
  • 30,724
  • 51
  • 192
  • 308
  • 2
    never-ever store components in your TableModel – kleopatra Feb 16 '12 at 17:04
  • What if I want to have something like iTunes, where there is a table of objects (songs) and you want the user to select multiple items with checkboxes? Would the best solution not be to simply make the first column of cells checkboxes? – Ky - Feb 16 '12 at 18:13
  • also, they're not STORED in the TableModel, just displayed there. – Ky - Feb 16 '12 at 18:17
  • Technically, you're still creating multiple classes with that code, since you have an [anonymous inner class](http://stackoverflow.com/questions/7778556/the-anonymous-class-conundrum). Your `new TableCellRenderer(){}` will end up being compiled as `Main$1.class` or something like that. – Kevin K Feb 16 '12 at 18:57
  • no, the first column would be booleans which are rendered and edited by checkBoxes. Don't know what _you_ mean by "just displayed" vs "stored" - for _me_, the check ´if (value instanceof Component)` doesn't make any sense for the former :-) Be sure to read and understand the tutorial chapter linked to in bullet 3 by @mKorbel – kleopatra Feb 17 '12 at 08:49
  • @kleopatra By "just displayed" vs "stored", I mean that I store the components in a class variable array, and simply return them using the `TableModel` – Ky - Feb 17 '12 at 18:58
  • @KevinK I know, but the question is more about the code than the compilation – Ky - Feb 17 '12 at 18:58
  • it's _not_ the task of the TableModel to provide components it wants to be rendered with - but idea to mix concerns, especially doing so against the winds of the framework – kleopatra Feb 17 '12 at 21:34
  • @kleopatra uh.... Could you say that in a less... cryptic way? – Ky - Feb 17 '12 at 21:46
  • cryptics were introduced by my keyboard - meant "bad idea" :-) Why do you insist in not following Swing design but instead fight it? (you did read the tutorial, didn't you?) – kleopatra Feb 17 '12 at 21:50
  • @kleopatra This question isn't about what Swing design is; I tend to follow that in full applications. Instead, this is about the best way to exemplify how to make a `JTable` render `Component`s – Ky - Feb 17 '12 at 22:47
  • @Supuhstar Thanks for providing full example. As you can see sorting/filtering is difficult to achieve in this table. Also its unnecessarily creating components which can be issue when you have large number of rows. BTW if you just want to show the components in tabular form why not use GridLayout? – Ashwinee K Jha Feb 18 '12 at 07:24
  • the best way is to not do it at all ;-) – kleopatra Feb 18 '12 at 09:49
  • @AKJ I ended up doing that XP – Ky - Feb 18 '12 at 22:21

4 Answers4

5

The TableModel models the data you want to track in the simplest way possible, for the sake of memory efficiency. The TableCellRenderer defines how to display that data in a table cell.

In your iTunes checkbox example, the simplest way to model the information from a checkbox is a boolean value (true/false). It's a lot more memory efficient to store a collection of 10,000 boolean objects than 10,000 JCheckBox objects.

The TableCellRenderer can then store a single JCheckBox object, and when it is asked for a component to use to paint the cell, it can check/uncheck the checkbox based on the value and return the same component every time. That way, you aren't creating thousands of UI components over and over again as the user scrolls through the table.

Kevin K
  • 9,344
  • 3
  • 37
  • 62
3

In general the division in so many components interacting is because of the design. This design tries to apply good principles, like separation of concerns. You can build one big thing that does everything or identify smaller parts each one with its responsibily. In the later case your code is more suitable for change because each class does only one thing and changes a lot of time implies touching one responsibility or two without breaking the general architecture of the solution.

In particular Swing applies MVC pattern that is a little bit verbose but tries to grasp a lot of good design principles. I understand it's not always the most simple thing to maintain but I have to recognize responsabilities are very decoupled.

The samples may be short. But they have to adhere to the philosophy and architecture of Swing. That's why they implement the same roles independently of the size of the code.

The rule of thumb for this (IMHO) is to find for each division the reason why the division was made.

Performance: don't worry if your code is split in several classes. It doesn't affect performance. Other thing (like time complexity) does. Or maybe some incorrect use of components, but if all is used as intended things would be fine.

Edit: hope this answer is useful! As you can see it's not swing oriented at all...

helios
  • 13,574
  • 2
  • 45
  • 55
  • I understand that, and that was my first thought... but making separate two or more classes to specify the behavior of a single component seems like just too much, and it probably takes up more drive space – Ky - Feb 16 '12 at 18:11
  • Yep. But a table is a very complex component and it has a lot of different particularities (so its design has a lot of interchageable parts). The other solution would be to polute the design with a lot of if's and bifurcations for deciding between implementations. Said that: I agree it probably could be simpler. Or at least, have some default, pre-built, solutions for common needs. Anyway I can't support what I say in a practical way because of my limited knowledge of Swing. :) Just don't worry about disk-space or memory-space, unless that space grows quadratic or exponential :) – helios Feb 17 '12 at 14:25
2

Somethings to consider:

  • From your example it appears the cell value is a Component. Is not that going to take huge memory for every non-trivial table?
  • Does the component paint properly when say row is selected, is in focus etc?
  • Unless your component is very smart you may have trouble making the cell editable.
Ashwinee K Jha
  • 9,187
  • 2
  • 25
  • 19
  • 1: No, because if the object that you want to put into the JTable is NOT a Component, then the default renderer is used. Also, the JTable cells are already components, anyway. 2: Yes 3: In this case, it will be something like a checkbox, button, progress bar, etc., which are things you don't want to edit, anyway. – Ky - Feb 16 '12 at 18:08
  • On some operating systems (e.g. Win7), components like checkboxes, buttons, etc. will animate when you hover the mouse over them. It can be nice to implement an editor for those cells so they act like "real" components when you mouseover them. – Kevin K Feb 16 '12 at 19:01
  • 1
    @Supuhstar As I understand the value object passed to the method should be value in the cell not the cell component. If you are populating each cell with say a button your code will work but I doubt it will scale much. – Ashwinee K Jha Feb 16 '12 at 19:54
  • @Supuhstar _Also, the JTable cells are already components, anyway_ That's **the-wrong-thing-to-do** 2. not in the snippet you are showing in your question – kleopatra Feb 17 '12 at 08:56
  • @kleopatra wrong or right, all `JTable` cells are `Components`; that's jut the way it is. 2: I'm sorry to say that it only works for displaying the component... You're right on that point – Ky - Feb 17 '12 at 18:56
  • @KevinK That would be very nice! Do you have any sample code? – Ky - Feb 17 '12 at 19:11
  • @Supuhstar Can you add a complete example of a JTable where value is component? – Ashwinee K Jha Feb 17 '12 at 19:14
  • @Supuhstar I don't have an example handy, but the basic strategy is pretty straightforward to explain. First, install a `TableCellEditor` on the cell that returns the same type of component as the renderer. Then, install a `MouseMotionListener` on the JTable, and when the mouse moves check if it is over the cell. If it is, call `JTable.editCellAt()`, but only if you're not already editing the cell. If it is not over the cell, cancel cell editing. – Kevin K Feb 17 '12 at 19:26
  • (darn keyboard, fighting me - so trying again) @Supuhstar _all JTable cells are Components; that's just the way it is_ no, that's just the way _you_ made it, incorrectly ;-) – kleopatra Feb 17 '12 at 21:53
1
  1. Renderer is only for visually decorating a cell's contents, e.g. setFont, setForeground, setBackground, isEnable, isVisible, etc.

  2. Don't create, set, change JComponents type in the Renderer at Runtime; this is a job for TableModel.

  3. If is possible, use DefaultTableModel and leverage the existing renderers for types known to JTable.

  4. You have know which type of Object/JComponent is/are present in the JTable's cell.

trashgod
  • 203,806
  • 29
  • 246
  • 1,045
mKorbel
  • 109,525
  • 20
  • 134
  • 319
  • I still don't understand 2, 3, or 4 : – Ky - Feb 16 '12 at 18:09
  • Sorry for the delay in returning to this answer. I've tried to explicate these important points. Here's a related [example](http://stackoverflow.com/a/9155689/230513); if you look through the editing history you can see how I perpetuated similar errors in overriding `JTable` methods instead of those of the `TableModel`. Note the different types from column "C3". – trashgod Feb 17 '12 at 23:25